'use strict';

/**
 * Licensed Materials - Property of IBM
 * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2020
 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * @module dashboard/render/VisView
 * @see VisView
 */
define(['jquery', 'underscore', 'doT', '../../lib/@waca/core-client/js/core-client/ui/core/View', './filter/FilterIndicator', './info/InfoIndicator', '../../widgets/livewidget/nls/StringResources', '../../lib/@waca/core-client/js/core-client/utils/Utils', './refreshtimer/RefreshTimerIndicator', 'text!./templates/EmptyVisualization.template', './forecastIndicator/ForecastIndicator', './VisTabs', '../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../widgets/livewidget/util/VisUtil', '../../lib/@waca/dashboard-common/dist/utils/MemUtil'], function ($, _, dot, View, FilterIndicator, InfoIndicator, StringResources, Utils, RefreshTimerIndicator, EmptyVisualizationTemplate, ForecastIndicator, VisTabs, BrowserUtils, VisUtil, MemUtil) {

	//Track sub-views created to help with cleanup
	var VisViewSubViews = ['filterIndicator', 'refreshTimerIndicator', 'conditionalViewIndicator', 'infoIndicator'];

	/**
  * VisView is the base class of all Visualization views
  * such as RaveView, GridView etc.
  */
	var VisView = View.extend({

		//The animation speed for all operations if no animation speed is set in properties.
		DEFAULT_ANIMATION_SPEED: 900,

		//The animation type (if none is set, default is currently to fadein).
		ANIMATION_TYPES: {
			NONE: 'none',
			DEFAULT: 'default',
			FADEIN: 'fadein',
			TRANSITION: 'transition',
			GROW: 'grow',
			REVEAL: 'reveal',
			BIGDATA: 'bigdata'
		},

		init: function init(options) {
			VisView.inherited('init', this, arguments);
			_.extend(this, arguments[0]);

			this.noRotate = true;
			this.isMaximizable = true;
			this.logger = options.logger;
			this.ownerWidget = options.ownerWidget;
			this.visualization = this.ownerWidget.getVisualizationApi();
			this.transactionApi = this.dashboardApi.getFeature('Transaction');
			this.internalVisDefinitions = this.dashboardApi.getFeature('VisDefinitions.internal');
			this.content = options.ownerWidget.content;

			this._processTemplateString();

			if (!options.proxiedView && !this._viewManagesOwnQuery()) {
				this._initIndicators();
			}

			this.visEl = this.el.firstChild ? this.el.firstChild : this.el;

			// create the visualization tabs (if supported)
			this.createTabs();

			//When set by an event handler (aka filter), this value controls the animation speed
			//for views that otherwise don't animate (but do support it).
			//If the definition already has animation, it will NOT override its value.
			this.overrideDefaultAnimationSpeed = -1;

			//This setting controls Non-VIPR visualizations allowing them to fade-in/fade-out during rendering.
			//VIPR visualizations use effects which are separate from this setting.
			//By default, Non-VIPR animation is off: (new for Endor) as it causes needless flashing and makes visualizaions appear slower.
			this.animationType = this.ANIMATION_TYPES.NONE;
			this.animationSpeed = this.DEFAULT_ANIMATION_SPEED;

			//The 'renderingNewData' state (maintained by the rendersequnce) is relevant in the context of the view's render() method.
			//It means that, for a particular view.render() call, new data is being rendered.
			this._renderingNewData = false;

			if (!options.proxiedView && !this._viewManagesOwnQuery()) {
				this.createView();
			}

			if (!options.proxiedView) {
				this._addPropertyHandler();
			}
		},

		createTabs: function createTabs() {
			var options = {};
			// reuse the existing DIV if already exists
			if (this.ownerWidget && this.ownerWidget.$el) {
				var $tabs = this.ownerWidget.$el.find('.visTabsView');
				options.el = $tabs.length ? $tabs[0] : null;
			}
			this._tabs = new VisTabs(options);

			// keep it hidden until render
			this._tabs.hide();
		},

		placeAt: function placeAt(element) {
			this.$el.prependTo(element); // insert view as the first element of the content container
			//if tab view exists, insert tabs as the first element
			if (this._tabs) {
				this._tabs.placeAt(element);
			}
		},

		setInExpandedMode: function setInExpandedMode() {
			var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
			if (conditionalViewIndicator) {
				conditionalViewIndicator.setInExpandedMode();
			}
		},

		onRestore: function onRestore() {
			var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
			if (conditionalViewIndicator) {
				conditionalViewIndicator.onRestore();
			}
		},

		/**
  * @returns if this specific view supports annotations. Returns false by default, expected to be overriden.
  */
		_doesViewSupportSmartAnnotations: function _doesViewSupportSmartAnnotations() {
			return false;
		},

		// to be implemented by chilren class
		doesVisPropertyMatchExpected: function doesVisPropertyMatchExpected() {
			return false;
		},

		// to be implemented by chilren class
		canApplyAutoBin: function canApplyAutoBin() {
			return false;
		},

		// to be implemented by chilren class
		getBinningConfig: function getBinningConfig() {
			return undefined;
		},

		/*
   * Reads the filter indicator config spec, and instantiates a filter indicator if applicable.
   */
		_initFilterIndicator: function _initFilterIndicator() {
			var filterSpec = this.getFilterIndicatorSpec();
			if (filterSpec && (filterSpec.localFilters || filterSpec.globalFilters || filterSpec.localTopBottoms || filterSpec.drillState)) {
				this.filterIndicator = new FilterIndicator(this.visModel, this.visualization, this.transactionApi, this.getController(), filterSpec, this.logger);
				this.ownerWidget.addIcon(this.filterIndicator.$filter, 'filterIcon');
			}
		},

		_initIndicators: function _initIndicators() {
			var _this = this;

			var visIndicators = this.content.getFeature('VisIndicators');
			if (visIndicators && this.ownerWidget && this.ownerWidget.$el) {
				this._initRefreshIndicator();
				this._initFilterIndicator();
				this._initInfoIndicator();
				this._initForecastingIndicator();
				var indicators = visIndicators.getIndicatorList();
				_.each(indicators, function (indicator) {
					_this.ownerWidget.addIcon(indicator.icon, indicator.name);
				});
			}
		},

		_initForecastingIndicator: function _initForecastingIndicator() {
			this.forecastIndicator = new ForecastIndicator({
				visModel: this.visModel,
				ownerWidget: this.ownerWidget,
				logger: this.logger,
				supportsForecasts: true,
				ownerView: this,
				visAPI: this.ownerWidget.visAPI,
				content: this.content
			});
			this.ownerWidget.addIcon(this.forecastIndicator.getIndicator(), 'forecastIcon');
		},

		_initInfoIndicator: function _initInfoIndicator() {
			this.infoIndicator = new InfoIndicator({
				visModel: this.visModel,
				ownerWidget: this.ownerWidget
			});
			this.ownerWidget.addIcon(this.infoIndicator.$el, 'infoIconDiv');
		},

		_initRefreshIndicator: function _initRefreshIndicator() {
			this.refreshTimerIndicator = new RefreshTimerIndicator(this.ownerWidget);
			this.ownerWidget.addIcon(this.refreshTimerIndicator.$icon, 'timerIcon');
		},

		/*
   * Indicates whether to render filter flyouts
   */
		getFilterIndicatorSpec: function getFilterIndicatorSpec() {
			return {
				localFilters: true,
				globalFilters: true,
				localTopbottoms: true,
				drillState: true
			};
		},

		getController: function getController() {
			return this.content && this.content.getFeature('InteractivityController.deprecated');
		},

		getSelector: function getSelector() {
			return this.content.getFeature('DataPointSelections.deprecated');
		},

		/*
   * Indicates whether to render topbottom flyouts. Unlike filter, topbottom only applys to the current viz there is no global
   */
		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns promise - By default, views don't have controls....return a resolved promise with an empty object.
   */
		whenVisControlReady: function whenVisControlReady() {
			return this._noOpRenderStep('base class whenVisControlReady called!');
		},

		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
   */
		whenVisSpecReady: function whenVisSpecReady() {
			return this._noOpRenderStep('base class whenVisSpecReady called!');
		},

		/**
  * @returns {boolean} true if this specific view supports annotations. Returns
  * false by default. Expected to be overriden
  */
		supportsAnnotations: function supportsAnnotations() {
			return false;
		},


		/**
  * @returns {boolean} true if this specific view supports annotations. Returns
  * false by default. Expected to be overriden
  */
		supportsForecasts: function supportsForecasts() {
			return false;
		},


		/**
  * Called before data is requested. Use this to clean up infoIndicator, etc.
  */
		preDataReady: function preDataReady() {
			// Reset any warnings/errors in the info indicator as we are starting fresh
			if (this.infoIndicator) {
				this.infoIndicator.clearMessagesWithIds(['visualization_notifications', 'moreDataIndicator', 'autobinSuggestion', 'data_notifications']);
			}
		},
		_hasMappedSlots: function _hasMappedSlots() {
			return this.visualization.getSlots().getMappedSlotList().length !== 0;
		},


		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns promise - By default, make a query for the data and do not index the results (ie: RaveView is an exception).
   */
		whenDataReady: function whenDataReady(renderContext) {
			var _this2 = this;

			var result = void 0;

			// A resize event is forced when the layout base view is initialized. This may cause a request
			// to render the visualization before the visControl is created. Guard against this.
			if (this._hasMappedSlots() && this.canExecuteQuery() && !this.hasUnavailableMetadataColumns() && !this.hasMissingFilters()) {
				renderContext.isDevInstall = this.ownerWidget.getDashboardApi().isDevInstall;
				result = this._executeQueries(renderContext).then(function (retData) {
					_this2.dashboardApi.triggerDashboardEvent('synchronize:pageContextFilters', { synchDataFilterEntries: retData.synchDataFilterEntries });
					if (_this2.filterIndicator) {
						_this2.filterIndicator.setSynchronizeDataFilters(retData.synchDataFilterEntries);
						_this2.filterIndicator.update();
					}

					_this2._updateInfoIndicator(retData, renderContext);

					return {
						data: retData,
						sameQueryData: _this2.isSameQueryData(renderContext, retData)
					};
				});
			} else {
				result = Promise.resolve({
					data: {}
				});
			}
			return result;
		},

		_updateInfoIndicator: function _updateInfoIndicator(queryResults, renderContext) {
			var hasMoreData = void 0;
			var queryThreshold = void 0;
			var warnings = void 0;

			if (renderContext.useAPI) {
				var queryId = _.find(queryResults.getQueryResultIdList(), function (id) {
					return queryResults.getResult(id).hasMoreData();
				});
				hasMoreData = queryId !== undefined;
				queryThreshold = hasMoreData && queryResults.getResult(queryId).getRowCount();
				warnings = queryResults.getWarningList();
			} else {
				hasMoreData = queryResults.hasMoreData;
				queryThreshold = queryResults.queryThreshold;
			}

			if (this.infoIndicator) {
				var infoItems = [];
				// @todo: utilize the QueryResultsAPI.getWarningList
				if (hasMoreData) {
					// add data clipping
					if (queryThreshold) {
						infoItems.push({
							id: 'moreDataIndicator',
							label: StringResources.get('moreDataIndicator', { threshold: queryThreshold })
						});
					}
					// check auto-binning: 1) is it supported and 2) not already binned
					if (this.canApplyAutoBin() && !this.hasBinnedDataItems()) {
						infoItems.push({
							id: 'autobinSuggestion',
							label: StringResources.get('autobinSuggestion'),
							isSubMessage: true
						});
					}
				}
				if (warnings && warnings.length) {
					warnings.forEach(function (warning) {
						infoItems.push({
							id: warning.resourceId,
							label: StringResources.get(warning.resourceId, warning.params),
							isSubMessage: !!warning.isSubMessage
						});
					});
				}
				this.infoIndicator.addInfo([{
					id: 'data_notifications',
					label: StringResources.get('dataNotifications'),
					items: infoItems
				}]);
			}
		},

		_executeQueries: function _executeQueries(renderContext) {
			if (renderContext.useAPI) {
				var queryExecution = this.content.getFeature('DataQueryExecution');
				if (renderContext.extraInfo && renderContext.extraInfo.dataQueryParams) {
					queryExecution.addRequestOptions(renderContext.extraInfo.dataQueryParams);
				}
				return queryExecution.executeQueries();
			} else {
				return this.visModel.whenQueryResultsReady(renderContext);
			}
		},

		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns {Promise}
   */
		whenSetDataReady: function whenSetDataReady() /* renderContext */{
			return Promise.resolve(true);
		},

		isSameQueryData: function isSameQueryData() {
			var renderContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
			var queryResult = arguments[1];

			// TODO clean logic when QueryAPI is live.
			if (renderContext.sameQueryData === undefined) {
				var currTag = queryResult && queryResult.getCacheValidateTag && queryResult.getCacheValidateTag();
				renderContext.sameQueryData = this._previousTag && this._previousTag === currTag;
				this._previousTag = currTag;
			}
			return !!(renderContext.sameQueryData || renderContext.extraInfo && renderContext.extraInfo.annotationRequest);
		},

		/* For Subclass to implement */
		updateInfoIndicator: function updateInfoIndicator(data) {
			if (this.infoIndicator) {
				this.infoIndicator.update(data);
			}
		},

		/**
   * The 'renderingNewData' state (maintained by the renderSequence) is relevant in the context of the view's render() method.
   * It means that, for a particular view.render() call, new data is being rendered.
   * (for example, typically, render following a resize would not be renderingNewData BUT,
   * If a resize occurs quickly after a render, 2 RenderSequence render() calls could result in one view:render() call
   * (effecively the 2 renders are "bundled into 1" from the perspective of the view so we are renderingNewData)
   * @param {boolean} flag - The renderingNewData value is maintained by the render sequence as follows:
   *		true: when the data task has executed a query and has new data.
   *		false: when the render task has completed a render
   */
		setRenderingNewData: function setRenderingNewData(flag) {
			this._renderingNewData = flag;
		},

		/**
   * @returns the state of the renderingNewData flag.  This call makes sense in the context of a particular view.render().
   */
		getRenderingNewData: function getRenderingNewData() {
			return this._renderingNewData;
		},

		/**
   * @returns true if all of the required slots are mapped and this visualization can be rendered.
   */
		isMappingComplete: function isMappingComplete() {
			return this.visualization.getSlots().isMappingComplete();
		},

		/**
   * Determine whether the query can be executed.
   * Generally the query shall execute when all required mapping is complete.
   * However some applications may require to execute the query regardless of the slot mapping state.
   * The config should only affect the behaviour of the query and should have no impact on the actual render
   * of the visualization. The render should continue to render only when isMappingComplete is true.
   */
		canExecuteQuery: function canExecuteQuery() {
			return this.dashboardApi.getConfiguration('forceQuery') === true || this.isMappingComplete();
		},

		isRenderWithoutCompletingMapping: function isRenderWithoutCompletingMapping() {
			var definition = this.visualization.getDefinition();
			return definition.getProperty('renderWithoutCompletingMapping') === true;
		},

		hasUnavailableMetadataColumns: function hasUnavailableMetadataColumns() {
			return this.visualization.hasUnavailableMetadataColumns();
		},
		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
   */
		whenHighlighterReady: function whenHighlighterReady() {
			return this._noOpRenderStep('base class whenHighlighterReady called!');
		},

		/**
   * Creates an object consumable by our info indicator
   * @param {object} id id of our info, needs a matching entry in our nls entries
   * @param {Array} items
   * @param {String} item.id
   * @param {String} item.caption
   */
		_getInfoIndicatorEntries: function _getInfoIndicatorEntries(id, items) {
			return [{
				id: id,
				label: StringResources.get(id),
				items: _.map(items, function (item) {
					return {
						id: item.id,
						label: item.caption
					};
				})
			}];
		},

		_setInfoIndicatorWarningsAndErrors: function _setInfoIndicatorWarningsAndErrors(data) {
			if (this.infoIndicator && data) {
				var errorId = 'smart_errors';
				var warningId = 'smart_warnings';
				var warnings = data.getWarnings();
				var errors = data.getErrors();
				this.infoIndicator.addInfo(this._getInfoIndicatorEntries(warningId, warnings));
				this.infoIndicator.addInfo(this._getInfoIndicatorEntries(errorId, errors));
			}
		},

		clearInfoIndicator: function clearInfoIndicator() {
			// Reset any warnings/errors in the info indicator as we are starting fresh
			if (this.infoIndicator) {
				//if our mapping isn't complete, just clear out the info indicator.
				if (!this.isMappingComplete()) {
					this.infoIndicator.reset();
				} else {
					for (var _len = arguments.length, msgId = Array(_len), _key = 0; _key < _len; _key++) {
						msgId[_key] = arguments[_key];
					}

					this.infoIndicator.clearMessagesWithIds(msgId);
				}
			}
		},
		clearAnnotationErrorsAndWarnings: function clearAnnotationErrorsAndWarnings() {
			this.clearInfoIndicator('smart_errors', 'smart_warnings', 'smart_exec_support_warnings', 'annotation_service_error');
		},


		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
   */
		whenAnnotatedResultsReady: function whenAnnotatedResultsReady(renderContext) {
			var _this3 = this;

			if (this.isMappingComplete() && !this.hasUnavailableMetadataColumns() && (this._checkAllSupportsAnnotations() || this._checkAllSupportsForecasts())) {
				return this.visModel.whenAnnotatedResultsReady(renderContext).then(function (data) {
					_this3._setInfoIndicatorWarningsAndErrors(data);
					return data;
				});
			} else {
				return Promise.resolve();
			}
		},

		addUnavailableAnnotationServiceErrorMessageToIndicator: function addUnavailableAnnotationServiceErrorMessageToIndicator() {
			if (this.infoIndicator) {
				this.infoIndicator.addInfo([{
					id: 'annotation_service_error',
					label: StringResources.get('smart_errors'),
					items: [{
						id: 'suggestions',
						label: StringResources.get('smart_annotation_insight_unavailable')
					}]
				}]);
			}
		},


		/**
   * Render Sequence - override this method when needed by a specific view type.
   * @returns {object} promise
   */
		whenPredictSuggestionsReady: function whenPredictSuggestionsReady(renderContext) {
			var _this4 = this;

			this.clearInfoIndicator('annotation_service_error', 'suggestions');

			// Clear the messages so that we aren't showing stale info
			this.visModel.clearInsightsIndicatorMessages();
			if (this.content) {
				this.content.getFeature('Forecast').clearForecastIndicatorMessages();
			}
			var supportsAnnotations = this._checkAllSupportsAnnotations();
			if (!supportsAnnotations) {
				this.visModel.resetAnnotations();
			}
			if (this._hasMappedSlots() && this.isMappingComplete()) {
				return this.visModel.whenPredictIsReady(renderContext, supportsAnnotations, this._checkAllSupportsForecasts()).catch(function (error) {
					_this4.addUnavailableAnnotationServiceErrorMessageToIndicator();
					_this4.logger.error('An error occurred while waiting for the annotation suggestions to be ready', error, _this4.visModel);
				});
			}
			return Promise.resolve();
		},

		_checkAllSupportsAnnotations: function _checkAllSupportsAnnotations() {
			return this.supportsAnnotations() && this._viewSupportsAnnotations();
		},

		//We enable annotations for preview launched from Driver analysis Viz key drivers,
		//Except that, for other previews, we disable annotations.
		_viewSupportsAnnotations: function _viewSupportsAnnotations() {
			if (this.ownerWidget.isPreview) {
				return this.ownerWidget.isPredictPreview === true;
			}
			return true;
		},

		_checkAllSupportsForecasts: function _checkAllSupportsForecasts() {
			return this.supportsForecasts() && this._viewSupportsForecasts();
		},

		_viewManagesOwnQuery: function _viewManagesOwnQuery() {
			if (this.ownerWidget.managesOwnQueries) {
				return this.ownerWidget.managesOwnQueries === true;
			}
			return false;
		},

		//We enable forecasts for preview launched from Driver analysis Viz key drivers,
		//Except that, for other previews, we disable annotations.
		_viewSupportsForecasts: function _viewSupportsForecasts() {
			if (this.ownerWidget.isPreview) {
				return this.ownerWidget.isPredictPreview === true;
			}
			return true;
		},

		/**
   * Render Sequence:
   * Each step in the render sequence returns a promise.
   * For any view, or any step, this method can be used to return a resolved promise with an empty object
   */
		_noOpRenderStep: function _noOpRenderStep() {
			//By default, VisViews have no underlying VisControl...return an empty, non-null object.
			return Promise.resolve({});
		},

		_addPropertyHandler: function _addPropertyHandler() {
			this.ownerWidget.model.on('change:properties', this.onChangeProperties, this);
			this.ownerWidget.model.on('change:fillColor', this.onChangeProperties, this);
		},

		/**
   * Set up the View and standard event handlers on the VisModel.
   */
		createView: function createView() {
			if (!this.visModel) {
				throw 'Invalid VisModel reference';
			}

			//Subscribe to model events
			this.visModelEvents = {
				'change:annotations': this.onChangeAnnotations,
				'change:pagecontext': this.onChangePageContext, //pageContext changes should be processed as filters.
				'change:refreshTimer': this.toggleRefreshTimerIndicator
			};

			this.ownerWidget.model.on('change:searchFilters', this.onChangeLocalFilter, this);
			this.ownerWidget.model.on('change:localFilters', this.onChangeLocalFilter, this);
			this.ownerWidget.model.on('change:conditions', this.onChangeConditions, this);
			this.ownerWidget.model.on('change:possibleKeyDrivers', this.onChangePossibleKeyDrivers, this);

			for (var key in this.visModelEvents) {
				if (this.visModelEvents.hasOwnProperty(key)) {
					this.visModel.on(key, this.visModelEvents[key], this);
				}
			}

			if (this.filterIndicator) {
				this.filterIndicator.initEvents();
			}

			if (this.refreshTimerIndicator) {
				this.refreshTimerIndicator.initEvents();
			}

			if (this.forecastIndicator) {
				this.forecastIndicator.initEvents(this.supportsForecasts());
			}

			var content = this.ownerWidget.content;
			content && content.on('all', this.onAllContentChanges, this);
			var visDefinitions = this.dashboardApi.getFeature('VisDefinitions');
			visDefinitions.on('refresh:definition', this.onRefreshVisDefinition, this);
			visDefinitions.on('refresh:all', this.onRefreshAllVisDefinition, this);

			var colorsService = this.dashboardApi.getFeature('Colors');
			colorsService.on('theme:changed', this.onChangeProperties, this);
		},

		/**
   * Event handler when possible key drivers are changed inside vis model
   */
		onChangePossibleKeyDrivers: function onChangePossibleKeyDrivers() {
			var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

			if (this._isViewSlotsEmpty()) {
				return this.ownerWidget._rebuildVis(event);
			}
			var reRenderOptions = {
				refresh: {
					predictSuggestions: true,
					keyDrivers: true,
					data: true
				}
			};
			if (this.filterIndicator) {
				this.filterIndicator.update();
			}
			this.appendExtraInfoToRenderOptions(reRenderOptions, event);
			if (this.ownerWidget.isVisible()) {
				this._reRender(reRenderOptions);
			} else {
				this.ownerWidget.setReRenderOnShow(reRenderOptions);
			}
		},

		onRefreshAllVisDefinition: function onRefreshAllVisDefinition() {
			var visDefinition = this.visualization.getDefinition();
			if (visDefinition.getState().getError() || this.content.getFeature('state').getError()) {
				this._reRender({
					refreshAll: true
				});
			}
		},

		onRefreshVisDefinition: function onRefreshVisDefinition(event) {
			var visDefinition = this.visualization.getDefinition();
			if (visDefinition && event && event.info && visDefinition.getId() === event.info.id) {
				this._reRender({
					refreshAll: true
				});
			}
		},

		onAllContentChanges: function onAllContentChanges(e) {
			if (e && e.info && e.info.refresh) {
				var renderOptions = {
					refresh: _.extend({}, e.info.refresh),
					extraInfo: {
						payloadData: {
							transactionToken: e.transactionToken
						}
					}

				};

				if (renderOptions.refresh.all) {
					// TODO: refreshAll should be changed into 'refresh.all' to be consistent with the refresh syntax
					renderOptions.refreshAll = renderOptions.refresh.all;
				}

				// Type change
				// highlighting is included because this event may have
				// been triggered from a failure handler.
				// ensure the remaining rendering sequence is covered
				// var reRenderOptions = {
				// 	refresh: {
				// 		visSpec: true,
				// 		data: true,
				// 		highlighter: true,
				// 		predictSuggestions: true
				// 	}
				// };
				// //If currently rendering icon view, ensure the proper control is loaded.
				// reRenderOptions.refresh.visControl = true;
				// this._reRender(this.appendExtraInfoToRenderOptions(reRenderOptions, event));

				// this.updateConditionalViewIndicator();

				// Todo: once we removed the logic for refresh properties pane upon re-render, this should be removed too
				// UndoRedo action will update the model but the old properties UI doesn't have state management, so we need to do a refresh for this scenario
				var isUndoRedo = e.context && e.context.undoRedo && e.info.featureName !== 'ConditionalFormatting';
				renderOptions.refresh.propertiesPane = this._refreshPropertiesPane(renderOptions, isUndoRedo);

				this._reRender(renderOptions);
			}
		},

		_refreshPropertiesPane: function _refreshPropertiesPane(renderOptions, isUndoRedo) {
			return !this._isLocalViz() || renderOptions.refresh.propertiesPane || !this._isNewConditionalFormattingEnabled() || !!isUndoRedo;
		},

		_isLocalViz: function _isLocalViz() {
			var localVizType = ['KPI', 'Singleton', 'Hierarchy', 'Crosstab', 'List', 'DataPlayer'];
			var visType = this.content.getFeature('Visualization').getType();
			return _.contains(localVizType, visType);
		},

		_isNewConditionalFormattingEnabled: function _isNewConditionalFormattingEnabled() {
			var cf_kpi = !this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'condFormat', 'disabled');
			var cf_xtab = !this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'xtabcondFormat', 'disabled');
			return cf_kpi || cf_xtab;
		},

		updateSubViews: function updateSubViews() {
			this.updateConditionalViewIndicator();
		},

		/**
   * If this view has a template string, make its content a child of the root "el".
   * If the template includes "data-attach-point" attributes, augment the view class
   * with them.
   * eg:
   * <root>
   *    <div class="x" data-attach-point='sliderNode'>
   *    <div class="y" data-attach-point='playerButtonNode'>
   * </root>
   *
   * would extend the VisView with members  sliderNode=div.x,   playerButtonNode=div.y
   */
		_processTemplateString: function _processTemplateString() {
			if (this.templateString) {
				//Populate the view with the template if one has been defined.
				var outTemplate = this.dotTemplate(this.templateString);
				if (!outTemplate) {
					outTemplate = this.templateString;
				}
				this.$el.html(outTemplate);

				var dataAttachPoints = this.el.querySelectorAll('div[data-attach-point]');
				var oAttachPoints = {};
				_.each(dataAttachPoints, function (dataAttachPoint) {
					oAttachPoints[dataAttachPoint.getAttribute('data-attach-point')] = dataAttachPoint;
				});
				_.extend(this, oAttachPoints);
			}
		},

		/**
   * Resize the el for this view to the specified bounds
   * @param bounds in form left, top, width, height.
   */
		resize: function resize(bounds) {
			if (bounds) {
				// capture widget height, if zero or undefined then get parent's height
				var height = bounds.height || this.$el.parent().height();
				// if we have tabs, we must compensate for the space it takes within the widget if the mapping is complete
				if (this._tabs && this._tabs.getTabsCount() > 0 && this.ownerWidget.allowShowTabs && this.ownerWidget.allowShowTabs() && this.isMappingComplete()) {
					height = height - this._tabs.$el.height();
				}

				var fSetBound = function fSetBound(style, styleAttr, val) {
					// Setting style may be expensive, do it only when necessary.
					if (val !== undefined && val !== parseInt(style[styleAttr], 10)) {
						style[styleAttr] = val + 'px';
					}
				};
				var style = this.el.style;
				fSetBound(style, 'left', bounds.left);
				fSetBound(style, 'top', bounds.top);
				fSetBound(style, 'width', bounds.width);
				fSetBound(style, 'height', height);
			}
		},

		/**
   * Remove this view...cleanup any event handlers set up
   * and perform base class cleanup.
   */
		// @override
		remove: function remove() {
			var _this5 = this;

			var finalRemove = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

			this.removeIconView();
			VisView.inherited('remove', this, arguments);

			// todo livewidget_cleanup -- it is weird that we pass a finalRemove
			// It seems to be used to delayed the destruction of a vipr widget
			// but we could handle this in a better way
			VisViewSubViews.forEach(function (view) {
				_this5[view] && _this5[view].remove();
				_this5[view] = null;
			});

			// external events
			for (var key in this.visModelEvents) {
				if (this.visModelEvents.hasOwnProperty(key)) {
					this.visModel.off(key, this.visModelEvents[key], this);
				}
			}

			this.ownerWidget.model.off('change:searchFilters', this.onChangeLocalFilter, this);
			this.ownerWidget.model.off('change:localFilters', this.onChangeLocalFilter, this);
			this.ownerWidget.model.off('change:properties', this.onChangeProperties, this);
			this.ownerWidget.model.off('change:fillColor', this.onChangeProperties, this);
			this.ownerWidget.model.off('change:conditions', this.onChangeConditions, this);
			this.ownerWidget.model.off('change:possibleKeyDrivers', this.onChangePossibleKeyDrivers, this);

			if (this._tabs) {
				this._tabs.remove();
				this._tabs = null;
			}

			var content = this.ownerWidget.content;
			content && content.off('all', this.onAllContentChanges, this);

			var visDefinitions = this.dashboardApi.getFeature('VisDefinitions');
			visDefinitions.off('refresh:definition', this.onRefreshVisDefinition, this);
			visDefinitions.off('refresh:all', this.onRefreshAllVisDefinition, this);

			var colorsService = this.dashboardApi.getFeature('Colors');
			colorsService.off('theme:changed', this.onChangeProperties, this);

			if (finalRemove) {
				MemUtil.destroy(this);
			}
		},

		onChangePageContext: function onChangePageContext(event) {
			var _this6 = this;

			return this.onChangeFilter(event).catch(function (e) {
				var LOG_LEVEL = e && e.message === 'dwErrorStaleRequest' ? 'debug' : 'error';
				_this6.logger[LOG_LEVEL](e);
			});
		},

		_rebuildVis: function _rebuildVis(event, options) {
			this._rebuildPending = true;
			var transactionToken = options && options.extraInfo && options.extraInfo.payloadData && options.extraInfo.payloadData.transactionToken;
			return this.transactionApi.registerTransactionHandler(transactionToken || {}, this.ownerWidget.id + '_rebuildvis', function () {
				var _this7 = this;

				this.ownerWidget._rebuildVis(event).then(function () {
					_this7._rebuildPending = false;
				});
			}.bind(this), options);
		},

		_getRenderTransaction: function _getRenderTransaction(renderOptions) {
			var transaction = renderOptions && renderOptions.extraInfo && renderOptions.extraInfo.payloadData && renderOptions.extraInfo.payloadData.transactionToken;
			return !transaction || transaction.transactionId ? transaction : null;
		},

		_reRender: function _reRender(options) {
			if (!this._rebuildPending) {
				if (this.ownerWidget.isVisible()) {
					var transactionToken = this._getRenderTransaction(options);
					// Only attempt to render requests with:
					// 1. no transaction token
					// 2. valid transaction token
					if (!transactionToken || this.transactionApi.isValidTransaction(transactionToken)) {
						return this.transactionApi.registerTransactionHandler(transactionToken || {}, this.ownerWidget.id, function (opt) {
							if (this.ownerWidget) {
								var visApi = this.ownerWidget.getAPI().getVisApi();
								return visApi.getRenderSequence().reRender(this._mergeOptions(opt));
							}
							return Promise.resolve();
						}.bind(this), options);
					}
				} else {
					this.ownerWidget.setReRenderOnShow(options);
				}
			}
			return Promise.resolve();
		},

		/**
  * @description This function will take the first object in the array and merge the 'refresh' parameters from the other objects into it
  * @param {Array} of render options
  * @returns {Object} the merged options object or the first object if there are no others to merge
  */
		_mergeOptions: function _mergeOptions(options) {
			var mergedOptions = options[0];
			var length = options.length;
			if (length > 1) {
				for (var i = 1; i < length; i++) {
					mergedOptions.refresh = this._mergeRefreshOptions(mergedOptions.refresh, options[i].refresh);
					if (options[i].refreshAll) {
						mergedOptions.refreshAll = options[i].refreshAll;
					}
				}
			}
			if (mergedOptions.refresh && mergedOptions.refresh.data && mergedOptions.refresh.dataIfQueryChanged) {
				// data refresh trumps dataIfQueryChanged
				delete mergedOptions.refresh.dataIfQueryChanged;
			}
			return mergedOptions;
		},

		_mergeRefreshOptions: function _mergeRefreshOptions(refreshOptions, options) {
			Object.keys(options).forEach(function (key) {
				if (!refreshOptions.hasOwnProperty(key)) {
					refreshOptions[key] = options[key];
				} else {
					refreshOptions[key] = refreshOptions[key] || options[key];
				}
			});
			return refreshOptions;
		},

		/**
   * Called when a filter on the VisModel changes.
   * By default....
   *
   *    re-render the view when the event sender is not the same as the receiver
   *    re-render the selections
   */
		// HANDLER: visModel.on('change:filters')
		onChangeFilter: function onChangeFilter(event) {
			var _this8 = this;

			event = event || {};
			var changeEvent = event.changeEvent;
			var transactionToken = this.transactionApi.startTransaction(changeEvent && changeEvent.data && changeEvent.data.transactionToken);
			var sender = event && event.sender;
			return VisUtil.validateVisDefinition(this.content, this.dashboardApi, { visId: this.ownerWidget.model.visId }).then(function (isValid) {
				if (isValid) {
					return _this8._queryChanged().then(function (result) {
						result = result || {};
						var refreshData = result.isRenderNeeded;
						var extraInfo = void 0;
						var renderOptions = {
							refresh: {
								dataIfQueryChanged: refreshData,
								annotation: true
							}
						};

						if (_this8.filterIndicator) {
							_this8.filterIndicator.setSynchronizeDataFilters(result.synchDataFilterEntries);
							_this8.filterIndicator.update();
						}
						if (!refreshData) {
							// optimization to avoid making unnecessary render requests
							if (!changeEvent || changeEvent.isInScope(_this8.ownerWidget.getScope(), _this8.ownerWidget.getEventGroupId())) {
								if (changeEvent && !changeEvent.brushingChanged() && _.every(changeEvent.getItems(), function (item) {
									return item.getValueCount() === 0;
								})) {
									// If the filter doesn't have any value and it is not a brushing event, do not rerender.
									return;
								}
								// @todo is this necessary?
								if (result.context) {
									result.context.visAPI = _this8.visAPI;
								}
							} else {
								_this8.visModel.renderComplete();
								return;
							}
							// ensure we don't introduce unnecessary animations upon re-render with no data change
							extraInfo = {
								entryDuration: 'zero',
								payloadData: { transactionToken: transactionToken }
							};
						}
						_this8._reRender(_this8.appendExtraInfoToRenderOptions(renderOptions, event, extraInfo)).then(function () {
							if (!refreshData) {
								_this8.dashboardApi.triggerDashboardEvent('dataInVis:selected', { payloadData: _this8.ownerWidget, sender: _this8.ownerWidget.id });
								var isOriginatorOfSelection = event.changeEvent && event.changeEvent.senderMatches(_this8.ownerWidget.id);
								//isOriginator means this widget is the originator of the selection change (not just one that is responding to the selection change).
								_this8.ownerWidget.trigger('visevent:selectionchanged', { sender: _this8.ownerWidget.id, isSelectionOriginator: isOriginatorOfSelection });
								_this8.renderFocused(!sender || sender === _this8.ownerWidget.id);
							}
						});
					});
				}
			}).finally(function () {
				_this8.transactionApi.endTransaction(transactionToken);
			});
		},

		_queryChanged: function _queryChanged() {

			// TODO Wrap to async result when use new query API. Need to be cleaned once switch to query api.
			if (this.ownerWidget.useNewQueryApi()) {
				var internalQueryExecution = this.content.getFeature('DataQueryExecution.internal');
				return internalQueryExecution.queryChanged().then(function (changed) {
					return { isRenderNeeded: changed };
				});
			} else {
				return this.visModel.queryChanged();
			}
		},

		toggleRefreshTimerIndicator: function toggleRefreshTimerIndicator(event) {
			if (this.refreshTimerIndicator.$icon) {
				if (event.autoRefresh) {
					this.refreshTimerIndicator.$icon.removeClass('dataWidgetTimersNone');
				} else {
					this.refreshTimerIndicator.$icon.addClass('dataWidgetTimersNone');
				}
			}
		},

		/**
   * Called when a filter that only affects this widget changes.
   * Such as from the filter dialog.
   *
   *    re-render the selections
   */
		onChangeLocalFilter: function onChangeLocalFilter(event) {
			var renderOptions = {
				refresh: {
					data: true,
					annotation: true
				}
			};

			this.ownerWidget.updateMissingFilters();

			/* vis isn't tied to data anymore*/
			if (this._isViewSlotsEmpty()) {
				this._rebuildVis(event, this.appendExtraInfoToRenderOptions(renderOptions, event));
				return;
			}

			this.visModel.setPendingFilters(false); //cancel any pending filters

			var sender = event && event.sender;

			this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
			this.renderFocused(!sender || sender === this.ownerWidget.id);

			if (this.filterIndicator) {
				this.filterIndicator.update();
			}
		},

		onChangePendingFilter: function onChangePendingFilter() {
			this.visModel.setPendingFilters(true);
		},

		/**
   * Slots include vis slots and local filters slots
   */
		_isViewSlotsEmpty: function _isViewSlotsEmpty() {
			return !this.visualization.getSlots().getMappedSlotList().length && this.visModel.getLocalFilters().isEmpty();
		},

		_annotationHasChanged: function _annotationHasChanged(event) {
			var prev = event && event.changeEvent && event.changeEvent.prevValue;
			var curr = event && event.changeEvent && event.changeEvent.value;

			if (prev && prev.selectedAnnotations && prev.selectedAnnotations.length || curr && curr.selectedAnnotations && curr.selectedAnnotations.length) {
				return true;
			} else {
				return false;
			}
		},

		onChangeAnnotations: function onChangeAnnotations(event) {
			if (this._annotationHasChanged(event)) {
				var renderOptions = {
					refresh: {
						dataIfQueryChanged: true,
						annotation: true
					},
					extraInfo: {
						annotationRequest: true
					}
				};
				return this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
			}
			return Promise.resolve();
		},

		onVisible: function onVisible() {},

		isVisible: function isVisible() {
			return this.$el.parent().is(':visible');
		},

		/**
   * Called when a property (like a font or a colour) changes.
   * By default....
   *    re-render the view
   *    re-render the selections
   * Set queryManager and render options if one of the properties requiring data refresh have changed (currently maintainAxisScales)
   * @param event - an event containing the single property from the property collection that changed (eg: 'showAxisTitles').
   */
		// HANDLER: visModel.on('change:property')
		onChangeProperties: function onChangeProperties(modelEvent) {
			var event = {
				name: 'properties',
				value: modelEvent && modelEvent.value,
				sender: null,
				changeEvent: modelEvent,
				refreshData: modelEvent && modelEvent.origCollectionEvent && modelEvent.origCollectionEvent.dataRefresh
			};

			var renderOptions = {
				refresh: {}
			};

			if (event && event.refreshData || renderOptions.refresh.data) {
				if (_.isEmpty(renderOptions.refresh)) {
					renderOptions.refresh.data = true;
				}
			}
			// re-render the visualization
			var reRenderOptions = this.appendExtraInfoToRenderOptions(renderOptions, event);
			this._reRender(reRenderOptions);
		},

		/**
   * Called when the conditional formatting changes
   */
		onChangeConditions: function onChangeConditions(event) {
			var renderOptions = {
				refresh: {}
			};
			this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
		},

		/**
   * Called when the container is entered.
   */
		onEnterContainer: function onEnterContainer() {
			/* to be overridden */
		},

		/**
   * Called when the container is exited.
   */
		onExitContainer: function onExitContainer() {
			/* to be overridden */
		},

		/**
   * update animation settings for transitions from one state to another according to the following rules:
   * 1) use the default defined in the definition properties unless override is set to exactly 0 (to turn it off)
   * 2) if no animation properties in the definition, use the override value if its > 0
   * 3) if not in the def properties and not overridden by an action, animation should be off.
   * NOTE: by default overrideDefaultAnimationSpeed is -1 which denotes it is not enabled.
   */
		updateAnimationSettings: function updateAnimationSettings(visdefProperties) {
			var visDefAnimationSpeed = visdefProperties && visdefProperties.animationSpeed ? visdefProperties.animationSpeed : 0;
			this.animationSpeed = visDefAnimationSpeed ? visDefAnimationSpeed : this.DEFAULT_ANIMATION_SPEED;
			if (this.overrideDefaultAnimationSpeed === 0 || this.visModel.getSuppressViewAnimations()) {
				//If an action has explicitly turned animation off, respect that option.
				this.animationSpeed = 0;
			} else if (this.overrideDefaultAnimationSpeed > 0 && visDefAnimationSpeed === 0) {
				//If animation is not on, use the override if its not off (-1)
				this.animationSpeed = this.overrideDefaultAnimationSpeed;
			}
		},

		/**
   * VIPR supports animation effects (which are always enabled)
   * It is possible to do a fade-in/fade-out type animation for non-VIPR visualizations.
   * In this case, renderComplete should be called by animate not render.
   * NOTE: This style of animation has been DISABLED for Endor and is likely to be removed/reworked in future
   *		 as it simply makes non-VIPR visualizations flash needlessly (and appear slower).
   * @returns true if non-VIPR animation has been enabled for a particular visualization type
   */
		isAnimationEnabledForNonVIPRVisualizations: function isAnimationEnabledForNonVIPRVisualizations() {
			return this.animationType && this.animationType !== this.ANIMATION_TYPES.NONE;
		},

		/**
   * For now, for grid/summary/trend etc, there is only one animation effect by default which is to fade out and fade in
   */
		animate: function animate(renderInfo) {
			var _this9 = this;

			this.visModel.renderCompleteBeforeAnimation();
			if (this.isAnimationEnabledForNonVIPRVisualizations()) {
				var renderComplete = function renderComplete() {
					_this9.visModel.renderComplete(renderInfo);
				};
				this.updateAnimationSettings();
				$(this.el).fadeOut(0);
				//If doing fade-in/fade-out animation, we need to call render complete at the end.
				$(this.el).fadeIn(this.animationSpeed, renderComplete);
			}
		},

		/**
   * Override this method to take the selection and highlight it.
   */
		renderSelected: function renderSelected() {},

		/**
   * Override this method to style focused item.
   */
		renderFocused: function renderFocused() {},

		/**
   * The base render method is called at the end of the subtype render() to provide any actions required
   * once render is prepared and submitted to the rendering engine (eg: RAVE).
   *
   * Its important to note that due to the async nature of rendering, this method may be called prior
   * to the actual render being completed.
   * @param {Object} renderInfo - renderInfo passed to all render methods (originates in the render sequence).
   * @param {boolean default=true} callRenderComplete - true if this function is being called at the end of the type-specific view render
   *			so that renderComplete will be called.  VIPRView is an exception.
   *			VIPRView calls VisView:render to do common processing but needs to handle renderComplete itself because of the way it sets/decorates data.
   */
		render: function render(renderInfo) {
			var callRenderComplete = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

			this.animate(renderInfo); //Note: animate is overridden to do nothing for RAVE as it has effects support

			this.updateConditionalViewIndicator({
				data: renderInfo.data
			});

			if (callRenderComplete && !this.isAnimationEnabledForNonVIPRVisualizations()) {
				//If animation is enabled, we need to wait until the end of the animation to call render complete.
				//See (VisView:animate()). NOTE: This style of animation is disabled for Endor.
				this.visModel.renderComplete(renderInfo);
			}
			return Promise.resolve(this);
		},

		/**
   * Removes the icon view element and remove hide class from the rave output area whose role is application
   */
		removeIconView: function removeIconView() {
			if (!(this.hasUnavailableMetadataColumns() || this.hasMissingFilters())) {
				this._removeMissingDataColumnWarning();
			}

			if (this.$iconView) {
				this.$el.find('div[role="application"]').removeClass('hide');
				this.$el.parent().removeClass('showIconView');
				this.$iconView.remove();
				this.$iconView = null;
			}
			this._removeVisualizationDropZones();
		},

		_removeVisualizationDropZones: function _removeVisualizationDropZones() {
			var dropZonesOverlayState = this.content.getFeature('DropZonesOverlayState');
			if (dropZonesOverlayState && dropZonesOverlayState.isEnabled()) {
				dropZonesOverlayState.hide();
			}
		},

		/**
   * Render warning container for this widget visualization
   */
		_renderWarningContainer: function _renderWarningContainer($containerNode) {
			this._removeWarningContainer();
			this.$warningContainer = $('<div></div>', {
				'class': 'warningContainer'
			});
			$containerNode.append(this.$warningContainer);
		},

		/**
   * Remove warning container
   */
		_removeWarningContainer: function _removeWarningContainer() {
			if (this.$warningContainer) {
				this.$warningContainer.remove();
				this.$warningContainer = null;
			}
		},

		_renderMissingDataColumnWarning: function _renderMissingDataColumnWarning($containerNode) {
			this._removeMissingDataColumnWarning();
			if (this.hasMissingFilters() && this.filterIndicator && this.filterIndicator.$filter) {
				this.filterIndicator.$filter.addClass('dataWidgetFiltersNone');
			}
			this.$dataUnavailable = $('<div></div>', {
				'class': 'data-unavailable'
			});
			this._renderWarningContainer($containerNode);
			this.$warningContainer.append(this.$dataUnavailable);

			var $warningIcon = $('<div></div>', {
				'aria-label': StringResources.get('warning'),
				'class': 'warningIcon'
			});
			this.$dataUnavailable.append($warningIcon);
			Utils.setIcon($warningIcon, 'common-warning', StringResources.get('warning'));

			var $warningText = $('<div></div>', {
				'class': 'warningText',
				'text': StringResources.get('datasetItemsUnavailable')
			});
			this.$dataUnavailable.append($warningText);
		},

		_removeMissingDataColumnWarning: function _removeMissingDataColumnWarning() {
			if (this.$dataUnavailable) {
				this.$dataUnavailable.remove();
				this.$dataUnavailable = null;
			}
			this._removeWarningContainer();
		},

		/**
   * Shows icon of the chart type and hide the rave output area whose role is application
   */
		renderIconView: function renderIconView() {
			if (this._tabs) {
				this._tabs.hide();
			}
			var $containerNode = this._prepareRenderIconView();
			this._renderIconView($containerNode);
			this._renderVisualizationDropZones();
		},

		_renderVisualizationDropZones: function _renderVisualizationDropZones() {
			var dropZonesOverlayDOM = this.content.getFeature('DropZonesOverlayDOM');
			var dropZonesOverlayState = this.content.getFeature('DropZonesOverlayState');
			if (dropZonesOverlayState && dropZonesOverlayState.isEnabled() && !this.hasMissingFilters()) {
				if (dropZonesOverlayDOM.isMounted()) {
					dropZonesOverlayState.show();
				} else {
					dropZonesOverlayDOM.render();
				}
			}
		},

		/**
   * Updates the smart annotations indicator if one exists
   */
		updateSmartAnnotationsIndicator: function updateSmartAnnotationsIndicator() {
			var smartAnnotationsIndicator = this.content.getFeature('SmartsIndicator');
			if (smartAnnotationsIndicator) {
				smartAnnotationsIndicator.update();
			}
		},

		updateConditionalViewIndicator: function updateConditionalViewIndicator(options) {
			var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
			if (conditionalViewIndicator) {
				conditionalViewIndicator.update(options);
			}
		},

		/**
   * Updates the forecast indicator if one exists
   */
		updateForecastIndicator: function updateForecastIndicator() {
			if (this.forecastIndicator) {
				this.forecastIndicator.update();
			}
		},

		/*Subclass can override the function when necessary*/
		_renderIconView: function _renderIconView($containerNode) {
			$containerNode.append(this.$iconView);

			this.$el.find('div[role="application"]').addClass('hide');
			this.$el.parent().addClass('showIconView');

			this.visModel.renderCompleteBeforeAnimation();
			this.visModel.renderComplete();
		},

		/**
   * Returns the content node. Can be overriden by sub classes.
   */
		getContentNode: function getContentNode() {
			return this.contentNode ? $(this.contentNode) : this.$el;
		},

		_prepareRenderIconView: function _prepareRenderIconView() {
			var definition = this.visualization.getDefinition();
			var icon = definition.getPlaceholderIconUri();
			var caption = definition.getLabel();
			var $containerNode = this.getContentNode();
			if (this.hasUnavailableMetadataColumns() || this.hasMissingFilters()) {
				this._renderMissingDataColumnWarning($containerNode);
			} else if (this.$dataUnavailable) {
				this._removeMissingDataColumnWarning();
			}

			var template = dot.template(EmptyVisualizationTemplate);

			if (this.$iconView) {
				this.$iconView.remove();
			}
			this.$iconView = $(template({
				icon: icon,
				caption: caption,
				title: StringResources.get('LIVE_empty_visualization_hint_title'),
				description: StringResources.get('LIVE_empty_visualization_hint_description')
			}));
			return $containerNode;
		},

		hasMissingFilters: function hasMissingFilters() {
			return this.ownerWidget.getUnavailableLocalFilter().length > 0;
		},

		/**
   * Uses the widget size in a given render context
   */
		resizeToWidget: function resizeToWidget(renderInfo) {
			this.resize(renderInfo.widgetSize);
		},

		getLabel: function getLabel() {
			var label;
			var definition = this.visualization.getDefinition();
			if (definition) {
				label = definition.getLabel();
			} else {
				label = StringResources.get('visualizationLabel');
			}
			return label;
		},

		getDescription: function getDescription() {
			var label = this.getLabel();
			var columns = this._getColumnInformationForLabel();
			var description = StringResources.get('dataWidgetDescription', {
				widgetLabel: label,
				columnNames: columns
			});
			return description;
		},

		_getColumnInformationForLabel: function _getColumnInformationForLabel() {
			var columns = '';
			var mappingInfo = this.visualization.getSlots().getMappingInfoList();
			for (var i = 0; i < mappingInfo.length; i++) {
				if (i > 0) {
					columns += ', ';
				}
				columns += mappingInfo[i].dataItem.getLabel();
			}
			return columns;
		},

		getCurrentViewSelector: function getCurrentViewSelector() {
			return null;
		},

		getProperties: function getProperties() {
			if (this.visModel.getDefinition().properties) {
				return Promise.resolve(this.visModel.getDefinition().properties.slice(0));
			}

			return Promise.resolve([]);
		},

		/**
   * @returns true if any any dataItems is binned
   */
		hasBinnedDataItems: function hasBinnedDataItems() {
			return !!this.visualization.getSlots().getMappingInfoList().find(function (mappingInfo) {
				return !!mappingInfo.dataItem.getBinning();
			});
		},

		/**
   * Generate thumbnail
   *
   * @return {Promise}
   */
		generateThumbnail: function generateThumbnail() {
			var _this10 = this;

			var isDisabled = false;
			var isMappingIncomplete = false;

			if (this._isThumbnailDisabled()) {
				isDisabled = true;
			}

			if (!this.isMappingComplete()) {
				isMappingIncomplete = true;
			}

			var promise = void 0;
			if (isDisabled || isMappingIncomplete) {
				promise = Promise.resolve();
			} else {
				promise = this.dashboardApi.getDashboardSvc('.Thumbnail').then(function (thumbnailSvc) {
					return thumbnailSvc.generateMarkup(_this10.$el.get(0));
				});
			}
			return promise.then(function (thumbnail) {
				return {
					isMappingIncomplete: isMappingIncomplete,
					isDisabled: isDisabled,
					thumbnail: thumbnail
				};
			});
		},

		_isThumbnailDisabled: function _isThumbnailDisabled() {
			//TODO: Augment this to disable thumbnail for any declared unsupportedBrowsers
			// disable thumbnails only for IE11 and lower; Edge is supposedly behaving good in most cases
			if (BrowserUtils.isIE() && !BrowserUtils.isIEEdge()) {
				var defn = this.visModel.getDefinition();
				if (defn && defn.thumbnailConfig) {
					//Disable thumbnails for visualizaions that delcares not supporting certain browsers
					var _defn$thumbnailConfig = defn.thumbnailConfig.unsupportedBrowsers,
					    unsupportedBrowsers = _defn$thumbnailConfig === undefined ? [] : _defn$thumbnailConfig;

					return unsupportedBrowsers.indexOf('IE') !== -1;
				}
			}
			return false;
		},

		onSummaryDataCellError: function onSummaryDataCellError() {
			var errorMsg = StringResources.get('errorCellWarning');
			this.infoIndicator.addInfo([{
				id: 'error_summary_cell',
				title: errorMsg,
				items: [{
					id: 'error_summary_cell',
					label: errorMsg,
					isSubMessage: true
				}]
			}]);
		},

		appendExtraInfoToRenderOptions: function appendExtraInfoToRenderOptions(renderOptions, event, extraInfo) {
			event = event && event.changeEvent || event;
			if (event && event.data) {
				if (renderOptions.extraInfo) {
					renderOptions.extraInfo.payloadData = event.data;
				} else {
					renderOptions.extraInfo = {
						payloadData: event.data
					};
				}
			}

			if (renderOptions && renderOptions.extraInfo && extraInfo) {
				renderOptions.extraInfo = _.extend(renderOptions.extraInfo, extraInfo);
			}

			return renderOptions;
		}
	});

	return VisView;
});
//# sourceMappingURL=VisView.js.map