123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- 'use strict';
- /**
- * Licensed Materials - Property of IBM
- * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2021
- * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
- */
- define(['jquery', '../../lib/@waca/dashboard-common/dist/core/Model', './WidgetModel', './LayoutModel', './ModelUtils', 'underscore', './EventGroups', './properties/PropertiesModel', '../../lib/@waca/core-client/js/core-client/utils/UniqueId'], function ($, BaseModel, WidgetModel, LayoutModel, ModelUtils, _, EventGroupCollection, PropertiesModel, UniqueId) {
- function createLayoutModel_default(spec, env) {
- return new LayoutModel(spec, env.boardModel, env.logger);
- }
- var Model = BaseModel.extend({
- nestedCollections: {
- eventGroups: EventGroupCollection
- },
- nestedModels: {
- properties: PropertiesModel
- },
- init: function init(boardSpec, options) {
- var _this = this;
- this._initBoardSpec = boardSpec;
- options = options || {};
- this.logger = options.logger;
- // TODO: remove depenendency on dashboardApi here. currently needed to be passed to the "pageContext" board model extension
- this.dashboardApi = options.dashboardApi;
- this.createLayoutModel = options.createLayoutModel || createLayoutModel_default;
- // Make sure the extensions are processed before we call the parents init
- if (options.whitelistAttrs) {
- this.whitelistAttrs = options.whitelistAttrs;
- } else {
- this.whitelistAttrs = ['name', 'layout', 'theme', 'version', 'eventGroups', 'datasetShaping', 'properties', 'content'];
- }
- var excludedProperties = ['dashboardColorSet', 'customColors', 'localCache', 'defaultLocale', 'fredIsRed'];
- this.content = ModelUtils.initializeContentModel(boardSpec, excludedProperties);
- options.boardModel = this;
- if (!boardSpec.properties) {
- boardSpec.properties = {};
- }
- // Need to set these since the boardModel is passed as the options when building the layout model.
- // Weird, but don't want to change the flow of layout model creation in R release
- this.defaultLocale = boardSpec.properties.defaultLocale;
- if (this.dashboardApi) {
- var userProfileService = this.dashboardApi.getGlassCoreSvc('.UserProfile');
- if (userProfileService) {
- this.contentLocale = userProfileService.preferences.contentLocale;
- }
- }
- _.extend(options, this.getLanguageModelOptions());
- this._updateNestedInfoWithExtensions(options.boardModelExtensions);
- Model.inherited('init', this, arguments);
- this.eventRouter = options.eventRouter;
- this._autoCreateExtensionModelsAndCollections(options.boardModelExtensions, boardSpec);
- this.id = options.id;
- this.name = options.name || this.name;
- this.widgetRegistry = options.widgetRegistry;
- this.layoutExtensions = options.layoutExtensions;
- this.widgetInstances = {};
- // TODO: Add reference to cleanup item here when it is created in Jira.
- // CADBC-832 is item that caused it to change. Eventually this block
- // of code will go away.
- Object.keys(boardSpec.widgets || {}).forEach(function (widgetId) {
- _this.createLegacyWidgetModel(boardSpec.widgets[widgetId]);
- });
- // Explore expects the BoardModel to populate the widgetInstances
- // within this init method but because of changes to how widgets are
- // instantiated they are now created from the LayoutModel though still
- // from a method in this class (see createLegacyWidgetModel below)
- // This doesn't work in Explore's case so the following continues
- // to allow Explore to function properly
- if (this.createLayoutModel !== createLayoutModel_default) {
- this._instantiateWidgetModels(boardSpec.layout.items);
- }
- this.layout = this.createLayoutModel(boardSpec.layout, {
- boardModel: this,
- logger: this.logger,
- dashboardApi: this.dashboardApi
- });
- if (!this.eventGroups) {
- this.set({
- eventGroups: new EventGroupCollection()
- }, {
- silent: true
- });
- }
- // cache the extensions, the boardmodel editor needs this to re-create the model after a manual edit.
- this.boardModelExtensions = options.boardModelExtensions;
- // The first layout item will be displayed by default. Keep the selected layout property in sync.
- if (this.layout.items && this.layout.items.length > 0) {
- var item = this.layout.items[0];
- this.setSelectedLayout(item.id);
- }
- // Keep the selected layout in sync.
- this.eventRouter && this.eventRouter.on('tab:tabChanged', this.onTabChanged, this);
- this.isUpgraded = options.isUpgraded;
- this.type = 'dashboard';
- },
- _instantiateWidgetModels: function _instantiateWidgetModels() {
- var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
- for (var i = 0; i < items.length; i++) {
- var item = items[i];
- if (item.items && item.items.length) {
- this._instantiateWidgetModels(item.items);
- } else {
- if (item.type === 'widget' && item.features && item.features['Models_internal']) {
- this.createLegacyWidgetModel(item.features['Models_internal']);
- }
- }
- }
- },
- /**
- * Returns the ContentModel
- * @return {Object}
- */
- getContentModel: function getContentModel() {
- return this.content;
- },
- /**
- * In Endor R7 (11.1.7) onward we now allow for features to load
- * their own data from the board spec. Through this mechanism this method
- * is called by the LayoutModel for specific content model objects that
- * have a 'Models.internal' feature on them.
- * 'Models.internal' is the place that widget data resides for now
- * instead of a top-level "widgets" property in the BoardModel here.
- * The BoardModel will still hold all widget instances though.
- * @param {Object} widgetSpec An object that represents a widget model
- */
- createLegacyWidgetModel: function createLegacyWidgetModel(widgetSpec) {
- if (!this.getWidgetModel(widgetSpec.id)) {
- this._addWidgetModel(widgetSpec, {
- addDashboardTranslatedLocales: false
- });
- }
- },
- /**
- * Adds the board model extension info to the the nestedCollections and nestedModels
- */
- _updateNestedInfoWithExtensions: function _updateNestedInfoWithExtensions(boardModelExtensions) {
- var _this2 = this;
- if (!boardModelExtensions) {
- return;
- }
- boardModelExtensions.forEach(function (extensionInfo) {
- var extensionName = extensionInfo.name;
- _this2.whitelistAttrs.push(extensionName);
- if (extensionInfo.type === 'collection') {
- _this2.nestedCollections[extensionName] = extensionInfo.class;
- } else {
- _this2.nestedModels[extensionName] = extensionInfo.class;
- }
- });
- },
- /**
- * Handles adding the model extension points to the whitelistAttrs array and as nestedModels
- * @param {Object} boardModelExtensions
- * @param {Object} boardSpec
- */
- _autoCreateExtensionModelsAndCollections: function _autoCreateExtensionModelsAndCollections(boardModelExtensions, boardSpec) {
- var _this3 = this;
- if (!boardModelExtensions) {
- return;
- }
- boardModelExtensions.forEach(function (extensionInfo) {
- var extensionName = extensionInfo.name;
- /**
- * If it's already in the boardSpec do nothing since the base Model class should of already made sure we were
- * dealing with actual Models and Collections. If it's missing and autoCreate is set to true then create the
- * Model or Collection
- */
- if (!boardSpec[extensionName] && extensionInfo.autoCreate) {
- if (extensionInfo.type === 'collection') {
- _this3._setCollection(extensionName, boardSpec[extensionName] || null, {
- silent: true,
- logger: _this3.logger,
- boardModel: _this3,
- dashboardApi: _this3.dashboardApi // FIXME - models should not rely on dashboardApi -- currently used by the pageContext
- });
- } else {
- _this3._setNestedModel(extensionName, boardSpec[extensionName] || {}, {
- silent: true,
- logger: _this3.logger,
- boardModel: _this3
- });
- }
- }
- });
- this._registerBoardModelExtensionTriggers(boardModelExtensions);
- },
- setDefaultLocale: function setDefaultLocale(locale) {
- this.defaultLocale = locale;
- },
- setTranslationLocale: function setTranslationLocale(locale) {
- this.properties.set({
- translationModeLocale: locale
- });
- this.translationLocale = locale;
- },
- _registerBoardModelExtensionTriggers: function _registerBoardModelExtensionTriggers(boardModelExtensions) {
- if (boardModelExtensions) {
- boardModelExtensions.forEach(function (extension) {
- if (extension.triggers && extension.triggers.length > 0) {
- extension.triggers.forEach(function (trigger) {
- this[extension.name].on(trigger.eventName, this.trigger.bind(this, trigger.event));
- }, this);
- }
- }, this);
- }
- },
- onTabChanged: function onTabChanged(event) {
- this.setSelectedLayout(event.modelId);
- },
- /**
- * @deprecated, use findWidgetById
- */
- getWidgetModel: function getWidgetModel(id) {
- return this.widgetInstances ? this.widgetInstances[id] : null;
- },
- getTimeLineEpisode: function getTimeLineEpisode(id) {
- return this.timeline ? this.timeline.episodes.get(id) : null;
- },
- setSelectedLayout: function setSelectedLayout(id) {
- this.selectedLayout = id;
- },
- getSelectedLayout: function getSelectedLayout() {
- return this.selectedLayout;
- },
- /**
- * Add a content to the model
- *
- * @param options
- * @param options.model - layout model to add
- * @param options.parentId - id of the parent layout
- * @param [options.insertBefore] - id of the layout to insert before
- * @param options.layoutProperties
- * @param sender - If available, the sender will be added to the event payload.
- * @param payloadData - Additional data that will be included in the event payload.
- * @throws throws an exception if any of the mandatory options are not provided
- */
- addContent: function addContent(options, sender, payloadData) {
- if (!options.parentId) {
- throw new Error('Invalid argument options.parentId provided');
- }
- if (!options.model) {
- throw new Error('Invalid argument options.model provided');
- }
- payloadData = this.checkPayloadData(payloadData);
- var model = Object.assign({}, options.model);
- // if no id provided on the model, generate a unique id
- // TODO: verify that drag and drop works as expected;
- // possibly will need to always refresh the id or check if model does not already exist
- if (!model.id || !options.modelIdsValid) {
- model.id = UniqueId.get('content');
- }
- if (options.layoutProperties && options.layoutProperties.style) {
- model.style = Object.assign({}, model.style || {}, options.layoutProperties.style);
- }
- this.layout.addArray([{
- parentId: options.parentId,
- insertBefore: options.insertBefore,
- model: model
- }], sender, payloadData);
- return model.id;
- },
- /**
- * Remove a content from the model.
- *
- * @param id
- * @param sender
- * @param payloadData - Additional data that will be included in the event payload.
- */
- removeContent: function removeContent(id, sender, payloadData) {
- payloadData = this.checkPayloadData(payloadData);
- this.layout.removeArray([id], sender, payloadData);
- },
- /**
- * Add a widget to the model.
- *
- * @param options
- * options.model
- * options.parentId
- * options.insertBefore
- * options.layoutProperties
- * @param sender - If available, the sender will be added to the event payload.
- * @param payloadData - Additional data that will be included in the event payload.
- */
- addWidget: function addWidget(options, sender, payloadData) {
- //If no parent id was passed in, cancel the add
- if (!options.parentId) {
- return;
- }
- // Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- payloadData = this.checkPayloadData(payloadData);
- var model = this._addWidgetModel(options.model, { id: options.id });
- if (options.id) {
- //this is when a widget was already loaded (but not added to canvas)
- //adding to canvas here will use the same id as in the widget for the widget and layout model
- //adding will not result in reloading of the widget, but will just add the required models (and layout view obj, which will contain the
- //pre-loaded widget)
- model.id = options.id;
- }
- var id = model.id;
- // Add to the layout
- var layoutModel = _.extend({}, options.layoutProperties, {
- type: 'widget',
- id: id
- });
- this.layout.addArray([{
- parentId: options.parentId,
- insertBefore: options.insertBefore,
- model: layoutModel
- }], sender, payloadData);
- return this._triggerAddRemove('addWidget', {
- op: 'addWidget',
- parameter: _.extend({}, options, {
- model: model.toJSON(),
- layoutProperties: $.extend(true, {}, layoutModel)
- })
- }, {
- op: 'removeWidget',
- parameter: id
- }, sender, payloadData);
- },
- /**
- *
- * Add a fragment to the model.
- *
- * passed model can contain a parent layout or array of layouts
- *
- * @param options
- * options.model which contains
- * {
- * layout: {
- * //one parent layout or parent layout with nested layouts
- * //one or widgets can be one or more of these layouts
- * id: xxxx
- * type: yyy
- * items: [{
- * id: x1
- * type: x2
- * },
- * ...
- * ],
- * style: {
- * height: aaa
- * width: bbb
- * top: ccc
- * left: ddd
- * }
- * },
- * episodes : [
- * {
- * "id": "x1",
- * "type": "widget",
- * "acts": [
- * {
- * "id": "act1",
- * "timer": 0,
- * "action": "show"
- * },
- * {
- * "id": "act2",
- * "timer": 5000,
- * "action": "hide"
- * }
- * ]
- * }
- * widgets : [
- * {
- * id: xxxx
- * type: yyyy
- * widget model data
- * },
- * ...
- * ],
- * datasetShapings : [
- * {
- * id: xxxx,
- * calculations: []
- * },
- * ...
- * ]
- * }
- * options.parentId
- * options.insertBefore
- *
- * If layout is an array, it should like:
- * layout: [{
- * id: xxxx
- * type: yyy
- * items: [{
- * id: x1
- * type: x2
- * },
- * ...
- * ],
- * style: {
- * height: aaa
- * width: bbb
- * top: ccc
- * left: ddd
- * }
- * }, {
- * id: xxxx
- * type: yyy
- * items: [{
- * id: x1
- * type: x2
- * },
- * ...
- * ]
- * }]
- * @param sender - If available, the sender will be added to the event payload.
- * @param payloadData - Additional data that will be included in the event payload.
- */
- addFragment: function addFragment(options, sender, payloadData) {
- var _this4 = this;
- // TODO this logic should be moved out of the model and into a BoardModelManager class
- // Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- payloadData = this.checkPayloadData(payloadData);
- //make a copy
- var fragSpec = _.extend({}, options.model);
- var datasources = this.dashboardApi.getFeature('dataSources.deprecated');
- var sourceCollection = datasources ? datasources.getSourcesCollection() : undefined;
- var sourceIdMap = sourceCollection ? sourceCollection.addSourcesForPin(fragSpec, { payloadData: payloadData }) : {};
- //add all widgets in fragment spec
- var widgetIdMap = {};
- _.each(fragSpec.widgets, function (widgetModel) {
- // replace widget ids in fragment with actual ones if the options tell us to
- // in some cases the id is already unique and valid. Mainly in the redo after undo case
- var oldId = widgetModel.id;
- if (!options.modelIdsValid) {
- widgetModel.id = undefined;
- }
- // Update the modelRef to the new sourceId if necessary
- var dataViews = widgetModel.data ? widgetModel.data.dataViews : null;
- if (dataViews) {
- dataViews.forEach(function (dataView) {
- if (sourceIdMap[dataView.modelRef]) {
- dataView.modelRef = sourceIdMap[dataView.modelRef];
- }
- });
- }
- var model = _this4._addWidgetModel(widgetModel);
- //keep a note of them, so that we can update layout spec
- widgetIdMap[oldId] = model.id;
- //update id in widgetModel with the new id from the model
- //That solves the redo (adding pin back) not working problem
- widgetModel.id = model.id;
- });
- //update the position information in layout if layoutProperies is defined
- //layoutProperties is defined when pin is created by clicking on create button
- if (options.layoutProperties && !_.isArray(fragSpec.layout)) {
- fragSpec.layout.style = fragSpec.layout.style || {};
- fragSpec.layout.style = _.extend(fragSpec.layout.style, options.layoutProperties.style);
- }
- //update widget ids in fragment spec to reflect new widget ids if we have not done it already
- if (!options.modelIdsValid) {
- fragSpec.layout = this._updateFragmentSpecLayout(fragSpec.layout, widgetIdMap, options);
- }
- //update drill through definitions
- var drillThroughService = void 0;
- try {
- drillThroughService = this.dashboardApi.getDashboardCoreSvc('DrillThroughService');
- } catch (error) {
- this.logger.info(error);
- }
- if (drillThroughService) {
- drillThroughService.addDrillThroughOnAddFragment(options.model, sourceIdMap, widgetIdMap, fragSpec);
- }
- // Add any custom colors
- if (fragSpec.properties && fragSpec.properties.customColors && fragSpec.properties.customColors.colors) {
- this.properties.customColors.addCustomColor(fragSpec.properties.customColors.colors);
- }
- if (fragSpec.fredIsRed && _.isEmpty(this.fredIsRed.colorMap)) {
- this.fredIsRed.colorMap = fragSpec.fredIsRed.colorMap;
- }
- //create layout using layout spec
- var layoutPayload = this.layout.addArray(this._getAddArrayPayload(fragSpec, options), sender, payloadData);
- //undo relies on just removing parent layout of the fragment
- var modelArray = layoutPayload.prevValue.parameter;
- // update pageContext
- _.each(fragSpec.pageContext, function (pageContextItem) {
- // TODO: For now, only origin=filter and tupleSet or conditions are handled
- if (pageContextItem.origin === 'filter') {
- var _options = void 0;
- if (pageContextItem.tupleSet) {
- _options = {
- values: _.values(JSON.parse(pageContextItem.tupleSet))
- };
- // TODO: Why is pagecontext conditions an array with'from' and 'to' arrays as well? I tried to see if it is possible
- // TODO: to have more than one value in the conditions property from the UI, I couldn't find a way. The code in
- // TODO: LiveWidget:PageContextRangeConditions.js expects values, it doesn't seem to handle arrays. This should be
- // TODO: looked at to be only scalar values, not arrays and handled in the spec schema accordingly so it validates
- } else if (pageContextItem.conditions) {
- _options = {
- 'condition': {
- attributeUniqueNames: pageContextItem.conditions[0].attributeUniqueNames,
- from: pageContextItem.conditions[0].from[0],
- to: pageContextItem.conditions[0].to[0]
- }
- };
- } else {
- _this4.logger.error('Error: AddFragment() - Only able to handle pageContext tupleSet and conditions, can not handle ' + JSON.stringify(pageContextItem));
- }
- _options = _.extend(_options, {
- scope: pageContextItem.scope,
- openViewOnLoad: false,
- exclude: pageContextItem.exclude,
- payloadData: payloadData
- });
- _this4.dashboardApi.getCanvasWhenReady().then(function (canvas) {
- canvas.filterApi.addFilter({
- sourceId: pageContextItem.sourceId,
- itemId: pageContextItem.hierarchyUniqueNames[0]
- }, _options);
- }).catch(function (error) {
- _this4.logger.error(error);
- });
- } else {
- _this4.logger.error('Error: AddFragment() - Only able to handle pagecontext.origin=filter, not ' + pageContextItem.origin);
- }
- });
- //trigger event
- return this._triggerAddRemove('addFragment', {
- op: 'addFragment',
- parameter: _.extend({}, options, {
- model: fragSpec,
- modelIdsValid: true,
- widgetIdMap: widgetIdMap
- })
- }, {
- op: 'removeFragment',
- parameter: modelArray
- }, sender, payloadData);
- },
- _getAddArrayPayload: function _getAddArrayPayload(fragSpec, options) {
- var aResult = [];
- if (_.isArray(fragSpec.layout)) {
- _.each(fragSpec.layout, function (entry) {
- aResult.push({
- parentId: options.parentId,
- insertBefore: options.insertBefore,
- model: entry
- });
- });
- return aResult;
- }
- return [{
- parentId: options.parentId,
- insertBefore: options.insertBefore,
- model: fragSpec.layout
- }];
- },
- _updateFragmentSpecLayout: function _updateFragmentSpecLayout(layout, widgetIdMap) {
- var boardModel = this;
- var update = function update(layout, widgetIdMap) {
- if (_.isArray(layout)) {
- _.each(layout, function (item) {
- update(item, widgetIdMap);
- });
- } else {
- if (layout.type !== 'widget') {
- // create a empty model to get an new ID.
- var layoutModel = boardModel.createLayoutModel({
- items: []
- }, {
- boardModel: boardModel,
- logger: boardModel.logger
- });
- layoutModel.off();
- widgetIdMap[layout.id] = layoutModel.id;
- layout.id = layoutModel.id;
- } else {
- layout.id = widgetIdMap[layout.id];
- }
- _.each(layout.items, function (item) {
- update(item, widgetIdMap);
- });
- }
- return layout;
- };
- if (layout) {
- return update(layout, widgetIdMap);
- } else {
- var addLayout = function addLayout(id) {
- return {
- id: id,
- type: 'widget'
- };
- };
- //this just adds widgets to a group layout - but does nothing about location of widgets or style applied
- //not sure if this is the best strategy
- //add a group layout if spec does not contain a layout
- return {
- type: 'group',
- items: _.map(_.values(widgetIdMap), addLayout)
- };
- }
- },
- _getObjectFromArrayById: function _getObjectFromArrayById(array) {
- var object = _.object(_.map(array, function (item) {
- return [item.id, item];
- }));
- return object;
- },
- /**
- *
- * removes a fragment from the model
- *
- * @param modelIds array of layout identifiers (typically will be one container id)
- *
- * @param sender - If available, the sender will be added to the event payload.
- * @param payloadData - Additional data that will be included in the event payload.
- */
- removeFragment: function removeFragment(modelIds, sender, payloadData) {
- return this.removeLayouts(modelIds, sender, payloadData);
- },
- /**
- * Remove a widget from the model.
- *
- * @param id
- * @param sender
- * @param payloadData - Additional data that will be included in the event payload.
- */
- removeWidget: function removeWidget(id, sender, payloadData) {
- // Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- payloadData = this.checkPayloadData(payloadData);
- this.trigger('pre:removeWidget', {
- id: id,
- sender: sender,
- data: _.extend({ runtimeOnly: true }, payloadData)
- });
- var payload = this.layout.removeArray([id], sender, payloadData);
- if (payload) {
- var widgetModel = this.widgetInstances[id];
- var evtData = this._triggerAddRemove('removeWidget', {
- op: 'removeWidget',
- parameter: id
- }, {
- op: 'addWidget',
- parameter: {
- parentId: payload.prevValue.parameter[0].parentId,
- model: widgetModel.toJSON(),
- layoutProperties: _.extend({}, payload.prevValue.parameter[0].model)
- }
- }, sender, payloadData);
- this.widgetInstances[id].off('change', this.onWidgetModelChange, this);
- delete this.widgetInstances[id];
- return evtData;
- }
- },
- getUsedCustomColors: function getUsedCustomColors(customColors) {
- var usedColors = Model.inherited('getUsedCustomColors', this, arguments);
- if (this.widgetInstances) {
- for (var widgetModel in this.widgetInstances) {
- usedColors = usedColors.concat(this.widgetInstances[widgetModel].getUsedCustomColors(customColors));
- }
- }
- if (this.layout) {
- usedColors = usedColors.concat(this.layout.getUsedCustomColors(customColors));
- }
- return _.uniq(usedColors, false);
- },
- /**
- * Remove a list of layouts from the board model. This method will also remove all the widgets contained in the layouts being removed
- *
- * @param id - could be a string with one id or an array if IDs
- * @param sender
- * @param payloadData - Additional data that will be included in the event payload.
- */
- removeLayouts: function removeLayouts(id, sender, payloadData) {
- // Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- payloadData = this.checkPayloadData(payloadData);
- var idArray = _.isArray(id) ? id : [id];
- this.trigger('pre:removeLayouts', {
- idArray: idArray,
- sender: sender,
- data: _.extend({ runtimeOnly: true }, payloadData)
- });
- var widgetIds = this.layout.listWidgets(idArray);
- var payload = this.layout.removeArray(idArray, sender, payloadData);
- if (payload) {
- var removedWidgets = {};
- var widgetId;
- for (var i = 0; i < widgetIds.length; i++) {
- widgetId = widgetIds[i];
- var widgetModel = this.widgetInstances[widgetId];
- if (widgetModel) {
- removedWidgets[widgetId] = widgetModel.toJSON();
- widgetModel.off();
- delete this.widgetInstances[widgetId];
- }
- }
- return this._triggerAddRemove('removeLayouts', {
- op: 'removeLayouts',
- parameter: idArray,
- removedWidgets: removedWidgets
- }, {
- op: 'addLayouts',
- parameter: {
- widgetSpecMap: removedWidgets,
- addLayoutArray: payload.prevValue.parameter
- }
- }, sender, payloadData);
- }
- },
- /**
- * Add layouts to the board. Layouts can contain widgets
- *
- * options.widgetSpecMap A map of widget model json to be added. The map key is the id used in the layout to reference the widget
- * options.addLayoutArray = [{
- * parentId - layout parent id where to add this layout
- * model - layout model json
- * }]
- *
- * @param options - widgetSpecMap, addLayoutArray
- * @param sender
- * @param payloadData - Additional data that will be included in the event payload.
- */
- addLayouts: function addLayouts(options, sender, payloadData) {
- // Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- payloadData = this.checkPayloadData(payloadData);
- // Add widgets
- if (options.widgetSpecMap) {
- for (var id in options.widgetSpecMap) {
- if (options.widgetSpecMap.hasOwnProperty(id)) {
- this._addWidgetModel(options.widgetSpecMap[id], { id: id });
- }
- }
- }
- var layout = options.parentId ? this.layout.findModel(options.parentId) : this.layout;
- if (!layout) {
- layout = this.layout;
- }
- // Add layouts
- var payload = layout.addArray(options.addLayoutArray, sender, payloadData);
- return this._triggerAddRemove('addLayouts', {
- op: 'addLayouts',
- parameter: _.extend({}, options, {
- widgetSpecMap: options.widgetSpecMap,
- addLayoutArray: payload.value.parameter
- })
- }, {
- op: 'removeLayouts',
- parameter: payload.prevValue.parameter
- }, sender, payloadData);
- },
- _isTypeRegistered: function _isTypeRegistered(type) {
- var contentTypeRegistry = this.dashboardApi.getFeature('ContentTypeRegistry');
- return contentTypeRegistry.isTypeRegistered(type);
- },
- duplicateLayout: function duplicateLayout(layoutId, sender, payload) {
- var _this5 = this;
- payload = this.checkPayloadData(payload);
- var layoutModel = this.layout.findModel(layoutId);
- var idMap = {};
- var clone = function clone() {
- if (layoutModel) {
- if (_this5._isTypeRegistered(layoutModel.type)) {
- var containerId = layoutModel.getParent().id;
- var content = _this5.dashboardApi.getFeature('Canvas').getContent(layoutId);
- return _this5.dashboardApi.getFeature('Canvas').addContent({
- containerId: containerId,
- spec: content.getFeature('Serializer').toJSON(),
- copyPaste: true
- }).then(function (newContent) {
- var newId = newContent.getId();
- idMap[layoutId] = newId;
- return newId;
- });
- } else {
- var widgetSpecMap = _this5._cloneWidgets(layoutModel, idMap);
- var _clone = _this5._cloneLayout(layoutModel, idMap, payload);
- var options = {
- parentId: layoutModel.getParent().id,
- widgetSpecMap: widgetSpecMap,
- addLayoutArray: [{
- model: _clone.toJSON(),
- insertBefore: layoutModel.type === 'widget' ? null : layoutModel.getNextSiblingId()
- }]
- };
- _this5.addLayouts(options, sender, payload);
- return Promise.resolve(_clone.id);
- }
- }
- return Promise.resolve();
- };
- return clone().then(function (cloneId) {
- // all work in being done in other methods that take care of the undo/redo stack management
- // however we still want to notify interested classes that a duplicate occurred.
- // simplest way is to manually trigger an event and not include a senders context
- // this should bypass the undoduplicateLayout/redo stack since this method did not do anything that needs to be undone.
- // we still send the payload data in case someone else needs to do some undoable work in this transaction.
- _this5.trigger('duplicateLayout', {
- layoutId: layoutId,
- cloneId: cloneId,
- idMap: idMap,
- sender: sender,
- data: payload
- });
- return cloneId;
- });
- },
- duplicateSelection: function duplicateSelection(options, sender, payload) {
- var ids = [];
- var payloadData = this.checkPayloadData(payload);
- payloadData.limitToBounds = [];
- _.each(options.modelIds, function (id) {
- if (options.outBoundIds && options.outBoundIds.filter(function (e) {
- return e.id === id;
- }).length > 0) {
- payloadData.limitToBounds.push(options.outBoundIds.filter(function (e) {
- return e.id === id;
- }));
- }
- ids.push(this.duplicateLayout(id, sender, payloadData));
- }.bind(this));
- return ids;
- },
- updateLayoutType: function updateLayoutType(options, sender, payloadData) {
- payloadData = this.checkPayloadData(payloadData);
- var layout = options.id ? this.layout.findModel(options.id) : this.layout;
- if (layout && options.type) {
- layout.set({
- type: options.type
- }, {
- sender: sender,
- payloadData: payloadData
- });
- }
- },
- /**
- * Update a layout's drop zones only if the layout has at least one drop zone.
- * All existing widgets will go untouched as this is purely a layout drop zone change
- *
- * @param options.id {string} The id of the layout that has the template/drop zones to be removed/added to
- * @param options.model {object} The user selected layout model that contains the drop zones
- * @param sender {object} use if a previous action has taken place and we want to combine this action with it
- * @param payload {object} the payload of a previous action that has taken place
- */
- updateLayoutDropZones: function updateLayoutDropZones(options, sender, payload) {
- /*
- * This little bit of code looks odd but the LayoutModel.findModel() method can potentially return null.
- * For this reason we need to do the check that the layout variable is a falsey value and if
- * so then we use the boardModel's layout instance
- */
- var layout;
- if (options.id) {
- layout = this.layout.findModel(options.id);
- }
- if (!layout) {
- layout = this.layout;
- }
- // Try to get a list of drop zones in the selected layout
- var dropZones = layout.findDropZones();
- // If no drop zones then there is nothing else to do
- if (!dropZones) {
- return payload;
- }
- // Remove the drop zones while saving the payload because it is needed when adding the new drop zones
- payload = this.removeLayouts(dropZones.ids, sender, payload);
- /*
- * Create the array list of drop zone layouts to add
- * Set the 'insertBefore' property to be the first widget as all widgets need to come after drop zone layouts.
- * If there are no widgets then firstWidget returns undefined which is fine
- */
- var firstWidget = layout.listWidgets([layout.id])[0];
- var addArray = [];
- for (var i = 0; i < options.model.items[0].items.length; i++) {
- addArray.push({
- parentId: dropZones.parentId,
- model: options.model.items[0].items[i],
- insertBefore: firstWidget
- });
- }
- payload = this.addLayouts({
- parentId: dropZones.parentId,
- addLayoutArray: addArray
- }, payload.sender, payload.data);
- return payload;
- },
- _cloneWidgets: function _cloneWidgets(layoutModel, idMap) {
- var widgetSpecMap = {};
- var widgets = layoutModel.listWidgets([layoutModel.id]);
- _.each(widgets, function (widgetId) {
- var widgetModel = this.widgetInstances[widgetId];
- var clonedWidget = widgetModel.cloneWidget(idMap);
- widgetSpecMap[clonedWidget.id] = clonedWidget.toJSON();
- }.bind(this));
- return widgetSpecMap;
- },
- _cloneLayout: function _cloneLayout(layoutModel, idMap, payload) {
- var clone = layoutModel.cloneLayout(idMap);
- if ((clone.type === 'widget' || clone.type === 'group') && clone.style && payload.limitToBounds && payload.limitToBounds.length <= 0) {
- // offset layout by 5% / 25px down/right for the cloned
- clone.style.top = clone.incrementStyleValue(clone.style.top);
- clone.style.left = clone.incrementStyleValue(clone.style.left);
- } else if (payload.limitToBounds && payload.limitToBounds.length > 0) {
- payload.limitToBounds[0].forEach(function (element) {
- if (element.bottom) {
- clone.style.top = clone.incrementStyleValue(clone.style.top);
- }
- if (element.right) {
- clone.style.left = clone.incrementStyleValue(clone.style.left);
- }
- if (!element.right && !element.bottom) {
- if (parseFloat(clone.decrementStyleValue(clone.style.top)) > 0) {
- clone.style.top = clone.decrementStyleValue(clone.style.top);
- }
- if (parseFloat(clone.decrementStyleValue(clone.style.left)) > 0) {
- clone.style.left = clone.decrementStyleValue(clone.style.left);
- }
- }
- });
- }
- return clone;
- },
- getLanguageModelOptions: function getLanguageModelOptions() {
- var addDashboardTranslatedLocales = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
- var languageOptions = {
- defaultLocale: this.defaultLocale,
- contentLocale: this.contentLocale,
- translationLocale: this.translationLocale
- };
- // Add in all the locales the dashboard is currently translated in. This is needed so that new MultilingualAttributes
- // default to the correct locale
- if (addDashboardTranslatedLocales) {
- var translationService = this.dashboardApi.getDashboardCoreSvc('TranslationService');
- languageOptions.availableDashboardLocales = translationService.getSelectedLanguages();
- }
- return languageOptions;
- },
- _addWidgetModel: function _addWidgetModel(widgetSpec) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- if (!widgetSpec.name) {
- widgetSpec.name = '';
- }
- //The widget registry supports plugin model classes for times when custom models need to be defined.
- //Live Widget (for example) is defined in an external component and has a custom model class.
- var WidgetModelClass = WidgetModel;
- if (widgetSpec && this.widgetRegistry && this.widgetRegistry[widgetSpec.type] && this.widgetRegistry[widgetSpec.type].ModelClass) {
- WidgetModelClass = this.widgetRegistry[widgetSpec.type].ModelClass;
- }
- var id = options.id,
- _options$addDashboard = options.addDashboardTranslatedLocales,
- addDashboardTranslatedLocales = _options$addDashboard === undefined ? true : _options$addDashboard;
- var languageModelOptions = this.getLanguageModelOptions(addDashboardTranslatedLocales);
- var model = new WidgetModelClass(widgetSpec, languageModelOptions);
- model.on('change', this.onWidgetModelChange, this);
- this.widgetInstances[id || model.id] = model;
- return model;
- },
- /**
- * Called when a layout change happens that we want to propagate. (e.g. layout move, resize, etc..)
- * The layout model takes care of creating the event payload with the undo/redo handler.
- *
- * @param payload
- * @param sender
- */
- onLayoutChange: function onLayoutChange(payload, sender) {
- this.trigger('change:layout', payload, sender);
- },
- /**
- * Helper function used to trigger the add/remove event
- *
- * @param eventName
- * @param value
- * @param prevValue
- * @param sender
- * @param payloadData - Additional data that will be included in the event payload.
- */
- _triggerAddRemove: function _triggerAddRemove(eventName, value, prevValue, sender, payloadData) {
- var payload = {
- value: value,
- prevValue: prevValue,
- sender: sender,
- senderContext: {
- applyFn: this.applyFn.bind(this)
- },
- data: payloadData
- };
- this.trigger(eventName, payload);
- return payload;
- },
- /**
- * Function used to handler the undo/redo for the board model updates
- * @param value
- * @param sender
- */
- applyFn: function applyFn(value, sender, name, payload) {
- if (value.op && typeof this[value.op] === 'function') {
- var args = [value.parameter];
- args.push(sender);
- args.push(payload);
- this[value.op].apply(this, args);
- } else {
- Model.inherited('applyFn', this, arguments);
- }
- },
- /**
- * Handles changes from widget models (which can be added/removed as widgets are added/removed) and notifies
- * listener of the change (so listener does not need to track addition/deletion of widget models
- *
- * @param payload
- */
- onWidgetModelChange: function onWidgetModelChange(payload) {
- var senderContext = payload.senderContext || {};
- var modelId = _.isObject(payload.sender) ? payload.sender.id : payload.sender;
- //TODO: Need to revist this when porting to Endor, why are we overwriting the applyFn?
- //https://github.ibm.com/BusinessAnalytics/dashboard-core/pull/1311
- if (payload && payload.senderContext && this.widgetInstances[modelId]) {
- payload.senderContext.applyFn = function () {
- var model = this.widgetInstances[modelId];
- if (model) {
- model.applyFn.apply(model, arguments);
- }
- }.bind(this);
- }
- this.trigger('widget:change', _.extend({
- modelId: payload.model ? payload.model.id : modelId,
- senderContext: senderContext
- }, payload));
- },
- toJSON: function toJSON() {
- var spec = Model.inherited('toJSON', this, [null, ['layout']]);
- var canvas = this.dashboardApi.getFeature('Canvas');
- var topLevelContent = canvas.findContent({ type: this.layout.type })[0];
- spec.layout = topLevelContent.getFeature('Serializer').toJSON();
- return spec;
- },
- /**
- * Check the payload data
- * Create a default transaction id if none is provided so that it is easier to bundler subsequent operation with this one
- * @param {Object} payloadData
- */
- // TODO: we're actually not "checking" anything in this method, should this be renamed?
- checkPayloadData: function checkPayloadData(payloadData) {
- if (payloadData) {
- return payloadData;
- }
- return {
- undoRedoTransactionId: _.uniqueId('boardModelTransaction')
- };
- },
- /**
- * Find widget model by id and type.
- *
- * @param {string} id - widget id
- *
- * @return {WidgetModel} widget model instance found
- */
- findWidgetById: function findWidgetById(id) {
- return this.widgetInstances ? this.widgetInstances[id] : null;
- },
- /**
- * Find widget model matching one of the ids
- *
- * @param {string[]} ids - widget ids to match
- *
- * @return {WidgetModel} widget model instance found
- */
- findWidgetByIds: function findWidgetByIds(ids) {
- var finding;
- ids.some(function (id) {
- var breakLoop = false;
- var widget = this.findWidgetById(id);
- if (widget) {
- finding = widget;
- breakLoop = true;
- }
- return breakLoop;
- }.bind(this));
- return finding;
- },
- findWidgetByCriteriaFn: function findWidgetByCriteriaFn(criteriaFn) {
- var finding;
- for (var key in this.widgetInstances) {
- if (this.widgetInstances.hasOwnProperty(key)) {
- var widgetInstance = this.widgetInstances[key];
- if (criteriaFn(widgetInstance)) {
- finding = widgetInstance;
- break;
- }
- }
- }
- return finding;
- },
- filterWidgetsByCriteriaFn: function filterWidgetsByCriteriaFn(criteriaFn) {
- var findings = [];
- for (var key in this.widgetInstances) {
- if (this.widgetInstances.hasOwnProperty(key)) {
- var widgetInstance = this.widgetInstances[key];
- if (criteriaFn(widgetInstance)) {
- findings.push(widgetInstance);
- }
- }
- }
- return findings;
- },
- /**
- * Override the base method since we need to deal with our widgetModels and layoutModel which are
- * not real nested models
- */
- getContentReferences: function getContentReferences() {
- var deploymentRefs = Model.inherited('getContentReferences', this, arguments);
- if (this.widgetInstances) {
- for (var widgetModel in this.widgetInstances) {
- deploymentRefs = deploymentRefs.concat(this.widgetInstances[widgetModel].getContentReferences());
- }
- }
- if (this.layout) {
- deploymentRefs = deploymentRefs.concat(this.layout.getContentReferences());
- }
- return _.uniq(deploymentRefs, false, function (ref) {
- return ref.value;
- });
- },
- /**
- * Should only be used by dashboard Serializer feature
- * @return {Object} the inital board spec
- */
- getInitialSpec: function getInitialSpec() {
- return this._initBoardSpec;
- },
- /**
- * Delete the initial board spec, it is invoked only once when canvas is ready
- */
- deleteInitialSpec: function deleteInitialSpec() {
- delete this._initBoardSpec;
- }
- });
- return Model;
- });
- //# sourceMappingURL=BoardModel.js.map
|