CanvasController.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. 'use strict';
  2. /*
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2020
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['../lib/@waca/core-client/js/core-client/ui/core/Class', './layout/LayoutController', './UndoRedoController', './CopyPasteController', './contentpane/PropertiesManager', '../app/EventRouter', '../api/impl/LegacyViewControllers', 'jquery', 'underscore'], function (Class, LayoutController, UndoRedoController, CopyPasteController, PropertiesManager, EventRouter, LegacyViewControllers, $, _) {
  8. return Class.extend({
  9. _MAX_DATASET_REFRESH_RATE: 5000, // ms
  10. _datasetRefreshInfo: {},
  11. init: function init(options) {
  12. this.$el = options.$el;
  13. // TODO - to be removed after refactoring
  14. this.glassContext = options.glassContext;
  15. this.services = options.services;
  16. // TODO - end to be removed
  17. this.dashboardApi = options.dashboardApi;
  18. this.model = options.boardModel;
  19. this.widgetLoader = options.widgetLoader;
  20. // TODO remove this.loader -- it is here because it is referenced by other components (e.g. explore)
  21. this.loader = options.widgetLoader;
  22. this.boardLoader = options.boardLoader;
  23. this._extensions = {};
  24. this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
  25. this.addRemoveNotifier = new EventRouter();
  26. this.addRemoveNotifier.on('widget:removeDone', this.onWidgetRemoveDone, this);
  27. this.undoRedoController = new UndoRedoController(this.model, {
  28. logger: this.logger,
  29. canvas: this.boardLoader.getCanvas(),
  30. transaction: this.dashboardApi.getFeature('Transaction')
  31. });
  32. this.layoutController = new LayoutController({
  33. // TODO - to be removed after refactoring
  34. glassContext: this.glassContext,
  35. services: this.services,
  36. appSettings: options.appSettings,
  37. // TODO - end to be removed
  38. contentFeatureLoader: options.contentFeatureLoader,
  39. dashboardApi: this.dashboardApi,
  40. eventRouter: options.eventRouter,
  41. $el: this.$el,
  42. boardModel: this.model,
  43. boardModuleFactory: options.boardModuleFactory,
  44. widgetLoader: this.widgetLoader,
  45. canvasNotifier: this.addRemoveNotifier,
  46. layoutExtensions: options.layoutExtensions,
  47. gatewayUrl: options.gatewayUrl,
  48. cdnUrl: options.cdnUrl,
  49. canvasController: this,
  50. canvas: this.boardLoader.getCanvas()
  51. });
  52. this.layoutController.createLayoutModules();
  53. var legacyViewControllers = new LegacyViewControllers({
  54. layoutController: this.layoutController
  55. });
  56. this.dashboardApi.getFeature('FeatureRegistry.internal').registerFeature('LegacyViewControllers', legacyViewControllers);
  57. // TODO: should be gone. We should use the canvas API instead
  58. this.dashboardApi.registerDashboardSvc('boardService', this.getDeprecatedCanvas());
  59. this.eventRouter = options.eventRouter;
  60. // register properties manager
  61. this.propertiesManager = new PropertiesManager({
  62. 'glassContext': this.glassContext,
  63. 'canvasController': this
  64. });
  65. this.registerExtension('propertiesManager', this.propertiesManager);
  66. this.dashboardApi.registerDashboardSvc('propertiesManager', this.propertiesManager);
  67. this.copyPasteController = new CopyPasteController({
  68. layoutController: this.layoutController,
  69. dashboardApi: this.dashboardApi,
  70. logger: this.logger,
  71. model: this.model,
  72. api: this.getDeprecatedCanvas(),
  73. type: this.dashboardApi.getType().toUpperCase()
  74. });
  75. this.model.on('change:datasetShaping', function (eventInfo) {
  76. // Do not refresh the canvas if the event was calculation add or remove
  77. // if (eventInfo && ( 'add' === eventInfo.name || 'remove' === eventInfo.name )
  78. // && eventInfo.origCollectionEvent && 'change:calculations' === eventInfo.origCollectionEvent.eventName) {
  79. // return;
  80. // }
  81. this.model.datasetShaping.each(function (dataSetShapingModel) {
  82. dataSetShapingModel.eventInfo = eventInfo;
  83. this._onChangeShapingSpec(dataSetShapingModel);
  84. }.bind(this));
  85. }.bind(this));
  86. $(document).on('keydown', this._onDocumentKeyDown);
  87. // TODO: for now, an object like this is more than sufficient.
  88. // if there is a growing need for a feature registry, we'll adjust.
  89. this._features = {};
  90. },
  91. initialize: function initialize() {
  92. if (this.layoutController != null) {
  93. return this.layoutController.initialize();
  94. }
  95. return Promise.reject();
  96. },
  97. /**
  98. * Execute a callback. The method will only execute the callback if the object is not destroyed
  99. * Typpically used in a promise callback because we might have destroyed the object before the promise was resolved.
  100. * @param {Function} callback
  101. */
  102. _prepareAsyncCallback: function _prepareAsyncCallback(callback) {
  103. return function () {
  104. if (!this._destroyed) {
  105. return callback.apply(undefined, arguments);
  106. }
  107. return Promise.reject('The live widget object was destroyed');
  108. }.bind(this);
  109. },
  110. getDeprecatedCanvas: function getDeprecatedCanvas() {
  111. if (this.layoutController != null) {
  112. return this.layoutController.getDeprecatedCanvas();
  113. }
  114. return null;
  115. },
  116. destroy: function destroy() {
  117. this._destroyed = true;
  118. // destroy the layout controller before the loader
  119. // If we have widgets, this will trigger a loader widget unload call.
  120. // layoutController.destroy() is also called by the new LegacyViewController feature when
  121. // the features are all destroyed.
  122. this.layoutController.destroy();
  123. this._features = null;
  124. this.widgetLoader = null;
  125. // TODO remove this.loader -- it is here because it is referenced by other components (e.g. explore)
  126. this.loader = null;
  127. this.boardLoader = undefined;
  128. this.layoutController = null;
  129. if (this.addRemoveNotifier) {
  130. this.addRemoveNotifier.off();
  131. this.addRemoveNotifier = null;
  132. }
  133. if (this.eventRouter) {
  134. this.eventRouter.off();
  135. this.eventRouter = null;
  136. }
  137. if (this.undoRedoController) {
  138. this.undoRedoController.clearStack();
  139. this.undoRedoController = null;
  140. }
  141. _.each(this._datasetRefreshInfo, function (value, datasetId) {
  142. this._stopDatasetRefresh(datasetId);
  143. });
  144. $(document).off('keydown', this._onDocumentKeyDown);
  145. },
  146. _getSelectedWidget: function _getSelectedWidget() {
  147. var currentSelectedWidget = this.$el.find('.widget.nodeSelected:visible');
  148. if (currentSelectedWidget.length) {
  149. return this.widgetLoader.getWidget(currentSelectedWidget[0]._layout.id);
  150. }
  151. return null;
  152. },
  153. // Copied this whole method from LayoutBaseView.js (authoring)
  154. // Previously we used the dashboard.getDeprecatedCanvas().addWidget(...)
  155. // method but a gating/regression defect for 11.1.7 came up for keyboard
  156. // while mouse use worked fine. They go on different paths and this change
  157. // is a hack as it just duplicates code to make things work similarly.
  158. // TODO: Consolidate the different ways we add content to the canvas - don't duplicate code
  159. _addWidget: function _addWidget(widgetSpec, isTouch) {
  160. var _this = this;
  161. var transaction = this.dashboardApi.getFeature('Transaction');
  162. var token = transaction.startTransaction();
  163. var spec = widgetSpec.model,
  164. parentId = widgetSpec.parentId,
  165. layoutProperties = widgetSpec.layoutProperties;
  166. var content = {
  167. containerId: parentId,
  168. properties: layoutProperties.style,
  169. spec: spec,
  170. type: 'widget.' + spec.type
  171. };
  172. return this.dashboardApi.getCanvas().addContent(content, token).then(function (content) {
  173. transaction.endTransaction(token);
  174. return _this.layoutController.whenWidgetRenderComplete(content.getId());
  175. }).then(function (layout) {
  176. var selectionHandler = _this.layoutController.interactionController.selectionHandler;
  177. selectionHandler.deselectAll();
  178. selectionHandler.selectNode(layout.domNode, { isTouch: isTouch });
  179. });
  180. },
  181. addDataItemsOrAddWidget: function addDataItemsOrAddWidget(columnInfo, isTouch) {
  182. var _this2 = this;
  183. // Use DataWidget's accepts and onDrop to build up the visualization
  184. // since they already have the proper logic to compare datasets.
  185. var widget = this._getSelectedWidget();
  186. if (widget && widget.accepts && widget.accepts(columnInfo)) {
  187. return Promise.resolve(widget.onDrop(columnInfo));
  188. }
  189. return this.getWidgetSpecFromDragObject(columnInfo).then(function (widgetSpec) {
  190. return _this2._addWidget(widgetSpec, isTouch);
  191. });
  192. },
  193. getWidgetSpecFromDragObject: function getWidgetSpecFromDragObject(dragObject) {
  194. var layoutProperties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  195. var widgetSpec = {
  196. parentId: this.id,
  197. layoutProperties: layoutProperties
  198. };
  199. var canvasDnD = this.dashboardApi.getFeature('CanvasDnD');
  200. return canvasDnD.onDrop(dragObject).then(function (newModel) {
  201. if (newModel) {
  202. widgetSpec.model = newModel;
  203. }
  204. return widgetSpec;
  205. });
  206. },
  207. /**
  208. * adds a pin and selects the added pin after it is done.
  209. *
  210. * @param {object} pinInfo - the description of the pin to add
  211. * @param {Boolean} isTouch - true if it is touch
  212. */
  213. addPin: function addPin(pinInfo, isTouch) {
  214. var id = this.getDeprecatedCanvas().addFragment(pinInfo);
  215. this.getDeprecatedCanvas().selectWidget(id, {
  216. isTouch: isTouch
  217. });
  218. },
  219. onWidgetRemoveDone: function onWidgetRemoveDone(event) {
  220. if (this.widgetLoader.isLoaded(event.id)) {
  221. this.widgetLoader.unLoadWidget(event.id, undefined, event);
  222. }
  223. },
  224. /**
  225. * Enables authoring functionality
  226. *
  227. * If page is loading, it will resume layout controller move to authoring after its done loading
  228. *
  229. * @return promise to indicate when move to authoring is complete
  230. */
  231. changeToAuthorMode: function changeToAuthorMode() {
  232. return this.layoutController.pageReady.then(this._prepareAsyncCallback(this.layoutController.changeToAuthorMode.bind(this.layoutController, {
  233. canvasNotifier: this.addRemoveNotifier
  234. })));
  235. },
  236. /**
  237. * Changes to consume mode
  238. */
  239. changeToConsumeMode: function changeToConsumeMode() {
  240. return this.layoutController.changeToConsumeMode();
  241. },
  242. changeToEventGroupMode: function changeToEventGroupMode() {
  243. return this.layoutController.pageReady.then(this._prepareAsyncCallback(this.layoutController.changeToEventGroupMode.bind(this.layoutController)));
  244. },
  245. /**
  246. Used to register providers in extensions. If the extension isn't available yet the provider information will be cached and
  247. passed to the extension once it's created.
  248. @param {string} extensionId - The ID of the extension to register the provider
  249. @param {handler} object - The object which will be registered as a provider in the extension
  250. **/
  251. addProviderForExtension: function addProviderForExtension(extensionId, handler) {
  252. var extension = this.getExtension(extensionId);
  253. if (extension) {
  254. if (extension.addProvider) {
  255. extension.addProvider(handler);
  256. } else {
  257. this.logger.debug('Extension with id ' + extensionId + ' does not have a "addProvider" method');
  258. }
  259. } else {
  260. if (!this._extensionProviders) {
  261. this._extensionProviders = {};
  262. }
  263. if (!this._extensionProviders[extensionId]) {
  264. this._extensionProviders[extensionId] = [];
  265. }
  266. this._extensionProviders[extensionId].push(handler);
  267. }
  268. },
  269. /**
  270. Registers an extension object in the canvas controller. Your extension should implement the 'addProvider' method
  271. if you want to let providers register to your extension
  272. @param {string} id - The id of the extension
  273. @param {object} extension - The extension object
  274. **/
  275. registerExtension: function registerExtension(id, extension) {
  276. this._extensions[id] = extension;
  277. if (this._extensionProviders && this._extensionProviders[id]) {
  278. if (extension.addProvider) {
  279. this._extensionProviders[id].forEach(function (provider) {
  280. extension.addProvider(provider);
  281. });
  282. } else {
  283. this.logger.debug('Extension with id ' + id + ' does not have a "addProvider" method');
  284. }
  285. }
  286. },
  287. getExtension: function getExtension(id) {
  288. return this._extensions[id] || null;
  289. },
  290. /*
  291. * Overwrites the document event handler that catches backspace key press and triggers a back
  292. * navigation and instead trigger a delete action (unless target is an input block)
  293. */
  294. _onDocumentKeyDown: function _onDocumentKeyDown(e) {
  295. if (e && e.keyCode === 8 && !e.metaKey && !e.shiftKey) {
  296. var target = e.srcElement || e.target;
  297. if (!target.tagName.toLowerCase().match(/input|textarea|select/igm) && target.getAttribute('contentEditable') !== 'true') {
  298. e.preventDefault();
  299. }
  300. }
  301. },
  302. onPageRenderComplete: function onPageRenderComplete() {
  303. return this.layoutController.onPageRenderComplete;
  304. },
  305. onPageReady: function onPageReady() {
  306. return this.layoutController.pageReady;
  307. },
  308. /**
  309. * Get canvas feature
  310. *
  311. * @param {string} id - canvas feature id
  312. *
  313. * @return {Promise} Promise resolved with the feature or with undefined if feature does not exist
  314. */
  315. getFeature: function getFeature(id) {
  316. var _this3 = this;
  317. return new Promise(function (resolve, reject) {
  318. if (_this3._features[id]) {
  319. resolve(_this3._features[id]);
  320. } else {
  321. reject('Feature not found');
  322. }
  323. });
  324. },
  325. /**
  326. * Register canvas feature
  327. *
  328. * @param {string} id - canvas feature id
  329. * @param {Feature} feature - canvas feature
  330. *
  331. * @return {Promise} Promise resolved when the registration is complete
  332. */
  333. registerFeature: function registerFeature(id, feature) {
  334. this._features[id] = feature;
  335. return Promise.resolve();
  336. }
  337. });
  338. });
  339. //# sourceMappingURL=CanvasController.js.map