LayoutPickerView.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. *
  5. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2019, 2020
  6. *
  7. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  8. */
  9. define(['../../lib/@waca/core-client/js/core-client/ui/core/View', '../../app/nls/StringResources', 'jquery', 'underscore', '../../lib/@waca/core-client/js/core-client/utils/Utils', 'text!./templates/LayoutPickerView.html', 'text!../layout/templates/listing.json', '../../lib/@waca/core-client/js/core-client/utils/Deferred', '../../lib/@waca/core-client/js/core-client/ui/KeyCodes', '../../lib/@waca/core-client/js/core-client/ui/Menu', '../../lib/@waca/core-client/js/core-client/utils/ContentFormatter', '../../lib/@waca/dashboard-common/dist/ui/dialogs/MessageBox', '../../lib/@waca/dashboard-common/dist/lib/@ba-ui-toolkit/ba-graphics/dist/icons-js/overflow-menu--horizontal_16', '../../lib/@waca/dashboard-common/dist/lib/@ba-ui-toolkit/ba-graphics/dist/illustrations-js/connect-images_128'], function (BaseView, stringResources, $, _, Utils, template, templateListing, Deferred, KeyCodes, Menu, ContentFormatter, MessageBox, contextMenuIcon, thumbnailPlaceholderIcon) {
  10. var LayoutPickerView = BaseView.extend({
  11. templateString: template,
  12. events: {
  13. 'primaryaction .layoutTemplateItem': 'onSelect',
  14. 'dblclick .layoutTemplateItem': 'onDoubleClick',
  15. 'keydown .layoutTemplateItem': 'onKeyDown',
  16. 'click .selected .layoutContextMenuButton': 'onContextMenuButtonClick'
  17. },
  18. init: function init(options) {
  19. options = options || {};
  20. LayoutPickerView.inherited('init', this, arguments);
  21. this.stringResources = options.stringResources || stringResources;
  22. var templates = options.templateListing || templateListing;
  23. this._showContextMenu = options.showContextMenu;
  24. this._dashboardTemplatesApi = options.dashboardTemplatesApi;
  25. // process the templates
  26. var defaultTemplates = this._processTemplates(JSON.parse(templates));
  27. // remove items from defaultTemplates that were passed in the excludeTemplates array
  28. if (options.excludeTemplates) {
  29. var newList = defaultTemplates.templates.filter(function (element) {
  30. return !_.contains(options.excludeTemplates, element.name);
  31. });
  32. defaultTemplates.templates = newList;
  33. }
  34. _.extend(this, _.defaults(options, defaultTemplates));
  35. this._thumbnailCache = [];
  36. this._addCustomTemplates(options.customTemplates);
  37. this._whenReady = new Deferred();
  38. this.whenReady = this._whenReady.promise;
  39. },
  40. _addCustomTemplates: function _addCustomTemplates(customTemplates) {
  41. var _this = this;
  42. (customTemplates || []).forEach(function (customTemplate) {
  43. _this.templates.push({
  44. label: customTemplate.defaultName,
  45. ariaLabel: customTemplate.defaultName,
  46. navigationTemplates: 'custom',
  47. location: customTemplate.location,
  48. isSelected: false,
  49. name: customTemplate.id,
  50. type: 'custom'
  51. });
  52. });
  53. },
  54. /**
  55. * This function takes the template object and sorts the array of templates so that the ones with
  56. * descriptions are on top as well as translates any labels and descriptions.
  57. *
  58. * @param templateListObj: Template object to sort.
  59. * @private
  60. * @returns Object: the sorted template object.
  61. **/
  62. _processTemplates: function _processTemplates(templateListObj) {
  63. var templateList = templateListObj.templates;
  64. var aWithDesc = [];
  65. var aWithoutDesc = [];
  66. for (var i = 0; i < templateList.length; i++) {
  67. // get the next template object
  68. var template = templateList[i];
  69. // translate the label
  70. if (template.label) {
  71. template.label = this.stringResources.get(template.label);
  72. }
  73. if (template.name) {
  74. template.ariaLabel = this.stringResources.get(template.name);
  75. }
  76. // sort by description into separate arrays
  77. if (template.description) {
  78. // translate the description and add to the array
  79. template.description = this.stringResources.get(template.description);
  80. aWithDesc.push(template);
  81. } else {
  82. // no description so just push to that array
  83. aWithoutDesc.push(template);
  84. }
  85. }
  86. return {
  87. 'templates': aWithDesc.concat(aWithoutDesc)
  88. };
  89. },
  90. render: function render() {
  91. this.$el.empty().addClass('layoutPickerView').attr('role', 'listbox').attr('aria-multiselectable', 'false').html(this.dotTemplate(this));
  92. this.filterTemplates();
  93. this.generatePreviews();
  94. },
  95. onSelect: function onSelect(event) {
  96. if (event.type === 'keydown' && event.keyCode === 32) {
  97. // keydown and spacebar
  98. // Stops the scroll bar from scrolling the page
  99. event.preventDefault();
  100. }
  101. if ((event.type === 'keyup' || event.type === 'keydown') && event.keyCode !== 13 && event.keyCode !== 32) {
  102. // ignore anything other than enter or space
  103. return;
  104. } else {
  105. event.stopPropagation();
  106. if (event.keyCode === 13 && $(event.currentTarget).hasClass('selected')) {
  107. // if the layout was already selected, enter key submits the layout form
  108. this.action();
  109. } else {
  110. this.selectLayout(event.currentTarget.dataset.id);
  111. if (_.isFunction(this.selectAction)) {
  112. this.selectAction(event);
  113. }
  114. }
  115. }
  116. },
  117. onDoubleClick: function onDoubleClick(event) {
  118. this.onSelect(event);
  119. this.action();
  120. },
  121. /**
  122. Enter and Space calling stopPropagation is a very specific fix for defect 255098. Solution with the least risk.
  123. *call stop propagation when Enter or Space is clicked, so that BaseBoardView.onKeyDown is not called
  124. *Those are the keys that trigger primaryaction
  125. *TODO: Please revisit after Endor release
  126. **/
  127. onKeyDown: function onKeyDown(event) {
  128. var items = this.$('.layoutEntry .layoutTemplateItem:not(.filtered)');
  129. var focusedItem = this.$('.layoutEntry .layoutTemplateItem[tabindex=0]');
  130. var focusIndex = items.index(focusedItem);
  131. switch (event.keyCode) {
  132. case KeyCodes.DOWN_ARROW:
  133. case KeyCodes.RIGHT_ARROW:
  134. focusIndex++;
  135. if (focusIndex >= items.length) {
  136. focusIndex = items.length - 1;
  137. }
  138. break;
  139. case KeyCodes.UP_ARROW:
  140. case KeyCodes.LEFT_ARROW:
  141. focusIndex--;
  142. if (focusIndex < 0) {
  143. focusIndex = 0;
  144. }
  145. break;
  146. case KeyCodes.HOME:
  147. focusIndex = 0;
  148. break;
  149. case KeyCodes.END:
  150. focusIndex = items.length - 1;
  151. break;
  152. case KeyCodes.ENTER:
  153. case KeyCodes.SPACE:
  154. event.stopPropagation();
  155. return;
  156. default:
  157. return; //For non-matched keys, stop processing immediately.
  158. }
  159. //Update tabindex and focus
  160. event.preventDefault();
  161. focusedItem.attr('tabindex', -1);
  162. items.eq(focusIndex).attr('tabindex', 0).focus();
  163. },
  164. setNavigationTemplate: function setNavigationTemplate(navTemplate, description) {
  165. var _this2 = this;
  166. var result = void 0;
  167. var animate = this.navTemplate;
  168. this.navTemplate = navTemplate;
  169. if (!animate) {
  170. this.setLabel();
  171. this.setFooter(description);
  172. this.filterTemplates();
  173. result = Promise.resolve();
  174. } else {
  175. // TODO: Probably shouldn't use jquery animation anymore. Switch to CSS
  176. var duration = 300;
  177. // show some animation if we are switching to a different navigation template
  178. result = new Promise(function (resolve, reject) {
  179. try {
  180. _this2.$el.fadeOut({
  181. complete: function complete() {
  182. _this2.setLabel();
  183. _this2.setFooter(description);
  184. _this2.filterTemplates();
  185. _this2.$el.fadeIn({
  186. complete: function complete() {
  187. _this2.checkSelection();
  188. resolve();
  189. },
  190. duration: duration
  191. });
  192. },
  193. duration: duration
  194. });
  195. } catch (error) {
  196. reject(error);
  197. }
  198. });
  199. }
  200. return result;
  201. },
  202. setLabel: function setLabel() {
  203. if (!this.disableHeaderSection) {
  204. this.layoutLabel = this.stringResources.get(this.navTemplate + 'LayoutLabel');
  205. var $header = this.$('.layoutHeader');
  206. $header.text(this.layoutLabel);
  207. var $view = $header.parent('.layoutPickerView');
  208. $view.attr('aria-label', this.stringResources.get('selectTemplateLabel'));
  209. }
  210. },
  211. setFooter: function setFooter(description) {
  212. this.$('.layoutFooter').text(description);
  213. },
  214. filterTemplates: function filterTemplates() {
  215. var posInSet = 1;
  216. var setSize = 0;
  217. _.each(this.templates, function (template) {
  218. var node = this.$('*[data-id=' + template.name + ']');
  219. if (template.navigationTemplates && template.navigationTemplates.indexOf(this.navTemplate) === -1) {
  220. node.addClass('filtered');
  221. node.closest('.layoutEntry').css('display', 'none').find('.layoutTemplateItem').attr('aria-posinset', null);
  222. if (node.hasClass('selected')) {
  223. node.removeClass('selected');
  224. }
  225. } else {
  226. node.removeClass('filtered');
  227. node.closest('.layoutEntry').css('display', '').find('.layoutTemplateItem').attr('aria-posinset', posInSet);
  228. setSize++;
  229. posInSet++;
  230. }
  231. }.bind(this));
  232. this.$('.layoutTemplateItem').attr('aria-setsize', setSize);
  233. this.checkSelection();
  234. },
  235. checkSelection: function checkSelection() {
  236. if (this.$('.selected:not(.filtered)').length === 0) {
  237. var firstTemplate, template;
  238. for (var i = 0; i < this.templates.length; i++) {
  239. template = this.templates[i];
  240. if (!template.navigationTemplates || template.navigationTemplates.indexOf(this.navTemplate) !== -1) {
  241. firstTemplate = template.name;
  242. break;
  243. }
  244. }
  245. this.selectLayout(firstTemplate);
  246. }
  247. },
  248. getNumberOfPossibleTemplates: function getNumberOfPossibleTemplates() {
  249. var navTemplate = this.navTemplate;
  250. var count = 0;
  251. _.each(this.templates, function (template) {
  252. if (!template.navigationTemplates || template.navigationTemplates.indexOf(navTemplate) !== -1) {
  253. count++;
  254. }
  255. });
  256. return count;
  257. },
  258. selectLayoutNode: function selectLayoutNode(node) {
  259. var $selected = this.$('.selected');
  260. $selected.attr('aria-checked', false).attr('tabindex', -1);
  261. $selected.removeClass('selected');
  262. $(node).attr('aria-checked', true).attr('tabindex', 0);
  263. $(node).addClass('selected').focus();
  264. },
  265. onOpen: function onOpen() {
  266. var _this3 = this;
  267. this.whenReady.then(function () {
  268. _this3.$('.selected').focus();
  269. });
  270. },
  271. selectLayout: function selectLayout(name) {
  272. Menu.hideOpenMenus();
  273. _.each(this.templates, function (template) {
  274. template.isSelected = template.name === name;
  275. }.bind(this));
  276. var selectedTemplate = this.$('.layoutTemplateItem[data-id=' + name + ']')[0];
  277. this.selectLayoutNode(selectedTemplate);
  278. if (this.navTemplate === 'custom') {
  279. this.applyContextMenuPermissions(name, selectedTemplate);
  280. }
  281. },
  282. /**
  283. * Asynch method that will load and return the selected page layout template
  284. */
  285. getSelectedLayoutSpec: function getSelectedLayoutSpec() {
  286. var selected = this.$('.selected').attr('data-id');
  287. return this.getLayoutSpecByName(selected);
  288. },
  289. getSelectedTemplate: function getSelectedTemplate() {
  290. return this.$('.layoutTemplateItem.selected');
  291. },
  292. getSelectedLayoutId: function getSelectedLayoutId() {
  293. var $selectedLayoutTemplate = this.$('.selected');
  294. if ($selectedLayoutTemplate.length > 0) {
  295. return $selectedLayoutTemplate[0].dataset.id;
  296. }
  297. return null;
  298. },
  299. _findLayoutByName: function _findLayoutByName(name) {
  300. return _.find(this.templates, function (template) {
  301. return template.name === name;
  302. });
  303. },
  304. getLayoutSpecByName: function getLayoutSpecByName(layoutName) {
  305. var _this4 = this;
  306. var layout = this._findLayoutByName(layoutName);
  307. if (!layout) {
  308. // Error loading the theme. We resolve with no definition.
  309. this.displayErrorMessage();
  310. return Promise.resolve();
  311. }
  312. return this.getLayoutSpec(layout, function () {
  313. // Error loading the theme. We resolve with no definition.
  314. _this4.displayErrorMessage();
  315. });
  316. },
  317. getLayoutSpec: function getLayoutSpec(template, errorHandler) {
  318. if (template.type === 'custom') {
  319. return Promise.resolve({ layout: template });
  320. }
  321. return new Promise(function (resolve, reject) {
  322. try {
  323. var requireFilePath = template.type === 'js' ? '' : 'text!';
  324. template.type = template.type ? template.type : 'json';
  325. requireFilePath += template.path + '/' + template.name;
  326. requireFilePath += template.type === 'js' ? '' : '.' + template.type;
  327. require([requireFilePath], function (layoutFileContent) {
  328. var layout = typeof layoutFileContent === 'string' ? JSON.parse(layoutFileContent) : layoutFileContent;
  329. resolve({
  330. layout: layout
  331. });
  332. }, function () {
  333. if (errorHandler) {
  334. errorHandler();
  335. }
  336. resolve();
  337. });
  338. } catch (error) {
  339. reject(error);
  340. }
  341. });
  342. },
  343. displayErrorMessage: function displayErrorMessage() {
  344. var msgBox = new MessageBox('error', this.stringResources.get('errorMessageTitle'), this.stringResources.get('errorLoadingLayoutFile'));
  345. msgBox.open();
  346. },
  347. generatePreviews: function generatePreviews() {
  348. var _this5 = this;
  349. var whenAllLoaded = [];
  350. _.each(this.templates, function (template) {
  351. if (template.type === 'custom') {
  352. var templateLayoutItem = _this5.$('*[data-id=' + template.name + ']').parent();
  353. ContentFormatter.middleShortenString(templateLayoutItem.find('.layoutLocation')[0]);
  354. ContentFormatter.middleShortenString(templateLayoutItem.find('.layoutThumbnailLabel')[0]);
  355. Utils.setIcon(templateLayoutItem.find('.layoutContextMenuButton'), contextMenuIcon.default.id);
  356. Utils.setIcon(templateLayoutItem.find('.layoutThumbnailContainer'), thumbnailPlaceholderIcon.default.id);
  357. var cachedThumbnail = _this5._thumbnailCache.find(function (thumbnail) {
  358. return thumbnail.name == template.name;
  359. });
  360. if (cachedThumbnail) {
  361. _this5._setTemplateThumbnail(template.name, cachedThumbnail.thumbnail);
  362. } else {
  363. _this5._dashboardTemplatesApi.getThumbnail(template.name).then(function (thumbnail) {
  364. _this5._thumbnailCache.push({ name: template.name, thumbnail: thumbnail });
  365. _this5._setTemplateThumbnail(template.name, thumbnail);
  366. });
  367. }
  368. } else {
  369. var promise = _this5.getLayoutSpec(template).then(function (_ref) {
  370. var layout = _ref.layout;
  371. var previewNode = _this5.$('*[data-id=' + template.name + ']');
  372. if (!template.label && !template.icon) {
  373. previewNode.append(_this5.getPreview(layout));
  374. }
  375. // set icon - webfont or svg
  376. if (template.icon) {
  377. var $templateIcon = _this5.$el.find('[data-id="' + template.name + '"]').find('.layoutIcon');
  378. Utils.setIcon($templateIcon, template.icon);
  379. }
  380. });
  381. whenAllLoaded.push(promise);
  382. }
  383. });
  384. Promise.all(whenAllLoaded).then(this._whenReady.resolve.bind(this.whenReady));
  385. },
  386. getPreview: function getPreview(layout) {
  387. var style = '';
  388. if (layout.style) {
  389. for (var name in layout.style) {
  390. if (layout.style.hasOwnProperty(name)) {
  391. style += name + ':' + layout.style[name] + ';';
  392. }
  393. }
  394. }
  395. var css = layout.css || '';
  396. css += ' ' + layout.type;
  397. var preview = '<div class="' + css + ' " style="' + style + '">';
  398. if (layout.items) {
  399. for (var i = 0; i < layout.items.length; i++) {
  400. preview += this.getPreview(layout.items[i]);
  401. }
  402. }
  403. preview += '</div>';
  404. return preview;
  405. },
  406. applyContextMenuPermissions: function applyContextMenuPermissions(templateId, selectedTemplateElement) {
  407. var _this6 = this;
  408. this._dashboardTemplatesApi.getTemplate(templateId).then(function (template) {
  409. _this6._selectedCustomTemplate = template;
  410. var contextMenuPermissions = ['write', 'execute'];
  411. if (_.intersection(template.permissions, contextMenuPermissions).length) {
  412. $(selectedTemplateElement).find('.layoutContextMenuButton').addClass('isVisible');
  413. }
  414. });
  415. },
  416. _setTemplateThumbnail: function _setTemplateThumbnail(id, image) {
  417. if (!id || !image) {
  418. return;
  419. }
  420. var templateItem = id && this.$('*[data-id=' + id + ']');
  421. var thumbnailContainer = templateItem.find('.layoutThumbnailContainer');
  422. var thumbnailImage = thumbnailContainer && thumbnailContainer.find('.layoutThumbnail');
  423. if (thumbnailImage && thumbnailImage.length) {
  424. thumbnailImage.attr('src', 'data:image/png;base64,' + image);
  425. var thumbnailPlaceholder = thumbnailContainer.find('svg').first();
  426. thumbnailPlaceholder && thumbnailPlaceholder.remove();
  427. }
  428. },
  429. onDeleteActionDone: function onDeleteActionDone(templateId) {
  430. var templateItem = templateId && this.$('*[data-id=' + templateId + ']');
  431. templateItem && templateItem.closest('.layoutEntry').remove();
  432. this.templates = this.templates.filter(function (template) {
  433. return template.name !== templateId;
  434. });
  435. },
  436. onContextMenuButtonClick: function onContextMenuButtonClick(event) {
  437. this._selectedCustomTemplate && this._showContextMenu(this._selectedCustomTemplate, event.pageX, event.pageY);
  438. },
  439. addMoreCustomTemplates: function addMoreCustomTemplates(customTemplates) {
  440. this._addCustomTemplates(customTemplates);
  441. this.render();
  442. }
  443. });
  444. return LayoutPickerView;
  445. });
  446. //# sourceMappingURL=LayoutPickerView.js.map