SceneLayout.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Storytelling (C) Copyright IBM Corp. 2015, 2019
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['gemini/dashboard/layout/views/PageCollectionView', 'jquery', 'underscore', '../../StoryService'], function (PageCollectionView, $, _, StoryService) {
  8. /**
  9. * An interface for classes that represent the layout for a story.
  10. * Implementing classes are required to create and instaniate a property named '_pageNavigationController'
  11. * @interface
  12. */
  13. var BLACKLISTED_PROPERTIES = ['layoutPositioning'];
  14. var TABSTOP_SELECTOR = ['button', 'input', 'select', 'textarea']
  15. // 'not' to exclude: tabindex < 0 || tabindex > 0 || none
  16. .map(function (s) {
  17. return s + ':not([tabindex])';
  18. }).concat(['[tabindex="0"]']).join();
  19. var SceneLayout = PageCollectionView.extend({
  20. init: function init(options) {
  21. var _this = this;
  22. SceneLayout.inherited('init', this, arguments);
  23. this._tabstops = [];
  24. this._scenes = [];
  25. this.sceneId = options.appSettings && options.appSettings.sceneId || null;
  26. this.dashboardApi = options.dashboardApi;
  27. this.stringResources = this.dashboardApi.getDashboardCoreSvc('.StringResources');
  28. // TODO: this.eventRouter will work as it is set in dashboard-core/LayoutBaseView which this class is extended from
  29. this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:select', this.onSceneSelected.bind(this));
  30. this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:next', this.onNextScene.bind(this));
  31. this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:previous', this.onPreviousScene.bind(this));
  32. this.layoutController.eventRouter.on('scene:jump', this.onJumpScene, this);
  33. this.layoutController.layoutReady(this.model.id).then(function () {
  34. _this.onLayoutReady();
  35. });
  36. this.sceneLayoutApi = {
  37. getAdjacentScenes: this._getAdjacentScenes.bind(this),
  38. getCurrentScene: this._getCurrentScene.bind(this),
  39. getNextScene: this._getNextScene.bind(this),
  40. getPreviousScene: this._getPreviousScene.bind(this),
  41. getSceneById: this._getSceneById.bind(this),
  42. getSceneByIndex: this._getSceneByIndex.bind(this),
  43. getSceneIndex: this._getSceneIndex.bind(this),
  44. getScenes: this._getScenes.bind(this)
  45. };
  46. },
  47. destroy: function destroy() {
  48. this._scenes = [];
  49. this._tabstops = [];
  50. this.layoutController.eventRouter.off('scene:jump', this.onJumpScene, this);
  51. SceneLayout.inherited('destroy', this, arguments);
  52. },
  53. onLayoutReady: function onLayoutReady() {
  54. _.each(this.model.items, this._addScene.bind(this));
  55. },
  56. getSceneLayoutApi: function getSceneLayoutApi() {
  57. return this.sceneLayoutApi;
  58. },
  59. extendApi: function extendApi(additionalApiMethods) {
  60. _.each(additionalApiMethods, function (apiMethod, key) {
  61. this.sceneLayoutApi[key] = apiMethod;
  62. }.bind(this));
  63. },
  64. _hasScene: function _hasScene(model) {
  65. var sceneId = this._getSceneId(model);
  66. var scene = _.findWhere(this._scenes, {
  67. id: sceneId
  68. });
  69. return scene ? true : false;
  70. },
  71. _addScene: function _addScene(model) {
  72. this._scenes.push(this._makeSceneInstance(model));
  73. },
  74. /**
  75. * @returns {Promise}
  76. */
  77. _removeScene: function _removeScene(model) {
  78. var _this2 = this;
  79. var remove = this._getScene(model);
  80. var removeIndex = this._getSceneIndex(model);
  81. // Always try to go to the next scene first
  82. var sibling = this._scenes[removeIndex + 1];
  83. if (!sibling) {
  84. // If removing the last scene we go to the previous scene
  85. sibling = this._scenes[removeIndex - 1];
  86. }
  87. this._scenes = _.without(this._scenes, remove);
  88. // at this point the view is out of the DOM
  89. this._tabstops = this._tabstops.filter(function (node) {
  90. if (!_this2.$el[0].contains(node)) {
  91. // lets restore the tabindex giving up our node reference.
  92. node.setAttribute('tabindex', '0');
  93. return false;
  94. }
  95. return true;
  96. });
  97. return this.didRemovePage({
  98. modelId: this.sceneId !== model.id ? this.sceneId : sibling.id
  99. });
  100. },
  101. /**
  102. * @param {Object} model The layout model of a scene
  103. */
  104. _makeSceneInstance: function _makeSceneInstance(model) {
  105. return {
  106. id: model.id,
  107. $el: this.$el.find('#' + this._getViewId(model.id)),
  108. getLayoutView: this.layoutController.getLayoutView.bind(this.layoutController, model.id),
  109. getLayoutViewWhenReady: this.layoutController.getLayoutViewWhenReady.bind(this.layoutController, model.id)
  110. };
  111. },
  112. /**
  113. * @param {Object} model The layout model of the scene being moved
  114. * @param {String} [beforeId] A string that represents the scene's new imediate sibling's DOM element
  115. */
  116. _updateSceneList: function _updateSceneList(model, beforeId) {
  117. // Get the id of scene AND remove it from list of scenes
  118. var sceneId = this._getSceneId(model);
  119. var scene = _.findWhere(this._scenes, { id: sceneId });
  120. this._scenes = _.without(this._scenes, scene);
  121. // Set the index to reinsert the scene at to the end of the list
  122. var index = this._scenes.length;
  123. // If we are moving the scene beside another scene then we need to find where
  124. if (beforeId) {
  125. var beforeSceneData = _.findWhere(this._scenes, { id: beforeId });
  126. // update index value to reinsert scene at a specific location
  127. index = _.indexOf(this._scenes, beforeSceneData);
  128. }
  129. // Update the list of scenes. Place the moved scene in the proper position
  130. this._scenes.splice(index, 0, this._makeSceneInstance(model));
  131. },
  132. _getScenes: function _getScenes() {
  133. return this._scenes;
  134. },
  135. /**
  136. * @param {Object} model The model of a scene (actual layout model or the runtime model version)
  137. * @returns {Object} The runtime version of a scene
  138. */
  139. _getScene: function _getScene(model) {
  140. return this._getSceneById(model.id);
  141. },
  142. _getAdjacentScenes: function _getAdjacentScenes(scene) {
  143. var scenes = [];
  144. var before = this._getPreviousScene(scene);
  145. var after = this._getNextScene(scene);
  146. if (before && before !== scene) {
  147. scenes.push(before);
  148. }
  149. if (after && after !== scene) {
  150. scenes.push(after);
  151. }
  152. return scenes;
  153. },
  154. _getCurrentScene: function _getCurrentScene() {
  155. return this._getSceneById(this.sceneId);
  156. },
  157. _getCurrentSceneIndex: function _getCurrentSceneIndex() {
  158. return this._getSceneIndexById(this.sceneId);
  159. },
  160. _getNextScene: function _getNextScene(scene) {
  161. var sceneIndex = this._getCurrentSceneIndex();
  162. if (scene && scene.id !== this.sceneId) {
  163. sceneIndex = this._getSceneIndex(scene);
  164. }
  165. if (sceneIndex >= 0 && sceneIndex < this._scenes.length - 1) {
  166. return this._getSceneByIndex(sceneIndex + 1);
  167. } else if (sceneIndex === this._scenes.length - 1) {
  168. return this._getSceneByIndex(0);
  169. }
  170. return null;
  171. },
  172. _getPreviousScene: function _getPreviousScene(scene) {
  173. var sceneIndex = this._getCurrentSceneIndex();
  174. if (scene && scene.id !== this.sceneId) {
  175. sceneIndex = this._getSceneIndex(scene);
  176. }
  177. if (sceneIndex > 0 && sceneIndex < this._scenes.length) {
  178. return this._getSceneByIndex(sceneIndex - 1);
  179. } else if (sceneIndex === 0) {
  180. return this._getSceneByIndex(this._scenes.length - 1);
  181. }
  182. return null;
  183. },
  184. _getSceneById: function _getSceneById(sceneId) {
  185. var scene = this._scenes.filter(function (scene) {
  186. return scene.id === sceneId;
  187. });
  188. return scene[0] || null;
  189. },
  190. /**
  191. *
  192. * @param {String} sceneId This is either the model Id or the scene'e element Id in the DOM
  193. * @returns {Number} The array index value or -1 if the scene was not found
  194. */
  195. _getSceneIndexById: function _getSceneIndexById(sceneId) {
  196. for (var i = 0; i < this._scenes.length; i++) {
  197. if (this._scenes[i].id === sceneId) {
  198. return i;
  199. }
  200. }
  201. return -1;
  202. },
  203. /**
  204. *
  205. * @param layoutModel layout model to get the scene index from
  206. * @returns the index or -1 if the model was not found
  207. */
  208. _getSceneIndex: function _getSceneIndex(model) {
  209. return this._getSceneIndexById(model.id);
  210. },
  211. _getSceneByIndex: function _getSceneByIndex(index) {
  212. if (index > -1 && index < this._scenes.length) {
  213. return this._scenes[index];
  214. }
  215. return null;
  216. },
  217. _getSceneId: function _getSceneId(model) {
  218. return model.id;
  219. },
  220. _getViewId: function _getViewId(modelId) {
  221. return modelId + '_tab';
  222. },
  223. /**
  224. * @param {String} modelId The scene's id
  225. * @returns {Promise}
  226. */
  227. _selectScene: function _selectScene(modelId) {
  228. return this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').invokeLifeCycleHandlers('scene:select', { modelId: modelId });
  229. },
  230. /**
  231. * @returns {Promise}
  232. */
  233. didRemovePage: function didRemovePage(options) {
  234. return this.onJumpScene(options);
  235. },
  236. // Overridden in authoring view
  237. onPlaybackNext: function onPlaybackNext() {
  238. this.layoutController.eventRouter.trigger('playback:next');
  239. return this;
  240. },
  241. // Overridden in authoring view
  242. onPlaybackPrevious: function onPlaybackPrevious() {
  243. this.layoutController.eventRouter.trigger('playback:prev');
  244. return this;
  245. },
  246. /**
  247. * @param {Object} event The object that is provided when triggering the scene:select event on the layout controller's eventRouter
  248. * @returns {Promise}
  249. */
  250. onSceneSelected: function onSceneSelected(event) {
  251. var _this3 = this;
  252. return this._pageNavigationController.jumpTo(_.extend(event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) {
  253. _this3._navigationComplete(_.extend(results, { play: event ? event.play : null }));
  254. });
  255. },
  256. /**
  257. * This method is only called when the eventRouter has had the "scene:next" event triggered
  258. * @returns {Promise}
  259. */
  260. onNextScene: function onNextScene(event) {
  261. var _this4 = this;
  262. return this._pageNavigationController.nextPage(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) {
  263. _this4._navigationComplete(_.extend(results, { play: event ? event.play : null }));
  264. });
  265. },
  266. /**
  267. * This method is only called when the eventRouter has had the "scene:previous" event triggered
  268. * @returns {Promise}
  269. */
  270. onPreviousScene: function onPreviousScene(event) {
  271. var _this5 = this;
  272. return this._pageNavigationController.previousPage(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) {
  273. _this5._navigationComplete(_.extend(results, { play: event ? event.play : null }));
  274. });
  275. },
  276. /**
  277. * @returns {Promise}
  278. */
  279. onJumpScene: function onJumpScene(event) {
  280. var _this6 = this;
  281. return this._pageNavigationController.jumpTo(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) {
  282. _this6._navigationComplete(_.extend(results, { play: event ? event.play : null }));
  283. });
  284. },
  285. _navigationStarted: function _navigationStarted(event) {
  286. this._navigationUpdated(event, 'navigation:started');
  287. },
  288. _navigationComplete: function _navigationComplete(event) {
  289. this._navigationUpdated(event, 'navigation:complete');
  290. this.currentSceneChanged();
  291. },
  292. _updateTabStops: function _updateTabStops() {
  293. // backup enabled tabstops
  294. var tabstops = [];
  295. this._scenes.forEach(function (scene) {
  296. var el = scene.$el[0].querySelector('.page.pagecontainer');
  297. var nodes = el ? el.querySelectorAll(TABSTOP_SELECTOR) : [];
  298. // can't use NodeList.forEach because of IE.
  299. // it's also faster so ok...
  300. for (var i = 0; i < nodes.length; i++) {
  301. tabstops.push(nodes[i]);
  302. }
  303. });
  304. this._tabstops = this._tabstops.concat(tabstops);
  305. this._tabstops = _.uniq(this._tabstops);
  306. var scene = this._getCurrentScene();
  307. var sceneEl = scene && scene.$el.length ? scene.$el[0] : null;
  308. this._tabstops.forEach(function (node) {
  309. if (sceneEl && sceneEl.contains(node)) {
  310. node.setAttribute('tabindex', '0');
  311. } else {
  312. node.setAttribute('tabindex', '-1');
  313. }
  314. });
  315. },
  316. _navigationUpdated: function _navigationUpdated(event, type) {
  317. // Update the current scene
  318. this.sceneId = event.scene.id;
  319. var targetIndex = this._getCurrentSceneIndex();
  320. var payload = {
  321. index: targetIndex, // This is still needed for StoryPaneController
  322. scene: event.scene,
  323. overview: event.overview || targetIndex < 0,
  324. play: event.play
  325. };
  326. this.layoutController.eventRouter.trigger(type, payload);
  327. },
  328. /**
  329. * called when the current scene changes for any reason
  330. */
  331. currentSceneChanged: function currentSceneChanged() {
  332. this._updateTabStops();
  333. },
  334. /**
  335. * START OF DOUBLE DELEGATION SECTION - DO NOT REMOVE
  336. *
  337. * Double delegation is needed as methods may get overridden when moving to authoring mode.
  338. * The event handlers registering these methods in consume view will need to invoke the overridden
  339. * method that comes from authoring view
  340. */
  341. _onPlaybackNext: function _onPlaybackNext() {
  342. return this.onPlaybackNext();
  343. },
  344. _onPlaybackPrevious: function _onPlaybackPrevious() {
  345. return this.onPlaybackPrevious();
  346. },
  347. _onKeyDown: function _onKeyDown(event) {
  348. return this.onKeyDown(event);
  349. },
  350. /**
  351. * END OF DOUBLE DELEGATION SECTION
  352. */
  353. getBannerProperty: function getBannerProperty() {
  354. return {
  355. 'value': this.stringResources.get('storyProperties'),
  356. 'name': 'banner',
  357. 'type': 'Banner',
  358. 'editable': false
  359. };
  360. },
  361. getProperties: function getProperties() {
  362. var _this7 = this;
  363. return SceneLayout.inherited('getProperties', this, arguments).then(function (properties) {
  364. properties = properties.filter(function (property) {
  365. return BLACKLISTED_PROPERTIES.indexOf(property.id) === -1;
  366. });
  367. properties.push(_this7._getConvertProperty());
  368. return properties;
  369. });
  370. },
  371. _getConvertProperty: function _getConvertProperty() {
  372. var _this8 = this;
  373. var panAndZoomItems = [{
  374. name: 'panAndZoom1',
  375. label: this.stringResources.get('PanAndZoomLayout1'),
  376. type: 'svg',
  377. value: 'dashboard-guidedjourney1_32'
  378. }, {
  379. name: 'panAndZoom2',
  380. label: this.stringResources.get('PanAndZoomLayout2'),
  381. type: 'svg',
  382. value: 'dashboard-guidedjourney2_32'
  383. }, {
  384. name: 'panAndZoom3',
  385. label: this.stringResources.get('PanAndZoomLayout3'),
  386. type: 'svg',
  387. value: 'dashboard-guidedjourney3_32'
  388. }, {
  389. name: 'panAndZoom4',
  390. label: this.stringResources.get('PanAndZoomLayout4'),
  391. type: 'svg',
  392. value: 'dashboard-guidedjourney4_32'
  393. }, {
  394. name: 'panAndZoom5',
  395. label: this.stringResources.get('PanAndZoomLayout5'),
  396. type: 'svg',
  397. value: 'dashboard-guidedjourney5_32'
  398. }, {
  399. name: 'panAndZoom6',
  400. label: this.stringResources.get('PanAndZoomLayout6'),
  401. type: 'svg',
  402. value: 'dashboard-timesequence_32'
  403. }];
  404. var collapsiblePickerSelectedName = void 0;
  405. var currentLayout = this.model.get('type') || 'slideshow';
  406. if (currentLayout.indexOf('panAndZoom') != -1) {
  407. var index = currentLayout.substring(currentLayout.length - 1, currentLayout.length) - 1;
  408. currentLayout = currentLayout.substring(0, currentLayout.length - 1);
  409. collapsiblePickerSelectedName = panAndZoomItems[index].name;
  410. }
  411. var dropDownOptions = {
  412. 'name': 'storyType',
  413. 'label': this.stringResources.get('storyTypeLabel'),
  414. 'defaultValue': currentLayout,
  415. 'options': [{
  416. label: this.stringResources.get('slideshowLabel'),
  417. value: 'slideshow'
  418. }, {
  419. label: this.stringResources.get('panAndZoomLabel'),
  420. value: 'panAndZoom'
  421. }]
  422. };
  423. var collapsiblePickerOptions = {
  424. 'type': 'CollapsiblePicker',
  425. 'id': 'layoutModel',
  426. 'name': 'layoutModel',
  427. 'label': this.stringResources.get('layoutLabel'),
  428. 'selectedName': collapsiblePickerSelectedName,
  429. 'placeholder': {
  430. name: '',
  431. label: '',
  432. type: 'svg',
  433. value: 'dashboard-unknown_16'
  434. },
  435. 'contentSize': 'large',
  436. 'items': panAndZoomItems,
  437. 'isRequired': true
  438. };
  439. return {
  440. 'type': 'TwoStageCombo',
  441. 'name': 'storyToStoryControl',
  442. 'id': 'storyToStoryCombo',
  443. 'sectionName': this.stringResources.get('scenesPropertiesSection'),
  444. 'sectionOpened': true,
  445. 'tabName': this.stringResources.get('tabName_general'),
  446. 'dropDownOptions': dropDownOptions,
  447. 'collapsiblePickerOptions': [null, collapsiblePickerOptions],
  448. 'onChange': function onChange(name, propertyValue) {
  449. var value = propertyValue;
  450. if (propertyValue && propertyValue.name) {
  451. value = value.name;
  452. }
  453. var storyService = new StoryService({
  454. dashboardApi: _this8.dashboardApi
  455. });
  456. storyService.updateStory({
  457. boardModel: _this8.model.boardModel,
  458. targetInfo: {
  459. type: value
  460. }
  461. });
  462. }
  463. };
  464. }
  465. });
  466. return SceneLayout;
  467. });
  468. //# sourceMappingURL=SceneLayout.js.map