'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2016, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['underscore', '../../widgets/livewidget/nls/StringResources', '../../dataSources/utils/DatasourceUtil', '../../util/DashboardFormatter', '../../lib/@waca/core-client/js/core-client/utils/UniqueId', '../../util/EventUtils'], function (_, StringResources, DatasourceUtil, Formatter, UniqueId, EventUtils) { 'use strict'; var Vis2SelectionController = function () { function Vis2SelectionController(attributes) { _classCallCheck(this, Vis2SelectionController); this._visAPI = attributes.visAPI || attributes.visModel; this._ownerWidget = attributes.visModel.ownerWidget; this.visualization = this._ownerWidget.content.getFeature('Visualization'); this._pageContextAPI = attributes.pageContext; this.synchronizeData = attributes.synchronizeData; this.dashboardApi = this._ownerWidget.getDashboardApi(); this.transaction = this.dashboardApi.getFeature('Transaction'); this.registerEvents(); } Vis2SelectionController.prototype.getAPI = function getAPI() { // Expose the controller as a deprecated feature for now return this; }; Vis2SelectionController.prototype.destroy = function destroy() { this.remove(); }; /** * Get the 'title' for the selections toolbar (an array of category values delimited by |, or the number of selections if there are multiple) * @param selections the selections defining the categories to present. * @param {boolean} options.isRightClick True Indicates title returned should be for right click. * @returns {string} the toolbar's title eg: Camping Equipment | 2004 for a cluster chart, or '2 selected' for a non-cluster multiselection */ Vis2SelectionController.prototype.getToolbarTitleForSelections = function getToolbarTitleForSelections(oSelections, options) { var aCustomSelections = this._getCustomTitleSelections(oSelections); var iNumSelected = oSelections.getCategorySetSize() + aCustomSelections.length; var pageContextSelectedCount = this.getSelectedCount(); if (options && options.isRightClick && pageContextSelectedCount) { iNumSelected = pageContextSelectedCount + aCustomSelections.length; } iNumSelected = iNumSelected || 1; // are there multiple selections? if (iNumSelected > 1) { return StringResources.get('multiselectMsg', { count: iNumSelected }); } else { var title = ''; var aCategorySelections = oSelections.getCategorySelections().concat(aCustomSelections); _.each(aCategorySelections, function (selection) { // Apply any formatting this category selection has. var dataItemAPI = selection.slotDataItem; if (selection && selection.value) { var displayValue = selection.value.hasOwnProperty('d') ? selection.value.d : selection.value; if (title !== '') { title += ' | '; } title += dataItemAPI ? Formatter.format(displayValue, dataItemAPI.getFormat()) : displayValue; } }.bind(this)); return title; } }; /** * Get number of selected values plus points (visible) * * @returns sum of selected values plus points */ Vis2SelectionController.prototype.getSelectedCount = function getSelectedCount() { var count = 0; var selections = this._visAPI.getFilterInfo({ type: 'selections' }); if (selections && selections[0]) { var values = selections[0].values || selections[0].valueSummary; count = values ? values.length : count; } return count; }; Vis2SelectionController.prototype._getSelectionValue = function _getSelectionValue(selection) { // Selection value can be one of the following: // 1. Primitive (Number) // 2. Object with 'd' for CatContDataItem (ie. lat long) // 3. Object with 'value' or 'v' if (selection.value.hasOwnProperty('d')) { return selection.value.d; } else if (selection.value.hasOwnProperty('v')) { return selection.value.v; } else if (selection.value.hasOwnProperty('value')) { return selection.value.value; } return selection.value; }; /** * Get the 'labels' with values that correspond to the given selections (a set of measure names and, where possible, their values/formats). * * @param selections * @returns {Array} */ Vis2SelectionController.prototype.getToolbarLabelsForSelections = function getToolbarLabelsForSelections(oSelections) { var aLabels = []; var aOrdinalSelections = oSelections.getOrdinalSelections(); var existingTooltipEntries = {}; _.each(aOrdinalSelections, function (selection) { var dataItemAPI = selection.slotDataItem; var dataItemId = dataItemAPI.getId(); var existingTooltipEntry = existingTooltipEntries[dataItemId] || { value: null, count: 0 }; //If the aggregation type isn't sum, append the aggregation type onto the name of the ordinal: eg: Revenue (Average) var ordinalFieldName = this._getOrdinalLabel(selection.slot, dataItemAPI); var ordinalValue = void 0; if (selection && selection.value || selection.value === 0) { var value = this._getSelectionValue(selection); // Guard against not having a valid number, maybe undefined if (!isNaN(value) && value !== null) { ordinalValue = Number(value); if (existingTooltipEntry.value !== null) { existingTooltipEntry.value += ordinalValue; } else { existingTooltipEntry.value = ordinalValue; } } else if (typeof value === 'string') { // handle the scenario that the value is already formatted existingTooltipEntry.value = value; } existingTooltipEntry.count++; } existingTooltipEntry.name = ordinalFieldName; existingTooltipEntry.dataType = dataItemAPI.getMetadataColumn().getDataType(); existingTooltipEntry.formatSpec = dataItemAPI.getFormat(); existingTooltipEntry.aggregate = dataItemAPI.getAggregation(); existingTooltipEntries[dataItemId] = existingTooltipEntry; }.bind(this)); _.each(existingTooltipEntries, function (entry) { var value = entry.value; if (entry.count > 1 && entry.aggregate !== 'sum' || value === 'N/A') { value = StringResources.get('value_is_not_available'); } if (value === null || value === undefined || value === '') { value = entry.formatSpec ? Formatter.format(entry.value, entry.formatSpec) : StringResources.get('value_is_not_available'); } aLabels.push({ 'name': entry.name, 'dataType': entry.dataType, 'formatSpec': entry.formatSpec, 'value': value }); }); aLabels = _.uniq(aLabels, function (label) { return label.name + label.value; }); aLabels.push.apply(aLabels, this._getCustomLabelSelections(oSelections)); return aLabels; }; Vis2SelectionController.prototype._getOrdinalLabel = function _getOrdinalLabel(slot, dataItem) { var aggregationType = dataItem.getAggregation(); return aggregationType === 'sum' ? dataItem.getLabel() : StringResources.get('aggregatedColumnLabel', { aggregationTypeLabel: StringResources.get(aggregationType), column: dataItem.getLabel() }); }; /** * Select a new tuple * Note: the intent of this method is that it is atomic (meaning a single transaction and event) * If multi-select is needed, selectionArguments should allow variations. * @param selectionArguments * itemIds (required) - the one or more item's that have values to set. * tuple - a single tuple where one entry represents one part of the tuple * each term of the tuple should be of form { u: 'value' } * command - one of 'update' => merges the new tuple with the existing selections * 'remove' => removes the new tuple from the existing selections * slotsToClear: - to support select (without ctrl), clear all visible slots prior to replaceing with the new value. */ Vis2SelectionController.prototype.select = function select(selectionArguments, transactionToken) { var _this = this; selectionArguments = selectionArguments || {}; selectionArguments.brushingSpec = this._getBrushingItemSpec(selectionArguments.itemIds); this._visAPI.setPendingFilters(selectionArguments.pending ? true : false); var subTransactionToken = this.transaction.startTransaction(transactionToken); var transactionId = subTransactionToken && subTransactionToken.transactionId || selectionArguments.undoRedoTransactionId || UniqueId.get('select'); var options = { payloadData: { undoRedoTransactionId: transactionId, silent: false, transactionToken: subTransactionToken }, multiTuple: selectionArguments.multiTuple, synchOptions: { pageContext: this._getPendingPageContext(selectionArguments), newSelectionIsEmpty: this._newSelectionIsEmpty.bind(this) }, skipFilterFocus: true }; return this.synchronizeData.handleBrushingSelection(selectionArguments, options).then(function () { delete options.synchOptions; selectionArguments.isSynchronizedEntry = false; _this._select(selectionArguments, options); }).finally(function () { _this.transaction.endTransaction(subTransactionToken); }); }; Vis2SelectionController.prototype._getPendingPageContext = function _getPendingPageContext() { var context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return context.pending ? this._visAPI.setPendingFilters(true) : this._pageContextAPI; }; //@returns the pageContext (normally, the actual pageContext but this can be overridden to be the pendingPageContext - (pendingFilters)) Vis2SelectionController.prototype._getPageContext = function _getPageContext(options) { return options && options.pageContext ? options.pageContext : this._pageContextAPI; }; /** * update a single item with the specified selectedValue * @param itemId - the itemId to update * @param selectedValue - a new value which should be of form { u: 'useValue' } * @param selectionArgs - command: 'update' - merge the value, 'remove' - remove the value from the tupleSet. * synchDataId: generated id shared between the pageContextEntry from different data sources * isSynchronizedEntry: true if selection is synchronized from another data source * @param options - options for this command including the transactionId or silent */ Vis2SelectionController.prototype._updateEdgeSelection = function _updateEdgeSelection(itemId, selectedValue, selectionArgs, options) { var brushingSpec = this._getBrushingItemSpec(itemId, /*forClear*/false, selectionArgs); this._getPageContext(options).updateFilterContext(brushingSpec, { command: selectionArgs.command, values: this._getBrushingValueSpec(selectedValue, options) }, options); }; /** * update the selections with a new dataPoint. * OR an edge with a value if itemIds/tuple are not an array OR an array of length 1. * @param itemIds - the items involved in the tuple (can be 1 which is equivalent to an edge selection) * @param valueTuple - a set of values of form {u: d:}. If no u and d are given, the value is assumed to be a use value. * @param selectionArgs - command: 'update' - merge the value, 'remove' - remove the value from the tupleSet. * synchDataId: generated id shared between the pageContextEntry from different data sources * isSynchronizedEntry: true if selection is synchronized from another data source * @returns the result of the update. */ Vis2SelectionController.prototype._updateEdgeOrPointSelection = function _updateEdgeOrPointSelection(itemIds, valueTuple, selectionArgs, options) { var brushingSpec = this._getBrushingItemSpec(itemIds, /*forClear*/false, selectionArgs); return this._getPageContext(options).updateFilterContext(brushingSpec, { command: selectionArgs.command, values: this._getBrushingValueSpec(valueTuple, options) }, options); }; /** * @param itemId - the itemId of the dataItem that might have selected values. * @returns true if the specified itemId has any selected items. */ Vis2SelectionController.prototype.isItemSelected = function isItemSelected(itemId) { var item = this._getBrushingItem(itemId); return item && item.getValueCount() > 0 ? true : false; }; /** * @param itemId - the itemId of the dataItem that might have selected values. * @param value - a value to check (should be of form { u: 'useValue' but 'useValue' is also accepted). * @returns true if the specified itemId has any selected items. */ Vis2SelectionController.prototype.isValueSelected = function isValueSelected(itemId, value) { var pci = this._getBrushingItem(itemId); if (pci) { var valueIds = pci.getValueIds(); return valueIds && valueIds.indexOf(value.u ? value.u : value) !== -1; } return false; }; Vis2SelectionController.prototype._getBrushingItem = function _getBrushingItem(itemId) { return this._pageContextAPI.getPageContextItem({ itemId: itemId, origin: 'visualization', scope: this._visAPI.getScope(), eventGroupId: this._visAPI.getEventGroupId() }); }; /** * @param itemId - the itemId of the dataItem that might have selected values. * @returns an array of selected values. If none are selected, an empty array is returned. */ Vis2SelectionController.prototype.getSelectedValues = function getSelectedValues(itemId) { var pci = this._getBrushingItem(itemId); if (pci) { return pci.getValueIds(); } return []; }; /** * @param itemIds - array of item IDs * @return array of tuples */ Vis2SelectionController.prototype.getSelectedTuples = function getSelectedTuples(itemIds) { var brushingSelector = this._getBrushingItemSpec(itemIds); var pcpoint = this._pageContextAPI.getPageContextItem(brushingSelector); if (pcpoint && pcpoint.getValueCount()) { return pcpoint.getValues(); } return []; }; /** * Return keys for items (datapoint/edge). * @param tuples Array of data points or edge object */ Vis2SelectionController.prototype.getItemKeys = function getItemKeys(tuples) { return _.map(tuples, function (tuple) { return EventUtils.getItemKey(tuple); }); }; //implementations functions. /** * implementation function. */ Vis2SelectionController.prototype._select = function _select(selectionArgs, options) { var _this2 = this; //itemIds - ensure itemIds and tupleSet is an array. options = options || {}; //If selectionArgs includes the pending flag, add the pendingPageContext (pendingFilters) to the options. options.pageContext = this._getPendingPageContext(selectionArgs); var itemIds = Array.isArray(selectionArgs.itemIds) ? selectionArgs.itemIds : [selectionArgs.itemIds]; var tuple = Array.isArray(selectionArgs.tuple) ? selectionArgs.tuple : [selectionArgs.tuple]; //Clear any items specified in slotsToClear. //An empty tuple can come in as one of the following: //1. undefined //2. [] //3. [[]] //Adding a check to ensure that if any of the 3 above are encountered, to return and not allow highlighting. var newSelectionIsEmpty = this._newSelectionIsEmpty(tuple); options.silent = !newSelectionIsEmpty; this._clearSlotSelections(selectionArgs.slotsToClear, options); if (newSelectionIsEmpty) { return; } if (selectionArgs.edgeSelect) { options.payloadData = options.payloadData || {}; //Currently, a few visualizations (mostly indentedList) break tuple selections down into selections on each edge //TODO: keep doing this for parity but should investigate if this ought to change. itemIds.forEach(function (itemId, i) { //An event should be sent on the final update. var flatTuple = _.flatten(tuple); options.payloadData.ignorePageContextChanged = i !== flatTuple.length - 1; options.silent = options.payloadData.ignorePageContextChanged; options.selectionInfo = selectionArgs.selectionInfo; _this2._updateEdgeSelection(itemIds[i], flatTuple[i], selectionArgs, options); }); } else { //Normal Case: Most visualizations do tuple selections as actual tuple selections (dataPoint selections) options.silent = false; options.selectionInfo = selectionArgs.selectionInfo; this._updateEdgeOrPointSelection(itemIds, tuple, selectionArgs, options); } }; /** * delete all page context entries that involve any of the hierarchies that are referenced in slotsToClear. * @param slotsToClear - the slots to clear hierarchies for (usually a simple call to getDataSlots() on the visualization) * @param options - typical options (silent, transactionId). Note that if newSelectionIsEmpty, silent will be false */ Vis2SelectionController.prototype._clearSlotSelections = function _clearSlotSelections(slotsToClear, options) { var itemIdsToClear = []; _.each(slotsToClear, function (slotAPI) { var slotDataItems = slotAPI.getDataItemList(); _.each(slotDataItems, function (dataItemAPI) { itemIdsToClear.push(dataItemAPI.getColumnId()); }.bind(this)); }.bind(this)); //By specifying hierarchiesContain on the brushingSpec, we can clear all context entries in 1 shot. var brushingSpec = this._getBrushingItemSpec(itemIdsToClear, /*forClear*/true); brushingSpec.hierarchiesContain = itemIdsToClear; return this._getPageContext(options).deletePageContextItems(brushingSpec, options); }; /** * @param itemIds - an itemId or array of itemIds. * @param forClear - if forClear is true, eventGroupId is not applied in the spec in order to clear any items which the source is moved to other event group. * @param selectionArgs - synchDataId: generated id shared between the pageContextEntry from different data sources * isSynchronizedEntry: true if selection is synchronized from another data source */ Vis2SelectionController.prototype._getBrushingItemSpec = function _getBrushingItemSpec(itemIds, forClear, selectionArgs) { itemIds = Array.isArray(itemIds) ? itemIds : [itemIds]; var hierarchies = _.map(itemIds, function (itemId) { return { hierarchyUniqueName: itemId }; }); var datasource = this.visualization.getDataSource(); var itemKey = { origin: 'visualization', eventSourceId: this._ownerWidget.getId(), sourceId: datasource && datasource.getId(), scope: this._visAPI.getScope(), eventGroupId: this._visAPI.getEventGroupId(), hierarchies: hierarchies, hierarchyUniqueNames: itemIds //TODO: We shouldn't need this but pageContext complains. }; var eventGroupId = this._visAPI.getEventGroupId(); if (eventGroupId && !forClear) { itemKey.eventGroupId = eventGroupId; } if (selectionArgs) { if (selectionArgs.synchDataId) { itemKey.synchDataId = selectionArgs.synchDataId; } if (selectionArgs.isSynchronizedEntry) { itemKey.isSynchronizedEntry = selectionArgs.isSynchronizedEntry; } } return itemKey; }; /** * This function accepts a single value or a single tuple as input. * The pageContext accepts a valueSpec which is either a simple object { u: d: } * or, for tuples, it expects "tupleSet form" * [ * [{u: d:}, {u: d}], //One tuple in the tupleset. * ] * @param tuple - usually a tuple but can be a single { u: d: } value. * @param option.multiTuple - Indicates that the tuple param consist of multiple * tuples. For example * [ * [{u: d1}, {u: d1}],[{u: d2}, {u: d2}],[{u: d3}, {u: d3}] * ] */ Vis2SelectionController.prototype._getBrushingValueSpec = function _getBrushingValueSpec(tuple, option) { //Handle special case where tuple is just an object (a singe tuple with 1 part) var isSingleValue = !Array.isArray(tuple) || tuple.length === 1; var value = []; tuple = Array.isArray(tuple) ? tuple : [tuple]; _.each(tuple, function (tuplePart) { if (Array.isArray(tuplePart)) { var v = []; _.each(tuplePart, function (tp) { v.push(this._getTuplePartValue(tp)); }.bind(this)); value.push(v); } else { value.push(this._getTuplePartValue(tuplePart)); } }.bind(this)); return isSingleValue || option.multiTuple ? value : [value]; }; Vis2SelectionController.prototype._getTuplePartValue = function _getTuplePartValue(tuplePart) { //Handle when tuplePart is an array as is the case in multi turple selection var resultValue = {}; if (tuplePart && tuplePart.u !== undefined) { resultValue.u = tuplePart.u; resultValue.d = tuplePart.d !== undefined ? tuplePart.d : tuplePart.u; if (tuplePart.p || tuplePart.pu) { resultValue.p = tuplePart.p || tuplePart.pu; } } else { resultValue.u = tuplePart; resultValue.d = tuplePart; } return resultValue; }; /** * Returns a boolean indicating whether can apply brushing for not * * @param {object} scope - the scope to verify the context of brushing */ Vis2SelectionController.prototype._canApplyBrushing = function _canApplyBrushing() { var scope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return scope.eventGroupId === this._visAPI.getEventGroupId() && scope.eventSourceId !== this._ownerWidget.getId() && scope.scope === this._visAPI.getScope(); }; /** * Call to handle the synchronize of data, i.e, create a page context entry if applicable per data source * * @param {object} payload - contains the context to handle the synchronization of data via the creation/update of a pagecontext entry of type 'visualization' per data source * */ Vis2SelectionController.prototype._handleSynchronizeData = function _handleSynchronizeData() { var _this3 = this; var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var synchData = payload.data; if (synchData) { // TODO livewidget cleanup -- use the new datasource instead of module here var module = this._visAPI.getModule(); var dataSource = this.visualization.getDataSource(); var thisTableRef = DatasourceUtil.getTableRef(dataSource, this.visualization.getSlots().getDataItemList().map(function (dataItem) { return dataItem.getColumnId(); })); var ignoreSynchBrushData = function ignoreSynchBrushData() { var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var ignoreSynchBrushData = data.ignoreSynchBrushData || []; return ignoreSynchBrushData.indexOf(_this3._ownerWidget.getId()) !== -1; }; var getData = function getData(sourceId) { return synchData.find(function (item) { return item.sourceId === sourceId && DatasourceUtil.haveATableReference(thisTableRef, item.tableRef); }); }; var data = getData(dataSource.getId()); if (data && !ignoreSynchBrushData(data) && !data.hasNotApplied && module && this._canApplyBrushing(payload.scope, thisTableRef, data.tableRef)) { //trigger broadcast to everyone with same source id but only one processing the triggering is sufficient to update the page context data.hasNotApplied = true; var selector = { origin: 'visualization', scope: payload.scope.scope, eventGroupId: payload.scope.eventGroupId, sourceId: dataSource.getId() }; var slotsToClear = data.getNetSlotsToClear({ dataSlots: this.visualization.getSlots().getMappedSlotList(), sourceModule: data.sourceModule, targetModule: module, tableNames: thisTableRef, pageContext: this._getPendingPageContext(payload.arguments), selector: selector, itemIds: data.itemIds, newSelectionIsEmpty: this._newSelectionIsEmpty(data.tuple) }); var selectionArguments = { command: payload.arguments.command, pending: payload.arguments.pending, edgeSelect: this._ownerWidget.getEdgeSelection(), slotsToClear: data.append ? [] : slotsToClear, itemIds: data.itemIds, tuple: data.tuple, synchDataId: data.synchDataId, isSynchronizedEntry: true }; this._select(selectionArguments, data.options); } } }; Vis2SelectionController.prototype._getCustomTitleSelections = function _getCustomTitleSelections(oSelections) { return typeof oSelections.getCustomTitleSelections === 'function' ? oSelections.getCustomTitleSelections() : []; }; Vis2SelectionController.prototype._getCustomLabelSelections = function _getCustomLabelSelections(oSelections) { return typeof oSelections.getCustomLabelSelections === 'function' ? oSelections.getCustomLabelSelections() : []; }; Vis2SelectionController.prototype._newSelectionIsEmpty = function _newSelectionIsEmpty(tuple) { return !tuple || !_.flatten(tuple).length; }; Vis2SelectionController.prototype.remove = function remove() { this.unregisterEvents(); }; Vis2SelectionController.prototype.registerEvents = function registerEvents() { this.dashboardApi.on('synchronizeData:applyBrushing', this._handleSynchronizeData, this); }; Vis2SelectionController.prototype.unregisterEvents = function unregisterEvents() { this.dashboardApi.off('synchronizeData:applyBrushing', this._handleSynchronizeData, this); }; return Vis2SelectionController; }(); return Vis2SelectionController; }); //# sourceMappingURL=Vis2SelectionController.js.map