'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2013, 2019 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['../../../../lib/@waca/core-client/js/core-client/ui/core/Class', 'underscore', '../../../../lib/@waca/dashboard-common/dist/ui/AuthoringToolbar', '../../../../lib/@waca/dashboard-common/dist/utils/ActionTypes', './Selection', '../../../../app/util/EventChainLocal', 'jquery'], function (Class, _, Toolbar, ActionTypes, Selection, EventChainLocal, $) { /** * Base interaction controller. This class only handles node selection. */ var Controller = Class.extend({ init: function init(options) { this.$el = options.$el; this.glassContext = options.glassContext; // TODO: clean this - This is only here because of other code referencing the dashboard Api off the controller. this.dashboardApi = options.dashboardAPI; this.transaction = options.transaction; this.toolbarConfig = options.toolbarConfig; this.canvas = options.canvas; this.appSettings = options.appSettings || {}; this.layoutController = options.layoutController; this.eventRouter = options.eventRouter; this.boardModel = options.boardModel; this.layout = this.boardModel && this.boardModel.layout; this._glass = options.glass ? options.glass : window.glass; this.services = options.services; this.config = this.appSettings.options && this.appSettings.options.config || {}; this.gatewayUrl = options.gatewayUrl; this.toolbarItems = []; this.dynamicToolbarItems = []; //toolbarItems that are non-default (associated with a selection context) this._toolbarDock = this.dashboardApi.getFeature('ToolbarDock'); this._contentActions = this.dashboardApi.getFeature('ContentActions'); this.toolbar = new Toolbar({ calculateBoundingRect: true, container: $('body'), reactToolbar: true }); this.defaultToolbarOptions = { textOnly: false, iconOnly: false }; this.toolbarMidShow = false; this.selectedNodes = null; if (this.eventRouter) { this.eventRouter.on('widget:availableActions', this.onUpdateAvailableActions, this); this.eventRouter.on('widget:maximize', this._hideToolbar, this); this.eventRouter.on('widget:hideToolbar', this._hideToolbar, this); this.eventRouter.on('widget:deleteItem', this._deleteItem, this); this.toolbar.on('toolbar:show:before', this._toolbarShowBefore, this); this.toolbar.on('toolbar:show', this._toolbarShowAfter, this); this.toolbar.on('toolbar:hide', this._toolbarHideAfter, this); } var _ref = options.selectionOptions || {}, deselectionSelector = _ref.deselectionSelector; this.selectionHandler = new Selection({ controller: this, canvas: this.canvas, transaction: this.transaction, deselectionSelector: deselectionSelector ? deselectionSelector.bind(null, this.layoutController) : null }); this.selectionHandler.on('selection:nodeSelected', this.onNodeSelected, this); this.selectionHandler.on('selection:nodeDeselected', this.onNodeDeselected, this); this.selectionHandler.on('selection:ready', this.onSelectionDone, this); // re-enable the toolbar when the user reselect widgets this.selectionHandler.on('selection:reselect', this.onWidgetReselect, this); // add keydown handler to set focus on the toolbar this.$el.on('keydown.InteractionController', this.onKeydown.bind(this)); }, /** * Check the app setting to see whether the toolbar is enabled or not * * @return {boolean} TRUE if enabled and FALSE if not */ isToolbarEnabled: function isToolbarEnabled() { var toolbar = this.toolbarConfig; return toolbar === undefined || toolbar === true || toolbar === 'true'; }, applyInteractions: function applyInteractions(interactions) { this.destroyInteractions(); this.toolbarItems = []; this.dynamicToolbarItems = []; //toolbarItems that are non-default (associated with a selection context) this.interactions = interactions; return this._attachInteractions(); }, finishMoveSelection: function finishMoveSelection(e) { this.selectionHandler.finishMoveSelection(e); }, /** * Handle keydown event */ onKeydown: function onKeydown(event) { // trap the F10 keydown event and focus the toolbar if visible if (event.keyCode === 121 && this.toolbar.focus()) { // stop propagation so that the ApplicationBar dosen't get focus event.stopPropagation(); // must return false to prevent browser F10 interaction return false; } }, /* * Handle scroll event * closes any open toolbar flyouts when the page is scrolled */ onPageScroll: function onPageScroll(event) { //scroll events belonging to children are propagated, make sure the scroll happened on the page, and that we aren't mid-show of a toolbar. //(This last condition has been added due to bug 250981. If you are scrolled to the very bottom of the canvas, the bootstrap popover display can briefly render the tooltip partially off screen causing this scroll event to fire for some reason. This does not happen if the canvas is not scrolled to the very bottom, but to avoid this, don't hide the toolbar if we get a scroll event fired during the show process) if (event.target.className.indexOf('page') > -1 && (event.target.className.indexOf('pageabsolute') > -1 || event.target.className.indexOf('pagecontainer') > -1) && !this.toolbarMidShow) { this._hideToolbar(); } }, getInteraction: function getInteraction(name) { var interaction = null; this.interactions.find(function (interactionStrategy) { interaction = interactionStrategy.getInteraction(name); return interaction; }); return interaction; }, _attachInteractions: function _attachInteractions() { var _this = this; var promise = Promise.resolve(); this.interactions && this.interactions.forEach(function (interaction) { promise = promise.then(function () { return interaction.attachInteractions(_this); }); }); return promise; }, addActionForSelector: function addActionForSelector(handler, selector, disableUserSelection) { this.selectionHandler.addActionForSelector(handler, selector, disableUserSelection); }, /** * Called when the selection gesture is complete. Here we handle the widget contextual toolbar. * @param param */ onSelectionDone: function onSelectionDone(param) { this.selectedNodes = param.selectedNodes; // Add widget specific actions to the toolbar if we have we a single widget selected this.removeDynamicToolbarAndRestoreWidgetToolbar(param); if (this.eventRouter) { this.eventRouter.trigger('selection:ready', param); } }, getSelectedNodes: function getSelectedNodes() { return this.selectedNodes ? this.selectedNodes : []; }, getInteractionProperties: function getInteractionProperties() { return this.selectionHandler.getInteractionProperties(); }, onNodeSelected: function onNodeSelected(param) { if (param && param.node) { if (param.node._layout) { param.node._layout.onSelect(); } } }, onNodeDeselected: function onNodeDeselected(param) { if (param && param.node) { if (param.node._layout) { param.node._layout.onDeselect(); } } }, addContextualToolbar: function addContextualToolbar(item) { this.addContextualToolbarToArray(item, this.toolbarItems); }, addContextualToolbarToArray: function addContextualToolbarToArray(item, itemArray) { var existingItem = _.find(itemArray, function (f) { return f.name === item.name; }); if (!existingItem) { itemArray.push(item); } }, removeContextualToolbar: function removeContextualToolbar(item) { if (item) { this.toolbarItems = this._removeToolbarItem(item, this.toolbarItems); } }, _removeToolbarItem: function _removeToolbarItem(item, toolbarItems) { return _.reject(toolbarItems, function (f) { return f.name === item.name; }); }, /** * Remove any dynamic toolbar items received by the widget iteself */ removeDynamicToolbarItems: function removeDynamicToolbarItems() { _.each(this.dynamicToolbarItems, function (item) { this.removeContextualToolbar(item); }.bind(this)); }, /** * Remove any widget specific item from the toolbar */ removeWidgetContextToolbarItems: function removeWidgetContextToolbarItems() { if (this.widgetContextItems) { if (this.widgetContextItems.onBlur) { this.widgetContextItems.onBlur(); } var items = this.widgetContextItems.items; for (var i = 0; i < items.length; i++) { this.removeContextualToolbar(items[i]); } this.widgetContextItems = null; } }, onSelectionMoveOrResize: function onSelectionMoveOrResize() { this.toolbar.hide(); this.eventRouter.trigger('widget:hideToolbar'); }, onSelectionDelete: function onSelectionDelete() { return this._hideToolbar(); }, removeDynamicToolbarAndRestoreWidgetToolbar: function removeDynamicToolbarAndRestoreWidgetToolbar(param) { var isEventPrevented = param.event && new EventChainLocal(param.event).getProperty('preventDefaultContextBar'); if (!isEventPrevented) { this._removeDynamicToolbarAndRestoreWidgetToolbar(param.event); this.eventRouter.trigger('widget:toolbarItems:changed'); } }, _updateToolbarDock: function _updateToolbarDock(nodes) { var _this2 = this; var idList = nodes.map(function (node) { return node.id; }); return this.toolbar.setSelectionContext(nodes).then(function () { _this2._toolbarDock.updateODTState(_this2._contentActions.getContentActionList(idList), idList); return; }); }, _removeDynamicToolbarAndRestoreWidgetToolbar: function _removeDynamicToolbarAndRestoreWidgetToolbar(event) { var options = event && event.options || { flyout: true }; var showToolbar = this.isToolbarEnabled() && !!options.flyout; // Generate the toolbar items even if we don't render the toolbar. This is used in Explore where a different toolbar // and a slightly different flow is used. // // YES - this has to be refactored... this.removeDynamicToolbarItems(); if (showToolbar) { this._updateToolbar({ nodes: this.selectedNodes, options: { isWidgetOdt: true, addClass: ['odtWidget'] } }, this.toolbarItems, true, event); } }, /** * update the toolbar with the specified set of availableActions * (actions which are relevant to a particular selection within the widget). * @param availableActions a set of actions to update the toolbar with. */ onUpdateAvailableActions: function onUpdateAvailableActions(availableActions) { this.dynamicToolbarItems = []; if (availableActions.toolbarOptions) { //New options for the toolbar created _.extend(this.toolbar.options, this.defaultToolbarOptions, availableActions.toolbarOptions); } else { _.extend(this.toolbar.options, this.defaultToolbarOptions); } _.each(availableActions.actions, function (availableAction) { this.addContextualToolbarToArray(availableAction, this.dynamicToolbarItems); }.bind(this)); var options = { nodes: availableActions.targetNode ? [availableActions.targetNode] : this.selectedNodes }; return this._updateToolbar(_.extend(options, availableActions), this.dynamicToolbarItems, true); }, /** * Update the toolbar. If there is no content or selected content, then we hide the toolbar, otherwise we update it and show it. * This function will show the toolbar using a setTimeout and supports canceling previous timers if they are still pending. * This is used to avoid showing different toolbars quickly one after the other. * * @param selectionContext * @param toolbarItems * @param overridePendingRequest - if true, and there is a pending toolbar.show and a new toolbar update occurs, then we cancel the pending toolbar. * @returns */ _updateToolbar: function _updateToolbar(selectionContext, toolbarItems, overridePendingRequest, event) { var nodes = selectionContext.nodes; var idList = _.map(nodes, function (node) { return node.id; }); var isWidgetOdt = selectionContext && selectionContext.options && selectionContext.options.isWidgetOdt; var widgetActionList = isWidgetOdt && this.canvas.getContentActionList(idList); var updatedToolBarItems = widgetActionList ? toolbarItems.concat(widgetActionList) : toolbarItems; var labels = selectionContext.labels; var hasContext = nodes && nodes.length > 0 || selectionContext.targetBounds; var hasContent = updatedToolBarItems.length > 0 || labels && labels.length > 0 || selectionContext.title && selectionContext.title.length > 0; var result = void 0; if (hasContext && hasContent) { result = this._createToolbarShowTimer(selectionContext, updatedToolBarItems, overridePendingRequest, event); } else { result = this._hideToolbar(); } return result; }, _createToolbarShowTimer: function _createToolbarShowTimer(selectionContext, toolbarItems, overridePendingRequest, event) { var _this3 = this; var updateToolbar; if (event && event.target) { updateToolbar = this._shouldUpdateToolbar(event); } else { updateToolbar = true; } var result = void 0; if (updateToolbar) { if (overridePendingRequest) { this._clearToolbarUpdateTimer(); } if (!this.toolbarUpdateTimer) { this.shownToolbarItems = toolbarItems; result = new Promise(function (resolve, reject) { try { var isWidgetOdt = selectionContext && selectionContext.options && selectionContext.options.isWidgetOdt; if (_this3._toolbarDock && _this3._toolbarDock.isContentToolbarDocked() && toolbarItems.length && isWidgetOdt) { _this3.toolbarUpdateTimer = setTimeout(function () { this._updateToolbarDock(selectionContext.nodes).then(resolve, reject); }.bind(_this3), 100); } else { _this3.toolbarUpdateTimer = setTimeout(function () { this._showContextualToolbar(selectionContext, toolbarItems).then(resolve, reject); }.bind(_this3), 100); } } catch (error) { reject(error); } }); } } else { result = Promise.resolve(); } return result; }, _shouldUpdateToolbar: function _shouldUpdateToolbar(event) { // When default is prevented, don't show the toolbar. if (!this.isToolbarEnabled()) { return false; } return !event.target.isContentEditable && !$(event.target).data('isContentEditable') && !event.target.tagName.toLowerCase().match(/input|textarea|select/img); }, _clearToolbarUpdateTimer: function _clearToolbarUpdateTimer() { if (this.toolbarUpdateTimer) { clearTimeout(this.toolbarUpdateTimer); this.toolbarUpdateTimer = null; } }, /** * Utility for hiding the odt/tooltip flyout * @param {Object} eventListener - custom event listener details for unbinding * @key classList @val {string} list of classes to find and apply the unbind to * @key event @val {string} event to unbind */ _hideToolbar: function _hideToolbar(eventListener) { var _this4 = this; this.shownToolbarItems = null; this._clearToolbarUpdateTimer(); //remove event listener when the toolbar is hidden - allow custom event details if (eventListener && eventListener.node && eventListener.eventName && eventListener.func) { eventListener.node.off(eventListener.eventName, eventListener.func); } else { this.$el.find('.page.pageabsolute, .page.pagecontainer').off('scroll.BaseController'); } var hideToolbarPromise = this.toolbar.hide(); hideToolbarPromise.then(function () { _this4.eventRouter.trigger('widget:hideToolbar:done'); }); return hideToolbarPromise; }, _deleteItem: function _deleteItem(options) { this.boardModel.removeLayouts([options.widgetId]); }, _toolbarShowBefore: function _toolbarShowBefore(payload) { this.toolbarMidShow = true; this.eventRouter.trigger('widget:toolbar:show:before', payload); }, _toolbarShowAfter: function _toolbarShowAfter(payload) { this.toolbarMidShow = false; this.eventRouter.trigger('widget:toolbar:show:after', payload); }, _toolbarHideAfter: function _toolbarHideAfter(payload) { this.toolbarMidShow = false; this.eventRouter.trigger('widget:toolbar:hide:after', payload); }, /** * Helper for handling scroll on a grid widget and hiding the tooltip on scroll. binds an event to the virtual scroll area * @param {Object} selectionContext - selectionContext passed from visEventHandler:_showSelectionInfo -> visActionHelper:showDataPointActions -> this._showContextualToolbar */ handleScrollForGridWidgets: function handleScrollForGridWidgets(selectionContext) { var _this5 = this; var gridViewClassName = 'grid-view'; var eventName = 'scroll.virtualScroll'; if (selectionContext && selectionContext.targetNode && selectionContext.targetNode.classList && selectionContext.targetNode.classList.contains) { if (selectionContext.targetNode.classList.contains(gridViewClassName)) { var scrollableArea = this._findScrollableArea(selectionContext.targetNode); if (!scrollableArea) { return; } var $scrollableAreaElement = $(scrollableArea); var options = { node: $scrollableAreaElement, eventName: eventName, func: this._hideToolbarForGridWidgets }; $scrollableAreaElement.on(eventName, function () { _this5._hideToolbarForGridWidgets(options); }); } } }, /** * Recursively walks down the children tree of a DOM node till it finds the right element * For this specific case its 3 children down * @param {DOM Element} ele - Dom element with children * @return if id matches 'scrollableArea' a DOM element, else undefined. */ _findScrollableArea: function _findScrollableArea() { var ele = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (ele.id && ele.id.match && ele.id.match('scrollableArea')) { return ele; } else if (ele.children && ele.children.length) { return this._findScrollableArea(ele.children[0]); } else { return false; } }, /** * Helper for hiding the toolbar when we scroll on a grid widget * @param {Object} opts * @param {DOM element} opts.node - node to bind/unbind listener to * @param {String} opts.eventName - DOM/node event to listen to/ bind/unbind from * @param {Function} opts.func - function we bind/unbind */ _hideToolbarForGridWidgets: function _hideToolbarForGridWidgets(opts) { this._hideToolbar(opts); }, /** * Update the contextual toolbar content and show it * * @param selectionContext * @param toolbarItems * @returns */ _showContextualToolbar: function _showContextualToolbar(selectionContext, toolbarItems) { this.toolbarUpdateTimer = null; var nodes = selectionContext.nodes; var labels = selectionContext.labels; this.eventRouter.trigger('textToolbar:hide'); var updateToolbar = function () { delete this.toolbar.options.placement; if (selectionContext.placement) { this.toolbar.options.placement = selectionContext.placement; } this.toolbar.setName(selectionContext.toolbarName); this.toolbar.setLabels({ title: selectionContext.title, labels: labels, tooltipContext: selectionContext.tooltipContext, tooltipRenderer: this.dashboardApi.getFeature('TooltipRenderer.react') }); // Add listener to close the toolbar when the user scrolls the page this.$el.find('.page.pageabsolute, .page.pagecontainer').on('scroll.BaseController', this.onPageScroll.bind(this)); // [250580] Remove the label hover tooltip on scroll in grid widgets if ((selectionContext.title || labels) && !selectionContext.options.isWidgetOdt) { this.handleScrollForGridWidgets(selectionContext); } if (selectionContext.options && selectionContext.options.hasOwnProperty('isHover') && selectionContext.options.isHover === true) { this.toolbar.options.hideOnMouseLeave = true; } else { this.toolbar.options.hideOnMouseLeave = false; } return this.toolbar.show(); }.bind(this); this.toolbar.clearHistory(); this.toolbar.clearItems(); toolbarItems.forEach(function (item) { if (item.order === undefined) { item.order = ActionTypes[item.name]; } }); this.toolbar.addItems(toolbarItems.sort(function (a, b) { if (a.order && b.order || !a.order && !b.order) { return a.order - b.order; } // defined order should be before undefined order if (a.order && !b.order) { // a before b return -1; } if (!a.order && b.order) { // b before a return 1; } })); this.toolbar.setOptions(selectionContext.options); if (selectionContext.targetBounds) { this.toolbar.options.preferredVertical = false; return this.toolbar.setSelectionBounds(selectionContext.targetBounds).then(function () { return updateToolbar(); }); } var isWidgetOrPagegroup = $(nodes).is('.widget, .pagegroup'); this.toolbar.options.iconOnly = isWidgetOrPagegroup; this.toolbar.options.preferredVertical = isWidgetOrPagegroup; return this.toolbar.setSelectionContext(nodes).then(function () { return updateToolbar(); }); }, onWidgetReselect: function onWidgetReselect(param) { // toggle the contextual toolbar - watch for dynamic toolbar if (this.toolbar.isOpened) { if (this.toolbar._viewStack.length === 0) { return this.toolbar.hide(); } } else { // Clear dynamic toolbar and show the widget contextual toolbar. this.removeDynamicToolbarAndRestoreWidgetToolbar(param); } }, destroyInteractions: function destroyInteractions() { // Cleanup and remove event handlers if (this.selectionHandler) { this.selectionHandler.disableInteraction(); } if (this.interactions) { this.interactions.forEach(function (interaction) { if (interaction.destroy) { interaction.destroy(); } }); this.interactions = null; } }, destroy: function destroy() { // Before disabling the interaction, // let us clear the selection handler events so that we don't trigger useless code on derstruction. if (this.selectionHandler) { this.selectionHandler.off(); this.destroyInteractions(); this.selectionHandler = null; } if (this.eventRouter) { this.eventRouter.off('widget:availableActions', this.onUpdateAvailableActions, this); this.eventRouter.off('widget:maximize', this._hideToolbar, this); this.eventRouter.off('widget:hideToolbar', this._hideToolbar, this); this.toolbar.off('toolbar:show:before', this._toolbarShowBefore, this); this.toolbar.off('toolbar:show', this._toolbarShowAfter, this); this.toolbar.off('toolbar:hide', this._toolbarHideAfter, this); } // remove event listener this.$el.off('keydown.InteractionController'); this.toolbar.remove(); } }); return Controller; }); //# sourceMappingURL=Controller.js.map