'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: Storytelling (C) Copyright IBM Corp. 2015, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['gemini/dashboard/layout/views/PageCollectionView', 'jquery', 'underscore', '../../StoryService'], function (PageCollectionView, $, _, StoryService) { /** * An interface for classes that represent the layout for a story. * Implementing classes are required to create and instaniate a property named '_pageNavigationController' * @interface */ var BLACKLISTED_PROPERTIES = ['layoutPositioning']; var TABSTOP_SELECTOR = ['button', 'input', 'select', 'textarea'] // 'not' to exclude: tabindex < 0 || tabindex > 0 || none .map(function (s) { return s + ':not([tabindex])'; }).concat(['[tabindex="0"]']).join(); var SceneLayout = PageCollectionView.extend({ init: function init(options) { var _this = this; SceneLayout.inherited('init', this, arguments); this._tabstops = []; this._scenes = []; this.sceneId = options.appSettings && options.appSettings.sceneId || null; this.dashboardApi = options.dashboardApi; this.stringResources = this.dashboardApi.getDashboardCoreSvc('.StringResources'); // TODO: this.eventRouter will work as it is set in dashboard-core/LayoutBaseView which this class is extended from this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:select', this.onSceneSelected.bind(this)); this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:next', this.onNextScene.bind(this)); this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').registerLifeCycleHandler('scene:previous', this.onPreviousScene.bind(this)); this.layoutController.eventRouter.on('scene:jump', this.onJumpScene, this); this.layoutController.layoutReady(this.model.id).then(function () { _this.onLayoutReady(); }); this.sceneLayoutApi = { getAdjacentScenes: this._getAdjacentScenes.bind(this), getCurrentScene: this._getCurrentScene.bind(this), getNextScene: this._getNextScene.bind(this), getPreviousScene: this._getPreviousScene.bind(this), getSceneById: this._getSceneById.bind(this), getSceneByIndex: this._getSceneByIndex.bind(this), getSceneIndex: this._getSceneIndex.bind(this), getScenes: this._getScenes.bind(this) }; }, destroy: function destroy() { this._scenes = []; this._tabstops = []; this.layoutController.eventRouter.off('scene:jump', this.onJumpScene, this); SceneLayout.inherited('destroy', this, arguments); }, onLayoutReady: function onLayoutReady() { _.each(this.model.items, this._addScene.bind(this)); }, getSceneLayoutApi: function getSceneLayoutApi() { return this.sceneLayoutApi; }, extendApi: function extendApi(additionalApiMethods) { _.each(additionalApiMethods, function (apiMethod, key) { this.sceneLayoutApi[key] = apiMethod; }.bind(this)); }, _hasScene: function _hasScene(model) { var sceneId = this._getSceneId(model); var scene = _.findWhere(this._scenes, { id: sceneId }); return scene ? true : false; }, _addScene: function _addScene(model) { this._scenes.push(this._makeSceneInstance(model)); }, /** * @returns {Promise} */ _removeScene: function _removeScene(model) { var _this2 = this; var remove = this._getScene(model); var removeIndex = this._getSceneIndex(model); // Always try to go to the next scene first var sibling = this._scenes[removeIndex + 1]; if (!sibling) { // If removing the last scene we go to the previous scene sibling = this._scenes[removeIndex - 1]; } this._scenes = _.without(this._scenes, remove); // at this point the view is out of the DOM this._tabstops = this._tabstops.filter(function (node) { if (!_this2.$el[0].contains(node)) { // lets restore the tabindex giving up our node reference. node.setAttribute('tabindex', '0'); return false; } return true; }); return this.didRemovePage({ modelId: this.sceneId !== model.id ? this.sceneId : sibling.id }); }, /** * @param {Object} model The layout model of a scene */ _makeSceneInstance: function _makeSceneInstance(model) { return { id: model.id, $el: this.$el.find('#' + this._getViewId(model.id)), getLayoutView: this.layoutController.getLayoutView.bind(this.layoutController, model.id), getLayoutViewWhenReady: this.layoutController.getLayoutViewWhenReady.bind(this.layoutController, model.id) }; }, /** * @param {Object} model The layout model of the scene being moved * @param {String} [beforeId] A string that represents the scene's new imediate sibling's DOM element */ _updateSceneList: function _updateSceneList(model, beforeId) { // Get the id of scene AND remove it from list of scenes var sceneId = this._getSceneId(model); var scene = _.findWhere(this._scenes, { id: sceneId }); this._scenes = _.without(this._scenes, scene); // Set the index to reinsert the scene at to the end of the list var index = this._scenes.length; // If we are moving the scene beside another scene then we need to find where if (beforeId) { var beforeSceneData = _.findWhere(this._scenes, { id: beforeId }); // update index value to reinsert scene at a specific location index = _.indexOf(this._scenes, beforeSceneData); } // Update the list of scenes. Place the moved scene in the proper position this._scenes.splice(index, 0, this._makeSceneInstance(model)); }, _getScenes: function _getScenes() { return this._scenes; }, /** * @param {Object} model The model of a scene (actual layout model or the runtime model version) * @returns {Object} The runtime version of a scene */ _getScene: function _getScene(model) { return this._getSceneById(model.id); }, _getAdjacentScenes: function _getAdjacentScenes(scene) { var scenes = []; var before = this._getPreviousScene(scene); var after = this._getNextScene(scene); if (before && before !== scene) { scenes.push(before); } if (after && after !== scene) { scenes.push(after); } return scenes; }, _getCurrentScene: function _getCurrentScene() { return this._getSceneById(this.sceneId); }, _getCurrentSceneIndex: function _getCurrentSceneIndex() { return this._getSceneIndexById(this.sceneId); }, _getNextScene: function _getNextScene(scene) { var sceneIndex = this._getCurrentSceneIndex(); if (scene && scene.id !== this.sceneId) { sceneIndex = this._getSceneIndex(scene); } if (sceneIndex >= 0 && sceneIndex < this._scenes.length - 1) { return this._getSceneByIndex(sceneIndex + 1); } else if (sceneIndex === this._scenes.length - 1) { return this._getSceneByIndex(0); } return null; }, _getPreviousScene: function _getPreviousScene(scene) { var sceneIndex = this._getCurrentSceneIndex(); if (scene && scene.id !== this.sceneId) { sceneIndex = this._getSceneIndex(scene); } if (sceneIndex > 0 && sceneIndex < this._scenes.length) { return this._getSceneByIndex(sceneIndex - 1); } else if (sceneIndex === 0) { return this._getSceneByIndex(this._scenes.length - 1); } return null; }, _getSceneById: function _getSceneById(sceneId) { var scene = this._scenes.filter(function (scene) { return scene.id === sceneId; }); return scene[0] || null; }, /** * * @param {String} sceneId This is either the model Id or the scene'e element Id in the DOM * @returns {Number} The array index value or -1 if the scene was not found */ _getSceneIndexById: function _getSceneIndexById(sceneId) { for (var i = 0; i < this._scenes.length; i++) { if (this._scenes[i].id === sceneId) { return i; } } return -1; }, /** * * @param layoutModel layout model to get the scene index from * @returns the index or -1 if the model was not found */ _getSceneIndex: function _getSceneIndex(model) { return this._getSceneIndexById(model.id); }, _getSceneByIndex: function _getSceneByIndex(index) { if (index > -1 && index < this._scenes.length) { return this._scenes[index]; } return null; }, _getSceneId: function _getSceneId(model) { return model.id; }, _getViewId: function _getViewId(modelId) { return modelId + '_tab'; }, /** * @param {String} modelId The scene's id * @returns {Promise} */ _selectScene: function _selectScene(modelId) { return this.dashboardApi.getDashboardCoreSvc('.LifeCycleManager').invokeLifeCycleHandlers('scene:select', { modelId: modelId }); }, /** * @returns {Promise} */ didRemovePage: function didRemovePage(options) { return this.onJumpScene(options); }, // Overridden in authoring view onPlaybackNext: function onPlaybackNext() { this.layoutController.eventRouter.trigger('playback:next'); return this; }, // Overridden in authoring view onPlaybackPrevious: function onPlaybackPrevious() { this.layoutController.eventRouter.trigger('playback:prev'); return this; }, /** * @param {Object} event The object that is provided when triggering the scene:select event on the layout controller's eventRouter * @returns {Promise} */ onSceneSelected: function onSceneSelected(event) { var _this3 = this; return this._pageNavigationController.jumpTo(_.extend(event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) { _this3._navigationComplete(_.extend(results, { play: event ? event.play : null })); }); }, /** * This method is only called when the eventRouter has had the "scene:next" event triggered * @returns {Promise} */ onNextScene: function onNextScene(event) { var _this4 = this; return this._pageNavigationController.nextPage(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) { _this4._navigationComplete(_.extend(results, { play: event ? event.play : null })); }); }, /** * This method is only called when the eventRouter has had the "scene:previous" event triggered * @returns {Promise} */ onPreviousScene: function onPreviousScene(event) { var _this5 = this; return this._pageNavigationController.previousPage(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) { _this5._navigationComplete(_.extend(results, { play: event ? event.play : null })); }); }, /** * @returns {Promise} */ onJumpScene: function onJumpScene(event) { var _this6 = this; return this._pageNavigationController.jumpTo(_.extend({}, event, { onTargetSelected: this._navigationStarted.bind(this) })).then(function (results) { _this6._navigationComplete(_.extend(results, { play: event ? event.play : null })); }); }, _navigationStarted: function _navigationStarted(event) { this._navigationUpdated(event, 'navigation:started'); }, _navigationComplete: function _navigationComplete(event) { this._navigationUpdated(event, 'navigation:complete'); this.currentSceneChanged(); }, _updateTabStops: function _updateTabStops() { // backup enabled tabstops var tabstops = []; this._scenes.forEach(function (scene) { var el = scene.$el[0].querySelector('.page.pagecontainer'); var nodes = el ? el.querySelectorAll(TABSTOP_SELECTOR) : []; // can't use NodeList.forEach because of IE. // it's also faster so ok... for (var i = 0; i < nodes.length; i++) { tabstops.push(nodes[i]); } }); this._tabstops = this._tabstops.concat(tabstops); this._tabstops = _.uniq(this._tabstops); var scene = this._getCurrentScene(); var sceneEl = scene && scene.$el.length ? scene.$el[0] : null; this._tabstops.forEach(function (node) { if (sceneEl && sceneEl.contains(node)) { node.setAttribute('tabindex', '0'); } else { node.setAttribute('tabindex', '-1'); } }); }, _navigationUpdated: function _navigationUpdated(event, type) { // Update the current scene this.sceneId = event.scene.id; var targetIndex = this._getCurrentSceneIndex(); var payload = { index: targetIndex, // This is still needed for StoryPaneController scene: event.scene, overview: event.overview || targetIndex < 0, play: event.play }; this.layoutController.eventRouter.trigger(type, payload); }, /** * called when the current scene changes for any reason */ currentSceneChanged: function currentSceneChanged() { this._updateTabStops(); }, /** * START OF DOUBLE DELEGATION SECTION - DO NOT REMOVE * * Double delegation is needed as methods may get overridden when moving to authoring mode. * The event handlers registering these methods in consume view will need to invoke the overridden * method that comes from authoring view */ _onPlaybackNext: function _onPlaybackNext() { return this.onPlaybackNext(); }, _onPlaybackPrevious: function _onPlaybackPrevious() { return this.onPlaybackPrevious(); }, _onKeyDown: function _onKeyDown(event) { return this.onKeyDown(event); }, /** * END OF DOUBLE DELEGATION SECTION */ getBannerProperty: function getBannerProperty() { return { 'value': this.stringResources.get('storyProperties'), 'name': 'banner', 'type': 'Banner', 'editable': false }; }, getProperties: function getProperties() { var _this7 = this; return SceneLayout.inherited('getProperties', this, arguments).then(function (properties) { properties = properties.filter(function (property) { return BLACKLISTED_PROPERTIES.indexOf(property.id) === -1; }); properties.push(_this7._getConvertProperty()); return properties; }); }, _getConvertProperty: function _getConvertProperty() { var _this8 = this; var panAndZoomItems = [{ name: 'panAndZoom1', label: this.stringResources.get('PanAndZoomLayout1'), type: 'svg', value: 'dashboard-guidedjourney1_32' }, { name: 'panAndZoom2', label: this.stringResources.get('PanAndZoomLayout2'), type: 'svg', value: 'dashboard-guidedjourney2_32' }, { name: 'panAndZoom3', label: this.stringResources.get('PanAndZoomLayout3'), type: 'svg', value: 'dashboard-guidedjourney3_32' }, { name: 'panAndZoom4', label: this.stringResources.get('PanAndZoomLayout4'), type: 'svg', value: 'dashboard-guidedjourney4_32' }, { name: 'panAndZoom5', label: this.stringResources.get('PanAndZoomLayout5'), type: 'svg', value: 'dashboard-guidedjourney5_32' }, { name: 'panAndZoom6', label: this.stringResources.get('PanAndZoomLayout6'), type: 'svg', value: 'dashboard-timesequence_32' }]; var collapsiblePickerSelectedName = void 0; var currentLayout = this.model.get('type') || 'slideshow'; if (currentLayout.indexOf('panAndZoom') != -1) { var index = currentLayout.substring(currentLayout.length - 1, currentLayout.length) - 1; currentLayout = currentLayout.substring(0, currentLayout.length - 1); collapsiblePickerSelectedName = panAndZoomItems[index].name; } var dropDownOptions = { 'name': 'storyType', 'label': this.stringResources.get('storyTypeLabel'), 'defaultValue': currentLayout, 'options': [{ label: this.stringResources.get('slideshowLabel'), value: 'slideshow' }, { label: this.stringResources.get('panAndZoomLabel'), value: 'panAndZoom' }] }; var collapsiblePickerOptions = { 'type': 'CollapsiblePicker', 'id': 'layoutModel', 'name': 'layoutModel', 'label': this.stringResources.get('layoutLabel'), 'selectedName': collapsiblePickerSelectedName, 'placeholder': { name: '', label: '', type: 'svg', value: 'dashboard-unknown_16' }, 'contentSize': 'large', 'items': panAndZoomItems, 'isRequired': true }; return { 'type': 'TwoStageCombo', 'name': 'storyToStoryControl', 'id': 'storyToStoryCombo', 'sectionName': this.stringResources.get('scenesPropertiesSection'), 'sectionOpened': true, 'tabName': this.stringResources.get('tabName_general'), 'dropDownOptions': dropDownOptions, 'collapsiblePickerOptions': [null, collapsiblePickerOptions], 'onChange': function onChange(name, propertyValue) { var value = propertyValue; if (propertyValue && propertyValue.name) { value = value.name; } var storyService = new StoryService({ dashboardApi: _this8.dashboardApi }); storyService.updateStory({ boardModel: _this8.model.boardModel, targetInfo: { type: value } }); } }; } }); return SceneLayout; }); //# sourceMappingURL=SceneLayout.js.map