'use strict'; /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['../lib/@waca/dashboard-common/dist/ui/SearchableListView', '../lib/@waca/core-client/js/core-client/utils/Deferred', 'text!./templates/PinView.html', 'text!./templates/PinListItem.html', 'text!./templates/EmptyPinsList.html', '../app/nls/StringResources', 'jquery', 'underscore', 'doT', '../lib/@waca/core-client/js/core-client/utils/Utils', '../lib/@waca/core-client/js/core-client/utils/BidiUtil', '../lib/@waca/core-client/js/core-client/utils/ContentFormatter', '../lib/@waca/dashboard-common/dist/ui/toolbar_components/ToggleMenuBar', '../lib/@waca/dashboard-common/dist/lib/@ba-ui-toolkit/ba-graphics/dist/illustrations-js/no-pins_128'], function (SearchableListView, Deferred, ViewTemplate, PinListItem, EmptyPinsList, StringResources, $, _, dot, Utils, BidiUtil, ContentFormatter, ToggleMenuBar, noPinIcon) { var DEFAULT_DELAY = 500; /** * The Panel which shows the pinned content */ var View = SearchableListView.extend({ events: { 'mousedown .pinItem>div:not(.prop-deleteButton-item)': 'onDragStart', 'mouseup .pinItem>div:not(.prop-deleteButton-item)': 'onMouseUp', 'dragstart .pinItem': 'onDragStart', 'primaryaction .prop-deleteButton-item': 'onSingleRemove', 'deleteaction .pinItem': 'onSingleRemove' }, itemTemplate: PinListItem, templateString: ViewTemplate, init: function init(attributes) { View.inherited('init', this, arguments); this.ajaxSvc = attributes.ajaxSvc; this.glassContext = attributes.glassContext; this.canvasController = attributes.canvasController; this.dashboardApi = this.canvasController.dashboardApi; this._icons = this.dashboardApi.getFeature('Icons'); this.logger = this.glassContext.getCoreSvc('.Logger'); this.dndManager = attributes.dndManager; this.dashboardPinningService = attributes.dashboardPinningService; this.smartNamingSvc = attributes.smartNamingSvc; this.promiseMap = {}; this.pinCount = 0; this.viewMode = attributes.viewMode; this.elementClass = 'pinsPanel'; this.dateFilterString = 'all'; if (this.dashboardPinningService) { //register creating-pin handler this.addHandler = this.dashboardPinningService.on('pin:created', this.addPin.bind(this)); //register deleting-pin handler this.postDeletionHandler = this.dashboardPinningService.on('pin:deleted', this.postDeletion.bind(this)); //register fake-deleting-pin handler this.fakeDeletionHandler = this.dashboardPinningService.on('pin:fakeDeleted', this.fakeDeletion.bind(this)); //register undo-deleting-pin handler this.undoDeletionHandler = this.dashboardPinningService.on('pin:undoDeletion', this.undoDeletion.bind(this)); } else { throw new Error('Pinning service not provided to PinsPanel constructor'); } }, /** * Click event handler * * @param event: * The event to process * @public */ onItemClick: function onItemClick(event) { // stop more events event.stopPropagation(); //clear the existing selection as we only supports single selection for now this._clearSelections(); // get target var $target = $(this.getTarget(event.currentTarget, 'pinItem')); this._toggleSelection($target); // update action buttons this._updateActionButtons(); }, onKeyDown: function onKeyDown(event) { var $target = $(this.getTarget(event.currentTarget, 'pinItem')); if (['Enter', ' '].indexOf(event.key) !== -1) { // Tab into the item does not select it so do the selection here and create on key if (!$target.hasClass('selected')) { this._toggleSelection($target); } this.onKeyDownToCreatePin(event); } else { View.inherited('onKeyDown', this, arguments); } }, /** * Alias for the click event handler * * @param event: * The event to process * @public */ onSelectItem: function onSelectItem(event) { this.onItemClick(event); }, /** * Click handler for the remove button. It handles deleting the pins and updating the UI. * * @public * @return promise */ onRemoveClick: function onRemoveClick() { // get selected pin var pinId = this._getSelectedPin(); return this.deletePin(pinId); }, /** * primary action handler for clicking/tapping the remove icon on a single pin in the list. * delete action handler for deleting a single list item in the list * it creates a pin object with the selected pin id and jQuery element, and calls deletePin() to delete the selected single pin * * @public * * @param {Event} EventObject - the event object to process * @return promise */ onSingleRemove: function onSingleRemove(event) { var pinEl = $(event.currentTarget).parents('.listitem')[0] || event.target; var pinToRemove = pinEl.getAttribute('data-id'); return this.deletePin(pinToRemove); }, /** * Called by the DnD manager to check if this drop zone accept the dragged object * * @returns {Boolean} */ accepts: function accepts(dragObject) { return dragObject.type === 'pin'; }, /** * Handles dragging action. * The function sends the id of a pin to pinning service and calls the dnd manager with the pin spec result * * @public * * @param {object} event - The event to process */ onDragStart: function onDragStart(event) { var _this = this; this._mouseUp = false; if (this.dashboardApi.getMode() !== this.dashboardApi.MODES.EDIT) { return Promise.resolve(true); } var pinEl = $(event.currentTarget).parents('.listitem')[0] || event.target; var id = pinEl.getAttribute('data-id'); if (id) { return this.getPin(id).then(function (pinSpec) { // If the mouse is up before we start dragging then just return if (_this._mouseUp) { return; } pinSpec = JSON.parse(JSON.stringify(pinSpec)); if (!_.isEmpty(pinSpec) && !_this._stoppedDrag) { _this.dndManager.startDrag({ event: event, type: 'pin', data: _this._buildDropInfo(pinSpec), avatar: _this._buildAvatar(pinSpec), moveXThreshold: 20, moveYThreshold: 20, callerCallbacks: { onDragDone: _this.onDragStop.bind(_this) } }); } }); } else { return Promise.resolve(); } }, // User might of single clicked, so stop the drag from starting onMouseUp: function onMouseUp() { this._mouseUp = true; this.onDragStop(); }, onDragStop: function onDragStop() { this._clearSelections(); }, /** * Gets a pin spec from calling glassContext using a pin id * * @public * * @param {stirng} id - The id of a pin * @return {string} pinSpec - The spec of a pin */ getPin: function getPin(id) { return this.dashboardPinningService.getPin(id); }, /** * A helper function to build avatar for a pin * @param {string} pinSpec - the spec of a pin * @return {object} - a jquery object with an img of the pin if present. */ _buildAvatar: function _buildAvatar(pinSpec) { var avatar = $('', { class: 'avatar pin' }); avatar.attr('src', pinSpec.thumbUri); return avatar; }, /** * a helper method to build drop info based on the pin spec that was received from the server * * @private * * @param {object} pinSpec - description of a pin * @return {object} dropInfo */ _buildDropInfo: function _buildDropInfo(pinSpec) { return { operation: 'new', pinSpec: pinSpec }; }, /** * Deletes pins by sending an ajax request and updates the UI * * @param id: * string - a pin id to delete * @public */ deletePin: function deletePin(id) { this.dashboardPinningService.deletePin(id); }, /** * Undo deletion handler. It gets called if the undo deletion event is triggered. * The selected pin slides down and the pin count increments by 1 * @param id: * string - a pin id to delete * @public * @return promise */ undoDeletion: function undoDeletion(pinId) { var that = this; var deferred = new Deferred(); this.promiseMap[pinId].promise.done(function () { var $pinItem = that.$el.find('.pinItem[data-id=' + pinId + ']'); $pinItem.slideDown(DEFAULT_DELAY, function () { // increment the pin count that.pinCount++; that._updatePinCount(); // remove from the promise map delete that.promiseMap[pinId]; deferred.resolve(); }); }); return deferred.promise; }, /** * Fake deletion handler. It gets called if the fake deletion event is triggered. * The selected pin slides up and the pin count decrements by 1 * * @param id: * string - a pin id to delete * @public */ fakeDeletion: function fakeDeletion(pinId) { var _this2 = this; var deferred = new Deferred(); //save the promise into the promise map this.promiseMap[pinId] = deferred; var $pinItem = this.$el.find('.pinItem[data-id=' + pinId + ']'); $pinItem.slideUp(DEFAULT_DELAY, function () { //decrement the pin count by one in the panel _this2.pinCount--; _this2._updatePinCount(); //clear the selection state and update the action button _this2._clearSelections(); _this2._updateActionButtons(); deferred.resolve(); }); }, /** * post deletion handler. It gets called if a pin:deleted event is triggered. * The pin item gets removed from the DOM after fake deletion finishes * * @param id: * string - a pin id to delete * @public * @returns promise */ postDeletion: function postDeletion(pinId) { var view = this; var deferred = new Deferred(); if (this.promiseMap[pinId]) { this.promiseMap[pinId].promise.done(function () { var $el = view.$el.find('.pinItem[data-id=' + pinId + ']'); $el.remove(); //make the first item in the list tabbable after deletion var firstEl = view.$el.find('.pinItem:first'); if (firstEl.length) { firstEl.attr('tabindex', '0'); } //remove from the promise map delete view.promiseMap[pinId]; deferred.resolve(); }); } else { deferred.resolve(); } return deferred.promise; }, /** * primary action handler for adding pin to canvas * * @public * @param {event} event - The event object to process */ onKeyDownToCreatePin: function onKeyDownToCreatePin(event) { // get selected pin var pinId = this._getSelectedPin(); if (pinId) { return this.getPin(pinId).then(function (pinSpec) { pinSpec = JSON.parse(JSON.stringify(pinSpec)); //Clone the pin spec, as the add operation is side-effecting, and we don't want any updates to change the cached pin. if (!_.isEmpty(pinSpec)) { var fragmentModel = { layout: pinSpec.content.layout, widgets: pinSpec.content.widgets, dataSources: pinSpec.content.dataSources, version: pinSpec.version ? pinSpec.content.specVersion : 6, sourceName: pinSpec.sourceName }; if (pinSpec.content.episodes) { fragmentModel.episodes = pinSpec.content.episodes; } var options = { model: fragmentModel }; if (pinSpec.content.layout && pinSpec.content.layout.style) { options.layoutProperties = { style: _.omit(pinSpec.content.layout.style, 'top', 'left') }; } var isTouch = event.type === 'tap'; this._addPinToLayout(options, isTouch); this._clearSelections(); } }.bind(this)); } else { return Promise.resolve(); } }, _addPinToLayout: function _addPinToLayout(options, isTouch) { this.canvasController.addPin(options, isTouch); }, /** * Callback function for when the render is completed. This one updates the pin count in the UI and puts the focus on the first pin. * * @public */ renderComplete: function renderComplete() { // update the pin count this._updatePinCount(); this._middleShortenPins(); this._setIcons(); this._setFocus(); }, renderList: function renderList(items) { this.pinCount = items ? items.length : 0; View.inherited('renderList', this, arguments); }, render: function render() { var deferred = new Deferred(); this.dndManager.removeDropTarget(this.el); View.inherited('render', this).done(function () { this.dndManager.addDropTarget(this.el, { accepts: this.accepts }); this._renderDateFilterDropdown().then(function () { deferred.resolve(); }); }.bind(this)); return deferred.promise; }, _filterByDate: function _filterByDate(filterString) { var _this3 = this; if (!filterString) { return; } this.dateFilterString = filterString; if (this.dateFilterString !== 'all') { this.$el.find('.filterWrapper').addClass('filtered'); } else { this.$el.find('.filterWrapper').removeClass('filtered'); } return this._getPins().then(function (pins) { _this3.pinCount = pins.length; _this3.renderList(pins); // re-apply any search that may already be in place. _this3.$el.find('.treeSearchInput').trigger('input'); _this3.renderComplete(); }); }, _renderDateFilterDropdown: function _renderDateFilterDropdown() { var _this4 = this; var $filterWrapper = this.$el.find('.filterWrapper'); var menuItems = [{ 'name': 'all', 'label': StringResources.get('pinDateFilterAll'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('all'); } }, { 'name': 'today', 'label': StringResources.get('pinDateFilterToday'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('today'); } }, { 'name': 'yesterday', 'label': StringResources.get('pinDateFilterYesterday'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('yesterday'); } }, { 'name': 'pastWeek', 'label': StringResources.get('pinDateFilterPastWeek'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('pastWeek'); } }, { 'name': 'pastMonth', 'label': StringResources.get('pinDateFilterPastMonth'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('pastMonth'); } }, { 'name': 'earlier', 'label': StringResources.get('pinDateFilterEarlier'), 'cssStyleClass': 'blueSelectedBar', 'action': function action() { _this4._filterByDate('earlier'); } }]; this.ddMenu = new ToggleMenuBar({ 'label': StringResources.get('pinDateFilter'), 'items': menuItems, 'actionElement': $filterWrapper, 'ddMenuPlacement': 'bottom-left', 'showTitle': false, 'updateLabel': true, 'icon': 'common-filter' }); return this.ddMenu.render().then(function ($el) { Utils.setIcon($el.find('.common-filter'), _this4._icons.getIcon('filter').id, StringResources.get('pinDateFilter')); _this4.$el.find('.filterWrapper').append($el); }); }, _setFocus: function _setFocus() { this.$el.find('.searchWrapper input').focus(); this.$el.find('.pinItem:first').attr('tabindex', '0'); }, _setIcons: function _setIcons() { var $deleteIcon = this.$el.find('.deleteButtonHolder:not(:has(svg))'); Utils.setIcon($deleteIcon, 'dashboard-remove', StringResources.get('pinRemove')); }, /** * shows an empty pins list with IBM bee when there is no pins * otherwise remove the empty content list */ _updateEmptyPinsList: function _updateEmptyPinsList() { if (!this.pinCount) { this.$el.find('.list').append(dot.template(EmptyPinsList)({ text: StringResources.get('NoPinsCollected'), emptyBeeIcon: noPinIcon.default.id })); } else { this.$el.find('.list .emptyTableContent').remove(); } }, _middleShortenPins: function _middleShortenPins() { if (this.pinCount) { var $pinTexts = this.$el.find('.pinName, .pinSource, .pinAge'); for (var i = 0; i < $pinTexts.length; i++) { ContentFormatter.middleShortenString($pinTexts[i]); } } }, /** * overridden method providing pin specific properties for dot. */ getCustomRenderProperties: function getCustomRenderProperties() { return { searchText: StringResources.get('pinSearchOld'), lbl_listView: StringResources.get('pinListView'), lbl_iconView: StringResources.get('pinIconView'), lbl_remove: StringResources.get('pinRemove'), lbl_add: StringResources.get('add'), roleType: 'listitem', searchIcon: this._icons.getIcon('search').id }; }, /** * This function is called by the renderer. It fetches the list of pins * * @public * @return promise */ getListItems: function getListItems() { var _this5 = this; return this._getPins().then(function (pins) { _this5.pinCount = pins.length; return pins; }); }, _getPins: function _getPins() { return this.dashboardPinningService.getPins(this.dateFilterString); }, /** * Add a pin to this view * * @public */ addPin: function addPin(pin) { if (!pin) { return; } // render if (this.dateFilterString !== 'yesterday' && this.dateFilterString !== 'earlier') { this.pinCount++; this._renderPin(pin); } this.$el.find('.treeSearchInput').trigger('input'); }, /** * Add a new pin to the rendered list. * @private */ _renderPin: function _renderPin(pin) { var sHtml = this._getListItemsHtml([pin]); var $list = this.$el.find(this.controlClassSelector); var view = this; // queue up pin to be added to the list $list.queue(function () { var $item = $(this); $(sHtml).hide().css('opacity', 0.0).prependTo($list).delay(200).slideDown('slow', function () { $(this).animate({ opacity: 1.0 }).show(); view.renderComplete(); // allow animations to complete before kicking off the next one. window.setTimeout(function () { $item.dequeue(); }, 500); }); }); }, /** * Callback function for each list item to process it before rendering * * @param item: * list item object to process * @public * @return Object: The processed list object */ prepareListItem: function prepareListItem(item) { item.cssClass = 'pinItem'; item.selected = false; item.canSingleDelete = true; item.label_delete = StringResources.get('pinRemove'); item.isSingleGroup = true; return item; }, /** * Helper function to toggle the users pin selection. * * @param item: JQuery list item object to select * @private */ _toggleSelection: function _toggleSelection($item) { // see if the item is selected and toggle accordingly this._selectItem($item, !$item.hasClass('selected')); }, /** * Helper function to select the given item * * @param item: JQuery list item object to select * @param select: Boolean flag to indicate select or deselect * @private */ _selectItem: function _selectItem($item, select) { if (select) { // style the item as selected $item.addClass('selected'); } else { // remove selected styling $item.removeClass('selected'); } }, /** * Helper function to set the disabled status of the action buttons * * @private */ _updateActionButtons: function _updateActionButtons() { // get buttons var $buttons = this.$el.find('.actionButton'); // check if there are selected pins if (this.$el.find('.pinItem.selected').length > 0 && this.dashboardApi.getMode() === this.dashboardApi.MODES.EDIT) { // enable action buttons $buttons.removeClass('disabled'); $buttons.prop('disabled', false); $buttons.attr('tabindex', '0'); } else { // disable action buttons $buttons.addClass('disabled'); $buttons.prop('disabled', true); $buttons.attr('tabindex', '-1'); } }, /** * Helper function to get the selected pin * * @private * @return the id of a pin */ _getSelectedPin: function _getSelectedPin() { // get selected pin list elements var $listItems = this.$el.find('.pinItem.selected'); if ($listItems.length === 1) { return $listItems[0].getAttribute('data-id'); } }, /** * Helper function to update the UI to indicate the number of pins * * @private */ _updatePinCount: function _updatePinCount() { // get the pin count element var formattedCount = BidiUtil.enforceNumericShaping(this.pinCount); this.$el.find('.pinCount').text(StringResources.get('pinCount', { smart_count: formattedCount, count: formattedCount })); this._updateEmptyPinsList(); }, /** * Overridden method to provide a list of items that are searchable for this view */ _getSearchableItems: function _getSearchableItems() { return this.dashboardPinningService.getCachedPins(this.dateFilterString); }, /** * Overridden method to provide the searchable string for a view object */ _getSearchableFieldValue: function _getSearchableFieldValue(value) { var _this6 = this; // concatenate multiple values to search var separator = '___'; var fields = value.displayName + separator + value.sourceName; if (value.contentType === 'boardFragment' && value.content.widgets) { // if looking at a boardFragment with widgets, use the mapping labels _.each(value.content.widgets, function (widget) { fields = fields + separator + _this6.smartNamingSvc.getLocalizedWidgetType(widget); _.each(widget.mapping, function (mapping) { fields = fields + separator + mapping.label; }); // check live widgets var items = widget.data && widget.data.dataViews && widget.data.dataViews[0] && widget.data.dataViews[0].dataItems; _.each(items, function (item) { fields = fields + separator + item.itemLabel; }); }); } return fields; }, _clearSelections: function _clearSelections() { this.$el.find('.listitem').removeClass('selected'); }, /** * Un-registers all of the handlers */ remove: function remove() { this.addHandler.remove(); this.postDeletionHandler.remove(); this.fakeDeletionHandler.remove(); this.undoDeletionHandler.remove(); View.inherited('remove', this, arguments); } }); return View; }); //# sourceMappingURL=PinsPanel.js.map