LayoutController.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  1. 'use strict';
  2. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: dashboard
  6. * (C) Copyright IBM Corp. 2013, 2020
  7. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  8. */
  9. define(['../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../view/features/content/contentViewDom/api/impl/ContentViewDomImpl', 'underscore', 'jquery', '../../app/util/EventChainLocal', './LayoutHelper', '../../lib/@waca/dashboard-common/dist/core/APIFactory', '../../api/deprecated/CanvasAPI', '../../canvas/DeprecatedCanvas', './authoring/interaction/Controller', './authoring/interaction/strategy/Common', '../../lib/@waca/core-client/js/core-client/utils/Deferred', '../../lib/@waca/core-client/js/core-client/utils/ClassFactory'], function (Class, ContentViewDomImpl, _, $, EventChainLocal, LayoutHelper, APIFactory, DeprecatedCanvasAPI, DeprecatedCanvas, Controller, CommonInteraction, Deferred, ClassFactory) {
  10. var LayoutController = null; // declaration
  11. LayoutController = Class.extend([], {
  12. init: function init(options) {
  13. LayoutController.inherited('init', this, arguments);
  14. this.contentFeatureLoader = options.contentFeatureLoader;
  15. //TODO - to be remove after refactoring
  16. this.services = options.services;
  17. this.appSettings = options.appSettings || {};
  18. if (this.appSettings.options && this.appSettings.options.config) {
  19. this.config = this.appSettings.options.config;
  20. } else {
  21. this.config = {};
  22. }
  23. // TODO - end
  24. this.dashboardApi = options.dashboardApi;
  25. this.canvas = options.canvas;
  26. this.glassContext = options.glassContext;
  27. this.transaction = this.dashboardApi.getFeature('Transaction');
  28. this.contentTypeRegistry = this.dashboardApi.getFeature('ContentTypeRegistry');
  29. this.isAuthoring = this.dashboardApi.getMode() === 'authoring';
  30. this.id = _.uniqueId('layoutController');
  31. this.$el = options.$el;
  32. this.viewsMap = {};
  33. this.boardModel = options.boardModel;
  34. this.boardModuleFactory = options.boardModuleFactory;
  35. this.interactionControllerDfd = new Deferred();
  36. this.widgetLoader = options.widgetLoader;
  37. this.canvasController = options.canvasController;
  38. this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
  39. this.colorsService = this.dashboardApi.getFeature('Colors');
  40. if (this.colorsService) {
  41. this.colorsService.on('theme:changed', this.onThemeChange, this);
  42. this.colorsService.on('colorSet:changed', this.onColorSetChanged, this);
  43. }
  44. this.topLayoutModel = this.boardModel.layout;
  45. this.topLayoutModel.on('change:layoutPositioning', this._onLayoutPositioning, this);
  46. this._setupViewModules(options.layoutExtensions || {});
  47. this.canvasNotifier = options.canvasNotifier;
  48. this.interactionController = null;
  49. this.eventRouter = options.eventRouter;
  50. if (this.eventRouter) {
  51. this.eventRouter.on('canvas:selectWidget', this.selectWidget, this);
  52. this.eventRouter.on('canvas:deselectWidget', this.deselectWidget, this);
  53. this.eventRouter.on('scene:select', this.deselectAllWidgets, this);
  54. this.eventRouter.on('scene:next', this.deselectAllWidgets, this);
  55. this.eventRouter.on('scene:previous', this.deselectAllWidgets, this);
  56. }
  57. this.gatewayUrl = options.gatewayUrl;
  58. this.cdnUrl = options.cdnUrl;
  59. this._init();
  60. // Create the deprecated canvas
  61. // This is the canvas implementation for all the deprecated APIs
  62. // It is is created in here because the deprecated APIs depend on the layout controller and the layout and widget views
  63. // The non deprecated canvas API is created earlier in the boardLoader.
  64. this.deprecatedCanvas = new DeprecatedCanvas(this.dashboardApi, this.boardModel, this.canvasController, this, this.widgetLoader);
  65. },
  66. initialize: function initialize() {
  67. return this.pageReady;
  68. },
  69. getDeprecatedCanvas: function getDeprecatedCanvas() {
  70. // Merge the public canvas api with the deprecated one
  71. // Eventually , the layout controller will stop creating the deprecated one onces all deprecated method are no longer used
  72. if (!this.mergedCanvasAPI) {
  73. this.mergedCanvasAPI = _.extend(this.deprecatedCanvas.getAPI(), this.canvas);
  74. }
  75. return this.mergedCanvasAPI;
  76. },
  77. _setupViewModules: function _setupViewModules(layoutExtensions) {
  78. this.consumeViews = layoutExtensions.consumeViews || {};
  79. this.authoringViews = layoutExtensions.authoringViews || {};
  80. },
  81. /** If the layout has 'subviews' (ie. tabs or scenes), return the id of the current one. */
  82. getCurrentSubViewId: function getCurrentSubViewId() {
  83. var topView = this.getView(this.topLayoutModel.id).view;
  84. return topView.getSelectedSubViewId();
  85. },
  86. /** If the layout has 'subviews' (ie. tabs or scenes), return the title of the current one. */
  87. getCurrentSubViewTitle: function getCurrentSubViewTitle() {
  88. var topView = this.getView(this.topLayoutModel.id).view;
  89. return topView.getSelectedSubViewTitle();
  90. },
  91. _animationHelper: function _animationHelper(el, property, value, duration) {
  92. return new Promise(function (resolve) {
  93. var transitionEndCallback = function transitionEndCallback() {
  94. el.style.transition = 'none';
  95. el.removeEventListener('transitionend', transitionEndCallback);
  96. resolve();
  97. };
  98. el.addEventListener('transitionend', transitionEndCallback);
  99. el.style.transition = property + ' ' + duration + 'ms';
  100. el.style[property] = value;
  101. // fail safe
  102. Promise.delay(duration + 100).then(transitionEndCallback);
  103. });
  104. },
  105. _init: function _init() {
  106. this._interactionControllerCreationPromise = this._createInteractionController().then(function () {
  107. var topView = this._getTopViewElement()[0];
  108. if (topView) {
  109. $(topView).toggleClass('authoringMode', this.isAuthoring);
  110. }
  111. }.bind(this));
  112. $(window).on('resize.layoutController' + this.id, this.onResize.bind(this));
  113. // set global right-click context menu suppression
  114. this.fnOnContextMenu = this.onContextMenu.bind(this);
  115. window.oncontextmenu = this.fnOnContextMenu;
  116. },
  117. createLayoutModules: function createLayoutModules() {
  118. var _this = this;
  119. this.pageReady = Promise.all([this._interactionControllerCreationPromise, this.createLayoutModule(this.topLayoutModel.id).then(function (layout) {
  120. if (layout) {
  121. return layout.renderContent();
  122. }
  123. })]);
  124. // Mark the page when the render is complete for the top layout and all widget underneath it.
  125. this.onPageRenderComplete = this.whenPageRenderComplete();
  126. this.onPageRenderComplete.then(function () {
  127. var topLayoutView = _this.getTopLayoutView();
  128. if (topLayoutView) {
  129. topLayoutView.$el.attr('data-render', 'renderComplete');
  130. }
  131. });
  132. },
  133. _getInteractionControllerInteractionModules: function _getInteractionControllerInteractionModules() {
  134. var paths = void 0;
  135. if (this.isAuthoring && !this.isEventGroupMode) {
  136. paths = ['dashboard-core/js/dashboard/layout/authoring/interaction/strategy/Authoring'];
  137. } else {
  138. paths = [];
  139. }
  140. return Promise.all(paths.map(function (path) {
  141. return ClassFactory.loadModule(path);
  142. }));
  143. },
  144. _shouldEnableInteractions: function _shouldEnableInteractions() {
  145. return !(this.dashboardApi.getAppConfig('enableInteractions') === false);
  146. },
  147. _createInteractionController: function _createInteractionController() {
  148. var _this2 = this;
  149. if (!this._shouldEnableInteractions() || this.interactionController) {
  150. return Promise.resolve();
  151. }
  152. return this._loadInteractionControllerConfig().then(function (selectionOptions) {
  153. var interactionController = new Controller({
  154. // TODO - to be removed after refactoring
  155. services: _this2.services,
  156. glassContext: _this2.glassContext,
  157. appSettings: _this2.appSettings,
  158. // TODO - end to be removed
  159. dashboardAPI: _this2.dashboardApi,
  160. toolbarConfig: _this2.dashboardApi.getAppConfig('toolbar'),
  161. transaction: _this2.transaction,
  162. canvas: _this2.canvas,
  163. boardModel: _this2.boardModel,
  164. layoutController: _this2,
  165. eventRouter: _this2.eventRouter,
  166. $el: _this2.$el,
  167. gatewayUrl: _this2.gatewayUrl,
  168. cdnUrl: _this2.cdnUrl,
  169. selectionOptions: selectionOptions
  170. });
  171. // TODO temporary code to register interaction controller needed by the widget action features, e.g. GroupAction
  172. _this2.dashboardApi.getFeature('internal').registerDashboardSvc('InteractionController.internal', interactionController);
  173. _this2.defaultInteractions = [new CommonInteraction(_this2.logger, _this2.dashboardApi)];
  174. _this2._setInteractionController(interactionController);
  175. return _this2._updateInteractionControllerInteractions();
  176. });
  177. },
  178. _setInteractionController: function _setInteractionController(controller) {
  179. this.interactionController = controller;
  180. this.interactionControllerDfd.resolve(controller);
  181. },
  182. getInteractionController: function getInteractionController() {
  183. return this.interactionControllerDfd.promise;
  184. },
  185. _loadInteractionControllerConfig: function _loadInteractionControllerConfig() {
  186. var _ref = this.dashboardApi.getAppConfig('selection') || {},
  187. deselectionSelector = _ref.deselectionSelector;
  188. if (deselectionSelector) {
  189. return new Promise(function (resolve, reject) {
  190. require([deselectionSelector], resolve, reject);
  191. }).then(function (module) {
  192. return {
  193. deselectionSelector: module
  194. };
  195. });
  196. }
  197. return Promise.resolve();
  198. },
  199. /**
  200. * Set the interaction controller strategy (authoring, consumption, event group).
  201. *
  202. * @return {Promise} Promise to be resolved when the strategy has been set
  203. */
  204. _updateInteractionControllerInteractions: function _updateInteractionControllerInteractions() {
  205. var _this3 = this;
  206. if (!this.interactionController) {
  207. return Promise.resolve();
  208. }
  209. return this._getInteractionControllerInteractionModules().then(function (Interactions) {
  210. var interactions = Interactions.map(function (Interaction) {
  211. return new Interaction(_this3.logger);
  212. });
  213. var allInteractions = _this3.defaultInteractions.concat(interactions);
  214. return _this3.interactionController.applyInteractions(allInteractions).then(function () {
  215. _this3.dashboardApi.getFeature('.LifeCycleManager').invokeLifeCycleHandlers('dashboard.layout.interactions.ready');
  216. });
  217. });
  218. },
  219. /**
  220. * Moves to consume mode
  221. *
  222. * Removes authoring functionality that was added to the all consume views.
  223. * Views associated with static widgets remain (they are not deleted)
  224. */
  225. changeToConsumeMode: function changeToConsumeMode() {
  226. var _this4 = this;
  227. this.isAuthoring = false;
  228. this.isEventGroupMode = false;
  229. return this._updateInteractionControllerInteractions().then(function () {
  230. _this4.removeAuthorModeFromLayout(_this4.topLayoutModel.id);
  231. _this4._getTopViewElement().removeClass('authoringMode').removeClass('eventGroupMode');
  232. });
  233. },
  234. /**
  235. * Removes author behavior to specified view
  236. *
  237. * @param id - layout identifier
  238. */
  239. removeAuthorModeFromLayout: function removeAuthorModeFromLayout(id) {
  240. var layoutModel = this.topLayoutModel.findModel(id);
  241. var view = $('#' + layoutModel.id, this.$el)[0];
  242. if (view && view._layout && view._layout.authorHelper) {
  243. view._layout.authorHelper.destroy();
  244. view._layout.authorHelper = undefined;
  245. if (view._layout.authorViewManager) {
  246. view._layout.authorViewManager.destroy();
  247. view._layout.authorViewManager = undefined;
  248. }
  249. }
  250. if (layoutModel.items) {
  251. for (var i = 0, iLen = layoutModel.items.length; i < iLen; i++) {
  252. this.removeAuthorModeFromLayout(layoutModel.items[i].id);
  253. }
  254. }
  255. },
  256. /**
  257. * Attaches author behavior
  258. *
  259. * @param options - {canvasNotifier: notifier obj}
  260. * @return promise to indicate when move to authoring is complete
  261. */
  262. changeToAuthorMode: function changeToAuthorMode(options) {
  263. var _this5 = this;
  264. this.isAuthoring = true;
  265. this.isEventGroupMode = false;
  266. this.canvasNotifier = options.canvasNotifier;
  267. //start with top layout
  268. return this.addAuthorModetoLayout(this.topLayoutModel.id, null).then(function () {
  269. _this5._getTopViewElement().addClass('authoringMode').removeClass('eventGroupMode');
  270. // initialize interaction controller strategy
  271. return _this5._updateInteractionControllerInteractions();
  272. });
  273. },
  274. changeToEventGroupMode: function changeToEventGroupMode() {
  275. var _this6 = this;
  276. this.isAuthoring = true;
  277. this.isEventGroupMode = true;
  278. return this._updateInteractionControllerInteractions().then(function () {
  279. _this6.removeAuthorModeFromLayout(_this6.topLayoutModel.id);
  280. _this6._getTopViewElement().removeClass('authoringMode').addClass('eventGroupMode');
  281. });
  282. },
  283. _getTopViewElement: function _getTopViewElement() {
  284. return this.$el.find('#' + this.topLayoutModel.id);
  285. },
  286. /**
  287. * Attaches author behavior to specified view
  288. *
  289. * @param id - layout identifier
  290. * @param parentLayout - parent view for layout being moved to author
  291. * @return promise to indicate when move to authoring is complete
  292. */
  293. addAuthorModetoLayout: function addAuthorModetoLayout(id, parentLayout) {
  294. var _this7 = this;
  295. var layoutModel = this.topLayoutModel.findModel(id);
  296. var node = $('#' + this.modelIdToNodeId(id), this.$el)[0];
  297. var pageLayoutView = node && node._layout;
  298. if (!pageLayoutView) {
  299. // the layout view does not exist. This is the case for static widget and layouts like group layout.
  300. // Create layout module will take care of create the layout view and the author helper.
  301. return this.createLayoutModule(id, parentLayout).then(function (layout) {
  302. return layout.renderContent();
  303. });
  304. }
  305. if (pageLayoutView.authorHelper) {
  306. // Authoring has already been enabled for this view.
  307. return Promise.resolve();
  308. }
  309. var options = {
  310. //TODO - remove after refactoring
  311. services: this.services,
  312. config: this.config,
  313. appSettings: this.appSettings,
  314. // TODO - end remove
  315. canvas: this.canvas,
  316. dashboardApi: this.dashboardApi,
  317. model: layoutModel,
  318. parentLayout: parentLayout,
  319. layoutController: this,
  320. canvasNotifier: this.canvasNotifier,
  321. consumeView: pageLayoutView,
  322. widgetChromeEventRouter: pageLayoutView.widgetChromeEventRouter,
  323. eventRouter: this.eventRouter
  324. };
  325. var modules = this._getLayoutModule(layoutModel, options);
  326. return ClassFactory.loadModule(modules.sAuthorHelper).then(function (PageLayoutAuthor) {
  327. // The authoring view will set the authorHelper reference of the consumeView to itself
  328. new PageLayoutAuthor(options);
  329. pageLayoutView.renderContent();
  330. //process children layouts
  331. var layoutModels = layoutModel.items;
  332. if (layoutModels && !pageLayoutView.preventAuthoringForChildren) {
  333. var promises = [];
  334. for (var i = 0, iLen = layoutModels.length; i < iLen; i++) {
  335. promises.push(_this7.addAuthorModetoLayout(layoutModels[i].id, pageLayoutView));
  336. }
  337. return Promise.all(promises);
  338. }
  339. });
  340. },
  341. getView: function getView(modelId) {
  342. return this.viewsMap[modelId];
  343. },
  344. markViewAsReady: function markViewAsReady(view) {
  345. if (this.viewsMap[view.id]) {
  346. this.viewsMap[view.id].viewReadyDeferred.resolve(view);
  347. }
  348. },
  349. /**
  350. * Create an instance of the layout module
  351. *
  352. * @param {string} pageId - layout page id
  353. * @param {View} parentLayout - parent view for layout being moved to author
  354. * @param {any} [additionalWidgetData] - data to be passed to the widget when the layout is created.
  355. *
  356. * @return {Promise} Promise to indicate when the creation is complete
  357. */
  358. createLayoutModule: function createLayoutModule(pageId, parentLayout, additionalWidgetData) {
  359. var existingView = this.viewsMap[pageId] && this.viewsMap[pageId].view;
  360. if (existingView) {
  361. /* No need to create the view. We have an existing view associated with the layout id that is being created.
  362. This might happen after the following scenario:
  363. - Create layout model A
  364. - Create layout model B
  365. - Move layout model A to be child of B.
  366. The view creation is asynchronous. This means that the process of the creation of A & B will start
  367. but the move operation will happen before the view are created so the changes will not be reflected in the views.
  368. so we end up with creating the view A and the view B with another instance of View A.
  369. The scenario that will cause this is the following:
  370. - group 2 objects.
  371. - delete 1 of the grouped objects.
  372. - This will delete the object and remove the group
  373. - undo the change.
  374. */
  375. var _promise = this.viewsMap[pageId].viewReady;
  376. _promise.then(function () {
  377. var nodeId = this.modelIdToNodeId(pageId);
  378. this.viewsMap[pageId].view.parentLayout = parentLayout;
  379. $(parentLayout.domNode).find('#' + nodeId).replaceWith(this.viewsMap[pageId].view.domNode);
  380. }.bind(this));
  381. return _promise;
  382. }
  383. var deferred = new Deferred();
  384. var page = this.topLayoutModel.findModel(pageId);
  385. var options = {
  386. // TODO - remove after refactoring
  387. services: this.services,
  388. config: this.config,
  389. appSettings: this.appSettings,
  390. // TODO - end remove
  391. canvas: this.canvas,
  392. content: this.canvas.getContent(pageId),
  393. dashboardApi: this.dashboardApi,
  394. eventRouter: this.eventRouter,
  395. model: page,
  396. layoutController: this,
  397. canvasNotifier: this.canvasNotifier,
  398. parentLayout: parentLayout,
  399. additionalWidgetData: additionalWidgetData,
  400. contentFeatureLoader: this.contentFeatureLoader
  401. };
  402. var layoutModules = this._getLayoutModule(page, options);
  403. var sPageLayout = layoutModules.sPageLayout;
  404. var sAuthorHelper = layoutModules.sAuthorHelper;
  405. this.viewsMap[pageId] = {
  406. viewReadyDeferred: deferred,
  407. viewReady: deferred.promise
  408. };
  409. var promise = void 0;
  410. if (sPageLayout) {
  411. var modules = ['dashboard-core/js/app/EventRouter'];
  412. if (sAuthorHelper) {
  413. modules.push(sAuthorHelper);
  414. }
  415. var pageLayout = void 0;
  416. if ((typeof sPageLayout === 'undefined' ? 'undefined' : _typeof(sPageLayout)) === 'object') {
  417. pageLayout = sPageLayout;
  418. } else {
  419. pageLayout = {
  420. path: sPageLayout,
  421. type: this.boardModuleFactory.getDefaultModuleType()
  422. };
  423. }
  424. promise = Promise.all([this.loadLayout(pageLayout), Promise.all(modules.map(function (module) {
  425. return ClassFactory.loadModule(module);
  426. }))]).then(function (Modules) {
  427. // update the parent layout in the options
  428. // there is a case where by the time we load the module, the parent has changed
  429. // Today, this is the case of undo a delete of an object that belongs to a group.
  430. // the operation will create the object with the page as a parent, then move it to the group object
  431. var parentModel = options.model.getParent();
  432. if (parentModel) {
  433. options.parentLayout = this.getLayoutView(parentModel.id);
  434. }
  435. var PageLayout = Modules[0];
  436. var EventRouter = Modules[1][0];
  437. var AuthorHelper = Modules[1][1];
  438. if (page.type === 'widget' || this.contentTypeRegistry.isTypeRegistered(page.type)) {
  439. options.widgetChromeEventRouter = new EventRouter();
  440. }
  441. var pageLayout = new PageLayout(options);
  442. if (AuthorHelper) {
  443. options.consumeView = pageLayout;
  444. // The authoring view will set the authorHelper reference of the consumeView to itself
  445. new AuthorHelper(options);
  446. }
  447. this.viewsMap[pageId].view = pageLayout;
  448. return pageLayout;
  449. }.bind(this));
  450. } else {
  451. promise = Promise.resolve();
  452. }
  453. return promise;
  454. },
  455. loadLayout: function loadLayout(module) {
  456. return this.boardModuleFactory.make(module.type || this.boardModuleFactory.getDefaultModuleType()).then(function (boardModule) {
  457. return boardModule.load(module).then(function (moduleInstance) {
  458. return moduleInstance.module;
  459. });
  460. });
  461. },
  462. removeView: function removeView(view) {
  463. if (this.viewsMap) {
  464. delete this.viewsMap[view.model.id];
  465. }
  466. },
  467. _getLayoutModule: function _getLayoutModule(layoutSpec, options) {
  468. var sPageLayout = void 0;
  469. var sPageLayoutAuthor = void 0;
  470. var modules = null;
  471. if (layoutSpec.type === 'widget' || layoutSpec.type === 'appwidget') {
  472. modules = this._getWidgetLayoutModule(layoutSpec, options);
  473. } else {
  474. sPageLayout = this.consumeViews[layoutSpec.type];
  475. if (!sPageLayout) {
  476. // layout views like group do not have consumption views.
  477. // Use the base view for them in authoring mode
  478. sPageLayout = 'dashboard-core/js/dashboard/layout/views/LayoutBaseView';
  479. }
  480. // else {
  481. // TODO: what do we do if we don't have a page layout?
  482. // }
  483. sPageLayoutAuthor = this.isAuthoring ? this.authoringViews[layoutSpec.type] || 'dashboard-core/js/dashboard/layout/authoring/views/LayoutBaseView' : undefined;
  484. modules = {
  485. sPageLayout: sPageLayout,
  486. sAuthorHelper: sPageLayoutAuthor
  487. };
  488. }
  489. return modules;
  490. },
  491. _getWidgetLayoutModule: function _getWidgetLayoutModule(layoutSpec, options) {
  492. var widgetModel = this.boardModel.getWidgetModel(layoutSpec.id);
  493. options.widgetModel = widgetModel;
  494. var type = widgetModel && widgetModel.type;
  495. options.widgetRegistry = this.widgetLoader.widgetRegistry[type];
  496. var sPageLayout = options.widgetRegistry && options.widgetRegistry.layoutConsumeView;
  497. var sPageLayoutAuthor = null;
  498. if (this.isAuthoring) {
  499. if (!sPageLayout) {
  500. //static widgets don't have a view associated in consume mode
  501. //use base widget view for them
  502. sPageLayout = 'dashboard-core/js/dashboard/layout/views/Widget';
  503. }
  504. if (options.widgetRegistry) {
  505. sPageLayoutAuthor = options.widgetRegistry.layoutAuthoringView;
  506. }
  507. }
  508. return {
  509. sPageLayout: sPageLayout,
  510. sAuthorHelper: sPageLayoutAuthor
  511. };
  512. },
  513. onThemeChange: function onThemeChange(payload) {
  514. if (payload.value !== payload.prevValue) {
  515. this.onResize({ reRender: true });
  516. }
  517. this.eventRouter.trigger('properties:refreshPane', {
  518. focusSelector: '.dropDowntheme'
  519. });
  520. },
  521. _onLayoutPositioning: function _onLayoutPositioning(payload) {
  522. if (payload.sender === 'UndoRedoController') {
  523. this.eventRouter.trigger('properties:refreshPane');
  524. } else {
  525. var layoutModels = this.topLayoutModel.findDescendantsWithType('genericPage');
  526. for (var _iterator = layoutModels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
  527. var _ref2;
  528. if (_isArray) {
  529. if (_i >= _iterator.length) break;
  530. _ref2 = _iterator[_i++];
  531. } else {
  532. _i = _iterator.next();
  533. if (_i.done) break;
  534. _ref2 = _i.value;
  535. }
  536. var layoutModel = _ref2;
  537. layoutModel.set({
  538. layoutPositioning: payload.value
  539. }, {
  540. payloadData: payload.data,
  541. sender: this.id
  542. });
  543. }
  544. }
  545. },
  546. onColorSetChanged: function onColorSetChanged() {
  547. this.colorsService.makeSureColorIsValidInModel({
  548. model: this.topLayoutModel,
  549. propertyName: 'css',
  550. offset: 2,
  551. valuePrefix: 'fill-'
  552. });
  553. this.colorsService.makeSureColorIsValidInModel({
  554. model: this.topLayoutModel,
  555. propertyName: 'fillColor',
  556. offset: 2
  557. });
  558. // Don't use the same offset as the fillColor or our tab text color will end up being the same color as the background
  559. this.colorsService.makeSureColorIsValidInModel({
  560. model: this.topLayoutModel,
  561. propertyName: 'tabTextColor',
  562. offset: 1
  563. });
  564. this.colorsService.makeSureColorIsValidInModel({
  565. model: this.topLayoutModel,
  566. propertyName: 'tabSelectedLineColor',
  567. offset: 1
  568. });
  569. this.colorsService.makeSureColorIsValidInModel({
  570. model: this.topLayoutModel,
  571. propertyName: 'tabBackgroundColor',
  572. offset: 1
  573. });
  574. },
  575. onResize: function onResize(renderOptions) {
  576. if (this.$el.is(':visible')) {
  577. // We only resize if the view is visible.
  578. var topLayoutView = this.getTopLayoutView();
  579. if (topLayoutView && topLayoutView.onResize) {
  580. topLayoutView.onResize(renderOptions);
  581. }
  582. }
  583. },
  584. getTopLayoutView: function getTopLayoutView() {
  585. return this.getLayoutView(this.topLayoutModel.id);
  586. },
  587. getTopLayoutViewWhenReady: function getTopLayoutViewWhenReady() {
  588. return this.getLayoutViewWhenReady(this.topLayoutModel.id);
  589. },
  590. getSelectedNodes: function getSelectedNodes() {
  591. return this.interactionController.getSelectedNodes();
  592. },
  593. getSelectedWidgets: function getSelectedWidgets() {
  594. var selectedLayout = this.boardModel.getSelectedLayout();
  595. var layout = this.topLayoutModel.findModel(selectedLayout);
  596. return layout ? layout.getSelectedWidgets() : [];
  597. },
  598. getLayoutContentContainer: function getLayoutContentContainer() {
  599. return LayoutHelper.getLayoutContentContainer(this.topLayoutModel.id, null, this.dashboardApi);
  600. },
  601. getLayoutContentContainerForDropTarget: function getLayoutContentContainerForDropTarget(dropTargetNode) {
  602. return LayoutHelper.getLayoutContentContainerForDropTarget(dropTargetNode, this.topLayoutModel, this.dashboardApi);
  603. },
  604. /**
  605. * Find the last visible page to insert. For example, if you have this hierarchy of pages: Block/Tab/Absolute.
  606. * It will return the Absolute of the selected tab.
  607. * Will return the first availabe drop zone if a template.
  608. */
  609. getLastVisiblePage: function getLastVisiblePage() {
  610. return LayoutHelper.getLastVisiblePage(this.topLayoutModel.id, null, this.dashboardApi);
  611. },
  612. setPreferredAddOptions: function setPreferredAddOptions(addOptions) {
  613. return LayoutHelper.setPreferredAddOptions(addOptions, this.topLayoutModel, this.dashboardApi);
  614. },
  615. setPreferredLocation: function setPreferredLocation(addOptions) {
  616. return LayoutHelper.setPreferredLocation(addOptions);
  617. },
  618. destroy: function destroy(event) {
  619. // Ensure we are not removing the context menu if has been set by some other class.
  620. if (this.fnOnContextMenu && window.oncontextmenu === this.fnOnContextMenu) {
  621. this.fnOnContextMenu = null;
  622. window.oncontextmenu = null;
  623. }
  624. $(window).off('resize.layoutController' + this.id);
  625. if (this.colorsService) {
  626. this.colorsService.off('theme:changed', this.onThemeChange, this);
  627. }
  628. if (this.topLayoutModel) {
  629. this.topLayoutModel.off('change:layoutPositioning', this._onLayoutPositioning, this);
  630. // destroy the top layout view.
  631. this.destroyLayoutView(this.topLayoutModel.id, event);
  632. }
  633. if (this.interactionController) {
  634. this.interactionController.destroy();
  635. }
  636. if (this.deprecatedCanvas) {
  637. this.deprecatedCanvas.destroy();
  638. }
  639. for (var prop in this) {
  640. // clear everything referenced by this object in case someone is still holding on to it.
  641. if (Object.prototype.hasOwnProperty.call(this, prop)) {
  642. delete this[prop];
  643. }
  644. }
  645. this._isDestroyed = true;
  646. },
  647. destroyLayoutView: function destroyLayoutView(layoutId, event) {
  648. var viewEntry = this.viewsMap[layoutId];
  649. viewEntry && viewEntry.view && viewEntry.view.destroy(event);
  650. },
  651. /**
  652. * Return the node id that maps to the given model id
  653. * @param modelId
  654. * @returns
  655. */
  656. modelIdToNodeId: function modelIdToNodeId(modelId) {
  657. return LayoutHelper.modelIdToNodeId(modelId);
  658. },
  659. /**
  660. * Return the model id for a given node id
  661. * @param nodeId
  662. * @returns
  663. */
  664. nodeIdToModelId: function nodeIdToModelId(nodeId) {
  665. return LayoutHelper.nodeIdToModelId(nodeId);
  666. },
  667. /**
  668. * Return a promise that will be resolved when the layout view is created and ready
  669. * At this point, the widget module (if available) might not be loaded and ready yet.
  670. *
  671. * @param modelId - id or array of ids
  672. *
  673. * @returns {Promise} promise to be fulfilled when the layout is ready
  674. */
  675. layoutReady: function layoutReady(modelId) {
  676. if (this._isDestroyed) {
  677. return Promise.reject('Layout Controller destroyed');
  678. }
  679. var idArray = modelId;
  680. if (!_.isArray(idArray)) {
  681. idArray = [modelId];
  682. }
  683. var views = _.pick.apply(_, [this.viewsMap].concat(idArray));
  684. return Promise.all(_.pluck(views, 'viewReady')).then(function () {
  685. var layout = void 0;
  686. if (_.isArray(modelId)) {
  687. layout = _.pluck(views, 'view');
  688. } else {
  689. layout = views[modelId] && views[modelId].view;
  690. }
  691. return layout;
  692. });
  693. },
  694. /**
  695. * Selects all layouts (groups, widgets, etc...) in the array of ids
  696. * @param layoutIds Array of ids
  697. * @param event The original event
  698. */
  699. selectLayouts: function selectLayouts(layoutIds, event) {
  700. var promises = [];
  701. var selectionHandler = this.interactionController.selectionHandler;
  702. _.each(layoutIds, function (layoutId) {
  703. var promise = this.whenWidgetRenderComplete(layoutId).then(function (layout) {
  704. selectionHandler.selectNode(layout.domNode, event);
  705. });
  706. promises.push(promise);
  707. }.bind(this));
  708. return Promise.all(promises);
  709. },
  710. /**
  711. * Return a promise that will be resolved when the widget module is created and ready
  712. *
  713. * @param modelId - id
  714. *
  715. */
  716. widgetReady: function widgetReady(modelId) {
  717. return this.layoutReady(modelId).then(function (layout) {
  718. if (this.viewsMap[modelId] && this.viewsMap[modelId].view && this.viewsMap[modelId].view.widgetReady) {
  719. return this.viewsMap[modelId].view.widgetReady().then(function () /* widgetAPI */{
  720. return layout;
  721. });
  722. } else {
  723. // Some widget in consume mode do not have modules.
  724. return layout;
  725. }
  726. }.bind(this));
  727. },
  728. /**
  729. * Return a promise that will be resolved when the widget is rendered
  730. *
  731. * @param modelId - id
  732. *
  733. */
  734. whenWidgetRenderComplete: function whenWidgetRenderComplete(modelId) {
  735. return this.layoutReady(modelId).then(function (layout) {
  736. if (this.viewsMap[modelId] && this.viewsMap[modelId].view && this.viewsMap[modelId].view.whenRenderComplete) {
  737. return this.viewsMap[modelId].view.whenRenderComplete().then(function () {
  738. return layout;
  739. });
  740. } else {
  741. // Some widget in consume mode do not have modules.
  742. return layout;
  743. }
  744. }.bind(this));
  745. },
  746. /**
  747. * Wait until page render is complete
  748. *
  749. * @param {Layout} pageLayout - page layout
  750. * @return {Promise} promise resolved when the page render is complete
  751. */
  752. whenPageRenderComplete: function whenPageRenderComplete(pageLayout) {
  753. return this.layoutReady(this.topLayoutModel.id).then(function () {
  754. var widgets = (pageLayout || this.topLayoutModel).findDescendantsWithType('widget');
  755. var promises = [];
  756. _.each(widgets, function (widget) {
  757. promises.push(this.whenWidgetRenderComplete(widget.id));
  758. }, this);
  759. return Promise.all(promises);
  760. }.bind(this));
  761. },
  762. /**
  763. * Return a promise that will be resolved when the widget is rendered
  764. *
  765. * @param modelId - identifier of widget the message is sent on behalf of
  766. * @param multiselect - flag indicating whether the selection is for multiple widgets
  767. * @param showContextBar - flag indicating whether to show the default context bar
  768. *
  769. */
  770. selectWidget: function selectWidget(event) {
  771. var modelId = event.modelId;
  772. var multiselect = event.multiselect;
  773. var showContextBar = event.showContextBar;
  774. return this.widgetReady(modelId).then(function (layout) {
  775. //widget param in widgetReady function is also passed in (can be used at a later time)
  776. if (!multiselect) {
  777. this.deselectAllWidgets();
  778. }
  779. var selectionHandler = this.interactionController.selectionHandler;
  780. //this prevents the context bar from appearing
  781. var eventChainLocal = new EventChainLocal(event);
  782. if (!showContextBar) {
  783. eventChainLocal.setProperty('preventDefaultContextBar', true);
  784. }
  785. if (selectionHandler) {
  786. selectionHandler.selectNode(layout.domNode, event);
  787. }
  788. }.bind(this));
  789. },
  790. deselectAllWidgets: function deselectAllWidgets() {
  791. if (this.interactionController) {
  792. this.interactionController.selectionHandler.deselectAll();
  793. }
  794. },
  795. deselectWidget: function deselectWidget(event) {
  796. var modelId = event.modelId;
  797. var layoutView = this.viewsMap[modelId] && this.viewsMap[modelId].view;
  798. var selectionHandler = this.interactionController.selectionHandler;
  799. if (!selectionHandler) {
  800. return Promise.resolve();
  801. }
  802. if (layoutView && layoutView.domNode) {
  803. return Promise.resolve(selectionHandler.deselectNode(layoutView.domNode));
  804. } else {
  805. return this.widgetReady(modelId).then(function (layout) {
  806. selectionHandler.deselectNode(layout.domNode);
  807. }.bind(this));
  808. }
  809. },
  810. moveLayout: function moveLayout(updateArray) {
  811. var _this8 = this;
  812. var transactionToken = this.transaction.startTransaction();
  813. updateArray.forEach(function (info) {
  814. _this8.canvas.moveContent(info.parentId, [info.id], transactionToken);
  815. });
  816. this.transaction.endTransaction(transactionToken);
  817. },
  818. // NOTE:
  819. // getLayoutView and getLayoutViewWhenReady should be combined into one.
  820. // `getLayoutViewWhenReady` - was created as part of the conversion to bluebird and the operation became async instead of sync.
  821. //
  822. // We can't guarantee that the view is ready so to be safe, one should always call getLayoutViewWhenReady.
  823. // Should be fixed as part of a bigger revamp
  824. //
  825. getLayoutViewWhenReady: function getLayoutViewWhenReady(modelId) {
  826. var viewEntry = this.viewsMap[modelId];
  827. if (!viewEntry) {
  828. return Promise.resolve(null);
  829. }
  830. return viewEntry.viewReady;
  831. },
  832. getLayoutView: function getLayoutView(modelId) {
  833. var viewEntry = this.viewsMap[modelId];
  834. return viewEntry && viewEntry.view;
  835. },
  836. onContextMenu: function onContextMenu(event) {
  837. var nodeName = event.target.nodeName;
  838. var $target = $(event.target);
  839. return nodeName === 'INPUT' || nodeName === 'TEXTAREA' || $target.attr('contenteditable') === 'true' || $target.closest('[contenteditable="true"]').length ? true : false;
  840. },
  841. getInteractionProperties: function getInteractionProperties() {
  842. return this.interactionController.getInteractionProperties();
  843. },
  844. hasMaximizedWidget: function hasMaximizedWidget() {
  845. var viewIds = Object.keys(this.viewsMap);
  846. for (var i = 0; i < viewIds.length; i++) {
  847. var view = this.getLayoutView(viewIds[i]);
  848. if (view && view.widgetAPI && view.widgetAPI.isWidgetMaximized && view.widgetAPI.isWidgetMaximized()) {
  849. return true;
  850. }
  851. }
  852. return false;
  853. },
  854. /**
  855. * @param layoutWidgetId The layout id for the widget being processed.
  856. * @param {boolean} noPageContextMerge if <tt>true</tt> the pageContext will not be merged into the Widget Model
  857. * @returns the widgetModel which is attached to the pin for this layoutWidgetId.
  858. * The widgetModel for pinning can be overridden (or returned) by a widget
  859. * if it implements the 'getWidgetModelForPinning' API. Otherwise, the model is returned from the board spec.
  860. */
  861. getLayoutWidgetModel: function getLayoutWidgetModel(layoutWidgetId, noPageContextMerge) {
  862. var widgetObj = this.widgetLoader.getWidget(layoutWidgetId);
  863. return widgetObj && widgetObj.getWidgetModelForPinning && !noPageContextMerge ? widgetObj.getWidgetModelForPinning() : this.boardModel.getWidgetModel(layoutWidgetId);
  864. }
  865. });
  866. return LayoutController;
  867. });
  868. //# sourceMappingURL=LayoutController.js.map