DataSlotsView.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776
  1. 'use strict';
  2. /*
  3. *+------------------------------------------------------------------------+
  4. *| Licensed Materials - Property of IBM
  5. *| IBM Cognos Products: Dashboard
  6. *| (C) Copyright IBM Corp. 2014, 2020
  7. *|
  8. *| US Government Users Restricted Rights - Use, duplication or disclosure
  9. *| restricted by GSA ADP Schedule Contract with IBM Corp.
  10. *+------------------------------------------------------------------------+
  11. */
  12. 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) {
  13. //These threshold values are taken from Watson Analytics Production Drag and Drop Implementation
  14. var DND_X_THRESHOLD = 10; //10 pixels movement for drag and drop data item between slots
  15. var DND_Y_THRESHOLD = 10; //10 pixels movement for drag and drop data item between slots
  16. /**
  17. * Function to convert an object to a representational string
  18. * @param {string} key - the key or id associated with this object
  19. * @param {object} o - the object to stringize
  20. * @param {[string|object]} filter - an array indicating elements to include in stringized version:
  21. * each element should be an object with members `key`, representing the key value in the object of the element to include,
  22. * and `type`, representing the type and therefore how to stringize the member, if no type is specified, string is assumed.
  23. * array elements may also be strings, which are the same as a `key` value in an object, and implicitly of type string.
  24. */
  25. function toStringKey(key, o, filter) {
  26. return [key, '_'].concat(_.map(filter, function (attr) {
  27. return o[attr];
  28. })).join('_');
  29. }
  30. function hasDimensions(models) {
  31. if (models.length > 0) {
  32. for (var index = 0; index < models.length; index++) {
  33. if ('attribute' === models[index].getType()) {
  34. return true;
  35. }
  36. }
  37. }
  38. return false;
  39. }
  40. /**
  41. * This class shows the data slots and context filters used in a data widget. It is used in the Data Widget Focus view.
  42. * It inherits BaseListView which handles a11y for lists.
  43. */
  44. var DataSlotsView = BaseListView.extend([FilterLabel], {
  45. templateString: template,
  46. events: {
  47. 'click ul.fields div.layerTitle div.layerExpanderButton': 'expandCollapseLayers',
  48. 'click ul.fields .slot.columnItem .listitem .menuoverflow': 'popupSlotOptions',
  49. 'click ul.fields .localFilter.columnItem .listitem:not(.unavailable) .menuoverflow': 'popupFilterOptions',
  50. 'click ul.fields .localFilter.columnItem .listitem:not(.unavailable) .slotInfo': 'openContextFilter',
  51. 'primaryaction ul.fields .localFilter': 'setAddFocus',
  52. 'primaryaction div.infographicZone': 'setAddFocus',
  53. '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
  54. 'mousemove ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
  55. 'mouseup ul.fields .slot .listitem:not(.unavailable)': 'onMouseUp', //To clear the drag and drop states
  56. '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
  57. 'touchmove ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
  58. 'touchend ul.fields .slot .listitem:not(.unavailable)': 'onMouseUp', //To clear the drag and drop states
  59. 'dragup ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
  60. 'dragdown ul.fields .slot .listitem:not(.unavailable)': 'onDragStart',
  61. 'keyup ul.fields .slot .listitem:not(.unavailable)': 'onEnterSlot',
  62. 'keyup ul.fields .localFilter .listitem:not(.unavailable)': 'onEnterLocalFilters',
  63. 'keydown ul.fields .slot .listitem, .dropfirst, .dropafter': 'onKeyDown',
  64. 'keyup ul.fields div.layerTitle div.layerExpanderButton': 'onEnterLayer',
  65. 'deleteaction ul.fields .localFilter .listitem:not(.missingFilter)': 'onDeleteKeyContextFilter',
  66. 'wheel ul.list.fields.slots': 'onScrollSlotsPanel'
  67. },
  68. init: function init(options) {
  69. // Note: Don't do this. visAPI = this.widget.visAPI.
  70. // Instead, use this.getModel function to get the visAPI.
  71. // The visAPI could be recreated for the widget hereby rendering
  72. // the object this.visAPI obsolete. see defect 245997
  73. DataSlotsView.inherited('init', this, arguments);
  74. // @todo: consider removing
  75. this.dashboardApi = options.dashboardApi;
  76. this.visualization = options.visualizationApi;
  77. this.slotsApi = this.visualization.getSlots();
  78. this.localFilters = this.visualization.getLocalFilters();
  79. this._dndManager = options.dndManager;
  80. this.widget = options.widget;
  81. this.content = this.widget.content;
  82. this.interactivityController = this.content.getFeature('InteractivityController.deprecated');
  83. this.missingFilters = options.missingFilters || [];
  84. this._changeEvents = {
  85. 'change:graphic': 1
  86. };
  87. this._registerEvents();
  88. //region layer is open by default
  89. this._visibleLayers = ['data.region'];
  90. this.transactionApi = this.dashboardApi.getFeature('Transaction');
  91. this.dataSources = this.dashboardApi.getFeature('DataSources');
  92. this.iconsFeature = this.dashboardApi.getFeature('Icons');
  93. this.visMapColumnsToSlot = this.content.getFeature('VisDnD.utils');
  94. },
  95. onKeyDown: function onKeyDown(event) {
  96. //Ctrl + V. Paste selected items in tree to slots
  97. if (event.keyCode === 86 && event.ctrlKey) {
  98. var moduleApi = this._getVisAPI().getModule();
  99. var mockDragObject = ShapingUIUtils.getCopiedTreeItems(moduleApi);
  100. if (!mockDragObject) {
  101. if (this.copyMappedItem) {
  102. mockDragObject = this.copyMappedItem;
  103. } else {
  104. return false;
  105. }
  106. }
  107. var dropNode = this.getFocusedDropNode();
  108. mockDragObject.droppable = !this._isDropNodeInvalid(mockDragObject, dropNode);
  109. if ($(dropNode).hasClass('localFilter') && this.acceptsContext(mockDragObject)) {
  110. this.onDropContext(mockDragObject, dropNode);
  111. } else if (this.accepts(mockDragObject)) {
  112. this.onDrop(mockDragObject, dropNode);
  113. }
  114. ShapingUIUtils.clearCopiedTreeItems();
  115. this.copyMappedItem = undefined;
  116. this.dashboardApi.triggerDashboardEvent('dataSourceGrid:clearSourceSelected');
  117. }
  118. //Ctrl + C, copy item for a swap operation.
  119. if (event.keyCode === 67 && event.ctrlKey) {
  120. ShapingUIUtils.clearCopiedTreeItems();
  121. this.copyMappedItem = this.getFocusedDragObject();
  122. }
  123. },
  124. getFocusedDropNode: function getFocusedDropNode() {
  125. var $element = $(document.activeElement);
  126. if ($element.hasClass('dropfirst')) {
  127. $element = $element.parent().parent();
  128. }
  129. return $element[0];
  130. },
  131. getFocusedDragObject: function getFocusedDragObject() {
  132. var $element = $(document.activeElement);
  133. if (!$element.hasClass('listitem')) {
  134. return false;
  135. }
  136. $element.addClass('addFocus');
  137. var mockDragObject = {
  138. $el: $element,
  139. type: 'slot',
  140. data: {
  141. slotId: $element.attr('data-slot-id'),
  142. mappingId: $element.attr('data-mapping-id')
  143. }
  144. };
  145. return mockDragObject;
  146. },
  147. getGraphicItem: function getGraphicItem() {
  148. var graphic = [];
  149. var graphicContent = this.content.getPropertyValue('value.graphic.content');
  150. if (graphicContent) {
  151. graphic.push({
  152. content: graphicContent,
  153. fillColor: this.content.getPropertyValue('value.graphic.fillColor'),
  154. currentScaleOption: this.content.getPropertyValue('value.graphic.currentScaleOption')
  155. });
  156. }
  157. return graphic;
  158. },
  159. onDropShapeWidget: function onDropShapeWidget(dragObject) {
  160. this.widget.onAddShapeWidget(dragObject.widgetSpec.model);
  161. this.updateShapeSlot();
  162. },
  163. updateShapeSlot: function updateShapeSlot() {
  164. var graphic = this.getGraphicItem();
  165. if (graphic.length) {
  166. var $infographicNode = this.$el.find('div.infographicZone');
  167. $infographicNode.removeClass('draggedOn');
  168. $infographicNode.find('div.graphic').html(graphic[0].content);
  169. }
  170. },
  171. rebuildEvents: function rebuildEvents() {
  172. for (var e in this._changeEvents) {
  173. if (this._changeEvents.hasOwnProperty(e)) {
  174. this._changeEvents[e].remove();
  175. }
  176. }
  177. this._registerEvents();
  178. },
  179. _registerEvents: function _registerEvents() {
  180. this.widget.model.on('change:localFilters', this._onChangeEvent, this);
  181. this.visualization.on('change:type', this._onChangeEvent, this);
  182. this.visualization.on('change:slots', this._onChangeEvent, this);
  183. this.content.on('change:property:value.graphic.content', this._onChangeEvent, this);
  184. var m = this._getVisAPI();
  185. for (var e in this._changeEvents) {
  186. if (this._changeEvents.hasOwnProperty(e)) {
  187. this._changeEvents[e] = m.on(e, this._onChangeEvent, this);
  188. }
  189. }
  190. },
  191. remove: function remove() {
  192. DataSlotsView.inherited('remove', this, arguments);
  193. this.widget.model.off('change:localFilters', this._onChangeEvent, this);
  194. //this.widget.model.off('change:data', this._onChangeEvent, this);
  195. //this.widget.model.off('change:slotmapping', this._onChangeEvent, this);
  196. this.visualization.off('change:slots', this._onChangeEvent, this);
  197. this.visualization.off('change:type', this._onChangeEvent, this);
  198. this.visualization.off('change:type', this._onChangeEvent, this);
  199. this.content.off('change:property:value.graphic.content', this._onChangeEvent, this);
  200. this.widget.model.off('change:localFilters', this._onChangeEvent, this);
  201. for (var e in this._changeEvents) {
  202. if (this._changeEvents.hasOwnProperty(e)) {
  203. this._changeEvents[e].remove();
  204. this._changeEvents[e] = null;
  205. }
  206. }
  207. this._changeEvents = [];
  208. this._clearToolbar();
  209. this._clearDropTargets();
  210. this.isOutsideFocusMode = false;
  211. this.focusModeBoundingClientRect = null;
  212. //To destroy the Event.js states since views inherit from Events.js
  213. DataSlotsView.inherited('destroy', this, arguments);
  214. },
  215. _clearDropTargets: function _clearDropTargets() {
  216. if (this._dndManager) {
  217. this.$('.listitem, ul.fields .localFilter').map(function (index, el) {
  218. this._dndManager.removeDropTarget(el);
  219. }.bind(this));
  220. }
  221. },
  222. _clearToolbar: function _clearToolbar() {
  223. this.launchedToolbarNodeId = null;
  224. this.isImplicitClosingTheBoolbar = false;
  225. if (this._actionMenuToolbar) {
  226. _.each(this._actionMenuToolbar.selectionNodes, function (node) {
  227. $(node).removeClass('selected');
  228. });
  229. //Passing true to request the 'toolbar:remove' to trigger
  230. this._actionMenuToolbar.remove(true);
  231. this._actionMenuToolbar = null;
  232. }
  233. var $node = this.$('.addFocus');
  234. if ($node.length > 0) {
  235. $node.removeClass('addFocus');
  236. }
  237. $node = this.$('.selected');
  238. if ($node.length > 0) {
  239. $node.removeClass('selected');
  240. }
  241. var $nodeList = this.$('.draggedOn');
  242. _.each($nodeList, function (node) {
  243. $node = $(node);
  244. $node.removeClass('draggedOn');
  245. });
  246. },
  247. /**
  248. * @param moduleRef (optional) - a moduleReference (id) which allows access to modules other than the default module.
  249. * @returns module information if the module has been loaded.
  250. */
  251. getModule: function getModule(moduleRef) {
  252. return this._getVisAPI().getModule(moduleRef);
  253. },
  254. /**
  255. * Builds up a list of slots to display on the dataslots view. If this chart has an archetype, this is
  256. * a merger of all the archetype slots in addition to the slots that are part of the visualisation
  257. */
  258. getSlotsInfo: function getSlotsInfo(hasCategory, localFilters) {
  259. var _this = this;
  260. return _.map(this.slotsApi.getSlotList(), function (slot) {
  261. return _this._buildSlotInfo(slot, hasCategory, localFilters);
  262. });
  263. },
  264. _isMultiMeasureSeries: function _isMultiMeasureSeries(dataItem) {
  265. return dataItem.getColumnId() === '_multiMeasuresSeries';
  266. },
  267. _getSlotIcon: function _getSlotIcon(slotDef) {
  268. var slotIconId = slotDef.getIcon();
  269. var icon = {};
  270. if (slotIconId) {
  271. icon = this.iconsFeature.getIcon(slotIconId);
  272. return icon;
  273. } else {
  274. return slotDef.getType() === 'ordinal' ? this.iconsFeature.getIcon('values') : this.iconsFeature.getIcon('category');
  275. }
  276. },
  277. _buildSlotInfo: function _buildSlotInfo(slot, hasCategory, localFilters) {
  278. var _this2 = this;
  279. var slotDef = slot.getDefinition();
  280. var icon = this._getSlotIcon(slotDef);
  281. var mappings = _.map(slot.getDataItemList(), function (dataItem) {
  282. var metadataColumn = dataItem.getMetadataColumn();
  283. var isMultiMeasuresSeries = _this2._isMultiMeasureSeries(dataItem);
  284. var isUnavailable = metadataColumn ? metadataColumn.isHidden() || metadataColumn.isMissing() : !isMultiMeasuresSeries;
  285. var mappingInfo = {
  286. columnId: dataItem.getColumnId(),
  287. name: isUnavailable ? stringResources.get('missingColumn', {
  288. 'columnLabel': _.escape(dataItem.getLabel())
  289. }) : _.escape(dataItem.getLabel()),
  290. mappingId: dataItem.getId(),
  291. configurable: !isMultiMeasuresSeries,
  292. unavailable: isUnavailable ? metadataColumn : null
  293. };
  294. if (hasCategory && localFilters) {
  295. _this2._buildFilterMappingInfoForDataItem(dataItem, slot, localFilters, mappingInfo);
  296. }
  297. return mappingInfo;
  298. });
  299. var isMapped = slot.getDataItemList().length > 0;
  300. var isShapeEnabled = !!this.content.getPropertyValue('value.graphic.content');
  301. var slotInfo = {
  302. 'id': slotDef.getId(),
  303. // This is used for UI only not the acutal dataset
  304. 'dataset': this._isSharingSlots() ? 'data' : slotDef.getDatasetIdList()[0],
  305. 'group': slotDef.getGroupId(),
  306. 'caption': slotDef.getCaption(),
  307. 'icon': icon,
  308. 'hidden': slotDef ? slotDef.isHidden() : false,
  309. 'mappings': mappings,
  310. 'noMapping': isMapped ? '' : 'noMapping',
  311. 'deletable': isMapped,
  312. 'optional': slotDef.isOptional(),
  313. 'showRequiredMarker': slotDef.getShowRequiredMarker(),
  314. 'shapeDropEnabled': isShapeEnabled,
  315. 'shapable': slotDef ? slotDef.isShapable() : false
  316. };
  317. return slotInfo;
  318. },
  319. _buildFilterMappingInfoForDataItem: function _buildFilterMappingInfoForDataItem(dataItem, slot, localFilters, mappingInfo) {
  320. var _this3 = this;
  321. var columnFilter = _.find(localFilters.models, function (filter) {
  322. if (!_this3._getVisAPI().isFilterEditable(filter)) {
  323. return false;
  324. }
  325. if (filter.filterBins) {
  326. if (dataItem.getBinning()) {
  327. return filter.id === dataItem.getId();
  328. }
  329. } else if (!filter.filterBins) {
  330. var columnId = dataItem.getColumnId();
  331. if (filter.aggregationType) {
  332. return filter.columnId === columnId && dataItem.getAggregation() === filter.aggregationType;
  333. } else {
  334. // If the case is when there is an ordinal and attribute we dont want to
  335. // display the filter beneath it.
  336. return filter.columnId === columnId && !(slot.getDefinition().getType() === 'ordinal' && dataItem.getType() === 'attribute');
  337. }
  338. }
  339. });
  340. //Only include the 'first filter' against an item (it can be edited).
  341. //Extra filters for an item or tuple (usually created with additional keeps/excludes) are shown as part of localFilters (and cant be edited)
  342. if (columnFilter) {
  343. var md = dataItem.getMetadataColumn();
  344. var columnMetadata = {
  345. getDataType: md.getDataType.bind(md),
  346. getType: md.getType.bind(md),
  347. getFormat: function getFormat() {
  348. return dataItem.getFormat();
  349. }
  350. };
  351. mappingInfo.filterString = this.getFilterLabel(columnFilter, columnMetadata);
  352. mappingInfo.numberOfFilters = this.getFilterNumber(columnFilter);
  353. }
  354. },
  355. /**
  356. * if binning is already applied but the vis is not supporting the binning or slot type is ordinal,
  357. * remove binning and local filters, this is needed when we change vis.
  358. *
  359. * @param slot A slot
  360. */
  361. _clearBinningDefnIfNeeded: function _clearBinningDefnIfNeeded() /*slot, transactionToken*/{
  362. // TODO livewidget_cleanup -- how does this relate to the cleanup happening inside the API
  363. //slot.setBinning(null, transactionToken);
  364. },
  365. _getVisAPI: function _getVisAPI() {
  366. return this.widget.visAPI;
  367. },
  368. /* Gets the number of filters applied on a selection filter view
  369. * @params columnFilter the filter applied.
  370. * @params json if the filter is in a json formatAction
  371. * @return the number of filters applied.
  372. */
  373. getFilterNumber: function getFilterNumber(columnFilter, json) {
  374. if (columnFilter.operator === 'in' || columnFilter.operator === 'notin' || columnFilter.operator === 'isnull') {
  375. var filterNum;
  376. if (json) {
  377. filterNum = columnFilter.values.length;
  378. } else {
  379. filterNum = columnFilter.values.models.length;
  380. }
  381. return filterNum;
  382. }
  383. return;
  384. },
  385. getLocalFiltersList: function getLocalFiltersList() {
  386. return this._getVisAPI().getLocalFiltersList();
  387. },
  388. getContextFilters: function getContextFilters() {
  389. var filters = [];
  390. var dataSource = this.visualization.getDataSource();
  391. _.each(this.getLocalFiltersList(), function (localFilter) {
  392. var columnId = localFilter.columnId;
  393. if (columnId) {
  394. var metadataColumn = dataSource.getMetadataColumn(columnId);
  395. if (metadataColumn) {
  396. filters.push({
  397. id: localFilter.id,
  398. columnId: localFilter.columnId,
  399. name: metadataColumn.getLabel(),
  400. filterString: this.getFilterLabel(localFilter, metadataColumn),
  401. numberOfFilters: this.getFilterNumber(localFilter, true)
  402. });
  403. }
  404. }
  405. }.bind(this));
  406. return filters;
  407. },
  408. isMappingComplete: function isMappingComplete() {
  409. return this.visualization.getSlots().isMappingComplete();
  410. },
  411. // if there is anything to preserve from the previous mapping
  412. // do so here
  413. _preserveData: function _preserveData() /*slot, mapping*/{
  414. // overriden by children
  415. },
  416. _convertTransactionTokenToLegacyOptions: function _convertTransactionTokenToLegacyOptions(transactionToken) {
  417. var oldOptions = {};
  418. if (transactionToken) {
  419. oldOptions.payloadData = {
  420. transactionToken: transactionToken
  421. };
  422. if (transactionToken.transactionId) {
  423. oldOptions.payloadData.undoRedoTransactionId = transactionToken.transactionId;
  424. }
  425. }
  426. return oldOptions;
  427. },
  428. /**
  429. * Performs swap mapping of slot data item 1 and slot data item 2
  430. */
  431. _swapSlotMapping: function _swapSlotMapping(dragObject, dropNode, transactionToken) {
  432. var sourceDataItemId = dragObject.data.mappingId;
  433. var targetDataItemId = dropNode.getAttribute('data-mapping-id');
  434. // If we are swapping onto same item, we are done
  435. if (sourceDataItemId === targetDataItemId) {
  436. return;
  437. }
  438. var options = {};
  439. var sourceSlotId = dragObject.data.slotId;
  440. var targetSlotId = dropNode.getAttribute('data-slot-id');
  441. if (targetSlotId === sourceSlotId) {
  442. // Handle swapping within the same slot
  443. options.afterItem = $(dropNode).is('.dropafter') || $(dropNode).is('.slot');
  444. if (options.afterItem) {
  445. targetDataItemId = $(dropNode).prev().attr('data-mapping-id');
  446. }
  447. this._swapItemsInSlot(targetSlotId, sourceDataItemId, targetDataItemId, options, transactionToken);
  448. } else if (!targetDataItemId) {
  449. // Handle moving an item
  450. var targetIndex = parseInt($(dropNode).attr('map-index'), 10);
  451. this._moveItemToSlot(targetSlotId, sourceDataItemId, targetIndex, transactionToken);
  452. } else {
  453. this._swapItemsBetweenSlots(sourceSlotId, targetSlotId, sourceDataItemId, targetDataItemId, transactionToken);
  454. }
  455. },
  456. _swapItemsBetweenSlots: function _swapItemsBetweenSlots(sourceSlotId, targetSlotId, sourceDataItemId, targetDataItemId, transactionToken) {
  457. // Update target slot items
  458. var targetSlot = this.slotsApi.getSlot(targetSlotId);
  459. var targetDataItemRefs = _.map(targetSlot.getDataItemList(), function (dataItem) {
  460. return dataItem.getId();
  461. });
  462. var targetIndex = targetDataItemRefs.indexOf(targetDataItemId);
  463. targetDataItemRefs[targetIndex] = sourceDataItemId;
  464. // Update source slot items
  465. var sourceSlot = this.slotsApi.getSlot(sourceSlotId);
  466. var sourceDataItemRefs = _.map(sourceSlot.getDataItemList(), function (dataItem) {
  467. return dataItem.getId();
  468. });
  469. var sourceIndex = sourceDataItemRefs.indexOf(sourceDataItemId);
  470. sourceDataItemRefs[sourceIndex] = targetDataItemId;
  471. // swap
  472. this.slotsApi.setDataItems(targetDataItemRefs, targetSlotId, transactionToken);
  473. this.slotsApi.setDataItems(sourceDataItemRefs, sourceSlotId, transactionToken);
  474. // @todo needs to be revise to be done behind the api
  475. this._clearBinningDefnIfNeeded(this.visualization.getSlots().getSlot(targetSlotId), transactionToken);
  476. },
  477. _swapItemsInSlot: function _swapItemsInSlot(slotId, sourceDataItemId, targetDataItemId, options, transactionToken) {
  478. var slot = this.slotsApi.getSlot(slotId);
  479. var dataItemRefs = _.map(slot.getDataItemList(), function (dataItem) {
  480. return dataItem.getId();
  481. });
  482. var sourceIndex = dataItemRefs.indexOf(sourceDataItemId);
  483. var targetIndex = dataItemRefs.indexOf(targetDataItemId);
  484. if (options.afterItem) {
  485. dataItemRefs.splice(targetIndex + 1, 0, sourceDataItemId);
  486. dataItemRefs.splice(sourceIndex > targetIndex ? sourceIndex + 1 : sourceIndex, 1);
  487. } else {
  488. dataItemRefs[sourceIndex] = targetDataItemId;
  489. dataItemRefs[targetIndex] = sourceDataItemId;
  490. }
  491. // swap
  492. this.slotsApi.setDataItems(dataItemRefs, slotId, transactionToken);
  493. },
  494. _moveItemToSlot: function _moveItemToSlot(targetSlotId, sourceDataItemId, targetIndex, transactionToken) {
  495. // Update target slot
  496. var targetSlot = this.slotsApi.getSlot(targetSlotId);
  497. var targetDataItemRefs = targetSlot.getDataItemList().map(function (dataItem) {
  498. return dataItem.getId();
  499. });
  500. // Add item to the target slot
  501. if (targetIndex === -1) {
  502. // At the end
  503. targetDataItemRefs.push(sourceDataItemId);
  504. } else {
  505. // Somewhere inside
  506. targetDataItemRefs.splice(targetIndex, 0, sourceDataItemId);
  507. }
  508. // move
  509. this.slotsApi.setDataItems(targetDataItemRefs, targetSlotId, transactionToken);
  510. this._clearBinningDefnIfNeeded(this.visualization.getSlots().getSlot(targetSlotId), transactionToken);
  511. },
  512. accepts: function accepts(dragObject, targetNode) {
  513. var acceptsOptions = { dropTarget: ShapingConstants.DROP_TARGET_OPTIONS.SLOT };
  514. if (targetNode) {
  515. acceptsOptions.targetNode = targetNode;
  516. }
  517. return dragObject.type === 'slot' || this.widget.accepts(dragObject, acceptsOptions);
  518. },
  519. _getMetadataPayloadColumns: function _getMetadataPayloadColumns(dragObject) {
  520. return dragObject && dragObject.data && dragObject.data.columns ? dragObject.data.columns : null;
  521. },
  522. _getDropMetadataColumns: function _getDropMetadataColumns(dragObject) {
  523. return dragObject && dragObject.data && dragObject.data.columns;
  524. },
  525. acceptsContext: function acceptsContext(dragObject) {
  526. var droppedColumns = this._getMetadataPayloadColumns(dragObject);
  527. if (droppedColumns) {
  528. var metadataColumns = _.map(droppedColumns, function (column) {
  529. return column.metadataColumn;
  530. });
  531. if (metadataColumns.length > 0 && metadataColumns[0].getType() === 'fact') {
  532. var dataItemAPIs = [];
  533. _.each(this.visualization.getSlots().getMappedSlotList(), function (mappedDataSlot) {
  534. dataItemAPIs = dataItemAPIs.concat(mappedDataSlot.getDataItemList());
  535. });
  536. // When data slots are empty, do not allow a numeric data item to be dropped to local filter area
  537. // Since the minmax value without context doesn't provide any meaning after a data item is dropped to a slot.
  538. if (dataItemAPIs.length === 0 || !hasDimensions(dataItemAPIs)) {
  539. return false;
  540. }
  541. }
  542. }
  543. return this.widget.accepts(dragObject, { dropTarget: ShapingConstants.DROP_TARGET_OPTIONS.FILTER });
  544. },
  545. onMouseDown: function onMouseDown(event) {
  546. this.startDrag = false;
  547. this.initialDragAndDropPosition = DomUtils.getEventPos(event);
  548. },
  549. onMouseUp: function onMouseUp(event) {
  550. this.startDrag = false;
  551. delete this.initialDragAndDropPosition;
  552. this.itemNotDragged(event);
  553. },
  554. onDragStart: function onDragStart(event) {
  555. if (!this._beginDragAndDrop(event) || this.startDrag) {
  556. return true;
  557. }
  558. this.startDrag = true;
  559. //drag and drop occurs so destroy the current opened toolbar if there is one opened
  560. this._clearToolbar();
  561. var $target = $(this.getTarget(event.target, 'listitem'));
  562. this.dropInfo = {
  563. operation: 'swap',
  564. initialTarget: $target,
  565. slotId: $target.attr('data-slot-id'),
  566. mappingId: $target.attr('data-mapping-id')
  567. };
  568. var menuOverflowIcon = this.iconsFeature.getIcon('overflowMenuHorizontal32');
  569. var disableIcon = this.iconsFeature.getIcon('common-nodrop');
  570. var $slotInfo = $target.find('.slotInfo').clone(false, false);
  571. var $menu = $('<div class="menuoverflow"><svg class="svgIcon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#' + menuOverflowIcon.id + '"></use></svg></div>');
  572. 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>');
  573. var $avatar = $('<div></div>', {
  574. 'class': 'listitem columnName avatarLive'
  575. });
  576. $avatar.append($unvalidAction).append($slotInfo).append($menu);
  577. this._dndManager.startDrag({
  578. event: event,
  579. type: 'slot',
  580. data: this.dropInfo,
  581. avatar: $avatar,
  582. callerCallbacks: {
  583. onDragDone: this.onDragStop.bind(this, $target),
  584. onMove: this.onMove.bind(this)
  585. },
  586. moveXThreshold: 20,
  587. moveYThreshold: 20
  588. });
  589. $target.addClass('isDragged');
  590. return true;
  591. },
  592. onDragStop: function onDragStop($target, event, payload) {
  593. this.startDrag = false;
  594. delete this.initialDragAndDropPosition;
  595. payload = payload || {};
  596. if (this.isOutsideFocusMode && payload.isDropped === false) {
  597. this.isOutsideFocusMode = false;
  598. var mapIndex = $target.attr('map-index');
  599. var slotId = $target.attr('data-slot-id');
  600. var slot = this.visualization.getSlots().getSlot(slotId);
  601. this.interactivityController.getActionHelper().getActionsForSlots([slot], mapIndex, $target[0]).then(function (actions) {
  602. for (var index = 0; index < actions.length; index++) {
  603. if (actions[index].spec && actions[index].spec.name == 'delete' && actions[index].spec.actions && _.isFunction(actions[index].spec.actions.apply)) {
  604. actions[index].spec.actions.apply();
  605. break;
  606. }
  607. }
  608. });
  609. return true;
  610. } else {
  611. return payload.isDropped;
  612. }
  613. },
  614. /**
  615. * Callback to handle the move event
  616. *
  617. * @param {object} event - the event object
  618. * @param {object} payload - the payload to process the event object
  619. */
  620. onMove: function onMove(event, payload) {
  621. payload = payload || {};
  622. var dragObject = payload.dragObject || {};
  623. this.isOutsideFocusMode = this._isOutsideFocusModeBoundary(dragObject);
  624. var $unvalid = $(dragObject.avatar).children('.unvalid');
  625. if (this.isOutsideFocusMode) {
  626. $unvalid.addClass('hidden');
  627. } else {
  628. var $target = $(payload.dropTargetNode);
  629. if ($target.hasClass('listitem') || $target.hasClass('dropafter') || $target.hasClass('slot') || $target.hasClass('localFilter')) {
  630. this.onDragEnter(dragObject, payload.dropTargetNode);
  631. } else {
  632. $unvalid.removeClass('hidden');
  633. }
  634. }
  635. },
  636. itemNotDragged: function itemNotDragged(event) {
  637. var $target = $(this.getTarget(event.target, 'listitem'));
  638. $target.removeClass('isDragged');
  639. },
  640. /**
  641. * Callback to handle the drag enter event
  642. *
  643. * @param {object} dragObject - the drag object context
  644. * @param {object} dropNode - the node potentially being dropped
  645. */
  646. onDragEnter: function onDragEnter(dragObject, dropNode) {
  647. dragObject.droppable = !this._isDropNodeInvalid(dragObject, dropNode);
  648. if (dragObject.droppable) {
  649. $(dropNode).addClass('draggedOn');
  650. if ($(dropNode).is('.columnItem')) {
  651. $(dropNode).find('.dropfirst').addClass('draggedOn');
  652. }
  653. $(dragObject.avatar).children('.unvalid').addClass('hidden');
  654. } else {
  655. $(dragObject.avatar).children('.unvalid').removeClass('hidden');
  656. }
  657. },
  658. /**
  659. * Handler when a data item is dragged on a data slot, to replace the previous slot.
  660. * @param dragObject - the drag and drop payload from metadata tree or data strip.
  661. * @param dropNode - the target of the drop.
  662. */
  663. onDrop: function onDrop(dragObject, dropNode) {
  664. if (!dragObject.droppable) {
  665. return; //Do nothing because, the dnd case is prohibited.
  666. }
  667. var transactionToken = this.transactionApi.startTransaction();
  668. $(dropNode).removeClass('draggedOn');
  669. this._setDataSource(dragObject, transactionToken);
  670. // The viz might not support the datasource so it might not be set
  671. var isDataSourceSet = !!this.visualization.getDataSource();
  672. if (isDataSourceSet) {
  673. if (dragObject.type === 'slot') {
  674. this._swapSlotMapping(dragObject, dropNode, transactionToken);
  675. } else {
  676. this._addSlotMapping(dragObject, dropNode, transactionToken);
  677. }
  678. }
  679. this.transactionApi.endTransaction(transactionToken);
  680. },
  681. _setDataSource: function _setDataSource(dragObject, transactionToken) {
  682. var sourceId = dragObject.data.sourceId;
  683. if (sourceId) {
  684. this.visualization.setDataSource(sourceId, transactionToken);
  685. }
  686. },
  687. _addSlotMapping: function _addSlotMapping(dragObject, dropNode, transactionToken) {
  688. var droppedColumns = this._getDropMetadataColumns(dragObject);
  689. var metadataColumns = _.map(droppedColumns, function (column) {
  690. return column.metadataColumn;
  691. });
  692. this.widget.addModelFilters(metadataColumns, transactionToken);
  693. var slotId = dropNode.getAttribute('data-slot-id');
  694. var options = {
  695. position: parseInt($(dropNode).attr('map-index'), 10),
  696. bReplace: $(dropNode).is('.listitem')
  697. };
  698. this.visMapColumnsToSlot.mapColumns(slotId, droppedColumns, options, transactionToken);
  699. },
  700. /*Data slot view that has unavailable columns*/
  701. _hasDataUnavailable: function _hasDataUnavailable($dropNode) {
  702. return $dropNode.is('.noMapping .unavailable');
  703. },
  704. /**
  705. * Handler when a data item is dropped in the Context Filter section. It adds it as Context Filter to the Data widget.
  706. * 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.
  707. */
  708. onDropContext: function onDropContext(dragObject) {
  709. var transactionToken = this.transactionApi.startTransaction();
  710. this._setDataSource(dragObject, transactionToken);
  711. // The viz might not support the datasource so it might not be set
  712. var isDataSourceSet = !!this.visualization.getDataSource();
  713. if (isDataSourceSet) {
  714. var droppedColumns = this._getMetadataPayloadColumns(dragObject);
  715. if (droppedColumns) {
  716. var metadataColumns = _.map(droppedColumns, function (column) {
  717. return column.metadataColumn;
  718. });
  719. if (metadataColumns) {
  720. metadataColumns = this.widget.addModelFilters(metadataColumns, transactionToken);
  721. if (metadataColumns.length > 0) {
  722. var filterColumnId = metadataColumns[0].getId();
  723. // don't process filter on the dataItem for a ordinal only view (ie: summary widget)
  724. var dataSlot = this.slotsApi.getSlot(filterColumnId);
  725. if (!dataSlot || dataSlot.getDefinition().getType() !== 'ordinal' || this._definitionHasCategory()) {
  726. var columnsWithMembers = dragObject.data.utils.getColumnsWithMembers(true /* filterMemberColumns */);
  727. var columnIds = Object.keys(columnsWithMembers);
  728. if (columnIds.length) {
  729. this._addLocalFiltersWithMembers(dragObject, transactionToken);
  730. } else {
  731. this._setContextFilter(metadataColumns[0], this.$('ul.fields .localFilter')[0], false, transactionToken);
  732. }
  733. }
  734. }
  735. }
  736. }
  737. }
  738. this.transactionApi.endTransaction(transactionToken);
  739. },
  740. _definitionHasCategory: function _definitionHasCategory() {
  741. var definition = this.visualization.getDefinition();
  742. if (definition) {
  743. var dataSlots = definition.getSlotList();
  744. for (var i = 0; i < dataSlots.length; i++) {
  745. if (dataSlots[i].getType() === 'category' || dataSlots[i].getType() === 'any') {
  746. return true;
  747. }
  748. }
  749. }
  750. return false;
  751. },
  752. _addLocalFiltersWithMembers: function _addLocalFiltersWithMembers(dragObject, transactionToken) {
  753. var columns = this._getDropMetadataColumns(dragObject);
  754. this.content.getFeature('VisDnD.utils').addMembersAsLocalFilters({ visualizationAPI: this.visualization, columns: columns }, transactionToken);
  755. },
  756. /**
  757. * Handler for the delete key on filter. In this case, we only want the Delete and Backspace key to work.
  758. */
  759. onDeleteKeyContextFilter: function onDeleteKeyContextFilter(e) {
  760. this.onRemoveContextFilter(e);
  761. },
  762. onEnterSlot: function onEnterSlot(e) {
  763. if (e.keyCode === 32 || e.keyCode === 13) {
  764. this.popupSlotOptions(e);
  765. }
  766. },
  767. onEnterLocalFilters: function onEnterLocalFilters(e) {
  768. if (e.keyCode === 32 || e.keyCode === 13) {
  769. this.popupFilterOptions(e);
  770. }
  771. },
  772. onEnterLayer: function onEnterLayer(e) {
  773. if (e.keyCode === 32 || e.keyCode === 13) {
  774. this.expandCollapseLayers(e);
  775. }
  776. },
  777. /**
  778. * Finds the proper slot data item element, verifies if the proper keys were pressed to delete.
  779. */
  780. _getTargetSlotDataItem: function _getTargetSlotDataItem(e) {
  781. return $(this.getTarget(e.target, 'listitem'));
  782. },
  783. /**
  784. * Handler for the delete button in a Context Filter, visualization will be updated.
  785. */
  786. onRemoveContextFilter: function onRemoveContextFilter(e) {
  787. e.preventDefault();
  788. e.stopPropagation();
  789. this._clearToolbar();
  790. var $slotDataItem = this._getTargetSlotDataItem(e);
  791. if ($slotDataItem) {
  792. var filterId = $slotDataItem.attr('data-filter-id');
  793. if (filterId) {
  794. var visAPI = this._getVisAPI();
  795. // we do not need to update the DOM as the slots re-render after filters modification completes
  796. visAPI.localFilters.removeFilterEntry({
  797. id: filterId
  798. });
  799. visAPI.localFilters.allFilterModificationComplete();
  800. }
  801. }
  802. },
  803. /**
  804. * no-op. We don't use selection for the slots. Needs to implement it because we use BaseListView.
  805. */
  806. onSelectItem: function onSelectItem() {
  807. return false;
  808. },
  809. _onChangeEvent: function _onChangeEvent() {
  810. this.slotsViewContentModified = true;
  811. this.render();
  812. },
  813. _onToolBarRemove: function _onToolBarRemove() /*options*/{
  814. //Handle after the toolbar is removed
  815. //This case occurs when the user click once to open the toolbar and click again to close the toolbar
  816. //Since reRender is begin called by 'toolbar:hide' and 'toolbar:remove' have an options parameter
  817. //Having this onToolBarRemove function avoid sending the wrong parameter value to the reRender calls
  818. this._reRender();
  819. },
  820. _reRender: function _reRender() {
  821. if (this.isImplicitClosingTheBoolbar) {
  822. this._clearToolbar();
  823. }
  824. if (this.slotsViewContentModified) {
  825. this.render();
  826. this.slotsViewContentModified = false;
  827. }
  828. },
  829. /**
  830. * Renders the UI based on a Dot template. It creates two lists, one for data slots and one for context filters.
  831. */
  832. render: function render() {
  833. var _this4 = this;
  834. // If dataslotsview is being torn down (destroy function will remove the dotTemplate).
  835. if (!this.dotTemplate) {
  836. return;
  837. }
  838. if (this._actionMenuToolbar) {
  839. //The actionMenu is showing. For simple actions like drill, it is simply closed
  840. //For actions that have parameters, the actionMenu is a 'subview' (eg sort, navigate, format etc.)
  841. //For subviews, the ui stays open and slots are only rendered when the subview is explicitly closed (or loses focus).
  842. if (this._actionMenuToolbar.itemMap && this._actionMenuToolbar.itemMap.subview) {
  843. return;
  844. }
  845. //No need to re-render the view since the tool bar gets delete here
  846. this.slotsViewContentModified = false;
  847. this._clearToolbar();
  848. }
  849. var hasCategory = this._definitionHasCategory();
  850. var localFilters = this._getVisAPI().localFilters || {};
  851. //Gather the info for each slot in an array to be passed to the Dot Template.=
  852. var slots = this.getSlotsInfo(hasCategory, localFilters);
  853. var maxWidth = this.$el.width();
  854. var filters = this.getContextFilters();
  855. var graphic = this.getGraphicItem();
  856. if (!this._needToReRender(slots, filters, graphic)) {
  857. return this;
  858. }
  859. // rendering starts here.
  860. // We will refresh the UI, remove the previous elements that were added as drop targets.
  861. this._clearDropTargets();
  862. this._updateMissingFilters();
  863. //maintain the scrollposition of the slots before rerendering
  864. var slotsViewScrollPosition = this.$el.find('.list.fields.slots').scrollTop();
  865. // Generate the html from the template using the slots and filters info collected above.
  866. //Passing in underscore as an option.
  867. var definition = this._getVisAPI().getDefinition();
  868. var chevronRightIcon = this.iconsFeature.getIcon('common-chevron_right');
  869. var menuOverflowIcon = this.iconsFeature.getIcon('overflowMenuHorizontal32');
  870. var errorIcon = this.iconsFeature.getIcon('common-warning');
  871. var closeIcon = this.iconsFeature.getIcon('common-close_16');
  872. var filterIcon = this.iconsFeature.getIcon('common-filter');
  873. var informationIcon = this.iconsFeature.getIcon('getInformation');
  874. var sHtml = this.dotTemplate({
  875. headerLabel: definition ? definition.caption : stringResources.get('evColumns'),
  876. isFilterable: hasCategory,
  877. slots: slots,
  878. datasets: definition ? this._getDatasets(definition.datasets) : null,
  879. visibleLayers: this._getVisibleLayers(),
  880. //todo api to get the dataslots
  881. groupedSlotsByDataset: this._getGroupedSlotsByDataset(slots),
  882. maxWidth: maxWidth + 'px',
  883. filterStringMaxWidth: maxWidth * 0.7 + 'px',
  884. ContextFiltersLabel: stringResources.get('evLocalFilters'),
  885. filterLabel: stringResources.get('evFilterTooltip'),
  886. filters: filters,
  887. missingFilters: this.missingFilters || [],
  888. missingFiltersLabel: stringResources.get('missingFiltering'),
  889. isMappingComplete: this.isMappingComplete(),
  890. graphic: graphic,
  891. infographicShapeLabel: stringResources.get('toolbarActionToggleShapeDrop'),
  892. requiredFieldsDescription: stringResources.get('LIVE_slots_required_field_description', {
  893. asterisk: '<span class="asterisk">*</span>'
  894. }),
  895. dragAndDropDescription: stringResources.get('LIVE_slots_drag_and_drop_description'),
  896. expandLabel: stringResources.get('evExpand'),
  897. collapseLabel: stringResources.get('evCollapse'),
  898. underscore: _,
  899. chevronRightIcon: chevronRightIcon.id,
  900. menuOverflowIcon: menuOverflowIcon.id,
  901. errorIcon: errorIcon.id,
  902. closeIcon: closeIcon.id,
  903. filterIcon: filterIcon.id,
  904. informationIcon: informationIcon.id
  905. });
  906. this._detachEvents();
  907. this.$el.empty().append(sHtml);
  908. this.$el.find('.list.fields.slots').scrollTop(slotsViewScrollPosition);
  909. this._hideText();
  910. this.setElement(this.$el);
  911. //Add the 4 drop targets
  912. this._addDropTargets();
  913. var $widgetExpanded = this.$el.closest('.widgetExpanded');
  914. if ($widgetExpanded.length > 0) {
  915. this.focusModeBoundingClientRect = $widgetExpanded[0].getBoundingClientRect();
  916. }
  917. this.updateShapeSlot();
  918. // attempt coach marks to all slots
  919. setTimeout(function () {
  920. _.each(_this4.slotsApi.getSlotList(), function (slot) {
  921. _this4._applySlotCoachMarks(slot);
  922. });
  923. }, 500);
  924. return this;
  925. },
  926. /**
  927. * Apply coach marks on the slot or data item
  928. * The slot coach mark consists with:
  929. * - showPopover: boolean flag indicating whether the popover show along with the coach mark.
  930. * - mappedOnly: boolean flag indicating whether the coach mark applies when the slot is mapped or
  931. * regardless of the mapped state.
  932. * - titleResource: resource name for the coach title.
  933. * - contentResource: resource name for the coach content.
  934. * @param slot {Object} - SlotAPI
  935. * @return {Promise}
  936. */
  937. _applySlotCoachMarks: function _applySlotCoachMarks(slot) {
  938. var slotDef = slot.getDefinition();
  939. var coachMark = slotDef.getCoachMark();
  940. if (coachMark) {
  941. var isMapped = slot.getDataItemList().length > 0;
  942. if (!coachMark.mappedOnly || isMapped) {
  943. var slotId = slot.getId();
  944. var selector = (isMapped ? '.listitem.columnName' : '.slot') + '[data-slot-id=' + slotId + ']';
  945. // Make the coachmark unique for each slot of every visualization
  946. var options = {
  947. id: this.visualization.getDefinition().getId() + '#' + slotId,
  948. $el: this.$el.find(selector),
  949. title: stringResources.get(coachMark.titleResource),
  950. contents: stringResources.get(coachMark.contentResource),
  951. showPopover: coachMark.showPopover
  952. };
  953. this.dashboardApi.prepareGlassOptions(options);
  954. return Utils.addCoachmark(options);
  955. }
  956. }
  957. return Promise.resolve();
  958. },
  959. _getGroupedSlotsByDataset: function _getGroupedSlotsByDataset(slots) {
  960. return _.groupBy(slots, 'dataset');
  961. },
  962. _getVisibleLayers: function _getVisibleLayers() {
  963. return this._visibleLayers;
  964. },
  965. _expandLayer: function _expandLayer(layer) {
  966. this._visibleLayers.push(layer);
  967. },
  968. _collapseLayer: function _collapseLayer(layer) {
  969. this._visibleLayers = _.without(this._visibleLayers, layer);
  970. },
  971. _hideText: function _hideText() {
  972. var $mappings = $('.columnItem:not(.noMapping)');
  973. $('.columnLabel').removeClass('hidden');
  974. _.each($mappings, function (mapItem) {
  975. var $mapItem = $(mapItem);
  976. $mapItem.find('.columnLabel').addClass('hidden');
  977. });
  978. },
  979. _changeTabindices: function _changeTabindices() /*a, b*/{
  980. // DataSlotsView.inherited('_changeTabIndices', this, null);
  981. return false;
  982. },
  983. /**
  984. * Verifies if the drop node is valid for dropping the item.
  985. */
  986. _isDropNodeInvalid: function _isDropNodeInvalid(dragObject, dropNode) {
  987. var $dropNode = $(dropNode);
  988. if ($dropNode.hasClass('localFilter')) {
  989. return false;
  990. }
  991. // @todo need to be revisited to use the new API
  992. // problem identified so far is in SlotAPI.supportsColumns (aka. itemsNotSupported in old SlotAPI)
  993. // in order to check the support availability the metadata needs to be a loaded which is not always the case
  994. // in the new SlotAPI
  995. var slots = this.visualization.getSlots();
  996. var destinationSlot = slots.getSlot(dropNode.getAttribute('data-slot-id'));
  997. var slotId = dragObject.data && dragObject.data.slotId;
  998. var dragObjectSlot = slotId ? slots.getSlot(slotId) : null;
  999. var dragMetadata = void 0;
  1000. var dragMembers = void 0;
  1001. if (dragObject.data.columns) {
  1002. dragMetadata = _.map(dragObject.data.columns, function (column) {
  1003. return column.metadataColumn;
  1004. });
  1005. dragMembers = dragObject.data.utils.getColumnsWithMembers();
  1006. var source = this.dataSources.getDataSource(dragObject.data.sourceId);
  1007. // TODO livewidget_cleanup .. the dragMetadataObject should retun new metadata objects.
  1008. dragMetadata = dragMetadata.map(function (legacy) {
  1009. return source.getMetadataColumn(legacy.getId());
  1010. });
  1011. } else {
  1012. if (dragObjectSlot) {
  1013. dragMetadata = dragObjectSlot.getDataItemList()[dragObject.data.initialTarget[0].getAttribute('map-index')].getMetadataColumn();
  1014. }
  1015. }
  1016. var itemsNotSupported = !destinationSlot.supportsColumns(dragMetadata || []);
  1017. //Get the dragged object mapping index and slot index specified in the template
  1018. //If it comes from the metadata tree, it will return NaN for both
  1019. var dragIndex = {
  1020. mapIndex: parseInt($(dragObject.data.initialTarget).attr('map-index'), 10),
  1021. slotIndex: parseInt($(dragObject.data.initialTarget).parents('.slot').attr('slot-index'), 10)
  1022. };
  1023. //Same thing but for the drop node
  1024. var dropIndex = {
  1025. mapIndex: parseInt($dropNode.attr('map-index'), 10),
  1026. slotIndex: $dropNode.is('.slot') ? parseInt($dropNode.attr('slot-index'), 10) : parseInt($dropNode.parents('.slot').attr('slot-index'), 10),
  1027. slotId: $dropNode.attr('data-slot-id')
  1028. };
  1029. // In the html the the dropzone before an item has the same index as the following item
  1030. // ---------- index 0
  1031. // [listitem] index 0
  1032. // ---------- index 1
  1033. // [listitem] index 1
  1034. // ---------- index 2
  1035. // [listitem] index 2
  1036. // ---------- index 3
  1037. // The there will always be a trailing dropzone with an (n+1) index value; n being the last item's index value
  1038. //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.
  1039. var dropInSamePosition =
  1040. // you can't drop the dragged item before itself
  1041. (dragIndex.mapIndex === dropIndex.mapIndex ||
  1042. // you can't drop the dragged item after itself
  1043. dragIndex.mapIndex + 1 === dropIndex.mapIndex) &&
  1044. // if it's in the same slot and the drop node is not an item
  1045. dragIndex.slotIndex === dropIndex.slotIndex && !$dropNode.is('.listitem');
  1046. var targetSlot = this.slotsApi.getSlot(dropIndex.slotId);
  1047. var dropSlotDefinition = targetSlot.getDefinition();
  1048. //For now there is only one case where it's not valid
  1049. var isMultiMeasureAndOrdinal = dropSlotDefinition.isMultiMeasureSupported();
  1050. //Drag member from a column already projected should not be double counted
  1051. var itemLimit = dropSlotDefinition.getMaxItems();
  1052. itemLimit = itemLimit === -1 ? 0 : itemLimit;
  1053. var dragMembersFromProjectedColumnsOnly = this._dragMembersFromProjectedColumnsOnly(targetSlot, dragMetadata, dragMembers);
  1054. var slotNotStackable = !$dropNode.hasClass('listitem') && !isMultiMeasureAndOrdinal && !dropSlotDefinition.isStackItems() && !dragMembersFromProjectedColumnsOnly && parseInt($dropNode.attr('mappedItems'), 10) >= itemLimit;
  1055. // fix defect 231765 (prevent multimeasure group from being dropped on (thus replaced) from the data tree), but swapping is allowed
  1056. var dataItemAPI = destinationSlot.getDataItemList()[dropIndex.mapIndex];
  1057. var isDropOnMultiMeasureDataItem = SlotAPIHelper.isMultiMeasuresSeriesSlot(destinationSlot) && dataItemAPI ? dataItemAPI.getColumnId() === '_multiMeasuresSeries' && !$dropNode.hasClass('dropafter') && Number.isNaN(dragIndex.slotIndex) : false;
  1058. return itemsNotSupported || dropInSamePosition || slotNotStackable || isDropOnMultiMeasureDataItem || !dragMembersFromProjectedColumnsOnly && !isMultiMeasureAndOrdinal && this._isExceedsItemsLimit(dropNode, dragObject, dragMembers) || this._isSwapValueSlotItemsWithMultiMeasures(dropNode, dragObject);
  1059. },
  1060. /**
  1061. * @return true if the new drop object only has members from projected columns
  1062. */
  1063. _dragMembersFromProjectedColumnsOnly: function _dragMembersFromProjectedColumnsOnly(targetSlot, dragColumns, dragMembers) {
  1064. // 1. Return false if dragMembers is empty
  1065. if (!dragMembers || _.isEmpty(dragMembers)) {
  1066. return false;
  1067. }
  1068. // 2. Check if there is a dragColumn that is NOT built from drag and drop members
  1069. var hasNonProjectedColumn = _.some(dragColumns, function (column) {
  1070. return !dragMembers[column.getId()] || !dragMembers[column.getId()].length;
  1071. });
  1072. // 3. Check all dragMembers are from already projected columns of the target slot if the first condition passes
  1073. if (!hasNonProjectedColumn) {
  1074. var projectedColumns = [];
  1075. _.each(targetSlot.getDataItemList(), function (dataItem) {
  1076. var metadataColumn = dataItem.getMetadataColumn();
  1077. if (metadataColumn) {
  1078. projectedColumns.push(metadataColumn.getId());
  1079. }
  1080. });
  1081. hasNonProjectedColumn = _.some(Object.keys(dragMembers), function (columnId) {
  1082. return projectedColumns.indexOf(columnId) === -1;
  1083. });
  1084. }
  1085. return !hasNonProjectedColumn;
  1086. },
  1087. /**
  1088. * A slot can set the maximum number of the items it contains.
  1089. * Particularly, for slot that can accept stackItems, slot definition
  1090. * has a maxStackItems attribute that specifies a maximum number of
  1091. * stackItems (from VIPR). For Vizs such as Xtab, Grid, Summary that does
  1092. * not support stackItems attribute, to avoid confusion, we use maxItems attribute
  1093. * to set the limit. By default, VIPR viz does not support maxItems attribute.
  1094. **/
  1095. _isExceedsItemsLimit: function _isExceedsItemsLimit(dropNode, dragObject, dragMembers) {
  1096. var slotId = $(dropNode).is('.slot') ? $(dropNode).attr('data-slot-id') : $(dropNode).parents('.slot').attr('data-slot-id');
  1097. if (!slotId) {
  1098. return false;
  1099. }
  1100. var targetSlot = this.slotsApi.getSlot(slotId);
  1101. var slotDef = targetSlot.getDefinition();
  1102. var limit = slotDef.getMaxItems() || -1;
  1103. if (limit < 0) {
  1104. return false;
  1105. }
  1106. var nCountOfItemsToDrop = 1;
  1107. if (dragObject.data.items) {
  1108. if (!dragMembers || _.isEmpty(dragMembers)) {
  1109. nCountOfItemsToDrop = dragObject.data.items.length;
  1110. } else {
  1111. // Do not double count member columns that already exist.
  1112. nCountOfItemsToDrop = 0;
  1113. var memberParentIds = Object.keys(dragMembers);
  1114. var mappedItemIds = this._getMappedItemsOfTargetSlot(targetSlot);
  1115. var diffs = memberParentIds.filter(function (id) {
  1116. return mappedItemIds.indexOf(id) === -1;
  1117. });
  1118. nCountOfItemsToDrop = diffs.length;
  1119. }
  1120. }
  1121. var nMappedItems = targetSlot.getDataItemList().length;
  1122. if ($(dropNode).hasClass('listitem') && nCountOfItemsToDrop === 1) {
  1123. return false;
  1124. }
  1125. return nCountOfItemsToDrop + nMappedItems > limit;
  1126. },
  1127. _getMappedItemsOfTargetSlot: function _getMappedItemsOfTargetSlot(slot) {
  1128. var mappedItems = [];
  1129. slot.getDataItemList().forEach(function (dataItem) {
  1130. if (dataItem.getMetadataColumn) {
  1131. mappedItems.push(dataItem.getMetadataColumn().getId());
  1132. }
  1133. });
  1134. return mappedItems;
  1135. },
  1136. _isSwapValueSlotItemsWithMultiMeasures: function _isSwapValueSlotItemsWithMultiMeasures(dropNode, dragObject) {
  1137. var dragObjectMappingId = dragObject && dragObject.data && dragObject.data.mappingId ? dragObject.data.mappingId : null;
  1138. var dropNodeSlotId = $(dropNode).is('.slot') ? $(dropNode).attr('data-slot-id') : $(dropNode).parents('.slot').attr('data-slot-id');
  1139. var isDropNodeMultiMeasuresSeries = $(dropNode).hasClass('listitem') && $(dropNode)[0].dataset && $(dropNode)[0].dataset.mappingId === '_multiMeasuresSeries';
  1140. var dragObjectSlotId = dragObject && dragObject.data ? dragObject.data.slotId : null;
  1141. var dropSlotAPI = dropNodeSlotId && this.slotsApi.getSlot(dropNodeSlotId).getDefinition();
  1142. var dragSlotAPI = dragObjectSlotId && this.slotsApi.getSlot(dragObjectSlotId).getDefinition();
  1143. // can not drag a multimeasure to a measure slot or replace a multmeasure dataItem (swapping is allowed)
  1144. return dragObjectMappingId === '_multiMeasuresSeries' && dropSlotAPI.getType() === 'ordinal' || (!dragSlotAPI || dragSlotAPI.getType() === 'ordinal') && isDropNodeMultiMeasuresSeries;
  1145. },
  1146. _addDropTargets: function _addDropTargets() {
  1147. var _this5 = this;
  1148. // Add DnD handlers
  1149. var fAccept = this.accepts.bind(this);
  1150. var fAcceptContext = this.acceptsContext.bind(this);
  1151. var fOnDrop = this.onDrop.bind(this);
  1152. var fOnDropContext = this.onDropContext.bind(this);
  1153. var fOnDragEnter = this.onDragEnter.bind(this);
  1154. var dndManager = this._dndManager;
  1155. var onDragLeave = function onDragLeave(dragObject, dropNode) {
  1156. var $dropNode = $(dropNode);
  1157. $dropNode.removeClass('draggedOn');
  1158. $dropNode.find('.dropfirst').removeClass('draggedOn');
  1159. };
  1160. //replace/swap
  1161. this.$('ul.fields .listitem').map(function (index, el) {
  1162. dndManager.addDropTarget(el, {
  1163. accepts: fAccept,
  1164. onDragEnter: fOnDragEnter,
  1165. onDragLeave: onDragLeave,
  1166. onDrop: fOnDrop,
  1167. priority: function priority() {
  1168. var $widget = _this5.$el.closest('.liveWidget');
  1169. return $widget.hasClass('widgetExpanded') ? 1 : 0;
  1170. }
  1171. });
  1172. });
  1173. //Add after
  1174. this.$('ul.fields .dropafter').map(function (index, el) {
  1175. dndManager.addDropTarget(el, {
  1176. accepts: fAccept,
  1177. onDragEnter: fOnDragEnter,
  1178. onDragLeave: onDragLeave,
  1179. onDrop: fOnDrop,
  1180. priority: function priority() {
  1181. var $widget = _this5.$el.closest('.liveWidget');
  1182. return $widget.hasClass('widgetExpanded') ? 1 : 0;
  1183. }
  1184. });
  1185. });
  1186. //add first
  1187. this.$('ul.fields .slot').map(function (index, el) {
  1188. dndManager.addDropTarget(el, {
  1189. accepts: fAccept,
  1190. onDragEnter: fOnDragEnter,
  1191. onDragLeave: onDragLeave,
  1192. onDrop: fOnDrop,
  1193. priority: function priority() {
  1194. var $widget = _this5.$el.closest('.liveWidget');
  1195. return $widget.hasClass('widgetExpanded') ? 1 : 0;
  1196. }
  1197. });
  1198. });
  1199. //Filter stuff
  1200. this.$('ul.fields .localFilter').map(function (index, el) {
  1201. dndManager.addDropTarget(el, {
  1202. accepts: fAcceptContext,
  1203. onDragEnter: fOnDragEnter,
  1204. onDragLeave: onDragLeave,
  1205. onDrop: fOnDropContext,
  1206. priority: 2 //When filter container and slots overlaps (slots list has scrollbars),
  1207. //filter container takes priority to accept the dropped in items
  1208. });
  1209. });
  1210. },
  1211. _needToReRender: function _needToReRender(slots, filters, graphic) {
  1212. // Optimization - Build a key based on what is shown as data slots.
  1213. var sRenderingKey = this._buildRenderingKey(slots, filters, graphic);
  1214. if (this._lastRender !== sRenderingKey) {
  1215. return true;
  1216. }
  1217. this._lastRender = sRenderingKey;
  1218. return false;
  1219. },
  1220. /**
  1221. * Returns a boolean to indicate whether the dragObject position is outside focus mode UI
  1222. *
  1223. * @param {object} dragObject - the drag object object context
  1224. *
  1225. * @return {boolean} true if the drag position is outside of focus mode UI else return false
  1226. */
  1227. _isOutsideFocusModeBoundary: function _isOutsideFocusModeBoundary(dragObject) {
  1228. dragObject = dragObject || {};
  1229. if (dragObject.position) {
  1230. var position = dragObject.position;
  1231. if (position.x < this.focusModeBoundingClientRect.left || position.x > this.focusModeBoundingClientRect.right || position.y < this.focusModeBoundingClientRect.top || position.y > this.focusModeBoundingClientRect.bottom) {
  1232. return true;
  1233. }
  1234. }
  1235. return false;
  1236. },
  1237. _buildRenderingKey: function _buildRenderingKey(slots, filters, graphic) {
  1238. var visId = this._getVisAPI().getDefinition().id;
  1239. // We use that key to prevent rendering the same UI multiple times because Rave can fire many rendering events.
  1240. var sRenderingKey = _.reduce(slots, function (key, s) {
  1241. s._unavailable = s.unavailable ? 1 : 0;
  1242. var slotsKey = toStringKey(key, s, ['id', 'icon', 'filterString', '_unavailable']);
  1243. return _.reduce(s.mappings, function (key, m) {
  1244. return toStringKey(key, m, ['columnId', 'mappingId']);
  1245. }, slotsKey);
  1246. }, visId);
  1247. sRenderingKey = _.reduce(filters, function (key, f) {
  1248. return toStringKey(key, f, ['id', 'filterString']);
  1249. }, sRenderingKey);
  1250. sRenderingKey = _.reduce(graphic, function (key, v) {
  1251. return toStringKey(key, v, ['content', 'fillColor', 'borderColor', 'currentScaleOption']);
  1252. }, sRenderingKey);
  1253. return sRenderingKey;
  1254. },
  1255. /**
  1256. * Brings up the UI to create a new context-type filter (ie: filter on a non-visible item).
  1257. * @param filterColumnId - the columnId of the filter to edit.
  1258. * @param node - the node to guide placement of the dialog.
  1259. * @param forceOpen - true if force open the filter dialog.
  1260. */
  1261. _setContextFilter: function _setContextFilter(metadataColumn, node, forceOpen, transactionToken) {
  1262. DynamicFileLoader.load(['dashboard-analytics/visualizations/interactions/FilterDropAction']).then(function (modules) {
  1263. var FilterDropAction = modules[0];
  1264. var fAction = this.interactivityController.getActionHelper().getNewFilterActionForContextColumn(FilterDropAction, metadataColumn);
  1265. if (!forceOpen && metadataColumn.isNamedSet()) {
  1266. var options = this._convertTransactionTokenToLegacyOptions(transactionToken);
  1267. this.localFilters.addFilter(fAction.itemContext, {
  1268. command: 'replace',
  1269. exclude: false,
  1270. valueDataItem: metadataColumn.getId(),
  1271. type: metadataColumn.isProperty() ? 'display' : undefined
  1272. });
  1273. this.localFilters.allFilterModificationComplete(options);
  1274. } else {
  1275. var sDialogModule = fAction.getEditorModuleName();
  1276. var viewOptions = fAction.getViewOptions();
  1277. DynamicFileLoader.load([sDialogModule]).then(function (modules) {
  1278. var view = new modules[0](viewOptions);
  1279. var preloadDone = view.preload ? view.preload() : Promise.resolve();
  1280. preloadDone.then(this._createContextFilter.bind(this, view, node)).then(function () {
  1281. view.renderCallBack(this._actionMenuToolbar);
  1282. }.bind(this));
  1283. }.bind(this));
  1284. }
  1285. }.bind(this));
  1286. return;
  1287. },
  1288. _createContextFilter: function _createContextFilter(view, element) {
  1289. var actions = [{
  1290. responsive: false,
  1291. editable: false,
  1292. changedAction: null,
  1293. subView: view,
  1294. type: 'SubView'
  1295. }];
  1296. //If it's from a dropzone open it at the label level
  1297. var node = $(element).find('.dropfirst');
  1298. if (node.length === 0) {
  1299. //If it's a click on a existing dataItem
  1300. node = $(element).find('.mappingLabel');
  1301. }
  1302. var toolbarOptions = {
  1303. textOnly: true,
  1304. container: $('body'),
  1305. notCentered: true,
  1306. popoverClass: 'popover actionToolbarPopover text'
  1307. };
  1308. this._setToolbarForSlot(actions, node, stringResources.get('toolbarActionFilter'), toolbarOptions);
  1309. },
  1310. _setToolbarForSlot: function _setToolbarForSlot(aActions, node, sLabel, toolbarOptions) {
  1311. var authToolbarOptions = _.extend({}, toolbarOptions, {
  1312. dataSlotsView: this
  1313. });
  1314. var toolbar = new DataSlotsViewAuthoringToolbar(authToolbarOptions);
  1315. toolbar.setName(sLabel);
  1316. toolbar.addItems(aActions);
  1317. toolbar.setSelectionContext([node]);
  1318. toolbar.show();
  1319. node.addClass('selected');
  1320. var target = node[0];
  1321. toolbar.on('toolbar:remove', this._onToolBarRemove.bind(this));
  1322. //Call set focus here to avoid flashing the selected node
  1323. toolbar.on('toolbar:show:before', this.setAddFocus.bind(this, { target: target }));
  1324. this._actionMenuToolbar = toolbar;
  1325. // Update the actions with the toolbar in case they need to exit during certain steps.
  1326. aActions.forEach(function (action) {
  1327. if (action && action.setToolbar) {
  1328. action.setToolbar(toolbar);
  1329. }
  1330. });
  1331. },
  1332. /**
  1333. * Brings up the UI to create a new filter for a visible slot.
  1334. * @param slot - the data slot to filter (ie: 'category' or 'xAxis')
  1335. * @param node - the node to guide placement of the dialog.
  1336. */
  1337. _setSlotMenuActions: function _setSlotMenuActions(slot, node) {
  1338. // WACA: TODO filters
  1339. // _setSlotFilter: function(slot, node) {
  1340. if (!slot) {
  1341. return;
  1342. }
  1343. var mapIndex = parseInt($(node).attr('map-index'));
  1344. return this.interactivityController.getActionHelper().getActionsForSlots([slot], mapIndex, node).then(function (actions) {
  1345. var aToolbarActions = [];
  1346. _.each(actions, function (action) {
  1347. aToolbarActions = aToolbarActions.concat(action.getAvailableActions());
  1348. });
  1349. aToolbarActions.forEach(function (action) {
  1350. if (action.order === undefined) {
  1351. action.order = ActionTypes[action.name];
  1352. }
  1353. });
  1354. aToolbarActions.sort(function (a, b) {
  1355. return a.order - b.order;
  1356. });
  1357. node = $(node).find('.menuoverflow');
  1358. var toolbarOptions = {
  1359. textOnly: true,
  1360. container: $('body'),
  1361. placement: 'right',
  1362. notCentered: true,
  1363. popoverClass: 'popover actionToolbarPopover text',
  1364. modal: true
  1365. };
  1366. this._setToolbarForSlot(aToolbarActions, node, null, toolbarOptions);
  1367. }.bind(this));
  1368. },
  1369. /**
  1370. * Handler when user clicks on a filter icon to edit a filter for either a visible slot or context entry.
  1371. * If this button represents a visible slot, a filter will be added on the slot.
  1372. * If not, a context filter will be added on the column.
  1373. * Normally, these two are the same but can differ when the user has used an attribute column
  1374. * in an ordinal slot (where the filter to be added would be a count type filter).
  1375. */
  1376. popupSlotOptions: function popupSlotOptions(e) {
  1377. if (this._canLaunchAuthoringToolbar(e)) {
  1378. var $slot = $(this.getTarget(e.target, 'listitem'));
  1379. var slotId = $slot.attr('data-slot-id');
  1380. this.launchedToolbarNodeId = $slot.attr('data-mapping-id');
  1381. return this._setSlotMenuActions(this.visualization.getSlots().getSlot(slotId), $slot[0]);
  1382. } else {
  1383. this._clearToolbar();
  1384. }
  1385. },
  1386. expandCollapseLayers: function expandCollapseLayers(e) {
  1387. var $layer = $('div.completeLayer.' + e.target.id.replace('.', '\\.'));
  1388. var $buttonDiv = $layer.find('.layerExpanderButton');
  1389. var $svg = $buttonDiv.find('svg');
  1390. var label;
  1391. if ($layer.hasClass('expanded')) {
  1392. $layer.removeClass('expanded');
  1393. $layer.addClass('collapsed');
  1394. $layer.attr('aria-expanded', 'false');
  1395. this._collapseLayer(e.target.id);
  1396. label = stringResources.get('evExpand');
  1397. } else if ($layer.hasClass('collapsed')) {
  1398. $layer.removeClass('collapsed');
  1399. $layer.addClass('expanded');
  1400. $layer.attr('aria-expanded', 'true');
  1401. this._expandLayer(e.target.id);
  1402. label = stringResources.get('evCollapse');
  1403. }
  1404. $buttonDiv.attr('title', label);
  1405. $svg.attr('title', label);
  1406. $svg.attr('aria-label', label);
  1407. },
  1408. openContextFilter: function openContextFilter(e) {
  1409. var $filter = $(this.getTarget(e.target, 'listitem'));
  1410. var uniqueId = $filter.attr('data-mapping-id');
  1411. var columnId = $filter.attr('data-column-id');
  1412. var filterId = $filter.attr('data-filter-id'); //Note: data-filter-id is the uniqueId of the FilterEntry.
  1413. var model = this._getVisAPI();
  1414. if (filterId) {
  1415. var filterEntry = model.localFilters.getFilterEntry({
  1416. id: filterId
  1417. });
  1418. if (filterEntry && !model.isFilterEditable(filterEntry)) {
  1419. //Don't allow click-edit to edit an 'extra filter' (ie: an additional filter created by keep/exclude)
  1420. return;
  1421. }
  1422. }
  1423. var metadataColumn = model.getMetadataColumn(columnId);
  1424. metadataColumn.uniqueId = uniqueId;
  1425. this._setContextFilter(metadataColumn, $filter, true);
  1426. },
  1427. popupFilterOptions: function popupFilterOptions(e) {
  1428. if (!this._canLaunchAuthoringToolbar(e)) {
  1429. this._clearToolbar();
  1430. return;
  1431. }
  1432. var $filter = $(this.getTarget(e.target, 'listitem'));
  1433. var columnId = $filter.attr('data-column-id');
  1434. var model = this._getVisAPI();
  1435. var metadataColumn = model.getMetadataColumn(columnId);
  1436. this.launchedToolbarNodeId = $filter.attr('data-filter-id'); //Note: data-filter-id is the uniqueId of the FilterEntry.
  1437. if (metadataColumn) {
  1438. this.interactivityController.getActionHelper().getActionsForLocalFilter(metadataColumn, this.launchedToolbarNodeId).then(function (actions) {
  1439. var aToolbarActions = [];
  1440. _.each(actions, function (action) {
  1441. aToolbarActions = aToolbarActions.concat(action.getAvailableActions());
  1442. });
  1443. aToolbarActions.sort(function (a, b) {
  1444. return a.order - b.order;
  1445. });
  1446. var node = $filter.find('.menuoverflow');
  1447. var toolbarOptions = {
  1448. textOnly: true,
  1449. container: $('body'),
  1450. placement: 'bottom',
  1451. notCentered: true,
  1452. popoverClass: 'popover actionToolbarPopover text'
  1453. };
  1454. this._setToolbarForSlot(aToolbarActions, node, null, toolbarOptions);
  1455. }.bind(this));
  1456. return;
  1457. }
  1458. },
  1459. /**
  1460. * Handler when user clicks on local filter box to set focus.
  1461. */
  1462. setAddFocus: function setAddFocus(e) {
  1463. var focusedItem = this.$('.addFocus');
  1464. focusedItem.removeClass('addFocus');
  1465. var $target = $(e.target);
  1466. $target.addClass('addFocus');
  1467. },
  1468. /**
  1469. * Returns true if data slot has the addFocus class
  1470. * addFocus class indicates slot where user wants model data inserted
  1471. */
  1472. isSlotSelected: function isSlotSelected() {
  1473. var resp = false;
  1474. var addFocusSlot = this.$el.find('.addFocus');
  1475. if (addFocusSlot.length > 0) {
  1476. resp = true;
  1477. }
  1478. return resp;
  1479. },
  1480. /**
  1481. * Returns true if data slot that has the addFocus class also has class filters
  1482. * this combination indicated the slot is the Local Filters slot
  1483. */
  1484. isSelectedSlotLocalFilters: function isSelectedSlotLocalFilters() {
  1485. var resp = false;
  1486. var addFocusSlot = this.$el.find('.addFocus');
  1487. if (addFocusSlot.length > 0 && addFocusSlot.hasClass('filters')) {
  1488. resp = true;
  1489. }
  1490. return resp;
  1491. },
  1492. /**
  1493. * on scrolling the panel, close the Action menu if it is open, thus preventing it from dangling
  1494. *
  1495. */
  1496. onScrollSlotsPanel: function onScrollSlotsPanel() {
  1497. this._clearToolbar();
  1498. },
  1499. _canLaunchAuthoringToolbar: function _canLaunchAuthoringToolbar(event) {
  1500. var $listItem = $(this.getTarget(event.target, 'listitem.columnName'));
  1501. var id = $listItem.attr('data-mapping-id') || $listItem.attr('data-filter-id');
  1502. var hasToolBar = this.launchedToolbarNodeId === id;
  1503. return hasToolBar ? false : true;
  1504. },
  1505. /**
  1506. * Return a boolean indicating whether is dragging or not
  1507. *
  1508. * @param {event} - JQuery event object
  1509. *
  1510. * @return {boolen} true if is dragging else return false
  1511. */
  1512. _beginDragAndDrop: function _beginDragAndDrop(event) {
  1513. if (!this.initialDragAndDropPosition) {
  1514. return false;
  1515. }
  1516. var currentPosition = DomUtils.getEventPos(event);
  1517. return Math.abs(this.initialDragAndDropPosition.pageX - currentPosition.pageX) >= DND_X_THRESHOLD || Math.abs(this.initialDragAndDropPosition.pageY - currentPosition.pageY) >= DND_Y_THRESHOLD;
  1518. },
  1519. _setIsImplicitClosingTheToolbar: function _setIsImplicitClosingTheToolbar(value) {
  1520. this.isImplicitClosingTheBoolbar = value;
  1521. },
  1522. _updateMissingFilters: function _updateMissingFilters() {
  1523. var _this6 = this;
  1524. this.widget.getUnavailableLocalFilter();
  1525. if (this.widget.visModelManager.filterSupport.missingFilters.length === 0) {
  1526. this.missingFilters.length = 0;
  1527. } else {
  1528. this.widget.visModelManager.filterSupport.missingFilters.forEach(function (filterFromWidget) {
  1529. //prevent duplicate
  1530. var isMissingExist = _.find(_this6.missingFilters, function (filterColumnId) {
  1531. return filterFromWidget.columnId === filterColumnId;
  1532. });
  1533. if (!isMissingExist) {
  1534. _this6.missingFilters.push(filterFromWidget.columnId);
  1535. }
  1536. });
  1537. }
  1538. },
  1539. _isSharingSlots: function _isSharingSlots() {
  1540. var slotList = this.slotsApi.getSlotList();
  1541. if (slotList && slotList.length > 0) {
  1542. var sharingSlots = this.slotsApi.getSlotList().filter(function (slotApi) {
  1543. return slotApi.getDefinition().getDatasetIdList().length > 1;
  1544. });
  1545. return sharingSlots.length > 0;
  1546. }
  1547. return false;
  1548. },
  1549. _getDatasets: function _getDatasets(datasets) {
  1550. // This is used for UI only not the acutal dataset
  1551. return !this._isSharingSlots() && datasets.length > 0 ? datasets : [{ name: 'data' }];
  1552. }
  1553. });
  1554. return DataSlotsView;
  1555. });
  1556. //# sourceMappingURL=DataSlotsView.js.map