Slot.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  4. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  5. /**
  6. * Licensed Materials - Property of IBM
  7. * IBM Cognos Products: Dashboard
  8. * (C) Copyright IBM Corp. 2018, 2021
  9. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  10. */
  11. define(['underscore', '../../lib/@waca/dashboard-common/dist/core/APIFactory', './SlotAPISpec', '../SlotAPI', '../../visualizations/interactions/BinningActionsUtils', '../../apiHelpers/SlotAPIHelper', '../../widgets/livewidget/nls/StringResources', '../../util/TransactionUtil'], function (_, APIFactory, SlotAPISpec, SlotAPI, BinningActionsUtils, SlotAPIHelper, StringResources, TransactionUtil) {
  12. var SlotImpl = function (_SlotAPISpec) {
  13. _inherits(SlotImpl, _SlotAPISpec);
  14. function SlotImpl(slotDefinitionAPI, filterSupport, slotsImpl, transaction, locale, visualizationImpl, dataModel, slotMappingModel) {
  15. _classCallCheck(this, SlotImpl);
  16. // @todo remove localFilters, slotDfn (for now used in creating MappedDataItem) plus SlotAPI
  17. var _this = _possibleConstructorReturn(this, _SlotAPISpec.call(this));
  18. _this.slotDefinitionAPI = slotDefinitionAPI;
  19. // @todo need to revise by using events
  20. _this.filterSupport = filterSupport;
  21. _this.metadataManager = filterSupport.getMetadata();
  22. _this.transaction = transaction;
  23. _this.locale = locale;
  24. _this.slotsImpl = slotsImpl;
  25. _this.visualizationImpl = visualizationImpl;
  26. _this.dataModel = dataModel;
  27. _this.slotMappingModel = slotMappingModel;
  28. _this.api = APIFactory.createAPI(_this, [SlotAPI]);
  29. return _this;
  30. }
  31. SlotImpl.prototype.setDefinition = function setDefinition(slotDefinitionAPI) {
  32. this.slotDefinitionAPI = slotDefinitionAPI;
  33. };
  34. SlotImpl.prototype.destroy = function destroy() {
  35. this.slotDefinitionAPI = null;
  36. this.filterSupport = null;
  37. this.metadataManager = null;
  38. this.transaction = null;
  39. this.locale = null;
  40. this.slotsImpl = null;
  41. this.visualizationImpl = null;
  42. this.dataModel = null;
  43. this.slotMappingModel = null;
  44. this.api = null;
  45. };
  46. SlotImpl.prototype.getAPI = function getAPI() {
  47. return this.api;
  48. };
  49. SlotImpl.prototype.getId = function getId() {
  50. return this.getDefinition().getId();
  51. };
  52. SlotImpl.prototype.getLocalFilters = function getLocalFilters() {
  53. return this.filterSupport.getLocalFilters();
  54. };
  55. SlotImpl.prototype.getDataSource = function getDataSource() {
  56. return this.slotsImpl.getDataSource();
  57. };
  58. SlotImpl.prototype.resolveDataViewId = function resolveDataViewId() {
  59. var viewId = void 0;
  60. // step 1 : check the first item (if any) that belongs to this slot
  61. var dataItemList = this.getDataItemList();
  62. if (dataItemList.length > 0) {
  63. viewId = this.dataModel.getDataViewIdForDataItem(dataItemList[0].getId());
  64. }
  65. // Step 2: If we have no mapped dataitem, search the slots that belong to the same dataset
  66. if (!viewId) {
  67. var slot = void 0;
  68. var slotDefinition = this.getDefinition();
  69. var slotDefinitionList = this.visualizationImpl.getDefinition().getSlotList();
  70. for (var i = 0; i < slotDefinitionList.length; i++) {
  71. if (slotDefinition.getId() !== slotDefinitionList[i].getId() && slotDefinition.getDatasetIdList()[0] === slotDefinitionList[i].getDatasetIdList()[0]) {
  72. // the slots are part of the same dataset
  73. // see if it has mapped dataitems.. then use the same view
  74. slot = this.slotsImpl.getSlot(slotDefinitionList[i].getId());
  75. dataItemList = slot.getDataItemList();
  76. if (dataItemList.length > 0) {
  77. viewId = this.dataModel.getDataViewIdForDataItem(dataItemList[0].getId());
  78. break;
  79. }
  80. }
  81. }
  82. }
  83. if (!viewId) {
  84. // we could not find a view.. we create a new one
  85. var dataView = this.dataModel.createDataView();
  86. var dataSource = this.visualizationImpl.getDataSource();
  87. // default the modelRef if we have other views
  88. if (dataSource) {
  89. this.dataModel.setModelRefInAllViews(dataSource.getId());
  90. }
  91. viewId = dataView.id;
  92. }
  93. return viewId;
  94. };
  95. /**
  96. * Return the slotImpl associated with the multi-measure series. We use the implementation so that
  97. * any operations are considered inernal and no events will be generated.
  98. */
  99. SlotImpl.prototype.getMultiMeasuresSeriesSlot = function getMultiMeasuresSeriesSlot() {
  100. var multiMeasureSlot = void 0;
  101. var dataset = this.getDefinition().getDatasetIdList()[0];
  102. var customMultiMeasureSlotId = this.slotMappingModel.getMultiMeasureSlotId(dataset);
  103. if (customMultiMeasureSlotId) {
  104. // slot may not exist, e.g changing visualization from one with slot series to one with color.
  105. multiMeasureSlot = this.slotsImpl.getSlotImpl(customMultiMeasureSlotId);
  106. }
  107. if (!multiMeasureSlot) {
  108. var defaultMultiMeasureSlot = SlotAPIHelper.getMultiMeasureSeriesSlot(this.visualizationImpl.getAPI(), dataset).getId();
  109. multiMeasureSlot = this.slotsImpl.getSlotImpl(defaultMultiMeasureSlot);
  110. }
  111. return multiMeasureSlot;
  112. };
  113. SlotImpl.prototype.getMultiMeasureSeriesPosition = function getMultiMeasureSeriesPosition() {
  114. var dataset = this.getDefinition().getDatasetIdList()[0];
  115. return this.slotMappingModel.getMultiMeasurePosition(dataset);
  116. };
  117. /**
  118. * Check how many measure we have in the slot and decide if we need to add, remove or update the measure series slot
  119. */
  120. SlotImpl.prototype._processMultiMeasure = function _processMultiMeasure() {
  121. if (!this.getDefinition().getProperty('multiMeasure')) {
  122. return;
  123. }
  124. var targetSlot = this.getMultiMeasuresSeriesSlot();
  125. if (targetSlot) {
  126. var currentDataItemList = this.getDataItemList();
  127. var multiMeasuresLabel = StringResources.get('MeasuresGroupCaption', {
  128. count: currentDataItemList.length
  129. });
  130. var multiMeasureDataItemId = SlotAPIHelper.MULTI_MEASURES_SERIES + '_' + targetSlot.getDefinition().getDatasetIdList()[0];
  131. var multiMeasureRequired = currentDataItemList.length > 1;
  132. var multiMeasureDataItem = this.slotsImpl.getDataItemImpl(multiMeasureDataItemId);
  133. var createMultiMeasureDataItem = function () {
  134. // Use the implementation to keep the actions internal (i.e. no events)
  135. this.slotsImpl.createDataItems([{
  136. id: multiMeasureDataItemId,
  137. columnId: SlotAPIHelper.MULTI_MEASURES_SERIES,
  138. itemLabel: multiMeasuresLabel
  139. }]);
  140. targetSlot.addDataItemsMapping([multiMeasureDataItemId], this.getMultiMeasureSeriesPosition());
  141. }.bind(this);
  142. var deleteMultiMeasureDataItem = function () {
  143. // Use the implementation to keep the actions internal (i.e. no events)
  144. this.slotsImpl.deleteDataItems([multiMeasureDataItemId]);
  145. targetSlot.removeDataItemsMapping([multiMeasureDataItemId]);
  146. }.bind(this);
  147. var updateMultiMeasureDataItem = function () {
  148. // Use the implementation to keep the actions internal (i.e. no events)
  149. this.slotsImpl.getDataItemImpl(multiMeasureDataItem.getId()).setLabel(multiMeasuresLabel);
  150. }.bind(this);
  151. if (multiMeasureRequired && multiMeasureDataItem) {
  152. updateMultiMeasureDataItem();
  153. } else if (multiMeasureRequired && !multiMeasureDataItem) {
  154. createMultiMeasureDataItem();
  155. } else if (!multiMeasureRequired && multiMeasureDataItem) {
  156. deleteMultiMeasureDataItem();
  157. }
  158. }
  159. };
  160. SlotImpl.prototype.getDataItemIndex = function getDataItemIndex(id) {
  161. var slotModel = this.slotMappingModel.getSlot(this.getId());
  162. return slotModel.dataItems.indexOf(id);
  163. };
  164. // Return the actual instance and not just the API interface
  165. SlotImpl.prototype.getDataItemImpl = function getDataItemImpl(dataItemId) {
  166. var impl = void 0;
  167. // Search the mapping model
  168. var slotModel = this.slotMappingModel.getMappedSlot(dataItemId);
  169. if (slotModel && slotModel.name === this.getId()) {
  170. // the dataItem is mapped to this slot
  171. impl = this.slotsImpl.getDataItemImpl(dataItemId);
  172. }
  173. return impl;
  174. };
  175. SlotImpl.prototype.getDataItem = function getDataItem(dataItemId) {
  176. var impl = this.getDataItemImpl(dataItemId);
  177. return impl ? impl.getAPI() : null;
  178. };
  179. SlotImpl.prototype.addDataItems = function addDataItems(dataItemSpecList, position, transactionToken) {
  180. var subTransaction = this.transaction.startTransaction(transactionToken);
  181. // Use the API to trigger the event
  182. var dataItemList = this.slotsImpl.getAPI().createDataItems(dataItemSpecList, subTransaction);
  183. this.getAPI().addDataItemsMapping(dataItemList.map(function (dataItem) {
  184. return dataItem.getId();
  185. }), position, subTransaction);
  186. this.transaction.endTransaction(subTransaction);
  187. return dataItemList;
  188. };
  189. SlotImpl.prototype.removeDataItems = function removeDataItems(dataItemIdList, transactionToken) {
  190. var _this2 = this;
  191. var subTransaction = this.transaction.startTransaction(transactionToken);
  192. this._removeSelectionsOnUnusedDataItems(dataItemIdList, subTransaction);
  193. this._removedUnmappedOrdinalSlotsWithFilters(dataItemIdList.map(function (id) {
  194. var dataItem = _this2.slotsImpl.getDataItemImpl(id);
  195. if (dataItem) {
  196. return {
  197. columnId: dataItem.getColumnId(),
  198. aggregationType: dataItem.getAggregation()
  199. };
  200. }
  201. }), subTransaction);
  202. this.getAPI().removeDataItemsMapping(dataItemIdList, subTransaction);
  203. // Use the API to trigger the event
  204. this.slotsImpl.getAPI().deleteDataItems(dataItemIdList, subTransaction);
  205. this.transaction.endTransaction(subTransaction);
  206. };
  207. SlotImpl.prototype._removeSelectionsOnUnusedDataItems = function _removeSelectionsOnUnusedDataItems(dataItemIdList, transactionToken) {
  208. var _this3 = this;
  209. var columnIdList = dataItemIdList.map(function (id) {
  210. return _this3.getDataItemImpl(id).getColumnId();
  211. });
  212. this.filterSupport.clearSelectionsByDataItemIds(columnIdList, TransactionUtil.transactionTokenToOptions(transactionToken));
  213. };
  214. SlotImpl.prototype._removedUnmappedOrdinalSlotsWithFilters = function _removedUnmappedOrdinalSlotsWithFilters(unusedFilterRef, transactionToken) {
  215. var localFilters = this.getLocalFilters();
  216. var filterEntryToRemove = _.find(localFilters.models, function (filterEntry) {
  217. if (filterEntry.isMissing) {
  218. return _.findWhere(unusedFilterRef, _.pick(filterEntry, 'columnId'));
  219. } else if (filterEntry.aggregationType) {
  220. /* Remove obsolete ordinal filter when there is no match for both column id and aggregation type.
  221. 1. when the data item is not projected anymore
  222. - eg. projected data in the slot but no aggregation type matching
  223. - eg. projected data items with ids 'col1' (sum), 'col1' (count), col2' but the filter was set on 'col1' (avg) */
  224. return _.findWhere(unusedFilterRef, _.pick(filterEntry, 'columnId', 'aggregationType'));
  225. } else {
  226. return _.findWhere(unusedFilterRef, _.pick(filterEntry, 'columnId'));
  227. }
  228. });
  229. if (filterEntryToRemove) {
  230. localFilters.removeFilterEntry(filterEntryToRemove);
  231. localFilters.allFilterModificationComplete(TransactionUtil.transactionTokenToOptions(transactionToken));
  232. }
  233. };
  234. SlotImpl.prototype.removeDataItemsMapping = function removeDataItemsMapping(dataItemIds) {
  235. var _this4 = this;
  236. var result = [];
  237. _.each(dataItemIds, function (dataItemId) {
  238. var dataItem = _this4.getDataItemImpl(dataItemId);
  239. if (dataItem) {
  240. _this4.slotMappingModel.unmapDataItemsFromSlots([dataItemId]);
  241. dataItem.setSlot(null);
  242. result.push(dataItem);
  243. } else {
  244. result.push(null);
  245. }
  246. });
  247. this._processMultiMeasure();
  248. return result;
  249. };
  250. SlotImpl.prototype.addDataItemsMapping = function addDataItemsMapping(dataItemIdList) {
  251. var _this5 = this;
  252. var position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
  253. var transactionToken = arguments[2];
  254. dataItemIdList.forEach(function (id, index) {
  255. var dataItem = _this5.slotsImpl.getDataItemImpl(id);
  256. // When dataItem is empty, we remove it from dataItemIdList, here is an example:
  257. // e.g. we now have this slots mapping
  258. // Length
  259. // - measure1
  260. // - measure2
  261. // Color
  262. // - Measures Group(2)
  263. //
  264. // now we move measure2 into `Color` and put it before `Measures Group(2)`, `setDataItems()` will be called with a
  265. // `dataItemIdList` including measure2's dataItemId and a multimeasure dataItemId, inside `setDataItems`, measure2 will be removed from original
  266. // slot(ie, Length) first, removing it will remove the related multimeasure data item(see _processMultiMeasure), then
  267. // `addDataItemsMappingthen` is called with the `dataItemIdList`, in this case, `dataItemIdList` contains the id of the deleted
  268. // multimeasure data item, so getDataItemImpl(id) will return null for the multimeasure data item.
  269. if (!dataItem) {
  270. dataItemIdList.splice(index, 1);
  271. return;
  272. }
  273. if (dataItem.getSlot()) {
  274. throw new Error('Cannot map an already mapped data item, it must to be unmapped first. The dataitem with id "' + id + '" is mapped to slot "' + dataItem.getSlot().getId() + '"');
  275. }
  276. });
  277. var actualPosition = position === -1 ? this.getDataItemList().length : position;
  278. this.dataModel.moveDataItemsToView(dataItemIdList, this.resolveDataViewId());
  279. this.slotMappingModel.mapDataItemsToSlot(dataItemIdList, this.getId(), position);
  280. dataItemIdList.forEach(function (id, index) {
  281. // If one of the dataitems is a measure series, then mark this slot as the preferred measure series slot.
  282. if (id.indexOf(SlotAPIHelper.MULTI_MEASURES_SERIES) > -1) {
  283. var dataset = _this5.getDefinition().getDatasetIdList()[0];
  284. _this5.slotMappingModel.setMultiMeasureSlotId(dataset, _this5.getId());
  285. _this5.slotMappingModel.setMultiMeasurePosition(dataset, actualPosition + index);
  286. }
  287. _this5.slotsImpl.getDataItemImpl(id).setSlot(_this5.getAPI(), transactionToken);
  288. });
  289. this._processMultiMeasure();
  290. };
  291. SlotImpl.prototype.getDataItemList = function getDataItemList() {
  292. var ignoreDefaultDataItemMapping = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  293. return this._getDataItemListWithOptions(ignoreDefaultDataItemMapping);
  294. };
  295. SlotImpl.prototype._getDataItemListWithOptions = function _getDataItemListWithOptions(ignoreDefaultDataItemMapping) {
  296. var _this6 = this;
  297. // TODO - livewidget cleanup - cache and free the list for performance
  298. var idList = this.slotMappingModel.getMappedDataItemIdList(this.getId());
  299. var list = idList.map(function (id) {
  300. var dataItem = _this6.slotsImpl.getDataItem(id);
  301. if (!dataItem) {
  302. throw new Error('The referenced data item \'' + id + '\' is not found in the widget.');
  303. }
  304. return dataItem;
  305. });
  306. if (list.length === 0 && !ignoreDefaultDataItemMapping) {
  307. var defaultFromSlot = this.getDefinition().getDefaultFromSlot();
  308. if (defaultFromSlot && defaultFromSlot.id) {
  309. var slot = this.slotsImpl.getSlot(defaultFromSlot.id);
  310. if (slot) {
  311. list = slot.getDataItemList();
  312. }
  313. }
  314. }
  315. return list;
  316. };
  317. SlotImpl.prototype.supportsColumns = function supportsColumns(metadataColumnList) {
  318. return !this.itemsNotSupported(metadataColumnList);
  319. };
  320. /**
  321. * @param metadata either an array of metadatacolumns or a single dataitemapi which both have access to their taxonomy(the class and family of the data column)
  322. * @returns a boolean whether the slot supports the taxonomy or datatype of the metadata item(s).
  323. * This method will check to make sure that the columns being dropped into the slots have the correct taxonomy class and family as per these rules:
  324. * 1) If the slot has no taxonomy defined, then it accepts all data columns
  325. * 2) If the slot is a lot/long, then it can accept columns that are also lat/long or if they do not have a taxonomy defined, then the slot will also accept numbers
  326. * 3) If the slot has a taxonomy defined which is not lat/long then it accepts everything except for lat/long columns
  327. */
  328. //jshint maxdepth:4
  329. SlotImpl.prototype.itemsNotSupported = function itemsNotSupported(metadata) {
  330. var slot = this.getDefinition();
  331. if (slot.getSubType()) {
  332. if (_.isArray(metadata)) {
  333. if (this._itemsNotSupportedFromTree(metadata)) {
  334. return true;
  335. }
  336. } else {
  337. if (this._itemsNotSupportedFromSlots(metadata)) {
  338. return true;
  339. }
  340. }
  341. }
  342. return false;
  343. };
  344. SlotImpl.prototype._isLatLong = function _isLatLong() {
  345. var slot = this.getDefinition();
  346. return slot.getSubType() === 'latitude' || slot.getSubType() === 'longitude';
  347. };
  348. SlotImpl.prototype._itemsNotSupportedFromTree = function _itemsNotSupportedFromTree(metadata) {
  349. var i;
  350. for (i = 0; i < metadata.length; i++) {
  351. var metadataItem = metadata[i];
  352. if (this._itemsNotSupportedHelper(metadataItem)) {
  353. return true;
  354. }
  355. }
  356. };
  357. //this block handles cases where you are swapping slots
  358. SlotImpl.prototype._itemsNotSupportedFromSlots = function _itemsNotSupportedFromSlots(metadata) {
  359. if (this._itemsNotSupportedHelper(metadata)) {
  360. return true;
  361. }
  362. };
  363. SlotImpl.prototype._itemsNotSupportedHelper = function _itemsNotSupportedHelper(metadataItem) {
  364. var slot = this.getDefinition();
  365. var taxonomy = metadataItem.getTaxonomyList() ? metadataItem.getTaxonomyList()[0] : null;
  366. if (this._isLatLong()) {
  367. if (taxonomy) {
  368. if (!(taxonomy.getFamily() === 'cLatitude' && slot.getSubType() === 'latitude' || taxonomy.getFamily() === 'cLongitude' && slot.getSubType() === 'longitude')) {
  369. return true;
  370. }
  371. } else if (!this._isSupportedDataType(metadataItem.getDataType())) {
  372. return true;
  373. }
  374. } else {
  375. if (taxonomy && (taxonomy.getFamily() === 'cLatitude' || taxonomy.getFamily() === 'cLongitude')) {
  376. return true;
  377. }
  378. }
  379. };
  380. SlotImpl.prototype._isSupportedDataType = function _isSupportedDataType(datatype) {
  381. if (this._isLatLong()) {
  382. return ['integer', 'decimal', 'double', 'float'].indexOf(datatype) > -1;
  383. }
  384. return true;
  385. };
  386. SlotImpl.prototype.getDefinition = function getDefinition() {
  387. return this.slotDefinitionAPI;
  388. };
  389. SlotImpl.prototype.isStacked = function isStacked() {
  390. var mappedDataItems = this.slotMappingModel.getMappedDataItemIdList(this.getId());
  391. return !!(mappedDataItems.length > 1 && this.getDefinition().isStackItems());
  392. };
  393. SlotImpl.prototype.hasUnavailableMetadataColumns = function hasUnavailableMetadataColumns() {
  394. var unavailableColumn = _.find(this.getDataItemList(), function (dataItem) {
  395. return dataItem.isColumnUnavailable();
  396. });
  397. return !!unavailableColumn;
  398. };
  399. SlotImpl.prototype._convertTransactionTokenToLegacyOptions = function _convertTransactionTokenToLegacyOptions(transactionToken) {
  400. var oldOptions = {};
  401. if (transactionToken) {
  402. oldOptions.payloadData = {
  403. transactionToken: transactionToken
  404. };
  405. if (transactionToken.transactionId) {
  406. oldOptions.payloadData.undoRedoTransactionId = transactionToken.transactionId;
  407. }
  408. }
  409. return oldOptions;
  410. };
  411. return SlotImpl;
  412. }(SlotAPISpec);
  413. return SlotImpl;
  414. });
  415. //# sourceMappingURL=Slot.js.map