123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776 |
- 'use strict';
- /*
- *+------------------------------------------------------------------------+
- *| Licensed Materials - Property of IBM
- *| IBM Cognos Products: Dashboard
- *| (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/BaseListView', 'text!./DataSlotsView.html', 'jquery', 'underscore', './DataSlotsViewAuthoringToolbar', '../../../DynamicFileLoader', '../../../dataSources/utils/ShapingConstants', '../../../dataSources/utils/ShapingUIUtils', '../../../visualizations/renderer/filter/FilterLabel', '../../../widgets/livewidget/nls/StringResources', '../../../apiHelpers/SlotAPIHelper', '../../../lib/@waca/baglass/js/baglass/utils/Utils', '../../../lib/@waca/dashboard-common/dist/utils/ActionTypes', '../../../lib/@waca/core-client/js/core-client/utils/dom-utils'], function (BaseListView, template, $, _, DataSlotsViewAuthoringToolbar, DynamicFileLoader, ShapingConstants, ShapingUIUtils, FilterLabel, stringResources, SlotAPIHelper, Utils, ActionTypes, DomUtils) {
- //These threshold values are taken from Watson Analytics Production Drag and Drop Implementation
- var DND_X_THRESHOLD = 10; //10 pixels movement for drag and drop data item between slots
- var DND_Y_THRESHOLD = 10; //10 pixels movement for drag and drop data item between slots
- /**
- * Function to convert an object to a representational string
- * @param {string} key - the key or id associated with this object
- * @param {object} o - the object to stringize
- * @param {[string|object]} filter - an array indicating elements to include in stringized version:
- * each element should be an object with members `key`, representing the key value in the object of the element to include,
- * and `type`, representing the type and therefore how to stringize the member, if no type is specified, string is assumed.
- * array elements may also be strings, which are the same as a `key` value in an object, and implicitly of type string.
- */
- function toStringKey(key, o, filter) {
- return [key, '_'].concat(_.map(filter, function (attr) {
- return o[attr];
- })).join('_');
- }
- function hasDimensions(models) {
- if (models.length > 0) {
- for (var index = 0; index < models.length; index++) {
- if ('attribute' === models[index].getType()) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * This class shows the data slots and context filters used in a data widget. It is used in the Data Widget Focus view.
- * It inherits BaseListView which handles a11y for lists.
- */
- var DataSlotsView = BaseListView.extend([FilterLabel], {
- templateString: template,
- events: {
- 'click ul.fields div.layerTitle div.layerExpanderButton': 'expandCollapseLayers',
- 'click ul.fields .slot.columnItem .listitem .menuoverflow': 'popupSlotOptions',
- 'click ul.fields .localFilter.columnItem .listitem:not(.unavailable) .menuoverflow': 'popupFilterOptions',
- 'click ul.fields .localFilter.columnItem .listitem:not(.unavailable) .slotInfo': 'openContextFilter',
- 'primaryaction ul.fields .localFilter': 'setAddFocus',
- 'primaryaction div.infographicZone': 'setAddFocus',
- 'mousedown ul.fields .slot .listitem:not(.unavailable)': 'onMouseDown', //To capture the positioning of the mousedown to determine the intention of the user to drag and drop or to open the Authoring Toolbar
- 'mousemove ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
- 'mouseup ul.fields .slot .listitem:not(.unavailable)': 'onMouseUp', //To clear the drag and drop states
- 'touchstart ul.fields .slot .listitem:not(.unavailable)': 'onMouseDown', //To capture the positioning of the mousedown to determine the intention of the user to drag and drop or to open the Authoring Toolbar
- 'touchmove ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
- 'touchend ul.fields .slot .listitem:not(.unavailable)': 'onMouseUp', //To clear the drag and drop states
- 'dragup ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
- 'dragdown ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
- 'keyup ul.fields .slot .listitem:not(.unavailable)': 'onEnterSlot',
- 'keyup ul.fields .localFilter .listitem:not(.unavailable)': 'onEnterLocalFilters',
- 'keydown ul.fields .slot .listitem, .dropfirst, .dropafter': 'onKeyDown',
- 'keyup ul.fields div.layerTitle div.layerExpanderButton': 'onEnterLayer',
- 'deleteaction ul.fields .localFilter .listitem:not(.missingFilter)': 'onDeleteKeyContextFilter',
- 'wheel ul.list.fields.slots': 'onScrollSlotsPanel'
- },
- init: function init(options) {
- // Note: Don't do this. visAPI = this.widget.visAPI.
- // Instead, use this.getModel function to get the visAPI.
- // The visAPI could be recreated for the widget hereby rendering
- // the object this.visAPI obsolete. see defect 245997
- DataSlotsView.inherited('init', this, arguments);
- // @todo: consider removing
- this.dashboardApi = options.dashboardApi;
- this.visualization = options.visualizationApi;
- this.slotsApi = this.visualization.getSlots();
- this.localFilters = this.visualization.getLocalFilters();
- this._dndManager = options.dndManager;
- this.widget = options.widget;
- this.content = this.widget.content;
- this.interactivityController = this.content.getFeature('InteractivityController.deprecated');
- this.missingFilters = options.missingFilters || [];
- this._changeEvents = {
- 'change:graphic': 1
- };
- this._registerEvents();
- //region layer is open by default
- this._visibleLayers = ['data.region'];
- this.transactionApi = this.dashboardApi.getFeature('Transaction');
- this.dataSources = this.dashboardApi.getFeature('DataSources');
- this.iconsFeature = this.dashboardApi.getFeature('Icons');
- this.visMapColumnsToSlot = this.content.getFeature('VisDnD.utils');
- },
- onKeyDown: function onKeyDown(event) {
- //Ctrl + V. Paste selected items in tree to slots
- if (event.keyCode === 86 && event.ctrlKey) {
- var moduleApi = this._getVisAPI().getModule();
- var mockDragObject = ShapingUIUtils.getCopiedTreeItems(moduleApi);
- if (!mockDragObject) {
- if (this.copyMappedItem) {
- mockDragObject = this.copyMappedItem;
- } else {
- return false;
- }
- }
- var dropNode = this.getFocusedDropNode();
- mockDragObject.droppable = !this._isDropNodeInvalid(mockDragObject, dropNode);
- if ($(dropNode).hasClass('localFilter') && this.acceptsContext(mockDragObject)) {
- this.onDropContext(mockDragObject, dropNode);
- } else if (this.accepts(mockDragObject)) {
- this.onDrop(mockDragObject, dropNode);
- }
- ShapingUIUtils.clearCopiedTreeItems();
- this.copyMappedItem = undefined;
- this.dashboardApi.triggerDashboardEvent('dataSourceGrid:clearSourceSelected');
- }
- //Ctrl + C, copy item for a swap operation.
- if (event.keyCode === 67 && event.ctrlKey) {
- ShapingUIUtils.clearCopiedTreeItems();
- this.copyMappedItem = this.getFocusedDragObject();
- }
- },
- getFocusedDropNode: function getFocusedDropNode() {
- var $element = $(document.activeElement);
- if ($element.hasClass('dropfirst')) {
- $element = $element.parent().parent();
- }
- return $element[0];
- },
- getFocusedDragObject: function getFocusedDragObject() {
- var $element = $(document.activeElement);
- if (!$element.hasClass('listitem')) {
- return false;
- }
- $element.addClass('addFocus');
- var mockDragObject = {
- $el: $element,
- type: 'slot',
- data: {
- slotId: $element.attr('data-slot-id'),
- mappingId: $element.attr('data-mapping-id')
- }
- };
- return mockDragObject;
- },
- getGraphicItem: function getGraphicItem() {
- var graphic = [];
- var graphicContent = this.content.getPropertyValue('value.graphic.content');
- if (graphicContent) {
- graphic.push({
- content: graphicContent,
- fillColor: this.content.getPropertyValue('value.graphic.fillColor'),
- currentScaleOption: this.content.getPropertyValue('value.graphic.currentScaleOption')
- });
- }
- return graphic;
- },
- onDropShapeWidget: function onDropShapeWidget(dragObject) {
- this.widget.onAddShapeWidget(dragObject.widgetSpec.model);
- this.updateShapeSlot();
- },
- updateShapeSlot: function updateShapeSlot() {
- var graphic = this.getGraphicItem();
- if (graphic.length) {
- var $infographicNode = this.$el.find('div.infographicZone');
- $infographicNode.removeClass('draggedOn');
- $infographicNode.find('div.graphic').html(graphic[0].content);
- }
- },
- rebuildEvents: function rebuildEvents() {
- for (var e in this._changeEvents) {
- if (this._changeEvents.hasOwnProperty(e)) {
- this._changeEvents[e].remove();
- }
- }
- this._registerEvents();
- },
- _registerEvents: function _registerEvents() {
- this.widget.model.on('change:localFilters', this._onChangeEvent, this);
- this.visualization.on('change:type', this._onChangeEvent, this);
- this.visualization.on('change:slots', this._onChangeEvent, this);
- this.content.on('change:property:value.graphic.content', this._onChangeEvent, this);
- var m = this._getVisAPI();
- for (var e in this._changeEvents) {
- if (this._changeEvents.hasOwnProperty(e)) {
- this._changeEvents[e] = m.on(e, this._onChangeEvent, this);
- }
- }
- },
- remove: function remove() {
- DataSlotsView.inherited('remove', this, arguments);
- this.widget.model.off('change:localFilters', this._onChangeEvent, this);
- //this.widget.model.off('change:data', this._onChangeEvent, this);
- //this.widget.model.off('change:slotmapping', this._onChangeEvent, this);
- this.visualization.off('change:slots', this._onChangeEvent, this);
- this.visualization.off('change:type', this._onChangeEvent, this);
- this.visualization.off('change:type', this._onChangeEvent, this);
- this.content.off('change:property:value.graphic.content', this._onChangeEvent, this);
- this.widget.model.off('change:localFilters', this._onChangeEvent, this);
- for (var e in this._changeEvents) {
- if (this._changeEvents.hasOwnProperty(e)) {
- this._changeEvents[e].remove();
- this._changeEvents[e] = null;
- }
- }
- this._changeEvents = [];
- this._clearToolbar();
- this._clearDropTargets();
- this.isOutsideFocusMode = false;
- this.focusModeBoundingClientRect = null;
- //To destroy the Event.js states since views inherit from Events.js
- DataSlotsView.inherited('destroy', this, arguments);
- },
- _clearDropTargets: function _clearDropTargets() {
- if (this._dndManager) {
- this.$('.listitem, ul.fields .localFilter').map(function (index, el) {
- this._dndManager.removeDropTarget(el);
- }.bind(this));
- }
- },
- _clearToolbar: function _clearToolbar() {
- this.launchedToolbarNodeId = null;
- this.isImplicitClosingTheBoolbar = false;
- if (this._actionMenuToolbar) {
- _.each(this._actionMenuToolbar.selectionNodes, function (node) {
- $(node).removeClass('selected');
- });
- //Passing true to request the 'toolbar:remove' to trigger
- this._actionMenuToolbar.remove(true);
- this._actionMenuToolbar = null;
- }
- var $node = this.$('.addFocus');
- if ($node.length > 0) {
- $node.removeClass('addFocus');
- }
- $node = this.$('.selected');
- if ($node.length > 0) {
- $node.removeClass('selected');
- }
- var $nodeList = this.$('.draggedOn');
- _.each($nodeList, function (node) {
- $node = $(node);
- $node.removeClass('draggedOn');
- });
- },
- /**
- * @param moduleRef (optional) - a moduleReference (id) which allows access to modules other than the default module.
- * @returns module information if the module has been loaded.
- */
- getModule: function getModule(moduleRef) {
- return this._getVisAPI().getModule(moduleRef);
- },
- /**
- * Builds up a list of slots to display on the dataslots view. If this chart has an archetype, this is
- * a merger of all the archetype slots in addition to the slots that are part of the visualisation
- */
- getSlotsInfo: function getSlotsInfo(hasCategory, localFilters) {
- var _this = this;
- return _.map(this.slotsApi.getSlotList(), function (slot) {
- return _this._buildSlotInfo(slot, hasCategory, localFilters);
- });
- },
- _isMultiMeasureSeries: function _isMultiMeasureSeries(dataItem) {
- return dataItem.getColumnId() === '_multiMeasuresSeries';
- },
- _getSlotIcon: function _getSlotIcon(slotDef) {
- var slotIconId = slotDef.getIcon();
- var icon = {};
- if (slotIconId) {
- icon = this.iconsFeature.getIcon(slotIconId);
- return icon;
- } else {
- return slotDef.getType() === 'ordinal' ? this.iconsFeature.getIcon('values') : this.iconsFeature.getIcon('category');
- }
- },
- _buildSlotInfo: function _buildSlotInfo(slot, hasCategory, localFilters) {
- var _this2 = this;
- var slotDef = slot.getDefinition();
- var icon = this._getSlotIcon(slotDef);
- var mappings = _.map(slot.getDataItemList(), function (dataItem) {
- var metadataColumn = dataItem.getMetadataColumn();
- var isMultiMeasuresSeries = _this2._isMultiMeasureSeries(dataItem);
- var isUnavailable = metadataColumn ? metadataColumn.isHidden() || metadataColumn.isMissing() : !isMultiMeasuresSeries;
- var mappingInfo = {
- columnId: dataItem.getColumnId(),
- name: isUnavailable ? stringResources.get('missingColumn', {
- 'columnLabel': _.escape(dataItem.getLabel())
- }) : _.escape(dataItem.getLabel()),
- mappingId: dataItem.getId(),
- configurable: !isMultiMeasuresSeries,
- unavailable: isUnavailable ? metadataColumn : null
- };
- if (hasCategory && localFilters) {
- _this2._buildFilterMappingInfoForDataItem(dataItem, slot, localFilters, mappingInfo);
- }
- return mappingInfo;
- });
- var isMapped = slot.getDataItemList().length > 0;
- var isShapeEnabled = !!this.content.getPropertyValue('value.graphic.content');
- var slotInfo = {
- 'id': slotDef.getId(),
- // This is used for UI only not the acutal dataset
- 'dataset': this._isSharingSlots() ? 'data' : slotDef.getDatasetIdList()[0],
- 'group': slotDef.getGroupId(),
- 'caption': slotDef.getCaption(),
- 'icon': icon,
- 'hidden': slotDef ? slotDef.isHidden() : false,
- 'mappings': mappings,
- 'noMapping': isMapped ? '' : 'noMapping',
- 'deletable': isMapped,
- 'optional': slotDef.isOptional(),
- 'showRequiredMarker': slotDef.getShowRequiredMarker(),
- 'shapeDropEnabled': isShapeEnabled,
- 'shapable': slotDef ? slotDef.isShapable() : false
- };
- return slotInfo;
- },
- _buildFilterMappingInfoForDataItem: function _buildFilterMappingInfoForDataItem(dataItem, slot, localFilters, mappingInfo) {
- var _this3 = this;
- var columnFilter = _.find(localFilters.models, function (filter) {
- if (!_this3._getVisAPI().isFilterEditable(filter)) {
- return false;
- }
- if (filter.filterBins) {
- if (dataItem.getBinning()) {
- return filter.id === dataItem.getId();
- }
- } else if (!filter.filterBins) {
- var columnId = dataItem.getColumnId();
- if (filter.aggregationType) {
- return filter.columnId === columnId && dataItem.getAggregation() === filter.aggregationType;
- } else {
- // If the case is when there is an ordinal and attribute we dont want to
- // display the filter beneath it.
- return filter.columnId === columnId && !(slot.getDefinition().getType() === 'ordinal' && dataItem.getType() === 'attribute');
- }
- }
- });
- //Only include the 'first filter' against an item (it can be edited).
- //Extra filters for an item or tuple (usually created with additional keeps/excludes) are shown as part of localFilters (and cant be edited)
- if (columnFilter) {
- var md = dataItem.getMetadataColumn();
- var columnMetadata = {
- getDataType: md.getDataType.bind(md),
- getType: md.getType.bind(md),
- getFormat: function getFormat() {
- return dataItem.getFormat();
- }
- };
- mappingInfo.filterString = this.getFilterLabel(columnFilter, columnMetadata);
- mappingInfo.numberOfFilters = this.getFilterNumber(columnFilter);
- }
- },
- /**
- * if binning is already applied but the vis is not supporting the binning or slot type is ordinal,
- * remove binning and local filters, this is needed when we change vis.
- *
- * @param slot A slot
- */
- _clearBinningDefnIfNeeded: function _clearBinningDefnIfNeeded() /*slot, transactionToken*/{
- // TODO livewidget_cleanup -- how does this relate to the cleanup happening inside the API
- //slot.setBinning(null, transactionToken);
- },
- _getVisAPI: function _getVisAPI() {
- return this.widget.visAPI;
- },
- /* Gets the number of filters applied on a selection filter view
- * @params columnFilter the filter applied.
- * @params json if the filter is in a json formatAction
- * @return the number of filters applied.
- */
- getFilterNumber: function getFilterNumber(columnFilter, json) {
- if (columnFilter.operator === 'in' || columnFilter.operator === 'notin' || columnFilter.operator === 'isnull') {
- var filterNum;
- if (json) {
- filterNum = columnFilter.values.length;
- } else {
- filterNum = columnFilter.values.models.length;
- }
- return filterNum;
- }
- return;
- },
- getLocalFiltersList: function getLocalFiltersList() {
- return this._getVisAPI().getLocalFiltersList();
- },
- getContextFilters: function getContextFilters() {
- var filters = [];
- var dataSource = this.visualization.getDataSource();
- _.each(this.getLocalFiltersList(), function (localFilter) {
- var columnId = localFilter.columnId;
- if (columnId) {
- var metadataColumn = dataSource.getMetadataColumn(columnId);
- if (metadataColumn) {
- filters.push({
- id: localFilter.id,
- columnId: localFilter.columnId,
- name: metadataColumn.getLabel(),
- filterString: this.getFilterLabel(localFilter, metadataColumn),
- numberOfFilters: this.getFilterNumber(localFilter, true)
- });
- }
- }
- }.bind(this));
- return filters;
- },
- isMappingComplete: function isMappingComplete() {
- return this.visualization.getSlots().isMappingComplete();
- },
- // if there is anything to preserve from the previous mapping
- // do so here
- _preserveData: function _preserveData() /*slot, mapping*/{
- // overriden by children
- },
- _convertTransactionTokenToLegacyOptions: function _convertTransactionTokenToLegacyOptions(transactionToken) {
- var oldOptions = {};
- if (transactionToken) {
- oldOptions.payloadData = {
- transactionToken: transactionToken
- };
- if (transactionToken.transactionId) {
- oldOptions.payloadData.undoRedoTransactionId = transactionToken.transactionId;
- }
- }
- return oldOptions;
- },
- /**
- * Performs swap mapping of slot data item 1 and slot data item 2
- */
- _swapSlotMapping: function _swapSlotMapping(dragObject, dropNode, transactionToken) {
- var sourceDataItemId = dragObject.data.mappingId;
- var targetDataItemId = dropNode.getAttribute('data-mapping-id');
- // If we are swapping onto same item, we are done
- if (sourceDataItemId === targetDataItemId) {
- return;
- }
- var options = {};
- var sourceSlotId = dragObject.data.slotId;
- var targetSlotId = dropNode.getAttribute('data-slot-id');
- if (targetSlotId === sourceSlotId) {
- // Handle swapping within the same slot
- options.afterItem = $(dropNode).is('.dropafter') || $(dropNode).is('.slot');
- if (options.afterItem) {
- targetDataItemId = $(dropNode).prev().attr('data-mapping-id');
- }
- this._swapItemsInSlot(targetSlotId, sourceDataItemId, targetDataItemId, options, transactionToken);
- } else if (!targetDataItemId) {
- // Handle moving an item
- var targetIndex = parseInt($(dropNode).attr('map-index'), 10);
- this._moveItemToSlot(targetSlotId, sourceDataItemId, targetIndex, transactionToken);
- } else {
- this._swapItemsBetweenSlots(sourceSlotId, targetSlotId, sourceDataItemId, targetDataItemId, transactionToken);
- }
- },
- _swapItemsBetweenSlots: function _swapItemsBetweenSlots(sourceSlotId, targetSlotId, sourceDataItemId, targetDataItemId, transactionToken) {
- // Update target slot items
- var targetSlot = this.slotsApi.getSlot(targetSlotId);
- var targetDataItemRefs = _.map(targetSlot.getDataItemList(), function (dataItem) {
- return dataItem.getId();
- });
- var targetIndex = targetDataItemRefs.indexOf(targetDataItemId);
- targetDataItemRefs[targetIndex] = sourceDataItemId;
- // Update source slot items
- var sourceSlot = this.slotsApi.getSlot(sourceSlotId);
- var sourceDataItemRefs = _.map(sourceSlot.getDataItemList(), function (dataItem) {
- return dataItem.getId();
- });
- var sourceIndex = sourceDataItemRefs.indexOf(sourceDataItemId);
- sourceDataItemRefs[sourceIndex] = targetDataItemId;
- // swap
- this.slotsApi.setDataItems(targetDataItemRefs, targetSlotId, transactionToken);
- this.slotsApi.setDataItems(sourceDataItemRefs, sourceSlotId, transactionToken);
- // @todo needs to be revise to be done behind the api
- this._clearBinningDefnIfNeeded(this.visualization.getSlots().getSlot(targetSlotId), transactionToken);
- },
- _swapItemsInSlot: function _swapItemsInSlot(slotId, sourceDataItemId, targetDataItemId, options, transactionToken) {
- var slot = this.slotsApi.getSlot(slotId);
- var dataItemRefs = _.map(slot.getDataItemList(), function (dataItem) {
- return dataItem.getId();
- });
- var sourceIndex = dataItemRefs.indexOf(sourceDataItemId);
- var targetIndex = dataItemRefs.indexOf(targetDataItemId);
- if (options.afterItem) {
- dataItemRefs.splice(targetIndex + 1, 0, sourceDataItemId);
- dataItemRefs.splice(sourceIndex > targetIndex ? sourceIndex + 1 : sourceIndex, 1);
- } else {
- dataItemRefs[sourceIndex] = targetDataItemId;
- dataItemRefs[targetIndex] = sourceDataItemId;
- }
- // swap
- this.slotsApi.setDataItems(dataItemRefs, slotId, transactionToken);
- },
- _moveItemToSlot: function _moveItemToSlot(targetSlotId, sourceDataItemId, targetIndex, transactionToken) {
- // Update target slot
- var targetSlot = this.slotsApi.getSlot(targetSlotId);
- var targetDataItemRefs = targetSlot.getDataItemList().map(function (dataItem) {
- return dataItem.getId();
- });
- // Add item to the target slot
- if (targetIndex === -1) {
- // At the end
- targetDataItemRefs.push(sourceDataItemId);
- } else {
- // Somewhere inside
- targetDataItemRefs.splice(targetIndex, 0, sourceDataItemId);
- }
- // move
- this.slotsApi.setDataItems(targetDataItemRefs, targetSlotId, transactionToken);
- this._clearBinningDefnIfNeeded(this.visualization.getSlots().getSlot(targetSlotId), transactionToken);
- },
- accepts: function accepts(dragObject, targetNode) {
- var acceptsOptions = { dropTarget: ShapingConstants.DROP_TARGET_OPTIONS.SLOT };
- if (targetNode) {
- acceptsOptions.targetNode = targetNode;
- }
- return dragObject.type === 'slot' || this.widget.accepts(dragObject, acceptsOptions);
- },
- _getMetadataPayloadColumns: function _getMetadataPayloadColumns(dragObject) {
- return dragObject && dragObject.data && dragObject.data.columns ? dragObject.data.columns : null;
- },
- _getDropMetadataColumns: function _getDropMetadataColumns(dragObject) {
- return dragObject && dragObject.data && dragObject.data.columns;
- },
- acceptsContext: function acceptsContext(dragObject) {
- var droppedColumns = this._getMetadataPayloadColumns(dragObject);
- if (droppedColumns) {
- var metadataColumns = _.map(droppedColumns, function (column) {
- return column.metadataColumn;
- });
- if (metadataColumns.length > 0 && metadataColumns[0].getType() === 'fact') {
- var dataItemAPIs = [];
- _.each(this.visualization.getSlots().getMappedSlotList(), function (mappedDataSlot) {
- dataItemAPIs = dataItemAPIs.concat(mappedDataSlot.getDataItemList());
- });
- // When data slots are empty, do not allow a numeric data item to be dropped to local filter area
- // Since the minmax value without context doesn't provide any meaning after a data item is dropped to a slot.
- if (dataItemAPIs.length === 0 || !hasDimensions(dataItemAPIs)) {
- return false;
- }
- }
- }
- return this.widget.accepts(dragObject, { dropTarget: ShapingConstants.DROP_TARGET_OPTIONS.FILTER });
- },
- onMouseDown: function onMouseDown(event) {
- this.startDrag = false;
- this.initialDragAndDropPosition = DomUtils.getEventPos(event);
- },
- onMouseUp: function onMouseUp(event) {
- this.startDrag = false;
- delete this.initialDragAndDropPosition;
- this.itemNotDragged(event);
- },
- onDragStart: function onDragStart(event) {
- if (!this._beginDragAndDrop(event) || this.startDrag) {
- return true;
- }
- this.startDrag = true;
- //drag and drop occurs so destroy the current opened toolbar if there is one opened
- this._clearToolbar();
- var $target = $(this.getTarget(event.target, 'listitem'));
- this.dropInfo = {
- operation: 'swap',
- initialTarget: $target,
- slotId: $target.attr('data-slot-id'),
- mappingId: $target.attr('data-mapping-id')
- };
- var menuOverflowIcon = this.iconsFeature.getIcon('overflowMenuHorizontal32');
- var disableIcon = this.iconsFeature.getIcon('common-nodrop');
- var $slotInfo = $target.find('.slotInfo').clone(false, false);
- var $menu = $('<div class="menuoverflow"><svg class="svgIcon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#' + menuOverflowIcon.id + '"></use></svg></div>');
- var $unvalidAction = $('<div class="unvalid hidden"><svg class="svgIcon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#' + disableIcon.id + '"></use></svg></div>');
- var $avatar = $('<div></div>', {
- 'class': 'listitem columnName avatarLive'
- });
- $avatar.append($unvalidAction).append($slotInfo).append($menu);
- this._dndManager.startDrag({
- event: event,
- type: 'slot',
- data: this.dropInfo,
- avatar: $avatar,
- callerCallbacks: {
- onDragDone: this.onDragStop.bind(this, $target),
- onMove: this.onMove.bind(this)
- },
- moveXThreshold: 20,
- moveYThreshold: 20
- });
- $target.addClass('isDragged');
- return true;
- },
- onDragStop: function onDragStop($target, event, payload) {
- this.startDrag = false;
- delete this.initialDragAndDropPosition;
- payload = payload || {};
- if (this.isOutsideFocusMode && payload.isDropped === false) {
- this.isOutsideFocusMode = false;
- var mapIndex = $target.attr('map-index');
- var slotId = $target.attr('data-slot-id');
- var slot = this.visualization.getSlots().getSlot(slotId);
- this.interactivityController.getActionHelper().getActionsForSlots([slot], mapIndex, $target[0]).then(function (actions) {
- for (var index = 0; index < actions.length; index++) {
- if (actions[index].spec && actions[index].spec.name == 'delete' && actions[index].spec.actions && _.isFunction(actions[index].spec.actions.apply)) {
- actions[index].spec.actions.apply();
- break;
- }
- }
- });
- return true;
- } else {
- return payload.isDropped;
- }
- },
- /**
- * Callback to handle the move event
- *
- * @param {object} event - the event object
- * @param {object} payload - the payload to process the event object
- */
- onMove: function onMove(event, payload) {
- payload = payload || {};
- var dragObject = payload.dragObject || {};
- this.isOutsideFocusMode = this._isOutsideFocusModeBoundary(dragObject);
- var $unvalid = $(dragObject.avatar).children('.unvalid');
- if (this.isOutsideFocusMode) {
- $unvalid.addClass('hidden');
- } else {
- var $target = $(payload.dropTargetNode);
- if ($target.hasClass('listitem') || $target.hasClass('dropafter') || $target.hasClass('slot') || $target.hasClass('localFilter')) {
- this.onDragEnter(dragObject, payload.dropTargetNode);
- } else {
- $unvalid.removeClass('hidden');
- }
- }
- },
- itemNotDragged: function itemNotDragged(event) {
- var $target = $(this.getTarget(event.target, 'listitem'));
- $target.removeClass('isDragged');
- },
- /**
- * Callback to handle the drag enter event
- *
- * @param {object} dragObject - the drag object context
- * @param {object} dropNode - the node potentially being dropped
- */
- onDragEnter: function onDragEnter(dragObject, dropNode) {
- dragObject.droppable = !this._isDropNodeInvalid(dragObject, dropNode);
- if (dragObject.droppable) {
- $(dropNode).addClass('draggedOn');
- if ($(dropNode).is('.columnItem')) {
- $(dropNode).find('.dropfirst').addClass('draggedOn');
- }
- $(dragObject.avatar).children('.unvalid').addClass('hidden');
- } else {
- $(dragObject.avatar).children('.unvalid').removeClass('hidden');
- }
- },
- /**
- * Handler when a data item is dragged on a data slot, to replace the previous slot.
- * @param dragObject - the drag and drop payload from metadata tree or data strip.
- * @param dropNode - the target of the drop.
- */
- onDrop: function onDrop(dragObject, dropNode) {
- if (!dragObject.droppable) {
- return; //Do nothing because, the dnd case is prohibited.
- }
- var transactionToken = this.transactionApi.startTransaction();
- $(dropNode).removeClass('draggedOn');
- this._setDataSource(dragObject, transactionToken);
- // The viz might not support the datasource so it might not be set
- var isDataSourceSet = !!this.visualization.getDataSource();
- if (isDataSourceSet) {
- if (dragObject.type === 'slot') {
- this._swapSlotMapping(dragObject, dropNode, transactionToken);
- } else {
- this._addSlotMapping(dragObject, dropNode, transactionToken);
- }
- }
- this.transactionApi.endTransaction(transactionToken);
- },
- _setDataSource: function _setDataSource(dragObject, transactionToken) {
- var sourceId = dragObject.data.sourceId;
- if (sourceId) {
- this.visualization.setDataSource(sourceId, transactionToken);
- }
- },
- _addSlotMapping: function _addSlotMapping(dragObject, dropNode, transactionToken) {
- var droppedColumns = this._getDropMetadataColumns(dragObject);
- var metadataColumns = _.map(droppedColumns, function (column) {
- return column.metadataColumn;
- });
- this.widget.addModelFilters(metadataColumns, transactionToken);
- var slotId = dropNode.getAttribute('data-slot-id');
- var options = {
- position: parseInt($(dropNode).attr('map-index'), 10),
- bReplace: $(dropNode).is('.listitem')
- };
- this.visMapColumnsToSlot.mapColumns(slotId, droppedColumns, options, transactionToken);
- },
- /*Data slot view that has unavailable columns*/
- _hasDataUnavailable: function _hasDataUnavailable($dropNode) {
- return $dropNode.is('.noMapping .unavailable');
- },
- /**
- * Handler when a data item is dropped in the Context Filter section. It adds it as Context Filter to the Data widget.
- * The whole section is the drop zone, not each filter slot. If a data item is already a filter, it will bring up the UI to update it.
- */
- onDropContext: function onDropContext(dragObject) {
- var transactionToken = this.transactionApi.startTransaction();
- this._setDataSource(dragObject, transactionToken);
- // The viz might not support the datasource so it might not be set
- var isDataSourceSet = !!this.visualization.getDataSource();
- if (isDataSourceSet) {
- var droppedColumns = this._getMetadataPayloadColumns(dragObject);
- if (droppedColumns) {
- var metadataColumns = _.map(droppedColumns, function (column) {
- return column.metadataColumn;
- });
- if (metadataColumns) {
- metadataColumns = this.widget.addModelFilters(metadataColumns, transactionToken);
- if (metadataColumns.length > 0) {
- var filterColumnId = metadataColumns[0].getId();
- // don't process filter on the dataItem for a ordinal only view (ie: summary widget)
- var dataSlot = this.slotsApi.getSlot(filterColumnId);
- if (!dataSlot || dataSlot.getDefinition().getType() !== 'ordinal' || this._definitionHasCategory()) {
- var columnsWithMembers = dragObject.data.utils.getColumnsWithMembers(true /* filterMemberColumns */);
- var columnIds = Object.keys(columnsWithMembers);
- if (columnIds.length) {
- this._addLocalFiltersWithMembers(dragObject, transactionToken);
- } else {
- this._setContextFilter(metadataColumns[0], this.$('ul.fields .localFilter')[0], false, transactionToken);
- }
- }
- }
- }
- }
- }
- this.transactionApi.endTransaction(transactionToken);
- },
- _definitionHasCategory: function _definitionHasCategory() {
- var definition = this.visualization.getDefinition();
- if (definition) {
- var dataSlots = definition.getSlotList();
- for (var i = 0; i < dataSlots.length; i++) {
- if (dataSlots[i].getType() === 'category' || dataSlots[i].getType() === 'any') {
- return true;
- }
- }
- }
- return false;
- },
- _addLocalFiltersWithMembers: function _addLocalFiltersWithMembers(dragObject, transactionToken) {
- var columns = this._getDropMetadataColumns(dragObject);
- this.content.getFeature('VisDnD.utils').addMembersAsLocalFilters({ visualizationAPI: this.visualization, columns: columns }, transactionToken);
- },
- /**
- * Handler for the delete key on filter. In this case, we only want the Delete and Backspace key to work.
- */
- onDeleteKeyContextFilter: function onDeleteKeyContextFilter(e) {
- this.onRemoveContextFilter(e);
- },
- onEnterSlot: function onEnterSlot(e) {
- if (e.keyCode === 32 || e.keyCode === 13) {
- this.popupSlotOptions(e);
- }
- },
- onEnterLocalFilters: function onEnterLocalFilters(e) {
- if (e.keyCode === 32 || e.keyCode === 13) {
- this.popupFilterOptions(e);
- }
- },
- onEnterLayer: function onEnterLayer(e) {
- if (e.keyCode === 32 || e.keyCode === 13) {
- this.expandCollapseLayers(e);
- }
- },
- /**
- * Finds the proper slot data item element, verifies if the proper keys were pressed to delete.
- */
- _getTargetSlotDataItem: function _getTargetSlotDataItem(e) {
- return $(this.getTarget(e.target, 'listitem'));
- },
- /**
- * Handler for the delete button in a Context Filter, visualization will be updated.
- */
- onRemoveContextFilter: function onRemoveContextFilter(e) {
- e.preventDefault();
- e.stopPropagation();
- this._clearToolbar();
- var $slotDataItem = this._getTargetSlotDataItem(e);
- if ($slotDataItem) {
- var filterId = $slotDataItem.attr('data-filter-id');
- if (filterId) {
- var visAPI = this._getVisAPI();
- // we do not need to update the DOM as the slots re-render after filters modification completes
- visAPI.localFilters.removeFilterEntry({
- id: filterId
- });
- visAPI.localFilters.allFilterModificationComplete();
- }
- }
- },
- /**
- * no-op. We don't use selection for the slots. Needs to implement it because we use BaseListView.
- */
- onSelectItem: function onSelectItem() {
- return false;
- },
- _onChangeEvent: function _onChangeEvent() {
- this.slotsViewContentModified = true;
- this.render();
- },
- _onToolBarRemove: function _onToolBarRemove() /*options*/{
- //Handle after the toolbar is removed
- //This case occurs when the user click once to open the toolbar and click again to close the toolbar
- //Since reRender is begin called by 'toolbar:hide' and 'toolbar:remove' have an options parameter
- //Having this onToolBarRemove function avoid sending the wrong parameter value to the reRender calls
- this._reRender();
- },
- _reRender: function _reRender() {
- if (this.isImplicitClosingTheBoolbar) {
- this._clearToolbar();
- }
- if (this.slotsViewContentModified) {
- this.render();
- this.slotsViewContentModified = false;
- }
- },
- /**
- * Renders the UI based on a Dot template. It creates two lists, one for data slots and one for context filters.
- */
- render: function render() {
- var _this4 = this;
- // If dataslotsview is being torn down (destroy function will remove the dotTemplate).
- if (!this.dotTemplate) {
- return;
- }
- if (this._actionMenuToolbar) {
- //The actionMenu is showing. For simple actions like drill, it is simply closed
- //For actions that have parameters, the actionMenu is a 'subview' (eg sort, navigate, format etc.)
- //For subviews, the ui stays open and slots are only rendered when the subview is explicitly closed (or loses focus).
- if (this._actionMenuToolbar.itemMap && this._actionMenuToolbar.itemMap.subview) {
- return;
- }
- //No need to re-render the view since the tool bar gets delete here
- this.slotsViewContentModified = false;
- this._clearToolbar();
- }
- var hasCategory = this._definitionHasCategory();
- var localFilters = this._getVisAPI().localFilters || {};
- //Gather the info for each slot in an array to be passed to the Dot Template.=
- var slots = this.getSlotsInfo(hasCategory, localFilters);
- var maxWidth = this.$el.width();
- var filters = this.getContextFilters();
- var graphic = this.getGraphicItem();
- if (!this._needToReRender(slots, filters, graphic)) {
- return this;
- }
- // rendering starts here.
- // We will refresh the UI, remove the previous elements that were added as drop targets.
- this._clearDropTargets();
- this._updateMissingFilters();
- //maintain the scrollposition of the slots before rerendering
- var slotsViewScrollPosition = this.$el.find('.list.fields.slots').scrollTop();
- // Generate the html from the template using the slots and filters info collected above.
- //Passing in underscore as an option.
- var definition = this._getVisAPI().getDefinition();
- var chevronRightIcon = this.iconsFeature.getIcon('common-chevron_right');
- var menuOverflowIcon = this.iconsFeature.getIcon('overflowMenuHorizontal32');
- var errorIcon = this.iconsFeature.getIcon('common-warning');
- var closeIcon = this.iconsFeature.getIcon('common-close_16');
- var filterIcon = this.iconsFeature.getIcon('common-filter');
- var informationIcon = this.iconsFeature.getIcon('getInformation');
- var sHtml = this.dotTemplate({
- headerLabel: definition ? definition.caption : stringResources.get('evColumns'),
- isFilterable: hasCategory,
- slots: slots,
- datasets: definition ? this._getDatasets(definition.datasets) : null,
- visibleLayers: this._getVisibleLayers(),
- //todo api to get the dataslots
- groupedSlotsByDataset: this._getGroupedSlotsByDataset(slots),
- maxWidth: maxWidth + 'px',
- filterStringMaxWidth: maxWidth * 0.7 + 'px',
- ContextFiltersLabel: stringResources.get('evLocalFilters'),
- filterLabel: stringResources.get('evFilterTooltip'),
- filters: filters,
- missingFilters: this.missingFilters || [],
- missingFiltersLabel: stringResources.get('missingFiltering'),
- isMappingComplete: this.isMappingComplete(),
- graphic: graphic,
- infographicShapeLabel: stringResources.get('toolbarActionToggleShapeDrop'),
- requiredFieldsDescription: stringResources.get('LIVE_slots_required_field_description', {
- asterisk: '<span class="asterisk">*</span>'
- }),
- dragAndDropDescription: stringResources.get('LIVE_slots_drag_and_drop_description'),
- expandLabel: stringResources.get('evExpand'),
- collapseLabel: stringResources.get('evCollapse'),
- underscore: _,
- chevronRightIcon: chevronRightIcon.id,
- menuOverflowIcon: menuOverflowIcon.id,
- errorIcon: errorIcon.id,
- closeIcon: closeIcon.id,
- filterIcon: filterIcon.id,
- informationIcon: informationIcon.id
- });
- this._detachEvents();
- this.$el.empty().append(sHtml);
- this.$el.find('.list.fields.slots').scrollTop(slotsViewScrollPosition);
- this._hideText();
- this.setElement(this.$el);
- //Add the 4 drop targets
- this._addDropTargets();
- var $widgetExpanded = this.$el.closest('.widgetExpanded');
- if ($widgetExpanded.length > 0) {
- this.focusModeBoundingClientRect = $widgetExpanded[0].getBoundingClientRect();
- }
- this.updateShapeSlot();
- // attempt coach marks to all slots
- setTimeout(function () {
- _.each(_this4.slotsApi.getSlotList(), function (slot) {
- _this4._applySlotCoachMarks(slot);
- });
- }, 500);
- return this;
- },
- /**
- * Apply coach marks on the slot or data item
- * The slot coach mark consists with:
- * - showPopover: boolean flag indicating whether the popover show along with the coach mark.
- * - mappedOnly: boolean flag indicating whether the coach mark applies when the slot is mapped or
- * regardless of the mapped state.
- * - titleResource: resource name for the coach title.
- * - contentResource: resource name for the coach content.
- * @param slot {Object} - SlotAPI
- * @return {Promise}
- */
- _applySlotCoachMarks: function _applySlotCoachMarks(slot) {
- var slotDef = slot.getDefinition();
- var coachMark = slotDef.getCoachMark();
- if (coachMark) {
- var isMapped = slot.getDataItemList().length > 0;
- if (!coachMark.mappedOnly || isMapped) {
- var slotId = slot.getId();
- var selector = (isMapped ? '.listitem.columnName' : '.slot') + '[data-slot-id=' + slotId + ']';
- // Make the coachmark unique for each slot of every visualization
- var options = {
- id: this.visualization.getDefinition().getId() + '#' + slotId,
- $el: this.$el.find(selector),
- title: stringResources.get(coachMark.titleResource),
- contents: stringResources.get(coachMark.contentResource),
- showPopover: coachMark.showPopover
- };
- this.dashboardApi.prepareGlassOptions(options);
- return Utils.addCoachmark(options);
- }
- }
- return Promise.resolve();
- },
- _getGroupedSlotsByDataset: function _getGroupedSlotsByDataset(slots) {
- return _.groupBy(slots, 'dataset');
- },
- _getVisibleLayers: function _getVisibleLayers() {
- return this._visibleLayers;
- },
- _expandLayer: function _expandLayer(layer) {
- this._visibleLayers.push(layer);
- },
- _collapseLayer: function _collapseLayer(layer) {
- this._visibleLayers = _.without(this._visibleLayers, layer);
- },
- _hideText: function _hideText() {
- var $mappings = $('.columnItem:not(.noMapping)');
- $('.columnLabel').removeClass('hidden');
- _.each($mappings, function (mapItem) {
- var $mapItem = $(mapItem);
- $mapItem.find('.columnLabel').addClass('hidden');
- });
- },
- _changeTabindices: function _changeTabindices() /*a, b*/{
- // DataSlotsView.inherited('_changeTabIndices', this, null);
- return false;
- },
- /**
- * Verifies if the drop node is valid for dropping the item.
- */
- _isDropNodeInvalid: function _isDropNodeInvalid(dragObject, dropNode) {
- var $dropNode = $(dropNode);
- if ($dropNode.hasClass('localFilter')) {
- return false;
- }
- // @todo need to be revisited to use the new API
- // problem identified so far is in SlotAPI.supportsColumns (aka. itemsNotSupported in old SlotAPI)
- // in order to check the support availability the metadata needs to be a loaded which is not always the case
- // in the new SlotAPI
- var slots = this.visualization.getSlots();
- var destinationSlot = slots.getSlot(dropNode.getAttribute('data-slot-id'));
- var slotId = dragObject.data && dragObject.data.slotId;
- var dragObjectSlot = slotId ? slots.getSlot(slotId) : null;
- var dragMetadata = void 0;
- var dragMembers = void 0;
- if (dragObject.data.columns) {
- dragMetadata = _.map(dragObject.data.columns, function (column) {
- return column.metadataColumn;
- });
- dragMembers = dragObject.data.utils.getColumnsWithMembers();
- var source = this.dataSources.getDataSource(dragObject.data.sourceId);
- // TODO livewidget_cleanup .. the dragMetadataObject should retun new metadata objects.
- dragMetadata = dragMetadata.map(function (legacy) {
- return source.getMetadataColumn(legacy.getId());
- });
- } else {
- if (dragObjectSlot) {
- dragMetadata = dragObjectSlot.getDataItemList()[dragObject.data.initialTarget[0].getAttribute('map-index')].getMetadataColumn();
- }
- }
- var itemsNotSupported = !destinationSlot.supportsColumns(dragMetadata || []);
- //Get the dragged object mapping index and slot index specified in the template
- //If it comes from the metadata tree, it will return NaN for both
- var dragIndex = {
- mapIndex: parseInt($(dragObject.data.initialTarget).attr('map-index'), 10),
- slotIndex: parseInt($(dragObject.data.initialTarget).parents('.slot').attr('slot-index'), 10)
- };
- //Same thing but for the drop node
- var dropIndex = {
- mapIndex: parseInt($dropNode.attr('map-index'), 10),
- slotIndex: $dropNode.is('.slot') ? parseInt($dropNode.attr('slot-index'), 10) : parseInt($dropNode.parents('.slot').attr('slot-index'), 10),
- slotId: $dropNode.attr('data-slot-id')
- };
- // In the html the the dropzone before an item has the same index as the following item
- // ---------- index 0
- // [listitem] index 0
- // ---------- index 1
- // [listitem] index 1
- // ---------- index 2
- // [listitem] index 2
- // ---------- index 3
- // The there will always be a trailing dropzone with an (n+1) index value; n being the last item's index value
- //This is to block the case where you drag an item and try to put it before itself or after itself in a same slot.
- var dropInSamePosition =
- // you can't drop the dragged item before itself
- (dragIndex.mapIndex === dropIndex.mapIndex ||
- // you can't drop the dragged item after itself
- dragIndex.mapIndex + 1 === dropIndex.mapIndex) &&
- // if it's in the same slot and the drop node is not an item
- dragIndex.slotIndex === dropIndex.slotIndex && !$dropNode.is('.listitem');
- var targetSlot = this.slotsApi.getSlot(dropIndex.slotId);
- var dropSlotDefinition = targetSlot.getDefinition();
- //For now there is only one case where it's not valid
- var isMultiMeasureAndOrdinal = dropSlotDefinition.isMultiMeasureSupported();
- //Drag member from a column already projected should not be double counted
- var itemLimit = dropSlotDefinition.getMaxItems();
- itemLimit = itemLimit === -1 ? 0 : itemLimit;
- var dragMembersFromProjectedColumnsOnly = this._dragMembersFromProjectedColumnsOnly(targetSlot, dragMetadata, dragMembers);
- var slotNotStackable = !$dropNode.hasClass('listitem') && !isMultiMeasureAndOrdinal && !dropSlotDefinition.isStackItems() && !dragMembersFromProjectedColumnsOnly && parseInt($dropNode.attr('mappedItems'), 10) >= itemLimit;
- // fix defect 231765 (prevent multimeasure group from being dropped on (thus replaced) from the data tree), but swapping is allowed
- var dataItemAPI = destinationSlot.getDataItemList()[dropIndex.mapIndex];
- var isDropOnMultiMeasureDataItem = SlotAPIHelper.isMultiMeasuresSeriesSlot(destinationSlot) && dataItemAPI ? dataItemAPI.getColumnId() === '_multiMeasuresSeries' && !$dropNode.hasClass('dropafter') && Number.isNaN(dragIndex.slotIndex) : false;
- return itemsNotSupported || dropInSamePosition || slotNotStackable || isDropOnMultiMeasureDataItem || !dragMembersFromProjectedColumnsOnly && !isMultiMeasureAndOrdinal && this._isExceedsItemsLimit(dropNode, dragObject, dragMembers) || this._isSwapValueSlotItemsWithMultiMeasures(dropNode, dragObject);
- },
- /**
- * @return true if the new drop object only has members from projected columns
- */
- _dragMembersFromProjectedColumnsOnly: function _dragMembersFromProjectedColumnsOnly(targetSlot, dragColumns, dragMembers) {
- // 1. Return false if dragMembers is empty
- if (!dragMembers || _.isEmpty(dragMembers)) {
- return false;
- }
- // 2. Check if there is a dragColumn that is NOT built from drag and drop members
- var hasNonProjectedColumn = _.some(dragColumns, function (column) {
- return !dragMembers[column.getId()] || !dragMembers[column.getId()].length;
- });
- // 3. Check all dragMembers are from already projected columns of the target slot if the first condition passes
- if (!hasNonProjectedColumn) {
- var projectedColumns = [];
- _.each(targetSlot.getDataItemList(), function (dataItem) {
- var metadataColumn = dataItem.getMetadataColumn();
- if (metadataColumn) {
- projectedColumns.push(metadataColumn.getId());
- }
- });
- hasNonProjectedColumn = _.some(Object.keys(dragMembers), function (columnId) {
- return projectedColumns.indexOf(columnId) === -1;
- });
- }
- return !hasNonProjectedColumn;
- },
- /**
- * A slot can set the maximum number of the items it contains.
- * Particularly, for slot that can accept stackItems, slot definition
- * has a maxStackItems attribute that specifies a maximum number of
- * stackItems (from VIPR). For Vizs such as Xtab, Grid, Summary that does
- * not support stackItems attribute, to avoid confusion, we use maxItems attribute
- * to set the limit. By default, VIPR viz does not support maxItems attribute.
- **/
- _isExceedsItemsLimit: function _isExceedsItemsLimit(dropNode, dragObject, dragMembers) {
- var slotId = $(dropNode).is('.slot') ? $(dropNode).attr('data-slot-id') : $(dropNode).parents('.slot').attr('data-slot-id');
- if (!slotId) {
- return false;
- }
- var targetSlot = this.slotsApi.getSlot(slotId);
- var slotDef = targetSlot.getDefinition();
- var limit = slotDef.getMaxItems() || -1;
- if (limit < 0) {
- return false;
- }
- var nCountOfItemsToDrop = 1;
- if (dragObject.data.items) {
- if (!dragMembers || _.isEmpty(dragMembers)) {
- nCountOfItemsToDrop = dragObject.data.items.length;
- } else {
- // Do not double count member columns that already exist.
- nCountOfItemsToDrop = 0;
- var memberParentIds = Object.keys(dragMembers);
- var mappedItemIds = this._getMappedItemsOfTargetSlot(targetSlot);
- var diffs = memberParentIds.filter(function (id) {
- return mappedItemIds.indexOf(id) === -1;
- });
- nCountOfItemsToDrop = diffs.length;
- }
- }
- var nMappedItems = targetSlot.getDataItemList().length;
- if ($(dropNode).hasClass('listitem') && nCountOfItemsToDrop === 1) {
- return false;
- }
- return nCountOfItemsToDrop + nMappedItems > limit;
- },
- _getMappedItemsOfTargetSlot: function _getMappedItemsOfTargetSlot(slot) {
- var mappedItems = [];
- slot.getDataItemList().forEach(function (dataItem) {
- if (dataItem.getMetadataColumn) {
- mappedItems.push(dataItem.getMetadataColumn().getId());
- }
- });
- return mappedItems;
- },
- _isSwapValueSlotItemsWithMultiMeasures: function _isSwapValueSlotItemsWithMultiMeasures(dropNode, dragObject) {
- var dragObjectMappingId = dragObject && dragObject.data && dragObject.data.mappingId ? dragObject.data.mappingId : null;
- var dropNodeSlotId = $(dropNode).is('.slot') ? $(dropNode).attr('data-slot-id') : $(dropNode).parents('.slot').attr('data-slot-id');
- var isDropNodeMultiMeasuresSeries = $(dropNode).hasClass('listitem') && $(dropNode)[0].dataset && $(dropNode)[0].dataset.mappingId === '_multiMeasuresSeries';
- var dragObjectSlotId = dragObject && dragObject.data ? dragObject.data.slotId : null;
- var dropSlotAPI = dropNodeSlotId && this.slotsApi.getSlot(dropNodeSlotId).getDefinition();
- var dragSlotAPI = dragObjectSlotId && this.slotsApi.getSlot(dragObjectSlotId).getDefinition();
- // can not drag a multimeasure to a measure slot or replace a multmeasure dataItem (swapping is allowed)
- return dragObjectMappingId === '_multiMeasuresSeries' && dropSlotAPI.getType() === 'ordinal' || (!dragSlotAPI || dragSlotAPI.getType() === 'ordinal') && isDropNodeMultiMeasuresSeries;
- },
- _addDropTargets: function _addDropTargets() {
- var _this5 = this;
- // Add DnD handlers
- var fAccept = this.accepts.bind(this);
- var fAcceptContext = this.acceptsContext.bind(this);
- var fOnDrop = this.onDrop.bind(this);
- var fOnDropContext = this.onDropContext.bind(this);
- var fOnDragEnter = this.onDragEnter.bind(this);
- var dndManager = this._dndManager;
- var onDragLeave = function onDragLeave(dragObject, dropNode) {
- var $dropNode = $(dropNode);
- $dropNode.removeClass('draggedOn');
- $dropNode.find('.dropfirst').removeClass('draggedOn');
- };
- //replace/swap
- this.$('ul.fields .listitem').map(function (index, el) {
- dndManager.addDropTarget(el, {
- accepts: fAccept,
- onDragEnter: fOnDragEnter,
- onDragLeave: onDragLeave,
- onDrop: fOnDrop,
- priority: function priority() {
- var $widget = _this5.$el.closest('.liveWidget');
- return $widget.hasClass('widgetExpanded') ? 1 : 0;
- }
- });
- });
- //Add after
- this.$('ul.fields .dropafter').map(function (index, el) {
- dndManager.addDropTarget(el, {
- accepts: fAccept,
- onDragEnter: fOnDragEnter,
- onDragLeave: onDragLeave,
- onDrop: fOnDrop,
- priority: function priority() {
- var $widget = _this5.$el.closest('.liveWidget');
- return $widget.hasClass('widgetExpanded') ? 1 : 0;
- }
- });
- });
- //add first
- this.$('ul.fields .slot').map(function (index, el) {
- dndManager.addDropTarget(el, {
- accepts: fAccept,
- onDragEnter: fOnDragEnter,
- onDragLeave: onDragLeave,
- onDrop: fOnDrop,
- priority: function priority() {
- var $widget = _this5.$el.closest('.liveWidget');
- return $widget.hasClass('widgetExpanded') ? 1 : 0;
- }
- });
- });
- //Filter stuff
- this.$('ul.fields .localFilter').map(function (index, el) {
- dndManager.addDropTarget(el, {
- accepts: fAcceptContext,
- onDragEnter: fOnDragEnter,
- onDragLeave: onDragLeave,
- onDrop: fOnDropContext,
- priority: 2 //When filter container and slots overlaps (slots list has scrollbars),
- //filter container takes priority to accept the dropped in items
- });
- });
- },
- _needToReRender: function _needToReRender(slots, filters, graphic) {
- // Optimization - Build a key based on what is shown as data slots.
- var sRenderingKey = this._buildRenderingKey(slots, filters, graphic);
- if (this._lastRender !== sRenderingKey) {
- return true;
- }
- this._lastRender = sRenderingKey;
- return false;
- },
- /**
- * Returns a boolean to indicate whether the dragObject position is outside focus mode UI
- *
- * @param {object} dragObject - the drag object object context
- *
- * @return {boolean} true if the drag position is outside of focus mode UI else return false
- */
- _isOutsideFocusModeBoundary: function _isOutsideFocusModeBoundary(dragObject) {
- dragObject = dragObject || {};
- if (dragObject.position) {
- var position = dragObject.position;
- if (position.x < this.focusModeBoundingClientRect.left || position.x > this.focusModeBoundingClientRect.right || position.y < this.focusModeBoundingClientRect.top || position.y > this.focusModeBoundingClientRect.bottom) {
- return true;
- }
- }
- return false;
- },
- _buildRenderingKey: function _buildRenderingKey(slots, filters, graphic) {
- var visId = this._getVisAPI().getDefinition().id;
- // We use that key to prevent rendering the same UI multiple times because Rave can fire many rendering events.
- var sRenderingKey = _.reduce(slots, function (key, s) {
- s._unavailable = s.unavailable ? 1 : 0;
- var slotsKey = toStringKey(key, s, ['id', 'icon', 'filterString', '_unavailable']);
- return _.reduce(s.mappings, function (key, m) {
- return toStringKey(key, m, ['columnId', 'mappingId']);
- }, slotsKey);
- }, visId);
- sRenderingKey = _.reduce(filters, function (key, f) {
- return toStringKey(key, f, ['id', 'filterString']);
- }, sRenderingKey);
- sRenderingKey = _.reduce(graphic, function (key, v) {
- return toStringKey(key, v, ['content', 'fillColor', 'borderColor', 'currentScaleOption']);
- }, sRenderingKey);
- return sRenderingKey;
- },
- /**
- * Brings up the UI to create a new context-type filter (ie: filter on a non-visible item).
- * @param filterColumnId - the columnId of the filter to edit.
- * @param node - the node to guide placement of the dialog.
- * @param forceOpen - true if force open the filter dialog.
- */
- _setContextFilter: function _setContextFilter(metadataColumn, node, forceOpen, transactionToken) {
- DynamicFileLoader.load(['dashboard-analytics/visualizations/interactions/FilterDropAction']).then(function (modules) {
- var FilterDropAction = modules[0];
- var fAction = this.interactivityController.getActionHelper().getNewFilterActionForContextColumn(FilterDropAction, metadataColumn);
- if (!forceOpen && metadataColumn.isNamedSet()) {
- var options = this._convertTransactionTokenToLegacyOptions(transactionToken);
- this.localFilters.addFilter(fAction.itemContext, {
- command: 'replace',
- exclude: false,
- valueDataItem: metadataColumn.getId(),
- type: metadataColumn.isProperty() ? 'display' : undefined
- });
- this.localFilters.allFilterModificationComplete(options);
- } else {
- var sDialogModule = fAction.getEditorModuleName();
- var viewOptions = fAction.getViewOptions();
- DynamicFileLoader.load([sDialogModule]).then(function (modules) {
- var view = new modules[0](viewOptions);
- var preloadDone = view.preload ? view.preload() : Promise.resolve();
- preloadDone.then(this._createContextFilter.bind(this, view, node)).then(function () {
- view.renderCallBack(this._actionMenuToolbar);
- }.bind(this));
- }.bind(this));
- }
- }.bind(this));
- return;
- },
- _createContextFilter: function _createContextFilter(view, element) {
- var actions = [{
- responsive: false,
- editable: false,
- changedAction: null,
- subView: view,
- type: 'SubView'
- }];
- //If it's from a dropzone open it at the label level
- var node = $(element).find('.dropfirst');
- if (node.length === 0) {
- //If it's a click on a existing dataItem
- node = $(element).find('.mappingLabel');
- }
- var toolbarOptions = {
- textOnly: true,
- container: $('body'),
- notCentered: true,
- popoverClass: 'popover actionToolbarPopover text'
- };
- this._setToolbarForSlot(actions, node, stringResources.get('toolbarActionFilter'), toolbarOptions);
- },
- _setToolbarForSlot: function _setToolbarForSlot(aActions, node, sLabel, toolbarOptions) {
- var authToolbarOptions = _.extend({}, toolbarOptions, {
- dataSlotsView: this
- });
- var toolbar = new DataSlotsViewAuthoringToolbar(authToolbarOptions);
- toolbar.setName(sLabel);
- toolbar.addItems(aActions);
- toolbar.setSelectionContext([node]);
- toolbar.show();
- node.addClass('selected');
- var target = node[0];
- toolbar.on('toolbar:remove', this._onToolBarRemove.bind(this));
- //Call set focus here to avoid flashing the selected node
- toolbar.on('toolbar:show:before', this.setAddFocus.bind(this, { target: target }));
- this._actionMenuToolbar = toolbar;
- // Update the actions with the toolbar in case they need to exit during certain steps.
- aActions.forEach(function (action) {
- if (action && action.setToolbar) {
- action.setToolbar(toolbar);
- }
- });
- },
- /**
- * Brings up the UI to create a new filter for a visible slot.
- * @param slot - the data slot to filter (ie: 'category' or 'xAxis')
- * @param node - the node to guide placement of the dialog.
- */
- _setSlotMenuActions: function _setSlotMenuActions(slot, node) {
- // WACA: TODO filters
- // _setSlotFilter: function(slot, node) {
- if (!slot) {
- return;
- }
- var mapIndex = parseInt($(node).attr('map-index'));
- return this.interactivityController.getActionHelper().getActionsForSlots([slot], mapIndex, node).then(function (actions) {
- var aToolbarActions = [];
- _.each(actions, function (action) {
- aToolbarActions = aToolbarActions.concat(action.getAvailableActions());
- });
- aToolbarActions.forEach(function (action) {
- if (action.order === undefined) {
- action.order = ActionTypes[action.name];
- }
- });
- aToolbarActions.sort(function (a, b) {
- return a.order - b.order;
- });
- node = $(node).find('.menuoverflow');
- var toolbarOptions = {
- textOnly: true,
- container: $('body'),
- placement: 'right',
- notCentered: true,
- popoverClass: 'popover actionToolbarPopover text',
- modal: true
- };
- this._setToolbarForSlot(aToolbarActions, node, null, toolbarOptions);
- }.bind(this));
- },
- /**
- * Handler when user clicks on a filter icon to edit a filter for either a visible slot or context entry.
- * If this button represents a visible slot, a filter will be added on the slot.
- * If not, a context filter will be added on the column.
- * Normally, these two are the same but can differ when the user has used an attribute column
- * in an ordinal slot (where the filter to be added would be a count type filter).
- */
- popupSlotOptions: function popupSlotOptions(e) {
- if (this._canLaunchAuthoringToolbar(e)) {
- var $slot = $(this.getTarget(e.target, 'listitem'));
- var slotId = $slot.attr('data-slot-id');
- this.launchedToolbarNodeId = $slot.attr('data-mapping-id');
- return this._setSlotMenuActions(this.visualization.getSlots().getSlot(slotId), $slot[0]);
- } else {
- this._clearToolbar();
- }
- },
- expandCollapseLayers: function expandCollapseLayers(e) {
- var $layer = $('div.completeLayer.' + e.target.id.replace('.', '\\.'));
- var $buttonDiv = $layer.find('.layerExpanderButton');
- var $svg = $buttonDiv.find('svg');
- var label;
- if ($layer.hasClass('expanded')) {
- $layer.removeClass('expanded');
- $layer.addClass('collapsed');
- $layer.attr('aria-expanded', 'false');
- this._collapseLayer(e.target.id);
- label = stringResources.get('evExpand');
- } else if ($layer.hasClass('collapsed')) {
- $layer.removeClass('collapsed');
- $layer.addClass('expanded');
- $layer.attr('aria-expanded', 'true');
- this._expandLayer(e.target.id);
- label = stringResources.get('evCollapse');
- }
- $buttonDiv.attr('title', label);
- $svg.attr('title', label);
- $svg.attr('aria-label', label);
- },
- openContextFilter: function openContextFilter(e) {
- var $filter = $(this.getTarget(e.target, 'listitem'));
- var uniqueId = $filter.attr('data-mapping-id');
- var columnId = $filter.attr('data-column-id');
- var filterId = $filter.attr('data-filter-id'); //Note: data-filter-id is the uniqueId of the FilterEntry.
- var model = this._getVisAPI();
- if (filterId) {
- var filterEntry = model.localFilters.getFilterEntry({
- id: filterId
- });
- if (filterEntry && !model.isFilterEditable(filterEntry)) {
- //Don't allow click-edit to edit an 'extra filter' (ie: an additional filter created by keep/exclude)
- return;
- }
- }
- var metadataColumn = model.getMetadataColumn(columnId);
- metadataColumn.uniqueId = uniqueId;
- this._setContextFilter(metadataColumn, $filter, true);
- },
- popupFilterOptions: function popupFilterOptions(e) {
- if (!this._canLaunchAuthoringToolbar(e)) {
- this._clearToolbar();
- return;
- }
- var $filter = $(this.getTarget(e.target, 'listitem'));
- var columnId = $filter.attr('data-column-id');
- var model = this._getVisAPI();
- var metadataColumn = model.getMetadataColumn(columnId);
- this.launchedToolbarNodeId = $filter.attr('data-filter-id'); //Note: data-filter-id is the uniqueId of the FilterEntry.
- if (metadataColumn) {
- this.interactivityController.getActionHelper().getActionsForLocalFilter(metadataColumn, this.launchedToolbarNodeId).then(function (actions) {
- var aToolbarActions = [];
- _.each(actions, function (action) {
- aToolbarActions = aToolbarActions.concat(action.getAvailableActions());
- });
- aToolbarActions.sort(function (a, b) {
- return a.order - b.order;
- });
- var node = $filter.find('.menuoverflow');
- var toolbarOptions = {
- textOnly: true,
- container: $('body'),
- placement: 'bottom',
- notCentered: true,
- popoverClass: 'popover actionToolbarPopover text'
- };
- this._setToolbarForSlot(aToolbarActions, node, null, toolbarOptions);
- }.bind(this));
- return;
- }
- },
- /**
- * Handler when user clicks on local filter box to set focus.
- */
- setAddFocus: function setAddFocus(e) {
- var focusedItem = this.$('.addFocus');
- focusedItem.removeClass('addFocus');
- var $target = $(e.target);
- $target.addClass('addFocus');
- },
- /**
- * Returns true if data slot has the addFocus class
- * addFocus class indicates slot where user wants model data inserted
- */
- isSlotSelected: function isSlotSelected() {
- var resp = false;
- var addFocusSlot = this.$el.find('.addFocus');
- if (addFocusSlot.length > 0) {
- resp = true;
- }
- return resp;
- },
- /**
- * Returns true if data slot that has the addFocus class also has class filters
- * this combination indicated the slot is the Local Filters slot
- */
- isSelectedSlotLocalFilters: function isSelectedSlotLocalFilters() {
- var resp = false;
- var addFocusSlot = this.$el.find('.addFocus');
- if (addFocusSlot.length > 0 && addFocusSlot.hasClass('filters')) {
- resp = true;
- }
- return resp;
- },
- /**
- * on scrolling the panel, close the Action menu if it is open, thus preventing it from dangling
- *
- */
- onScrollSlotsPanel: function onScrollSlotsPanel() {
- this._clearToolbar();
- },
- _canLaunchAuthoringToolbar: function _canLaunchAuthoringToolbar(event) {
- var $listItem = $(this.getTarget(event.target, 'listitem.columnName'));
- var id = $listItem.attr('data-mapping-id') || $listItem.attr('data-filter-id');
- var hasToolBar = this.launchedToolbarNodeId === id;
- return hasToolBar ? false : true;
- },
- /**
- * Return a boolean indicating whether is dragging or not
- *
- * @param {event} - JQuery event object
- *
- * @return {boolen} true if is dragging else return false
- */
- _beginDragAndDrop: function _beginDragAndDrop(event) {
- if (!this.initialDragAndDropPosition) {
- return false;
- }
- var currentPosition = DomUtils.getEventPos(event);
- return Math.abs(this.initialDragAndDropPosition.pageX - currentPosition.pageX) >= DND_X_THRESHOLD || Math.abs(this.initialDragAndDropPosition.pageY - currentPosition.pageY) >= DND_Y_THRESHOLD;
- },
- _setIsImplicitClosingTheToolbar: function _setIsImplicitClosingTheToolbar(value) {
- this.isImplicitClosingTheBoolbar = value;
- },
- _updateMissingFilters: function _updateMissingFilters() {
- var _this6 = this;
- this.widget.getUnavailableLocalFilter();
- if (this.widget.visModelManager.filterSupport.missingFilters.length === 0) {
- this.missingFilters.length = 0;
- } else {
- this.widget.visModelManager.filterSupport.missingFilters.forEach(function (filterFromWidget) {
- //prevent duplicate
- var isMissingExist = _.find(_this6.missingFilters, function (filterColumnId) {
- return filterFromWidget.columnId === filterColumnId;
- });
- if (!isMissingExist) {
- _this6.missingFilters.push(filterFromWidget.columnId);
- }
- });
- }
- },
- _isSharingSlots: function _isSharingSlots() {
- var slotList = this.slotsApi.getSlotList();
- if (slotList && slotList.length > 0) {
- var sharingSlots = this.slotsApi.getSlotList().filter(function (slotApi) {
- return slotApi.getDefinition().getDatasetIdList().length > 1;
- });
- return sharingSlots.length > 0;
- }
- return false;
- },
- _getDatasets: function _getDatasets(datasets) {
- // This is used for UI only not the acutal dataset
- return !this._isSharingSlots() && datasets.length > 0 ? datasets : [{ name: 'data' }];
- }
- });
- return DataSlotsView;
- });
- //# sourceMappingURL=DataSlotsView.js.map
|