123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155 |
- '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(['../lib/@waca/core-client/js/core-client/ui/core/Events', '../lib/@waca/core-client/js/core-client/utils/Deferred', '../api/Error', 'underscore', 'jquery', '../utils/HtmlXSSUtils', '../DynamicFileLoader', '../api/impl/Widget', '../utils/ContentUtil', './ExpandedController', './FocusView'], function (Events, Deferred, ApiError, _, $, HtmlCleanser, DynamicFileLoader, WidgetAPIImpl, ContentUtil, ExpandedController, FocusView) {
- /**
- * A widget will normally size itself, but when it has an error, default dimensions are specified so the error message is readable
- */
- var ERROR_MESSAGE_MIN_DIMENSIONS = {
- width: 300,
- height: 300
- };
- /**
- * Base Widget
- *
- * Base class for Gemini widgets. This class encapsulates the interactions with the dashboard infrastructure.
- *
- * Widgets would have these life cycle functions
- * render : render the initial view
- * onContainerReady : called when container is ready. container provides a Widget Model object in this call. Widgets
- * can react to model changes by adding listeners as well as update the model so canvas container gets notified
- * destroy : cleanup on destruction
- *
- */
- var WidgetBase = Events.extend({
- expanded: false,
- // constructor
- init: function init(options) {
- WidgetBase.inherited('init', this, arguments);
- this.dashboardApi = options.dashboardApi;
- var usePreferredSizeConfig = [undefined, true, 'true'].indexOf(this.dashboardApi.getAppConfig('usePreferredSize')) !== -1;
- var usePreferredSizeModel = options.initialConfigJSON && options.initialConfigJSON.usePreferredSize;
- this.usePreferredSize = usePreferredSizeModel === false ? false : usePreferredSizeConfig;
- this.whenContainerIsReady = new Deferred();
- this.id = options.id;
- this.canvas = options.canvas;
- this.content = options.content;
- this._stateAPI = this.content.getFeature('state.internal');
- this._stateAPI.onChangeError(this.onStateChangeError.bind(this));
- this.initialConfigJSON = options.initialConfigJSON;
- this.el = options.el;
- this.$el = $(this.el);
- this.$widgetContainer = $(options.widgetContainer);
- this.eventRouter = options.eventRouter;
- this.services = options.services;
- if (options.registry) {
- this.properties = options.registry.properties;
- }
- var dashboardInternalFeature = this.dashboardApi.getFeature('internal');
- if (dashboardInternalFeature) {
- this.eventGroups = dashboardInternalFeature.getBoardModel().eventGroups;
- }
- this.colorsService = this.dashboardApi.getFeature('Colors');
- // Can we register the events after render?
- this.registerEvents(options.eventRouter);
- this.contributionSpec = options.registry;
- this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
- this.errorView = options.errorView;
- this.propertiesUtil = options.propertiesUtil;
- // Register the widget api as a deprecated content feature
- // Don't pass the widget iteself otherwise it will be destroyed twice (by the fetureLoader and the widget loader)
- options.contentFeatureLoader.registerDeprecatedFeature(this.id, 'WidgetAPI.deprecated', {
- getAPI: this.getAPI.bind(this)
- });
- this._expandModeContainerSelector = '.boardPageView:visible > .pageViewContent > .dashboardFrame > .dashboardFrameCentre';
- },
- getDashboardApi: function getDashboardApi() {
- return this.dashboardApi;
- },
- /**
- * Subclasses can call this method to extend the widget API with additional methods
- * @deprecated Visualization API should exposed in the getFeature response
- */
- _extendAPI: function _extendAPI(extensions) {
- var widgetAPI = this.getAPI();
- for (var name in extensions) {
- if (typeof extensions[name] === 'function') {
- widgetAPI[name] = extensions[name];
- }
- }
- },
- /**
- * For now, only live widgets support features
- */
- getFeature: function getFeature() /*name*/{
- return null;
- },
- /**
- * For now, only live widgets support features
- */
- setFeatureEnabled: function setFeatureEnabled() /*name, isEnabled*/{},
- getId: function getId() {
- return this.id;
- },
- /*
- * Return the widget API object
- */
- getAPI: function getAPI() {
- if (!this.widgetAPI) {
- this.widgetAPI = new WidgetAPIImpl(this).getAPI();
- }
- return this.widgetAPI;
- },
- /**
- * Helper to register board events handlers for infrastructure events
- * Derived Widget classes would override this function to add their handlers
- *
- */
- registerEvents: function registerEvents() {
- this.dashboardApi.on('widget:stopMove', this.onStopMove, this);
- this.dashboardApi.on('widget:startMove', this.onStartMove, this);
- this.dashboardApi.on('widget:onDetailErrors', this.addErrorDetailsHandler, this);
- if (this.colorsService) {
- this.colorsService.on('colorSet:changed', this.onDashboardColorSetChanged, this);
- }
- },
- unregisterEvents: function unregisterEvents() {
- this.dashboardApi.off('widget:stopMove', this.onStopMove, this);
- this.dashboardApi.off('widget:startMove', this.onStartMove, this);
- this.dashboardApi.off('widget:onDetailErrors', this.addErrorDetailsHandler, this);
- if (this.colorsService) {
- this.colorsService.off('colorSet:changed', this.onDashboardColorSetChanged, this);
- }
- },
- registerEventGroup: function registerEventGroup(transactionId) {
- transactionId = transactionId || _.uniqueId('_addToDefaultGroup_');
- // check if the widget is already assigned to an eventGroup
- if (this.eventGroups) {
- var group = this.eventGroups.findGroup(this.id);
- if (!group || group.getPageId() !== this.getContainerPageId()) {
- // obtain the default event group of the current page
- group = this.eventGroups.getDefaultGroup(this.getContainerPageId(), {
- payloadData: {
- undoRedoTransactionId: transactionId
- }
- });
- // assign to the default event group
- this.eventGroups.addToGroup(group.id, [this.id], {
- payloadData: {
- undoRedoTransactionId: transactionId
- }
- });
- return true;
- }
- }
- return false;
- },
- /**
- * Helper to register Widget Chrome event handlers
- * Derived Widget classes would override this function to add their handlers
- *
- * @param eventRouter - event router shared between 'widget chrome' and widget
- */
- registerWidgetChromeEvents: function registerWidgetChromeEvents(eventRouter) {
- //TODO: derived classes override this to setup handlers on view events or to fire requests to view
- if (eventRouter) {
- eventRouter.on('widget:onResize', this.resize, this);
- eventRouter.on('widget:onShow', this.onShow, this);
- eventRouter.on('widget:onHide', this.onHide, this);
- eventRouter.on('widget:onMaximize', this.onMaximize, this);
- eventRouter.on('widget:onRestore', this.onRestore, this);
- eventRouter.on('widget:onTitleChange', this.onTitleChange, this);
- eventRouter.on('widgetchrome:selected', this.onChromeSelected, this);
- eventRouter.on('widgetchrome:deselected', this.onChromeDeselected, this);
- eventRouter.on('widget:onAuthoringMode', this.onAuthoringMode, this);
- eventRouter.on('widget:onConsumeMode', this.onConsumeMode, this);
- eventRouter.on('widget:onEventGroupMode', this.onEventGroupMode, this);
- eventRouter.on('widget:onEnterContainer', this.onEnterContainer, this);
- eventRouter.on('widget:onExitContainer', this.onExitContainer, this);
- eventRouter.on('layout:fillColorChange', this.onPagefillColorChange, this);
- }
- },
- /**
- * Helper to unregister Widget Chrome event handlers
- * Derived Widget classes would override this function to add their handlers
- *
- * @param eventRouter - event router shared between 'widget chrome' and widget
- */
- unregisterWidgetChromeEvents: function unregisterWidgetChromeEvents(eventRouter) {
- //TODO: derived classes override this to setup handlers on view events or to fire requests to view
- if (eventRouter) {
- eventRouter.off('widget:onResize', this.resize, this);
- eventRouter.off('widget:onShow', this.onShow, this);
- eventRouter.off('widget:onHide', this.onHide, this);
- eventRouter.off('widget:onMaximize', this.onMaximize, this);
- eventRouter.off('widget:onRestore', this.onRestore, this);
- eventRouter.off('widget:onTitleChange', this.onTitleChange, this);
- eventRouter.off('widgetchrome:selected', this.onChromeSelected, this);
- eventRouter.off('widgetchrome:deselected', this.onChromeDeselected, this);
- eventRouter.off('widget:onAuthoringMode', this.onAuthoringMode, this);
- eventRouter.off('widget:onConsumeMode', this.onConsumeMode, this);
- eventRouter.off('widget:onEventGroupMode', this.onEventGroupMode, this);
- eventRouter.off('widget:onEnterContainer', this.onEnterContainer, this);
- eventRouter.off('widget:onExitContainer', this.onExitContainer, this);
- eventRouter.off('layout:fillColorChange', this.onPagefillColorChange, this);
- }
- },
- _registerModelEvents: function _registerModelEvents() {
- this.model.on('change', this._onModelChange, this);
- this.model.on('change:fillColor', this.applyFillColor, this);
- this.model.on('change:borderColor', this.applyBorderColor, this);
- this._modelEventsRegistered = true;
- },
- _unregisterModelEvents: function _unregisterModelEvents() {
- if (this._modelEventsRegistered) {
- this.model.off('change', this._onModelChange, this);
- this.model.off('change:fillColor', this.applyFillColor, this);
- this.model.off('change:borderColor', this.applyBorderColor, this);
- this._modelEventsRegistered = false;
- }
- },
- onStartMove: function onStartMove() {},
- onStopMove: function onStopMove() {},
- onPagefillColorChange: function onPagefillColorChange() /*payload*/{
- // To be overriden by sub classes
- },
- onDashboardColorSetChanged: function onDashboardColorSetChanged() /*payload*/{
- this.colorsService.makeSureColorIsValidInModel({
- model: this.model,
- propertyName: 'fillColor'
- });
- this.colorsService.makeSureColorIsValidInModel({
- model: this.model,
- propertyName: 'borderColor'
- });
- },
- getDefaultValue: function getDefaultValue(property) {
- var propSelected = _.find(this.properties, function (prop) {
- return prop.id === property;
- });
- if (propSelected) {
- return propSelected.defaultValue;
- }
- return;
- },
- /**
- * Widget Life cycle handler - gets called when container is ready
- * Canvas provides WidgetModel obj to widget, so that changes to it can be tracked at the Widget as well as canvas side
- */
- onContainerReady: function onContainerReady(containerContext) {
- var model = containerContext.model,
- widgetChromeEventRouter = containerContext.widgetChromeEventRouter,
- isAuthoringMode = containerContext.isAuthoringMode,
- _containerContext$add = containerContext.additionalWidgetData,
- additionalWidgetData = _containerContext$add === undefined ? {} : _containerContext$add,
- layoutAPI = containerContext.layoutAPI;
- this.model = model;
- this.widgetChromeEventRouter = widgetChromeEventRouter;
- this.isAuthoringMode = isAuthoringMode;
- this.addPayloadData = additionalWidgetData.addPayloadData;
- this.layoutAPI = layoutAPI;
- if (this.model) {
- this._registerModelEvents();
- this.addWhiteListAttrs('fillColor', 'borderColor', 'animationEntrance', 'animationExit');
- this.addColorProperties(['fillColor', 'borderColor']);
- if (this.model.localizedProps && this.model.localizedProps.length) {
- this.dashboardApi.getFeature('TranslationService').registerView({ view: this, model: this.model });
- }
- }
- this.registerWidgetChromeEvents(this.widgetChromeEventRouter);
- this.whenContainerIsReady.resolve();
- },
- /**
- * Get layout API
- *
- * @return {LayoutAPI} layout API
- */
- getLayoutAPI: function getLayoutAPI() {
- return this.layoutAPI;
- },
- onAuthoringMode: function onAuthoringMode() {
- this.isAuthoringMode = true;
- this.isEventGroupMode = false;
- },
- onConsumeMode: function onConsumeMode() {
- this.isAuthoringMode = false;
- this.isEventGroupMode = false;
- },
- onEventGroupMode: function onEventGroupMode() {
- this.isAuthoringMode = true;
- this.isEventGroupMode = true;
- this._setEventGroupOverlayContent(this.eventGroups.findGroup(this.id));
- },
- getEventGroupId: function getEventGroupId() {
- return this.eventRouter ? this.eventRouter.channelId : undefined;
- },
- setEventRouter: function setEventRouter(newEventRouter, options) {
- if (!newEventRouter && this.eventRouter) {
- // unregister from the current event router
- this.unregisterEvents(this.eventRouter);
- this.onRemoveCurrentEventRouter(options);
- this.unregistered = true;
- } else if (this.unregistered || newEventRouter.channelId !== this.eventRouter.channelId) {
- // unregister first and then...
- if (!this.unregistered) {
- this.unregisterEvents(this.eventRouter);
- this.onRemoveCurrentEventRouter(options);
- }
- // register with the new event router
- this.eventRouter = newEventRouter;
- if (this.eventRouter) {
- this.registerEvents(this.eventRouter);
- this.onNewEventRouter(options);
- this.unregistered = false;
- }
- }
- this._setEventGroupOverlayContent();
- },
- onRemoveCurrentEventRouter: function onRemoveCurrentEventRouter() /*options*/{
- // To be implemented by concrete classes
- },
- onNewEventRouter: function onNewEventRouter() /*options*/{
- // To be implemented by concrete classes
- },
- _setEventGroupOverlayContent: function _setEventGroupOverlayContent() {
- var group = this.eventGroups.findGroup(this.id);
- var node = this.getWidgetStyleNode();
- var overlayContent = node.find('.eventGroupOverlayContent');
- if (group && overlayContent) {
- overlayContent.text(group.getGroupIndex());
- }
- },
- /**
- * Event handler to be overridden by extended classes.
- * Intended to be used to handle navigation within a widget
- */
- onEnterContainer: function onEnterContainer() {
- /* meant to be overridden */
- },
- /**
- * Event handler to be overridden by extended classes.
- * Intended to be used to exit the navigation within a widget
- */
- onExitContainer: function onExitContainer() {
- /* meant to be overridden */
- },
- /**
- * Widget Life cycle handler - gets called when board spec gets changed e.g. when undo/redo happens
- * Derived classes override this function
- *
- * @param options The updated model
- */
- _onModelChange: function _onModelChange(options) {
- // call register properties to refresh the properties UI on undo/redo
- // We re-register only if we have previously registered.
- if (options && options.sender === 'UndoRedoController') {
- this.refreshPropertiesPane();
- }
- },
- /**
- * Widget Life cycle handler - gets called when widget is destroyed
- * Derived classes override this function
- */
- destroy: function destroy() {
- this._unregisterModelEvents();
- if (this._expanded) {
- this._expanded.remove();
- }
- this.unregisterEvents(this.eventRouter);
- this.unregisterWidgetChromeEvents(this.widgetChromeEventRouter);
- if (this.model) {
- if (this.model.localizedProps && this.model.localizedProps.length) {
- this.dashboardApi.getFeature('TranslationService').deregisterView(this.model.id);
- }
- this.model.contentReferences = [];
- }
- this._expanded = null;
- this.eventRouter = null;
- },
- /**
- * Returns property from model
- *
- * @param The property name to get
- */
- get: function get(propertyName) {
- return this.model ? this.model[propertyName] : undefined;
- },
- /**
- * Sets a hash of attributes on this widget's model and notifies the canvas of the change.
- *
- * @param The properties to set on the model
- * @param Model set options, see Model.js for details
- */
- set: function set(attributes, options) {
- if (this.model && attributes) {
- options = options || {};
- options = _.defaults(options, {
- sender: this.id
- });
- this.model.set(attributes, options);
- }
- },
- /**
- * Trigger an event through the canvas infrastructure eventing mechanism
- *
- * @param eventName The name of the event to trigger
- * @param event The event
- */
- triggerExternalEvent: function triggerExternalEvent(eventName, event) {
- if (this.eventRouter) {
- this.eventRouter.trigger(eventName, event);
- }
- },
- /**
- * @param options
- */
- onTitleChange: function onTitleChange() /*options*/{
- //handle title update
- },
- /*
- * Called when properties are changed on the properties pane for this widget. Overridden by derived classes
- */
- onPropertyUpdate: function onPropertyUpdate(propChange) {
- var prop = {};
- prop[propChange.category] = propChange.item;
- var data = null;
- if (propChange.transactionId) {
- data = {
- undoRedoTransactionId: propChange.transactionId,
- transactionToken: propChange.transactionToken
- };
- }
- if (propChange.category === 'fillColor' || propChange.category === 'borderColor') {
- this.colorsService.prepareForColorModelChange(prop, propChange.category);
- }
- this.set(prop, {
- silent: false,
- payloadData: data
- });
- },
- /**
- * Notify the layout to update the widget label
- */
- updateDescription: function updateDescription(label) {
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:updateDescription', {
- value: label
- });
- }
- },
- /**
- * Notify the layout to update the widget aria-label
- */
- updateWidgetArialabel: function updateWidgetArialabel(label) {
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:updateWidgetArialabel', {
- value: label
- });
- }
- },
- /**
- * Notify the layout to clear the widget aria-label
- */
- clearWidgetArialabel: function clearWidgetArialabel() {
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:clearWidgetArialabel');
- }
- },
- setLayoutProperties: function setLayoutProperties(properties) {
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:setLayoutProperties', properties);
- }
- },
- /**
- * @param icon - object - A jquery object referencing an element to add
- * @param name - string - A string representing the name of the icon
- * @param location - string|undefined - A string value of 'footer' to add an icon to the widget footer. undefined to keep backwards
- * compatability for adding icon to header
- */
- addIcon: function addIcon(icon, name) {
- if (this.widgetChromeEventRouter) {
- var location = void 0;
- var $header = this._isMaximized && this._expanded && this._expanded.containerElement && this._expanded.containerElement.find('.widgetHeader');
- if ($header && $header.length) {
- location = $header.find('.widgetIcons');
- if (location.length === 0) {
- location = $header;
- }
- }
- this.widgetChromeEventRouter.trigger('widget:addIcon', {
- widgetIcon: icon,
- name: name,
- location: location
- });
- }
- },
- /**
- * Update the widget container to reflect the common style properties in the model
- */
- applyCommonProperties: function applyCommonProperties() {
- if (this.model) {
- this.applyFillColor({
- value: this.model.fillColor
- });
- this.applyBorderColor({
- value: this.model.borderColor
- });
- var showTitle = this.model.showTitle;
- if (!showTitle) {
- showTitle = this.getDefaultValue('showTitle');
- }
- this.showTitle({
- value: showTitle
- });
- }
- },
- showTitle: function showTitle(data) {
- if (!data.value) {
- var $parent = this.$el.parent();
- if ($parent.hasClass('widget')) {
- $parent.find('.textArea').addClass('hidden');
- }
- }
- },
- changeTitleType: function changeTitleType(data) {
- if (data.value === 'smart') {
- var widgetSpec = this.model.toJSON();
- widgetSpec.name = '';
- var value = this.dashboardApi.getDashboardCoreSvc('.SmartNamingSvc').getWidgetName(widgetSpec);
- this.$el.parent().find('.textArea').text(value);
- }
- },
- /**
- * Apply the fill color value to the view node
- * @param data
- */
- applyFillColor: function applyFillColor(data) {
- this.applyColor(data, 'fill');
- },
- /**
- * Apply the border color value to the view node
- * @param data
- */
- applyBorderColor: function applyBorderColor(data) {
- this.applyColor(data, 'border');
- },
- /**
- * Apply color (border/fill) value to the view node
- * @param data
- */
- applyColor: function applyColor(data, type) {
- var $node = this.getWidgetStyleNode();
- $node.each(function (index, node) {
- // We set the classname this way so that it works for svg elements. JQuery add/removeClass does not work with svg elements.
- // clear previous fill color
- var re = new RegExp('\\s*\\b' + type + '-[^\\s]*\\b', 'g');
- var className = node.getAttribute('class') || '';
- className = className.replace(re, '');
- if (data.value) {
- className += ' ' + this.getThemeColorClassName(data.value, type);
- }
- node.setAttribute('class', className);
- }.bind(this));
- },
- getWidgetStyleNode: function getWidgetStyleNode() {
- return this.$el.closest('.widget');
- },
- getThemeColorClassName: function getThemeColorClassName(color, type) {
- return type + '-' + color;
- },
- resize: function resize() {
- // handle resize update
- },
- onShow: function onShow() {},
- onHide: function onHide() {},
- /**
- * To be overridden by extended classes.
- * Intended to be used to re-render a widget when it is revealed
- */
- reveal: function reveal() {},
- onChromeSelected: function onChromeSelected() {
- //update state info
- this.chromeSelected = true;
- this.triggerExternalEvent('widget:selected', {
- sender: this.model.id,
- payloadData: this
- });
- },
- onChromeDeselected: function onChromeDeselected() {
- //update state info
- this.chromeSelected = false;
- this.triggerExternalEvent('widget:deselected', {
- sender: this.model.id
- });
- this.triggerExternalEvent('properties:deregister', {
- sender: this.model.id
- }); //TODO do we need sender ?
- },
- showWarning: function showWarning(msg, params) {
- this.showError(msg, params, 'warning');
- },
- _updateErrorContainer: function _updateErrorContainer() /*msg, errorContainer*/{},
- _updateErrorType: function _updateErrorType(msg, errorType) {
- return errorType ? errorType : 'error';
- },
- /**
- * Check whether the widget is displaying an error
- *
- * @return {boolean} TRUE is widget has error and FALSE if not
- */
- hasError: function hasError() {
- //TODO: Add a property on the widget model to expose the error state instead of
- //using the DOM.
- return this.$el.has('.errorContainer').length > 0;
- },
- /**
- * Check whether the widget has warning
- *
- * @return {boolean} TRUE is widget has warning and FALSE if not
- */
- hasWarning: function hasWarning() {
- //TODO: Add a property on the widget model to expose the warning state instead of
- //using the DOM.
- return this.$el.has('.warningContainer').length > 0;
- },
- getError: function getError() {
- return this.errorMessage;
- },
- onStateChangeError: function onStateChangeError(error) {
- if (error) {
- this.renderError(error.getMessage(), error.getParams(), error.getType());
- } else {
- this._clearError();
- }
- },
- renderError: function renderError(msg, params, type) {
- // Always keep track of the last query response that caused an error
- if (params && params.errorInfo) {
- this._lastErrorInfo = params.errorInfo;
- }
- type = this._updateErrorType(msg, type);
- var hasError = this.hasError();
- if (this.el) {
- var $errorContainer = this.errorView.renderContainer({
- id: this.id + 'Title',
- type: type,
- msg: {
- str: msg,
- params: params
- }
- });
- this._updateErrorContainer(msg, $errorContainer);
- this.$el.find('.errorContainer').remove();
- // Only cause the widget to resize on the first error we show or it'll keep shrinking on every subsequent error
- if (this.widgetChromeEventRouter && !hasError) {
- var errorContainerStyle = {
- 'min-height': Math.max(ERROR_MESSAGE_MIN_DIMENSIONS.height, this.$el.innerHeight()),
- 'min-width': ERROR_MESSAGE_MIN_DIMENSIONS.width
- };
- this.widgetChromeEventRouter.trigger('widget:showError', errorContainerStyle, this);
- }
- // Replace the text in helper node for Jaws reading.
- var msgText = this.errorView.makeNlsMessage({
- str: msg,
- params: params
- });
- this.errorMessage = msgText;
- this.updateWidgetArialabel(msgText);
- this.$el.append($errorContainer);
- 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();
- }
- }
- },
- showError: function showError(msg, params, type) {
- var error = new ApiError({ 'msg': msg, 'params': params }, { 'type': type });
- this._stateAPI.setError(error);
- },
- /**
- * Add onClick handler to the error div if details are enabled and we have information about an http request that failed
- */
- addErrorDetailsHandler: function addErrorDetailsHandler() {
- if (window.dashboardErrorDetailsEnabled === true && this._lastErrorInfo && this.$el.has('.errorContainer').length > 0) {
- this.$el.find('.dashboardMessageBox').onClick(this._showErrorDetails.bind(this));
- }
- },
- _showErrorDetails: function _showErrorDetails() {
- if (this._lastErrorInfo) {
- return DynamicFileLoader.load(['ui/dialogs/MessageBox']).then(function (Modules) {
- var MessageBox = Modules[0];
- var detailsObj = {
- httpResponse: {
- status: this._lastErrorInfo.status,
- details: this._lastErrorInfo.responseJSON ? this._lastErrorInfo.responseJSON : {}
- },
- httpRequest: {
- querySpec: this._lastErrorInfo.querySpec ? this._lastErrorInfo.querySpec : {}
- },
- widgetSpec: this.model
- };
- var msgBox = new MessageBox('selectableInfo', 'Error details', JSON.stringify(detailsObj, null, 4));
- msgBox.open();
- }.bind(this));
- }
- return Promise.resolve();
- },
- clearError: function clearError() {
- this._stateAPI.clearError();
- },
- /**
- * Clears inline error message
- */
- _clearError: function _clearError() {
- this._lastQueryErrorInfo = null;
- this.errorMessage = null;
- if (this.hasError()) {
- if (this.widgetChromeEventRouter) {
- var payload = {};
- this.widgetChromeEventRouter.trigger('widget:clearError', payload);
- }
- this.$el.find('.errorContainer').remove();
- }
- },
- clearMoreDataIndicator: function clearMoreDataIndicator() {
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:clearMoreDataIndicator');
- }
- },
- addWhiteListAttrs: function addWhiteListAttrs() {
- var args = Array.prototype.slice.call(arguments, 0);
- if (this.model && args.length > 0) {
- if (this.model.whitelistAttrs) {
- this.model.whitelistAttrs = _.uniq(this.model.whitelistAttrs.concat(args));
- } else {
- this.model.whitelistAttrs = args;
- }
- }
- },
- addColorProperties: function addColorProperties(colorProperties) {
- if (this.model) {
- if (this.model.colorProperties) {
- this.model.colorProperties = this.model.colorProperties.concat(colorProperties);
- } else {
- this.model.colorProperties = colorProperties;
- }
- }
- },
- addContentReferences: function addContentReferences(references) {
- if (this.model) {
- this.model.contentReferences = this.model.contentReferences.concat(references);
- }
- },
- removeWhiteListAttrs: function removeWhiteListAttrs() {
- if (this.model && this.model.whitelistAttrs) {
- var args = Array.prototype.slice.call(arguments, 0);
- this.model.whitelistAttrs = _.difference(this.model.whitelistAttrs, args);
- }
- },
- //when we 'expand' a widget, we still want to allow access to data strip and properties panels
- //so, a blocker is placed behind the widget over the canvas container
- //this selector identifies the container - should probably use a better/more decoupled way
- getExpandStartingPosition: function getExpandStartingPosition() {
- //when we 'expand' a widget, there is animation to zoom it to the bigger size (and back)
- //coordinates for the animation use screen coordinates, while the widget being zoomed uses relative position within the
- //chrome and also within the preview div
- //if we use the offset coordinates of this.$el, the starting position will get offset a bit
- //hence this adjustment to make it line up properly
- var screenVsRelativeOffset = $(this._expandModeContainerSelector).offset().top;
- var bounds = this.$el.offset();
- bounds.top -= screenVsRelativeOffset;
- bounds.width = this.$el.width();
- bounds.height = this.$el.height();
- screenVsRelativeOffset = $(this._expandModeContainerSelector).offset().left;
- bounds.left -= screenVsRelativeOffset;
- return bounds;
- },
- getExpandViewContent: function getExpandViewContent() {
- return $('<span class="collapseIcon" title="collapse"></span>');
- },
- isWidgetMaximized: function isWidgetMaximized() {
- return this._isMaximized === true;
- },
- // TODO: Focus mode needs to be a feature
- onMaximize: function onMaximize() {
- if (this._isMaximized || !this.isMaximizeSupported) {
- return;
- }
- this._isMaximized = true;
- this.triggerExternalEvent('widget:maximize', {
- id: this.id
- });
- if (this._expanded) {
- this._expanded.remove();
- }
- //save this before getExpandStartingPosition() gets called
- this._restoreToParent = this.$el.parent();
- this._expanded = new FocusView({
- owner: this,
- content: this.content,
- data: this.el,
- containerElement: $(this._expandModeContainerSelector),
- launchPoint: this.getWidgetStyleNode()[0],
- dashboardState: this.dashboardApi.getFeature('DashboardState')
- });
- return this._expanded.render();
- },
- toggleExpanded: function toggleExpanded(toggle) {
- if (toggle === undefined) {
- toggle = !this.expanded;
- }
- if (toggle !== this.expanded) {
- this.expanded = toggle;
- return this._getExpandedController().toggle(toggle);
- }
- return Promise.resolve();
- },
- _getExpandedController: function _getExpandedController() {
- if (!this._expandedController) {
- this._expandedController = new ExpandedController({
- widget: this
- });
- }
- return this._expandedController;
- },
- whenRenderComplete: function whenRenderComplete() {
- this._stateAPI.setStatus(this._stateAPI.STATUS.RENDERED);
- // The base implementation does not have a render. Once constructed, we assume that it is rendered.
- return Promise.resolve();
- },
- /**
- * This method is called when restoring a widget from maximized back to its parent container.
- * It returns a promise when its parent exist and undefined otherwise
- * @return Promise or undefined
- */
- onRestore: function onRestore() {
- this._isMaximized = false;
- this.triggerExternalEvent('widget:restore', {
- id: this.id
- });
- if (this._restoreToParent) {
- return this.getVisBounds().then(function (bounds) {
- //set dimensions so this.resize() works (when expanded, this.$el would have been resized to a bigger size)
- this.$el.css({
- width: bounds.width,
- height: bounds.height
- });
- this._restoreToParent.append(this.$el);
- this._restoreToParent = null;
- this.applyCommonProperties();
- this.resize();
- }.bind(this));
- }
- },
- /*
- * Returns an array of toolbar items to be displayed. Overridden by derived classes
- */
- getContextToolbarItems: function getContextToolbarItems() {
- return [];
- },
- /**
- * returns containerPageId that the widget belongs to
- */
- getContainerPageId: function getContainerPageId() {
- var type = this.dashboardApi.getAppConfig('pageContainerType');
- var pageContent = ContentUtil.getPageContent(this.content, type);
- if (pageContent) {
- return pageContent.getId();
- }
- return null;
- },
- /**
- * Reset the widget size to the preferred size.
- *
- */
- resizeToPreferredSize: function resizeToPreferredSize() {
- // to be implemented by concrete widgets
- },
- /**
- * Trigger the setPreferred size event to notify listeners (e.g the widget layout view)
- * of the preferred size.
- *
- * @param preferredSize This is the visualization's preferred size gotten from its definition
- * @param options Object that contains other information for the layout view such as transaction id
- */
- setPreferredSize: function setPreferredSize(preferredSize, options) {
- if (!this.usePreferredSize) {
- return;
- }
- var payload = {
- preferredSize: preferredSize,
- options: options
- };
- if (this.widgetChromeEventRouter) {
- this.widgetChromeEventRouter.trigger('widget:setPreferredSize', payload, this);
- }
- },
- /**
- * Get the appropriate visualization size from the layout view. This is an asynchronous function.
- * @returns {Promise} A Deferred's promise object that gets resolved with the vis bounds/size
- */
- getVisBounds: function getVisBounds() {
- var dfd = new Deferred();
- // The vis bounds depends on whether data widget is been rendered in focus mode,
- // intent view or otherwise.
- if (!this.widgetChromeEventRouter || this._isMaximized && this._expanded) {
- dfd.resolve({
- top: 0,
- left: 0,
- width: this.$el.innerWidth(),
- height: this.$el.innerHeight()
- });
- } else if (this._isIntentViewMode()) {
- // Widget is being rendered in intent results dialog
- var nPreviewContainer = this.$el.parents('.intent-results-preview');
- dfd.resolve({
- top: 0,
- left: 0,
- width: nPreviewContainer.innerWidth(),
- height: nPreviewContainer.innerHeight()
- });
- } else {
- // TODO: never pass a deferred/promise through an event!
- //trigger event to layoutview and attach oDeffered object as payload
- this.widgetChromeEventRouter.trigger('widget:getSize', {
- deferred: dfd
- }, this);
- }
- return dfd.promise;
- },
- /**
- * Determine if the widget is been rendered in an intent view dialog
- */
- _isIntentViewMode: function _isIntentViewMode() {
- //TODO (STORY 53043) This is a hacky approach. Needs rework.
- return this.$el.parents('.intent-results-preview').length > 0;
- },
- /**
- * Get the type of of the widget
- *
- * @return {string} type of the widget
- */
- getType: function getType() {
- return this.model.type;
- },
- _filterActiveAutoBinProperty: function _filterActiveAutoBinProperty(properties) {
- var _this = this;
- return _.filter(properties, function (property) {
- if (['autoBin.toggle', 'autoBin.count'].includes(property.id)) {
- return _this.visModelManager.getDefinition().binningConfig && _this.visModelManager.getDefinition().binningConfig.auto === true;
- }
- return true;
- });
- },
- onPropertyChange: function onPropertyChange(propertyName, value) {
- this.onPropertyUpdate(this.propertiesUtil.buildPropChangePayload(propertyName, value));
- },
- refreshPropertiesPane: function refreshPropertiesPane(options) {
- if (this.model) {
- this.triggerExternalEvent('properties:refreshPane', _.extend({
- 'sender': this.model.id
- }, options));
- }
- },
- isValidHtmlContent: function isValidHtmlContent(widgetContent) {
- return HtmlCleanser.isValidHtmlContent(widgetContent);
- },
- /**
- * check if the value of a property matched expected
- * @param {String} propertyName
- * @param {object} expectedValue
- * @return {boolean} true if it matches
- */
- doesVisPropertyMatchExpected: function doesVisPropertyMatchExpected(propertyName, expectedValue) {
- if (this._currVis && this._currVis.doesVisPropertyMatchExpected) {
- return this._currVis.doesVisPropertyMatchExpected(propertyName, expectedValue);
- }
- },
- /**
- * check if contextual grid is supported for this widget
- * to be overriden by children class
- * @return {Promise} promise resolves with true if enabled
- */
- isContextualGridEnabled: function isContextualGridEnabled() {
- return Promise.resolve(false);
- },
- _invokeLifeCycleHandlers: function _invokeLifeCycleHandlers(name, payload) {
- var _this2 = this;
- return this.dashboardApi.getDashboardSvc('.LifeCycleManager').then(function (LifeCycleManager) {
- return LifeCycleManager.invokeLifeCycleHandlers(name, payload);
- }).catch(function (e) {
- _this2.logger.error(e);
- });
- },
- /**
- * Render widget
- *
- * @return {Promise} promise resolved with current $el
- */
- render: function render() {
- this._stateAPI.setStatus(this._stateAPI.STATUS.RENDERING);
- return Promise.resolve(this.$el);
- }
- });
- return WidgetBase;
- });
- //# sourceMappingURL=WidgetBase.js.map
|