'use strict';
/*
* +------------------------------------------------------------------------+
* | Licensed Materials - Property of IBM
* | IBM Cognos Products: Dashboard
* | (C) Copyright IBM Corp. 2014, 2020
* |
* | US Government Users Restricted Rights - Use, duplication or disclosure
* | restricted by GSA ADP Schedule Contract with IBM Corp.
* +------------------------------------------------------------------------+
*/
define(['./DeprecateWidgetBase', '../../widgets/livewidget/nls/StringResources', 'jquery', '../../lib/@waca/core-client/js/core-client/utils/Deferred', 'underscore', './VisualizationCreator', './expandmode/VisExpandMode', './models/LiveWidgetModel', './features/FeatureLoader', '../../api/impl/Visualization', '../../visualizations/renderer/filter/FilterLabelHelper', '../../visualizations/vipr/VIPR', '../../visualizations/vipr/VIPRLibraries', './SdkShowError', './util/VisUtil', './nls/StringResources', '../../view/features/content/LiveWidgetInternal/LiveWidgetInternal', '../../dataSources/utils/DatasourceUtil', '../../lib/@waca/dashboard-common/dist/utils/MemUtil', 'react', 'react-dom', '../../lib/@waca/dashboard-common/dist/utils/SkeletonPlaceholder'], function (DeprecateWidgetBase, resources, $, Deferred, _, VisualizationCreator, VisExpandMode, LiveWidgetModel, FeatureLoader, VisualizationImpl, FilterLabelHelper, VIPR, VIPRLibraries, SdkShowError, VisUtil, StringResources, LiveWidgetInternal, DatasourceUtil, MemUtil, React, ReactDOM, SkeletonPlaceholder) {
'use strict';
/**
* INTENT: LiveWidget: The Live widget (LightWeight Interactive Visualization and Exploration widget) provides the controlling API
* for creating, modifying and rendering a live widget.
* TODO: The functionality in this class should be investigated and moved into more focused sub-objects.
*/
var LiveWidget = DeprecateWidgetBase.extend({
/**
* Ajax service instance
* @type {AjaxService}
*/
ajaxSvc: null,
palettePropertyToTypeMap: {
condColorPalette: 'ConditionalPalette',
contColorPalette: 'HeatPalette',
colorPalette: 'ColorPalette'
},
/**
* create the visualization given the spec.
*/
init: function init(options) {
var _this = this;
// arguments: id, initialConfigJSON, el, eventRouter
LiveWidget.inherited('init', this, arguments);
this.contentFeatureLoader = options.contentFeatureLoader;
this.visualization = this.content.getFeature('Visualization');
this.predictData = options.predictData;
this.interactivitySettings = options.interactivitySettings || {};
this.forceDisabledThumbnail = options.forceDisabledThumbnail;
this.isPreview = options.isPreview;
this.isPredictPreview = options.isPredictPreview;
this.isFocusModeDisabled = !!options.focusModeDisabled;
this._managesOwnQueries = options.managesOwnQueries || false;
this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
this.ajaxSvc = this.dashboardApi.getGlassCoreSvc('.Ajax');
this.transaction = this.dashboardApi.getFeature('Transaction');
this.featureSet = options.featureSet;
if (options.widgetModel) {
this.model = options.widgetModel;
} else if (options.widgetSpec) {
this.model = new LiveWidgetModel(options.widgetSpec);
} else {
this.logger.error('LiveWidget expects its widgetModel in the constructor.');
}
this.renderCompleteDeferred = new Deferred();
// Avoids clearing the renderCompleteDeferred in renderStart if first renderStart
// is not the one with parameter 'initial' (e.g: resize event)
this._isCurrentlyRendering = true;
// Track the number of complete loads on this widget
this._renderCount = 0;
//If true, widget render will be trigger on show, regardless of whether it was already rendered
this.rerenderOnShow = null;
this.updateDecorationsOnShow = false;
//holds any dnd drop targets we create
this._dropTargets = [];
if (this.initialConfigJSON) {
this.bOpeningBoard = !!this.initialConfigJSON.visId;
this.options = options;
this.createFeatureLoaderAndVisualizationPromise = this.createFeatureLoaderAndVisualizationAPI();
}
// Expose the following interface methods to the data widget
// TODO: Remove once the API refactoring is complete
this.api = {
getId: this.getId.bind(this),
changeVisType: this.onPropertyUpdate.bind(this),
aggregate: this.changeAggregation.bind(this),
getDOM: this.getDOM.bind(this),
myClass: 'LiveWidget - api'
};
this._extendAPI({
getVisualization: this.getVisualizationApi.bind(this)
});
/**
* @deprecated
* Deprecated Widget APIs
*/
this._extendAPI({
getWidgetLocalFilters: this.getWidgetLocalFilters.bind(this),
getWidgetGlobalFilters: this.getWidgetGlobalFilters.bind(this),
getWidgetTopBottom: this.getWidgetTopBottom.bind(this),
getDataGridHelpers: this.getDataGridHelpers.bind(this),
getMatchingFeatures: this.getMatchingFeatures.bind(this),
getVisApi: function getVisApi() {
return _this.visAPI;
},
getVisId: this.getVisId.bind(this),
render: this.render.bind(this),
whenRenderStart: this.whenRenderStart.bind(this),
whenRenderComplete: this.whenRenderComplete.bind(this),
updateTitle: this.updateTitle.bind(this),
allowShowTabs: this.allowShowTabs.bind(this),
getSavedPrompts: this.getSavedPrompts.bind(this)
});
this._setupDND(options.widgetContainer);
this.colorsService = this.dashboardApi.getFeature('Colors');
},
getWidgetTopBottom: function getWidgetTopBottom() {
var topBottoms = [];
var formattedTopBottom = [];
if (this.visAPI) {
var filterLabelHelper = new FilterLabelHelper({ 'dataSource': this.visualization.getDataSource(), 'visAPI': this.visAPI });
topBottoms = this.visAPI.getTopBottomInfo();
if (topBottoms.length > 0) {
formattedTopBottom = filterLabelHelper.generateTopBottomListItems(topBottoms);
}
}
return formattedTopBottom;
},
/**
* Execute a callback. The method will only execute the callback if the object is not destroyed
* Typpically used in a promise callback because we might have destroyed the object before the promise was resolved.
* @param {Function} callback
*/
_prepareAsyncCallback: function _prepareAsyncCallback(callback) {
return function () {
if (!this._destroyed) {
return callback.apply(undefined, arguments);
}
return Promise.reject('The live widget object was destroyed');
}.bind(this);
},
registerLiveWidgetInternal: function registerLiveWidgetInternal() {
// State feature is a core feature that requires to be available prior to initialize
this.contentFeatureLoader.registerFeature(this.id, 'livewidget.internal', new LiveWidgetInternal({
widget: this
}));
},
initialize: function initialize() {
var _this2 = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.registerLiveWidgetInternal();
if (!this.visualization.getType()) {
var transaction = this.dashboardApi.getFeature('Transaction');
var transactionToken = transaction.startTransactionById(options.transactionId);
this.showLoadingAnimation({ loadingText: StringResources.get('processing') });
return this.content.getFeature('Visualization.SmartsRecommender').recommendBestVisualization(transactionToken).then(function () {
var featureLWSegment = _this2.dashboardApi.getFeature('LiveWidgetSegment');
if (featureLWSegment) {
var visId = _this2.visualization.getDefinition().getId();
featureLWSegment.track({ category: 'fromRecommendation', recommendation: visId });
}
return _this2._initialize(options);
}).finally(function () {
transaction.endTransaction(transactionToken);
});
} else {
return this._initialize(options);
}
},
_initialize: function _initialize() {
var _this3 = this;
return this.createFeatureLoaderAndVisualizationPromise.then(this._prepareAsyncCallback(function () {
_this3.visualization = _this3.content.getFeature('Visualization');
if (!_this3._ondataSourceChange) {
_this3._ondataSourceChange = _this3.visualization.on('change:dataSource', function () {
_this3.registerShapingModelEvents();
});
}
_this3.registerShapingModelEvents();
return _this3.createVisualization(_this3.content.getFeature('Visualization.legacy').getLegacyManagers());
}));
},
/**
* @private
**/
registerShapingModelEvents: function registerShapingModelEvents() {
var _this4 = this;
this._removeShapingModelEvents(); // Make sure we remove any existing events
var dataSource = this.visualization.getDataSource();
if (dataSource) {
// @todo we should introduce events for DataSourceAPI (ie. refresh)
var deprecatedDataSources = this.dashboardApi.getFeature('dataSources.deprecated');
if (deprecatedDataSources) {
deprecatedDataSources.getModule(dataSource.getId()).then(this._prepareAsyncCallback(function (module) {
_this4.shapingEventHandler = module.on('shapingmodel:changed', _this4._onShapingModelChange.bind(_this4));
}));
}
}
},
_onShapingModelChange: function _onShapingModelChange(event) {
if (this.visAPI) {
this.visAPI.clearModelInvalid();
this.visAPI.validateFilters();
}
//Stop widget refresh on noRefresh flag
if (!event.noRefresh) {
this._refreshWidgets(event);
}
this.trigger('shapingmodel:changed', event);
},
_removeShapingModelEvents: function _removeShapingModelEvents() {
if (this.shapingEventHandler) {
this.shapingEventHandler.remove();
this.shapingEventHandler = null;
}
},
createFeatureLoaderAndVisualizationAPI: function createFeatureLoaderAndVisualizationAPI() {
this.featureLoader = new FeatureLoader(this.getAPI(), this.dashboardApi);
return this.featureLoader.loadFeatures(this.featureSet);
},
getVisualizationApi: function getVisualizationApi() {
return this.content.getFeature('Visualization');
},
setFeatureEnabled: function setFeatureEnabled(name, isEnabled) {
this.featureLoader.setFeatureEnabled(name, isEnabled);
},
getFeature: function getFeature(name) {
return this.featureLoader.getFeature(name);
},
getMatchingFeatures: function getMatchingFeatures(matchCriteria) {
return this.featureLoader.getMatchingFeatures(matchCriteria);
},
getExtraRenderSequenceSteps: function getExtraRenderSequenceSteps() {
return this.featureLoader.getExtraRenderSequenceSteps();
},
isOptimizeForSize: function isOptimizeForSize() {
return this.optimizeForSize;
},
getInteractivitySettings: function getInteractivitySettings() {
return this.interactivitySettings;
},
/**
* NOTE: this is temporarily exposed to expose necessary APIs for the contextual grid. Do not use
* as these helpers will and should be removed.
*
* @returns an API of helpers useful for the contextual data grid
*/
getDataGridHelpers: function getDataGridHelpers() {
var _this5 = this;
return {
getData: function getData() {
return _this5.get('data');
},
getPageContextApi: function getPageContextApi() {
return _this5.pageContextAPI;
},
getVisApi: function getVisApi() {
return _this5.visAPI;
}
};
},
/**
* @param n - the n'th decoratorAPI (most visualizations have only 1 decoratorAPI
* (ie: 1 overall set (dataSet), 1 set of items, 1 set of points)
* Maps can have more.
* @returns the n'th decoratorAPI from the view (default to the first)
*/
getDecoratorAPI: function getDecoratorAPI(n) {
n = n || 0;
return this._callVisFunction('getDecoratorAPI', n);
},
getDecoratorAPIs: function getDecoratorAPIs() {
return this._callVisFunction('getDecoratorAPIs');
},
getCustomData: function getCustomData() {
return this._currVis && this._currVis.predictHandler && this._currVis.predictHandler.getCustomData && this._currVis.predictHandler.getCustomData();
},
/**
* Generate thumbnail
*
* @param {Object} [options] - generate options
* @param {Object} [options.size] - aspect ratio to generate
* @param {number} [options.size.width=150] - aspect ratio width
* @param {number} [options.size.height=100] - aspect ratio height
* @param {string} [options.type=svg] - type of thumbnail to generate
*
* @return {Promise}
*/
generateThumbnail: function generateThumbnail(options) {
return this._callVisFunction('generateThumbnail', options, Promise.resolve(undefined));
},
_initializeAppSettings: function _initializeAppSettings() {
//get the name of the host application service, so that live widget can
//'publish' features to it
this._hostApplicationName = this.dashboardApi.getAppConfig('hostApplicationName');
this._thumbnailConfig = this.dashboardApi.getAppConfig('thumbnail');
if ([false, 'false'].indexOf(this._thumbnailConfig) !== -1) {
this._thumbnailConfig = false;
}
},
/**
* @returns the fredIsRedAPI which implements api's to get colours by value.
*/
getFredIsRed: function getFredIsRed() {
if (this.boardModel && this.boardModel.properties && this.boardModel.properties.fredIsRed) {
return this._fredIsRed;
}
},
/**
* create a visualization.
* 1) create a module
* 2) load (or select) a visualization definition.
* 3) create the VisModelManager for this definition.
* 4) apply properties.
*
*/
createVisualization: function createVisualization(legacyManagers) {
var _this6 = this;
if (!this._visualizationCreator) {
this._visualizationCreator = new VisualizationCreator({
legacyManagers: legacyManagers,
ownerWidget: this,
widgetModel: this.model,
dashboardAPI: this.dashboardApi,
logger: this.logger,
initialConfigJSON: this.options.initialConfigJSON,
content: this.content,
contentFeatureLoader: this.contentFeatureLoader
});
}
var promise;
if (this.dashboardApi) {
promise = Promise.all([this.dashboardApi.getDashboardSvc('SynchronizeDataService')]).then(this._prepareAsyncCallback(function (_ref) {
var synchronizeDataService = _ref[0];
var synchData = synchronizeDataService.getSynchronizeData();
var boardModel = _this6.boardModel;
// get & init fredIsRed feature
_this6._fredIsRed = _this6.dashboardApi.getFeature('FredIsRed');
var pageContext = boardModel.get('pageContext');
if (_this6._visualizationCreator) {
_this6._visualizationCreator.setPageContext(pageContext && pageContext.getAPI());
_this6._visualizationCreator.setSynchronizeData(synchData);
return _this6._visualizationCreator.createVisualization();
}
}));
} else {
promise = this._visualizationCreator.createVisualization();
}
return promise;
},
/**
* Called when module used by this visualization is altered. As it affects a wide variety of objects, it's better to
* simply destroy the existing visualization and its models completely and build a new one of the same type
*
* @param {boolean} undoRedo -when true, this method will not persist values to the widget model (as they are already part of the undo
* information). when false or not defined, the VisualizationCreator.recreateVisualization() is responsible for
* persisting the dataset/mapping information to the widget model.
*
* @param {boolean} forceRerender true: force data widget to render even it was rendered already. It is introduced for
* situations such as when data widget responds to dataset ID change event.
*/
_rebuildVis: function _rebuildVis(event) {
var _this7 = this;
this.setNotRendered();
this.clearCurrentVisualization();
return this._visualizationCreator.recreateVisualization(event).then(this._prepareAsyncCallback(function () {
return _this7.render();
}));
},
setVisModelManager: function setVisModelManager(visModelManager, visAPI) {
this.visModelManager = visModelManager;
this.visAPI = visAPI;
this.visualization.on('change:slots', this._onChangeSlots, this);
},
/**
* Function used for setting the RerenderOnShow variable.
* @param isResizing we make this option available because when a user toggles
* we sometimes lose the dimensions of the visualization and then it will render
* with weird dimensions. This happens with xtab and table.
*/
setReRenderOnShow: function setReRenderOnShow(options) {
this.rerenderOnShow = options;
},
/**
* check if showTab is allowed
* @returns true if showTabs config is set true and not a in a preview or in focus mode
* otherwise return false
*/
allowShowTabs: function allowShowTabs() {
var showTabs = this.getDashboardApi().getAppConfig('showTabs') || false;
return showTabs ? !this.isPreview : this.isInFocusMode && this.isInFocusMode();
},
/**
* Update widget title based on recommendation from smarts
* @returns a promise which resolves when async request is done or fails.
*/
// updateTitle: function(event) {
// const options = {
// refresh : event && event.info && event.info.refresh || {},
// extraInfo: {
// payloadData : {
// transactionToken : event && event.transactionToken || {}
// }
// }
// };
// const transactionApi = this.dashboardApi.getFeature('Transaction');
// const transactionToken = event && event.transactionToken;
// if (!transactionToken || transactionApi.isValidTransaction(transactionToken)) {
// const handlerId = 'titleUpdate' + this.id;
// return transactionApi.registerTransactionHandler(transactionToken || {}, handlerId, function(opt) {
// return this._updateTitle(opt);
// }.bind(this), options);
// }
// return Promise.resolve();
// },
_onChangeSlots: function _onChangeSlots(event) {
this.updateTitle();
// when slot mapping becomes incomplete, clear custom data selections
if (this.visualization && !this.visualization.getSlots().isMappingComplete()) {
this._currVis && this._currVis.eventHandler && this._currVis.eventHandler.clearCustomDataSelection({
payloadData: {
transactionToken: event && event.transactionToken
}
});
}
},
updateTitle: function updateTitle() {
var _this8 = this;
var smartTitleFeature = this.content.getFeature('SmartTitle');
var smartTitleEnabled = smartTitleFeature && smartTitleFeature.shouldUseGeneratedTitle();
if (smartTitleEnabled === true) {
/**
* setting SMART title should pass runTimeOnly:true flag in payloadData because
* a) the action alone should not mark board dirty
* b) the action alone should not be added to undo redo stack
* c) we don’t want undo/redo and silent is too much of a hammer
*
* title is stored in widget model. title change happens together with widget action (eg. replace slot/ adding a widget)
* the board will be dirty after the widget action (vis transation)
* when the user undos the widget action, the previous model is restored with the older title
*/
var options = {
payloadData: {
runtimeOnly: true
}
};
if (this.visualization.getSlots().isMappingComplete()) {
return this.content.getFeature('Visualization.SmartsRecommender.deprecated').getRecommendedTitle().then(this._prepareAsyncCallback(function (title) {
var shouldSetTitle = smartTitleFeature && smartTitleFeature.shouldSetTitle(title);
if (shouldSetTitle) {
var titleHtml = smartTitleFeature && smartTitleFeature.getTitleHtmlWithPreviousFormat(title);
_this8._setTitle(title, titleHtml, options);
}
})).catch(this._prepareAsyncCallback(function (e) {
_this8.logger.error('Could not get title for vis');
_this8.logger.error(e);
_this8._setTitle(null, null, options);
}));
} else {
options.silent = true;
//Show no title when showing vis placeholder
this._setTitle(null, null, options);
return Promise.resolve();
}
} else {
return Promise.resolve();
}
},
_setTitle: function _setTitle(title, titleHtml, options) {
if (this.visModelManager) {
this.visModelManager.setTitle(title, titleHtml, options);
}
},
/**
* get widget title
* @returns a title
*/
getTitle: function getTitle() {
return this.visModelManager.getTitle();
},
/**
* @returns true if the property is not a 'general' property (i.e. is a
* visualization property that can be overriden by a user)
*/
isAnOverridableVisProperty: function isAnOverridableVisProperty(property) {
var commonProps = {
'fillColor': true,
'borderColor': true,
'transparency': true,
'showTitle': true,
'titleMode': true,
'saveTitle': true,
'queryRefresh': true
};
return !commonProps[property];
},
/**
* Re send the query when user clicks on Retry link
*/
_onRetry: function _onRetry() {
this.visAPI.clearModelInvalid();
this.visAPI.getRenderSequence().reRender({
refreshAll: true
});
},
/**
* Convert error type to warning for prompted sign on error
*/
_updateErrorType: function _updateErrorType(errorMsg) {
if (errorMsg && errorMsg === 'dwPromptSignonCancelWarning') {
return 'warning';
}
return LiveWidget.inherited('_updateErrorType', this, arguments);
},
/**
* Add Retry link after the warning message if message is a prompted sign on one.
*/
_updateErrorContainer: function _updateErrorContainer(msg, $errorContainer) {
if (msg === 'dwPromptSignonCancelWarning') {
var $retry = $('
' + resources.get('retry') + '
');
$retry.on('primaryaction', this._onRetry.bind(this));
// add Retry link
$errorContainer.append($retry);
}
},
destroy: function destroy() {
if (this._ondataSourceChange) {
this._ondataSourceChange.remove();
}
if (this.visualization) {
this.visualization.off('change:slots', this.updateTitle, this);
}
if (this._currVis) {
this._currVis.remove(true);
}
if (this.visModelManager) {
this.dashboardApi.getDashboardCoreSvc('TranslationService').deregisterView(this.visModelManager.id);
}
if (this.featureLoader) {
//Unload features loaded with the widget's feature loader as defined in com.ibm.bi.dashboard.live-features.
//eg: For explore: Visualization, vis-badges, NLT, summarizer, title fetcher, vis-image-capture etc.
// For liveWidget, only the summaryFeature is loaded in this way.
this.featureLoader.unloadFeatures();
}
this._dropTargets && this._dropTargets.forEach(function (target) {
return target.remove();
});
if (this.visExpandMode) {
this.visExpandMode.destroy();
}
if (this._visualizationCreator) {
this._visualizationCreator.destroy();
}
this._destroyCustomVisShowError();
LiveWidget.inherited('destroy', this, arguments);
// Clear an content to avoid an memory leak in case some objects are holding on to this
MemUtil.destroy(this);
},
_destroyCustomVisShowError: function _destroyCustomVisShowError() {
if (this.sdkShowError) {
this.sdkShowError.destroy();
this.sdkShowError = null;
}
},
setEventRouter: function setEventRouter() /*eventRouter, options*/{
LiveWidget.inherited('setEventRouter', this, arguments);
},
/**
* @returns the scope of this widget for selection purposes.
* TODO: The scope is currently just the tabId but should be the tabId + the eventGroupId.
* Need to understand why the groupId is based on the dataSetId.
*/
getScope: function getScope() {
return this.getContainerPageId();
},
/**
* Return the widget model that is persisted as part of the pin definition.
* This is the same as the original widget model EXCEPT special cases:
* - When filters from the source dashboard need to be merged to produce 'portable filters' in the pin.
* In this case, the model is cloned, widget-to-widget filters are cleared and local filters are assigned the merged result.
* - ContainerPageId should be removed so that new containerPageId get assigned PinOnDrop
* @returns the widget model that is persisted as part of the pin definition.
*/
getWidgetModelForPinning: function getWidgetModelForPinning() {
var widgetModelClone = $.extend(true, {}, this.model);
delete widgetModelClone.containerPageId;
var portableFilters = this.visAPI.getAllFiltersAsLocalFiltersForPinning();
if (portableFilters) {
widgetModelClone.localFilters = portableFilters;
delete widgetModelClone.filters;
return widgetModelClone;
}
return widgetModelClone;
},
/**
* @param {boolean} bForceRender, we only change this.isRendered to be true if needed.
*/
setNotRendered: function setNotRendered(bForceRender) {
if (bForceRender) {
this.isRendered = false;
}
},
/**
* This will clear the loading class, removing the icon.
*/
removeLoadingAnimation: function removeLoadingAnimation() {
clearTimeout(this.showLoadingAnimationTimer);
if (this.$loadingIndicatorEl) {
this.$loadingIndicatorEl.remove();
this.$loadingIndicatorEl = null;
}
},
/**
* This will add the loading class to the view $el, showing a loading icon
*/
showLoadingAnimation: function showLoadingAnimation() {
var _this9 = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (this.showLoadingAnimationTimer) {
clearTimeout(this.showLoadingAnimationTimer);
}
// todo livewidget_cleanup - doesn't seem right to access the internal of the livewidget here.
this.showLoadingAnimationTimer = setTimeout(function () {
if (!MemUtil.isDestroyed(_this9) && !_this9.$loadingIndicatorEl) {
_this9.$loadingIndicatorEl = $('');
_this9.$el.append(_this9.$loadingIndicatorEl);
var placeholderImage = void 0;
if (!options.loadingText) {
placeholderImage = _this9._getPlaceholder() || 'dashboard-analytics/images/placeholders/autoviz.svg';
//only show the placeholder icon on first render
if (_this9._firstRenderComplete) {
placeholderImage = null;
}
_this9.skeletonPlaceholder = ReactDOM.render(React.createElement(SkeletonPlaceholder, { shown: true, placeholderImage: placeholderImage }), _this9.$loadingIndicatorEl[0]);
} else {
_this9.skeletonPlaceholder = ReactDOM.render(React.createElement(SkeletonPlaceholder, { shown: true, placeholderText: options.loadingText }), _this9.$loadingIndicatorEl[0]);
}
}
}, 1000);
},
/**
* @override
*/
renderError: function renderError(msg) {
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var customVisErrorContext = this._getCustomVisErrorContext(params.errorInfo);
this._destroyCustomVisShowError();
if (customVisErrorContext) {
var hasError = this.hasError();
var $originalContentBeforeError = null;
if (!hasError) {
$originalContentBeforeError = this.$el.children();
$originalContentBeforeError.detach();
}
if (this.el) {
this.$el.find('.errorContainer').remove();
this.errorMessage = msg;
this.updateWidgetArialabel(msg);
this.sdkShowError = new SdkShowError(this.$el[0], customVisErrorContext.msgTitle, customVisErrorContext.msgBody, customVisErrorContext.msgIcon);
this.sdkShowError.showError();
this.addErrorDetailsHandler();
if (this.renderComplete) {
//If a renderComplete function is available, call it as we have rendered what we can and anything waiting on this widget to render shouldn't be blocked.
this.renderComplete();
}
if ($originalContentBeforeError) {
this._$originalContentBeforeError = $originalContentBeforeError;
}
}
} else {
LiveWidget.inherited('renderError', this, arguments);
}
},
_getCustomVisErrorContext: function _getCustomVisErrorContext() {
var errorInfo = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var context = void 0;
if (errorInfo && errorInfo.errorCode === VIPRLibraries.LOAD_DEFINITION_ERROR) {
var isCustomVisPermissionError = void 0;
var isCustomVisDisconnectError = errorInfo.id === 'visualizationPreview';
if (!isCustomVisDisconnectError) {
isCustomVisPermissionError = !VIPRLibraries.isSystemLib(errorInfo.id, VIPR.getSystemLibIds());
}
if (isCustomVisPermissionError || isCustomVisDisconnectError) {
errorInfo.isRendered = true;
context = {
msgTitle: isCustomVisPermissionError ? StringResources.get('extPermissionTitle') : StringResources.get('extVisNoSDKConnection'),
msgBody: isCustomVisPermissionError ? StringResources.get('extPermissionBody') : StringResources.get('extVisNoSDKPreviewMsg'),
msgIcon: isCustomVisPermissionError ? 'permission' : 'disconnect'
};
}
}
return context;
},
clearCurrentVisualization: function clearCurrentVisualization() {
if (this._currVis) {
this._currVis = null;
}
},
/**
* TODO: this api should not be in LiveWidget.js [Post-m31]
* @private
* @return {SlotAPI} Slot Model APIs for the slot that applies the infographic shape
**/
_getShapableSlotAPI: function _getShapableSlotAPI(options) {
options = options || {};
var oSlotAPIs = this.visualization.getSlots().getMappedSlotList();
if (!oSlotAPIs || oSlotAPIs.length === 0) {
return null;
}
var oSlotAPI;
var oDropNode = options.dropNode; //Drop node that accepts dropped in shape widget
if (oDropNode && oDropNode.dataset && oDropNode.dataset.slotId) {
oSlotAPI = this.content.getFeature('Visualization').getSlots().getSlot(oDropNode.dataset.slotId);
} else {
oSlotAPI = _.find(oSlotAPIs, function (slotAPI) {
return slotAPI.getDefinition().getProperty('shapable') === true; //Find the first shapable slot
});
}
return oSlotAPI;
},
onHeatScalePaletteChanged: function onHeatScalePaletteChanged() {
LiveWidget.inherited('onHeatScalePaletteChanged', this, arguments);
return this.visAPI.hasHeatByItem() ? this.visAPI.updateConditionalPalette() : Promise.resolve();
},
/**
* A custom palette was just deleted, update any model properties that reference that palette
* @param {string} paletteId - the id of the palette that was deleted
* @param {string} action - the action on the palette, either update or delete
*/
onCustomPaletteChanged: function onCustomPaletteChanged(paletteId, action) {
var _this10 = this;
LiveWidget.inherited('onCustomPaletteChanged', this, arguments);
var rerender = false;
var updateContionalPalette = false;
for (var propertyName in this.palettePropertyToTypeMap) {
if (this.visAPI.getPropertyValue(propertyName) === paletteId) {
rerender = true;
// Special case for conditional palettes, we need to update the cached colors in the
// conditional palette model object
if (propertyName === 'condColorPalette') {
updateContionalPalette = true;
}
if (action === 'delete') {
this.updateVisProperties({
id: propertyName,
value: this.colorsService.getDefaultPaletteName(this.palettePropertyToTypeMap[propertyName])
}, {
payloadData: {
// Can't under a delete of a palette
skipUndoRedo: true
}
});
}
}
}
var updateConditional = updateContionalPalette ? this.visAPI.updateConditionalPalette() : Promise.resolve();
return updateConditional.then(this._prepareAsyncCallback(function () {
if (rerender) {
return _this10.visAPI.getRenderSequence().reRender();
}
}));
},
/**
* Called when the page fill color changes
* We need to reRender to apply the proper foreground colors
*/
onPagefillColorChange: function onPagefillColorChange() {
DeprecateWidgetBase.inherited('onPagefillColorChange', this, arguments);
return this.visAPI.getRenderSequence().reRender();
},
onDashboardColorSetChanged: function onDashboardColorSetChanged() {
LiveWidget.inherited('onDashboardColorSetChanged', this, arguments);
return this.visAPI.getRenderSequence().reRender();
},
onColorPaletteChanged: function onColorPaletteChanged() {
LiveWidget.inherited('onColorPaletteChanged', this, arguments);
return this.visAPI.hasHeatByItem() ? this.visAPI.updateConditionalPalette() : Promise.resolve();
},
onFredIsRedChanged: function onFredIsRedChanged() {
if (this.isVisible()) {
return this.visAPI.getRenderSequence().reRender();
} else {
this.setReRenderOnShow({ resizing: true });
}
},
/**
* Register external events the data widget is interested in.
*
*/
// @Override event registration function called by WidgetBase.
registerEvents: function registerEvents() {
LiveWidget.inherited('registerEvents', this, arguments);
this.dashboardApi.on('pagecontext:filterContextUpdated', this.onPageContextChanged, this);
this.dashboardApi.on('pagecontext:brushingContextUpdated', this.onPageContextChanged, this);
this.dashboardApi.on('synchronize:pageContextFilters', this._setSynchronizePageContextFilters, this);
this.dashboardApi.on('ambiguousConnection:resolved', this.onAmbiguousConnectionResolved, this);
this.colorsService.on('fredIsRed:changed', this.onFredIsRedChanged, this);
},
unregisterEvents: function unregisterEvents() {
LiveWidget.inherited('unregisterEvents', this, arguments);
this.dashboardApi.off('widget:onDeleteAction', this.onDeleteAction, this);
this.dashboardApi.off('pagecontext:filterContextUpdated', this.onPageContextChanged, this);
this.dashboardApi.off('pagecontext:brushingContextUpdated', this.onPageContextChanged, this);
this.dashboardApi.off('synchronize:pageContextFilters', this._setSynchronizePageContextFilters, this);
this.dashboardApi.off('ambiguousConnection:resolved', this.onAmbiguousConnectionResolved, this);
this.colorsService.off('fredIsRed:changed', this.onFredIsRedChanged, this);
},
/**
* Register external events the data widget is interested in.
*
* @param eventRouter The external event router.
*/
registerWidgetChromeEvents: function registerWidgetChromeEvents(eventRouter) {
LiveWidget.inherited('registerWidgetChromeEvents', this, arguments);
// TODO: derived classes override this to setup handlers on view events or to fire requests to view
if (eventRouter) {
eventRouter.on('widget:onDeleteAction', this.onDeleteAction, this);
}
},
isVisible: function isVisible() {
return $(this.el).parent().is(':visible');
},
reRender: function reRender(extraInfo) {
if (this.isVisible()) {
return this.visAPI.reRender(extraInfo);
} else {
this.setReRenderOnShow({
refresh: {
data: true
},
extraInfo: extraInfo
});
return Promise.resolve({ isVisible: false });
}
},
/**
* render the visualization
* @param renderOptions - options that are used in rendering. Usually these are renderSequence options (like refresh)
* By default, render options are null and that performs a "full render". If it's not set, we assume we render for widgets support interactive.
* @returns a promise which is resolved with the results of the render
* (either the renderContext result or exception from the render sequence or a visibility indicator.)
*/
render: function render() {
var _this11 = this;
var renderOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var result = void 0;
if (this.isVisible()) {
console.debug('waiting to Render...');
this.isRendered = true;
result = Promise.all([this.whenContainerIsReady.promise, this.readyDfd.promise]).then(this._prepareAsyncCallback(function () {
// Ensure container is ready before calling setPreferred size
console.debug('wait complete...containerIsReady/getRenderSequence and render');
//Render won't be called until the visModelManager is created.
if (!_this11.visModelManager) {
_this11.visModelManager = _this11._visualizationCreator.visModelManager;
}
var options = {
undoRedoTransactionId: _this11.addPayloadData && _this11.addPayloadData.undoRedoTransactionId,
transactionToken: _this11.addPayloadData && _this11.addPayloadData.transactionToken
};
if (_this11._renderCount !== 0) {
renderOptions = renderOptions || {
refreshAll: true
};
}
_this11.setPreferredSize(_this11.visualization.getDefinition().getPreferredSize(), options);
return _this11.visAPI.getRenderSequence().render(renderOptions)
//Note: the renderSequence resolves with its renderContext.
.then(_this11._prepareAsyncCallback(function (renderSequenceRenderContext) {
var initialExpanded = _this11.dashboardApi.getAppConfig('expandWidget') || false;
return _this11.toggleExpanded(initialExpanded).then(function () {
return renderSequenceRenderContext;
});
}));
}));
} else {
console.debug('Widget Invisible. Skipping render');
//The widgetLoader calls render when any widget creation is complete...but if this widget is invisible (ie on invisible tab or aborted),
//render for a livewidget is designed to do nothing (for performance). It will render on show.
result = Promise.resolve({ isVisible: false });
}
return result;
},
_getDnDTargetData: function _getDnDTargetData() {
return {
el: this.el,
type: 'widget.live',
info: {
node: this.el,
contentId: this.content.getId()
}
};
},
/**
* NOTE: The widget accepts api is only called for A11Y support
* (It is called by CanvasController:addDataItemsOrAddWidget if this widget is the currently selected content on the dashboard.)
* For CA, this occurs when a user selects data in the tree and clicks Shift-rightarrow...other applications might have other gestures for this.
* For "normal Drag&Drop", see _setupDND (below) and VisDnD.
* @param {Object} source - the drag source object as defined by the caller and accepted by the implementation of VisDnD
* @returns true if the source is accepted by this widget
*/
accepts: function accepts(source) {
var visDnD = this.content.getFeature('VisDnD');
return visDnD && visDnD.accepts(source, this._getDnDTargetData());
},
/**
* NOTE: The widget onDrop api is only called for A11Y support
* (It is called by CanvasController:addDataItemsOrAddWidget if this widget is the currently selected content on the dashboard.)
* For CA, this occurs when a user selects data in the tree and clicks Shift-rightarrow...other applications might have other gestures for this.
* For "normal Drag&Drop", see _setupDND (below) and VisDnD.
* @param {Object} source - the drag source object as defined by the caller and accepted by the implementation of VisDnD
* @returns true if the source is accepted by this widget
*/
onDrop: function onDrop(source) {
var visDnD = this.content.getFeature('VisDnD');
visDnD && visDnD.onDrop(source, this._getDnDTargetData());
},
_getDnD: function _getDnD() {
// fall back to the old .DndManager
return this.dashboardApi.getFeature('DashboardDnd.internal') || this.dashboardApi.getFeature('.DndManager');
},
/**
* Creates our dnd nodes and handlers
* @param widgetContainer [optional] if defined, the container of this widget
*/
_setupDND: function _setupDND(widgetContainer) {
var _this12 = this;
this._initializeAppSettings();
var dndManager = this._getDnD();
var widgetFocus = '.widgetFocus *';
var _onDragEnter = function _onDragEnter(target) {
//if we're in focus mode
var node = target && target.info && target.info.node;
var isFocusMode = $(node).is(widgetFocus);
var dropTargetNode = node;
if (!isFocusMode && widgetContainer) {
dropTargetNode = widgetContainer;
}
_this12._dropTargetNode = dropTargetNode;
$(dropTargetNode).addClass('draggedOn');
_this12._showVisualizationDropZones();
};
var _onDragLeave = function _onDragLeave(target) {
$(_this12._dropTargetNode).removeClass('draggedOn');
_this12._hideVisualizationDropZones(target);
};
// Register live widget as a drop target
this._dropTargets.push(dndManager.registerDropTarget({
el: this.el,
type: 'widget.live',
info: function info(domNode, event) {
return {
dragInfo: {
position: {
x: event.clientX,
y: event.clientY
}
},
node: domNode,
contentId: _this12.content.getId(),
onDragEnter: _onDragEnter,
onDragLeave: _onDragLeave
};
}
}));
if (widgetContainer) {
this._dropTargets.push(dndManager.registerDropTarget({
el: widgetContainer,
type: 'widget.live',
info: function info(domNode, event) {
return {
dragInfo: {
position: {
x: event.clientX,
y: event.clientY
}
},
node: domNode,
contentId: _this12.content.getId(),
onDragEnter: _onDragEnter,
onDragLeave: _onDragLeave
};
}
}));
}
},
/**
* Returns the placeholder icon for the current vis definition
*/
_getPlaceholder: function _getPlaceholder() {
var def = this.visualization.getDefinition();
return def && def.getPlaceholderIconUri();
},
_showVisualizationDropZones: function _showVisualizationDropZones() {
if (this.visualization.getSlots().isMappingComplete()) {
var dropZonesOverlayDOM = this._getContent().getFeature('DropZonesOverlayDOM');
var dropZonesOverlayState = this._getContent().getFeature('DropZonesOverlayState');
if (dropZonesOverlayState && dropZonesOverlayState.isEnabled()) {
if (dropZonesOverlayDOM.isMounted()) {
dropZonesOverlayState.show();
} else {
dropZonesOverlayDOM.render();
}
dropZonesOverlayDOM.reassessDropTarget(this._dropTargetNode);
}
}
},
_hideVisualizationDropZones: function _hideVisualizationDropZones(target) {
var dropZonesOverlayState = this._getContent().getFeature('DropZonesOverlayState');
if (dropZonesOverlayState && dropZonesOverlayState.isEnabled() && this.visualization.getSlots().isMappingComplete()) {
var targetInfo = target && target.info;
//overlay drop zones should be hidden for:
//1) moving from this widget to the canvas (targetInfo is undefined in the new target in this case)
//2) moving from this widget to another widget (target will have a different contentId than ours)
//3) moving from this widget to an overlay in another widget (contentId's are defined in targets for both overlays and widgets)
if (!targetInfo || !targetInfo.node || !targetInfo.contentId || targetInfo.contentId !== this.content.getId()) {
dropZonesOverlayState.hide();
}
}
},
/*
* Called after card is animated into view
*/
postRenderAnimation: function postRenderAnimation() {
setTimeout(function () {
// useful for IT test. We are adding a flag to indicate the Focus widget is done with its expanding animation.
// With IT tests, events like click and drag will miss their target if the elements are moving in the page.
$('.dialogBlocker .card').addClass('doneAnim');
}, 500);
setTimeout(this.resize.bind(this), 50);
},
/**
* Externally consumable api to access the local filters of the widget in text form.
* @returns an array of all the filters with the following params.
ariaDeleteLabel: "Press DELETE key to delete filter."
ariaEditLabel: "Press ENTER key to delete filter."
dataId: "{"origin":"visualization","sourceId":"model000001629afa21a4_00000002","scope":"page1","eventGroupId":"page1:1","huns":[{"hierarchyUniqueName":"sales_and_marketing_csv.Product_line"}]}"
deleteEnabled : true
deleteLabel : "DeleteFilter"
description : "Includes: Personal Accessories"
editEnabled : false
editLabel : "Edit filter"
svgHref : ""#common-filter""
title : "Product line"
toolTip : "Includes: Personal Accessories"
type : "Filter"
*/
getWidgetLocalFilters: function getWidgetLocalFilters() {
var formattedFilters = [];
if (this.visAPI) {
var filters = this.visAPI.getFilterInfo();
if (filters && filters.localFilters && filters.localFilters.length > 0) {
var filterLabelHelper = new FilterLabelHelper({ 'dataSource': this.visualization.getDataSource(), 'dashboardApi': this.dashboardApi });
formattedFilters = filterLabelHelper.generateFilterListItems(filters.localFilters);
}
}
return formattedFilters;
},
/**
* Externally consumable api to access the global filters of the widget in text form.
* @returns an array of all the filters with the following params.
ariaDeleteLabel: "Press DELETE key to delete filter."
ariaEditLabel: "Press ENTER key to delete filter."
dataId: "{"origin":"visualization","sourceId":"model000001629afa21a4_00000002","scope":"page1","eventGroupId":"page1:1","huns":[{"hierarchyUniqueName":"sales_and_marketing_csv.Product_line"}]}"
deleteEnabled : true
deleteLabel : "DeleteFilter"
description : "Includes: Personal Accessories"
editEnabled : false
editLabel : "Edit filter"
svgHref : ""#common-filter""
title : "Product line"
toolTip : "Includes: Personal Accessories"
type : "Filter"
*/
getWidgetGlobalFilters: function getWidgetGlobalFilters() {
var filters = [];
var formattedFilters = [];
var filterLabelHelper = new FilterLabelHelper({ 'dataSource': this.visualization.getDataSource(), 'dashboardApi': this.dashboardApi });
filters = this.visAPI.getFilterInfo();
if (filters.filters.length > 0) {
formattedFilters = filterLabelHelper.generateFilterListItems(filters.filters);
}
return formattedFilters;
},
getVisId: function getVisId() {
return this.model.visId;
},
/**
* Called when a hidden widget is shown
*/
onShow: function onShow(options) {
var result = void 0;
if (!this.isRendered || this.rerenderOnShow) {
//If rendering non interactive (eg: thumbnails), disable features which are interactive-only - otherwise, enable them.
var isInteractive = !(options && options.isBackgroundRun);
this.featureLoader.setMatchingFeaturesEnabled('interactive-only', isInteractive);
//renderOptions are for this onShow...by default, do a full render (renderOptions=null))
//but honour renderOptions carried forward in the rerenderOnShow member.
var renderOptions = this.isRendered && this.rerenderOnShow ? this.rerenderOnShow : null;
if (isInteractive) {
this.rerenderOnShow = null;
} else {
//add rerenderOnShow options for the next, interactive onShow
this._addInteractiveStepsToRerenderOnShow();
}
result = this.render(renderOptions, isInteractive);
} else {
this._callVisFunction('onVisible');
result = Promise.resolve();
}
if (this.updateDecorationsOnShow) {
var decoratorAPI = this.getDecoratorAPI();
if (decoratorAPI) {
decoratorAPI.updateDecorations();
}
this.updateDecorationsOnShow = false;
}
return result;
},
//Get all of the render sequence steps that are associated with interactive-only features
//and set them to be refreshed.
_addInteractiveStepsToRerenderOnShow: function _addInteractiveStepsToRerenderOnShow() {
var _this13 = this;
var stepIds = this.featureLoader.getMatchingFeatureStepIds('interactive-only');
if (stepIds.length) {
this.rerenderOnShow = this.rerenderOnShow || {};
this.rerenderOnShow.refresh = this.rerenderOnShow.refresh || {};
stepIds.forEach(function (stepId) {
_this13.rerenderOnShow.refresh[stepId] = true;
});
}
},
_completeRenderStart: function _completeRenderStart(renderState) {
if (this.renderStartDeferred) {
this.renderStartDeferred.resolve(renderState);
this.renderStartDeferred = null;
}
},
/**
* Called everytime we start a new render
*
* @param {boolean} [initial=false] - whether is initial render or not
* @param {boolean} [resizing=false] - whether is rendering due to a resize
* @param {boolean} [hideToolbar=true] - whether the toolbar should be hidden as part of the render or not
*
* @return {boolean} - true if the renderCompleteDeferred was reset. Really only used for unit tests
*/
renderStart: function renderStart() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
hideToolbar: true
};
var hideToolbar = options.hideToolbar === undefined ? true : !!options.hideToolbar;
var renderState = {
renderCount: this._renderCount,
isInitial: !!options.initial,
isResizing: !!options.resizing
};
if (renderState.isResizing && hideToolbar) {
// TODO: revisit triggering this event in livewidget
this.dashboardApi.triggerDashboardEvent('widget:hideToolbar');
}
//only try to trigger title preparation if the widget is being rendered (and visible)
this._triggerTitlePreparation();
this.trigger('renderStart', renderState);
var content = this._getContent();
if (content) {
var stateApi = content.getFeature('state.internal');
stateApi.setStatus(stateApi.STATUS.RENDERING);
}
// Don't do anything for the initial render since we've created renderCompleteDeferred was created in the constructor
if (options.initial || this._isCurrentlyRendering) {
this._isCurrentlyRendering = true;
this._completeRenderStart(renderState);
return false;
}
this._isCurrentlyRendering = true;
this.renderCompleteDeferred = new Deferred();
this._completeRenderStart(renderState);
return true;
},
/**
* Since the render process can be async, this method exists to allow views to notify their model (and, in turn, users of
* this widget) that render is complete. via the event router. NOTE: The api of the data widget is sent as the payload of
* this event.
*
* @param {object} renderInfo - information about rendering
*/
renderComplete: function renderComplete() {
var renderInfo = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var resizeFlag = !!(renderInfo && renderInfo.visView && renderInfo.visView.resizing);
var renderingNewData = !!(renderInfo && renderInfo.visView && renderInfo.visView.getRenderingNewData());
//isResizing is only true if this render is ONLY resizing....
//ie: subsequent tasks may not need to do any work if only resizing but would if data changes and we're resizing as well.
var isResizing = resizeFlag && !renderingNewData;
this._isCurrentlyRendering = false;
this._renderCount++;
var stateApi = this.content.getFeature('state.internal');
stateApi.setStatus(stateApi.STATUS.RENDERED);
this.$el.attr('renderCount', this._renderCount);
this.$el.attr('appcues-data-id', 'widget-content');
this.dashboardApi.triggerDashboardEvent('rendered', this.api);
this.trigger('renderComplete', {
isResizing: isResizing,
renderCount: this._renderCount
});
// For widgets that have truthy expandOnRender property, open the dataset panel
if (this.get('expandOnRender')) {
// Remove the flag so we no longer open the dataset panel on the next time
this.set({
expandOnRender: undefined
}, {
merge: true,
remove: true,
silent: true
});
this._invokeLifeCycleHandlers('pre:widget.maximize', {
id: this.id
});
}
this.renderCompleteDeferred.resolve({
renderCount: this._renderCount,
isResizing: isResizing
});
this.updateDescription(this._getVisualizationDefaultLabel());
if (this.layoutAPI && this.layoutAPI.updateTitle) {
//Title may have changed silently during render - send update event to ensure it's in sync
var smartTitleEnabledConfig = this.getDashboardApi().getAppConfig('smartTitle');
var smartTitleFeature = this.content.getFeature('SmartTitle');
var smartTitleEnabled = smartTitleEnabledConfig || smartTitleFeature && smartTitleFeature.shouldUseGeneratedTitle();
if (smartTitleEnabled) {
this.layoutAPI.updateTitle();
}
}
},
_callVisFunction: function _callVisFunction(functionName, args, defaultReturn) {
if (this._currVis && typeof this._currVis[functionName] === 'function') {
return this._currVis[functionName].apply(this._currVis, Array.isArray(args) ? args : [args]);
}
return defaultReturn;
},
_getVisualizationDefaultLabel: function _getVisualizationDefaultLabel() {
var description = this._callVisFunction('getDescription', null, resources.get('visualizationLabel'));
description = resources.get('WidgetLabelWithDescripion', {
label: description,
description: resources.get('f10KeyDescription')
});
return description;
},
_triggerTitlePreparation: function _triggerTitlePreparation() {
if (!this._titlePrepDone) {
if (this.widgetChromeEventRouter) {
this.widgetChromeEventRouter.trigger('title:containerReady', this);
this._titlePrepDone = true;
}
}
},
addTranslationIcon: function addTranslationIcon(multilingualAttributesInfo) {
var _this14 = this;
var num = 0;
var widgetTitleTranslationIconAdded = false;
var decoratorAPI = this.getDecoratorAPI();
var isVizDecarationUpdated = false;
var translationService = this.dashboardApi.getDashboardCoreSvc('TranslationService');
multilingualAttributesInfo.forEach(function (_ref2) {
var propertyParentModel = _ref2.propertyParentModel,
propertyName = _ref2.propertyName;
// Handle the widget title
if ((propertyName === 'titleHtml' || propertyName === 'name') && !widgetTitleTranslationIconAdded) {
var header = _this14.$el.parent().find('.widgetHeader');
if (!translationService.hasTranslationIcon(header)) {
translationService.appendTranslationIcon(header);
widgetTitleTranslationIconAdded = true;
num++;
}
} else {
var icons = _this14._decorateWithTranslationIcon(decoratorAPI, propertyParentModel, '!');
isVizDecarationUpdated = icons > 0;
num += icons;
}
});
if (isVizDecarationUpdated) {
if (this.isVisible()) {
if (decoratorAPI) {
decoratorAPI.updateDecorations();
}
} else {
this.updateDecorationsOnShow = true;
}
}
return num;
},
removeTranslationIcon: function removeTranslationIcon(multilingualAttributesInfo) {
var _this15 = this;
var num = 0;
var isTitleDecorationCleared = false;
var decoratorAPI = this.getDecoratorAPI();
var isVizDecarationUpdated = false;
var translationService = this.dashboardApi.getDashboardCoreSvc('TranslationService');
multilingualAttributesInfo.forEach(function (_ref3) {
var propertyParentModel = _ref3.propertyParentModel,
propertyName = _ref3.propertyName;
if ((propertyName === 'titleHtml' || propertyName === 'name') && !isTitleDecorationCleared) {
var header = _this15.$el.parent().find('.widgetHeader');
if (translationService.hasTranslationIcon(header)) {
translationService.removeTranslationIcon(header);
isTitleDecorationCleared = true;
num++;
}
} else {
var icons = _this15._decorateWithTranslationIcon(decoratorAPI, propertyParentModel, '');
isVizDecarationUpdated = icons > 0;
num += icons;
}
});
if (isVizDecarationUpdated) {
if (this.isVisible()) {
if (decoratorAPI) {
decoratorAPI.updateDecorations();
}
} else {
this.updateDecorationsOnShow = true;
}
}
return num;
},
_decorateWithTranslationIcon: function _decorateWithTranslationIcon(decoratorAPI, propertyParentModel, value) {
var num = 0;
var slots = this.content.getFeature('Visualization').getSlots().getSlotList();
var mappedSlots = this.content.getFeature('Visualization').getSlots().getMappedSlotList();
var mappedSlotsNameSet = mappedSlots.reduce(function (map, slot) {
map[slot.getId()] = true;
return map;
}, {});
var slotIndex = -1;
slots.forEach(function (slot) {
var slotDef = slot.getDefinition();
var isMapped = mappedSlotsNameSet[slot.getId()];
if (isMapped || slotDef.getProperty('mldIndicatorIfUnmapped')) {
slotIndex++;
if (slotDef.getProperty('mldTitleProperty') !== propertyParentModel.id || !propertyParentModel.multilingual) {
return;
}
if (isMapped) {
var dataItems = slot.getDataItemList();
dataItems.forEach(function (dataItem) {
var id = dataItem.getColumnId();
// Skip the virtual multi measure series data item
if (id !== '_multiMeasuresSeries') {
if (decoratorAPI) {
decoratorAPI.decorateItem(slotIndex, 'attentionBadge', value);
}
num++;
}
});
} else if (slotDef.getProperty('mldIndicatorIfUnmapped')) {
if (decoratorAPI) {
decoratorAPI.decorateItem(slotIndex, 'attentionBadge', value);
}
num++;
}
}
});
return num;
},
onAuthoringMode: function onAuthoringMode() {
LiveWidget.inherited('onAuthoringMode', this, arguments);
// Update the description when we switch modes (e.g. remove F10 instruction to launch the focus view from the widget
// description
this.updateDescription(this._getVisualizationDefaultLabel());
},
onConsumeMode: function onConsumeMode() {
LiveWidget.inherited('onConsumeMode', this, arguments);
// Update the description when we switch modes
this.updateDescription(this._getVisualizationDefaultLabel());
},
isInFocusMode: function isInFocusMode() {
return this._isMaximized;
},
/**
* @returns true when the widget has completed its first render. NOTE: Currently, the change is restricted to the needs of
* createLiveWidget (first render only). An additional change should be made to allow a caller to identify when
* renders are complete for subsequent interactive operations.
*/
isFirstRenderComplete: function isFirstRenderComplete() {
return this._renderCount > 0;
},
/**
* Return a promise that will be resolved when the widget starts rendering
* @returns {Promise} resolved when widget starts rendering
*/
whenRenderStart: function whenRenderStart() {
if (!this.renderStartDeferred) {
this.renderStartDeferred = new Deferred();
}
return this.renderStartDeferred.promise;
},
/**
* Return a promise that will be resolved when the widget is rendered.
*
* @returns
*/
whenRenderComplete: function whenRenderComplete() {
return this.renderCompleteDeferred.promise;
},
/**
* Process changes on the pageContext.
* @param event - the event payload (which includes the pageContextResult API object)
*/
onPageContextChanged: function onPageContextChanged(event) {
// Make sure the event matches the assetId which is unique accross all sources.
if (!this.visualization || !this.visualization.getDataSource()) {
return;
}
if (event.ignorePageContextChanged && event.ignorePageContextChanged()) {
return;
}
if (event.noSelfRefresh && event.noSelfRefresh() && event.senderMatches && event.senderMatches(this.id)) {
return;
}
if (event.isInScope(this.getScope())) {
this.visAPI.clearModelInvalid();
if (this.isVisible()) {
this._callVisFunction('onChangePageContext', {
eventName: 'change:pagecontext',
changeEvent: event
});
} else {
this.rerenderOnShow = { refresh: { data: true, annotation: true } };
}
}
},
onAmbiguousConnectionResolved: function onAmbiguousConnectionResolved(payload) {
var state = this.visAPI.getInvalidReason();
if (payload && payload.sourceId === this.visAPI.getModule().getSourceId() && state && (state.code === 'CQE-801' || state.code === 'CQE-802')) {
// Listen and refresh widget only after an ambiguous connection is resolved and widget is still
// in ambiguous connection error state.
this.reRender({
refresh: true
});
}
},
_sourceIdsMatch: function _sourceIdsMatch(payload, module) {
payload = payload || {};
var sourceIds = payload.getSourceIds ? payload.getSourceIds() : [];
return module ? _.contains(sourceIds, module.getSourceId()) : false;
},
_assetIdsMatch: function _assetIdsMatch(payload, module) {
payload = payload || {};
var assetIds = payload.getAssetIds ? payload.getAssetIds() : [];
return module ? _.contains(assetIds, module.getAssetId()) : false;
},
idsNotInModule: function idsNotInModule(sourceIds, assetIds) {
var module = this.visAPI.getModule();
var payload = {
getSourceIds: function getSourceIds() {
return sourceIds;
},
getAssetIds: function getAssetIds() {
return assetIds;
}
};
if (_.isArray(sourceIds) && !!sourceIds.length && !this._sourceIdsMatch(payload, module)) {
return true;
}
return _.isArray(assetIds) && !!assetIds.length && !this._assetIdsMatch(payload, module);
},
changeAggregation: function changeAggregation(mapping, newAggregationType, options) {
this.visModelManager.changeAggregationType(mapping, newAggregationType, options);
},
/**
* check if the widget supports contextual grid
* @return {Promise} - promise resolves with true if contextualGrid is supported
*/
isContextualGridEnabled: function isContextualGridEnabled() {
var _this16 = this;
return this.visualizationDfd.promise.then(this._prepareAsyncCallback(function () {
return _this16.doesWidgetSupportContextualGrid();
}));
},
/**
* @override
* Notify Properties pane on chrome getting selected as well
*/
onChromeSelected: function onChromeSelected(isGroupSelect) {
LiveWidget.inherited('onChromeSelected', this, arguments);
this.onFocus();
if (this.isAuthoringMode && this.widgetChromeEventRouter && !isGroupSelect) {
this.widgetChromeEventRouter.trigger('title:chromeSelected');
}
},
/**
* Handler for Widget chrome deselect event
*/
onChromeDeselected: function onChromeDeselected() {
LiveWidget.inherited('onChromeDeselected', this, arguments);
this.focusOn = false;
if (this.isAuthoringMode && this.widgetChromeEventRouter) {
this.widgetChromeEventRouter.trigger('title:chromeDeselected');
}
this.toggleLassoMode(false);
},
/**
* Handler for Focus event Gets list of available visualizations for the widget and notifies Property pane so that it can
* show properties to change
*/
onFocus: function onFocus() {
var _this17 = this;
return this.readyDfd.promise.then(this._prepareAsyncCallback(function () {
_this17.refreshPropertiesPane();
_this17.focusOn = true;
}));
},
/**
* Event handler: Process a change to the UI for a property
*
* @param info = A structure from the UI including: 1) the category of the property (the property name or class), 2) the new
* value
*/
onPropertyUpdate: function onPropertyUpdate(info) {
if (this.isAnOverridableVisProperty(info.category)) {
var prop = {
id: info.category,
value: info.item
};
/* since changing any of auto binning properties make changes in data, the local filters will be removed. so,
need to create new transaction to keeps track of local filters for undo changes in auto binning properties.
*/
this.updateVisProperties(prop, {
payloadData: {
undoRedoTransactionId: info.transactionId || info.undoRedoTransactionId,
transactionToken: info.transactionToken
},
silent: info.silent
});
} else if (info.category === 'saveTitle') {
if (this.widgetChromeEventRouter) {
this.widgetChromeEventRouter.trigger('title:updateModel', info);
}
} else {
// if it wasn't a data widget-specific property, pass it down to the baseWidget class to see if wants to handle it
LiveWidget.inherited('onPropertyUpdate', this, [info]);
}
},
/**
* @returns the DOM element for this widget.
*/
getDOM: function getDOM() {
return this.el;
},
//avoid the term expandView as this denotes the view part but not the general mode.
getExpandViewContent: function getExpandViewContent(data, $container) {
var _this18 = this;
$container.addClass('liveWidget');
return this.visExpandMode.getExpandModeContent($container).catch(function (err) {
_this18.renderError(err);
return Promise.reject(err);
});
},
/**
* Triggers event in VisModelManager for exiting the widget
*/
onExitContainer: function onExitContainer() {
this._callVisFunction('onExitContainer');
},
/**
* Triggers event in VisModelManager for entering the widget
*/
onEnterContainer: function onEnterContainer() {
this._callVisFunction('onEnterContainer');
},
/**
* Called on restoring widget from focus view to original state (from ExpandedView) after the animation/transition from
* maximize to original state is done
*
* This will then ensure widget is moved back to the previous parent (canvas layout) & do cleanup
*/
onRestore: function onRestore() {
if (this._$focusView) {
this._$focusView.find('.focusSidebar').hide();
}
var visExpandMode = this.dashboardApi.getCanvas().getContent(this.getId()).getFeature('VisExpandMode');
visExpandMode.restore();
this._$focusView = null;
if (this.$placeholder) {
this.$placeholder.remove();
}
LiveWidget.inherited('onRestore', this, arguments);
},
reveal: function reveal() {
return this._callVisFunction('reveal', null, Promise.resolve());
},
getEdgeSelection: function getEdgeSelection() {
return this._currVis && this._currVis.eventHandler ? this._currVis.eventHandler.getEdgeSelection() : false;
},
isLassoSelectActive: function isLassoSelectActive() {
return this._currVis && this._currVis.eventHandler ? this._currVis.eventHandler.isLassoSelectActive() : false;
},
/**
* Toggle the state of lasso mode
*
* @param {boolean} [state] - The final state of lasso mode for the Vis. True means
* switch on, false means switch off. When not provided, the current state is inverted.
*/
toggleLassoMode: function toggleLassoMode(state) {
return this._currVis && this._currVis.eventHandler ? this._currVis.eventHandler.toggleLassoSelect(state) : false;
},
mapVizRenderForPrint: function mapVizRenderForPrint() {
return this.visAPI.mapVizRenderForPrint();
},
_setSynchronizePageContextFilters: function _setSynchronizePageContextFilters() {
var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.synchDataFilterEntries = payload.synchDataFilterEntries;
},
getSynchDataFilterEntries: function getSynchDataFilterEntries() {
return this.synchDataFilterEntries;
},
getTranslationIconEl: function getTranslationIconEl() {
return this.$el.parent().find('.widgetHeader');
},
onHide: function onHide() {
if (this._isCurrentlyRendering) {
this.setReRenderOnShow({
resizing: true
});
}
},
resize: function resize() {
var _this19 = this,
_arguments = arguments;
var content = this._getContent();
if (content) {
return VisUtil.validateVisDefinition(content, this.dashboardApi, { visId: this.model.visId }).then(function (isValid) {
if (_this19._previousDefinitionState === false && isValid === true) {
content.getFeature('Visualization').getDefinition().refresh();
}
_this19._previousDefinitionState = isValid;
return isValid;
}).then(this._prepareAsyncCallback(function () {
return LiveWidget.inherited('resize', _this19, _arguments);
}));
} else {
LiveWidget.inherited('resize', this, arguments);
}
},
_getContent: function _getContent() {
if (!this.content) {
this.content = this.dashboardApi.getCanvas() && this.dashboardApi.getCanvas().getContent(this.id);
}
return this.content;
}
});
/**
* Gets the default spec for a DataWidet. This takes a few options options.archetype: The archetype of the visualiztion
* options.visId: The visId of the visualization options.avatarIcon: The icon to show as an avatar when dragging the incomplete
* LiveWidget into it's location
*
* Also sets the expandOnRender option to force the widget to expand as soon as it's placed so that the user can fill out the
* appropriate slots
*/
LiveWidget.getDefaultSpec = function (name, options) {
return Promise.resolve({
model: {
mapping: [],
type: 'live',
visId: options.visId,
expandOnRender: true,
avatarHtml: ''
},
ignoreMe: name
});
};
return LiveWidget;
});
//# sourceMappingURL=LiveWidget.js.map