WidgetAddUIHelper.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2015, 2020
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['require', '../lib/@waca/core-client/js/core-client/ui/core/Class', '../lib/@waca/core-client/js/core-client/utils/Deferred', 'jquery', 'underscore', '../nls/StringResources'], function (require, Class, Deferred, $, _, stringResources) {
  8. /**
  9. * A helper that provides the event handlers typically used to add a widget.
  10. */
  11. var WidgetAddUIHelper = Class.extend({
  12. init: function init(options) {
  13. WidgetAddUIHelper.inherited('init', this, arguments);
  14. this.dashboardApi = options.dashboardApi;
  15. this.requireFn = options.requireFn || require;
  16. this.logger = this.dashboardApi.getGlassCoreSvc('.Logger');
  17. },
  18. fetchWidgetList: function fetchWidgetList(url) {
  19. var _this = this;
  20. return this.panelAttributes.glassContext.getCoreSvc('.Ajax').ajax({
  21. url: url,
  22. // TODO: For some reason Safari on iOS was caching results, force off for now. This shouldn't be required.
  23. cache: false
  24. }).then(function (widgets) {
  25. return _this.fetchRequiredWidgetAttributes(widgets).then(function () {
  26. return widgets;
  27. });
  28. }).catch(function () {
  29. var msg = stringResources.get('errorLoadingWidgetList');
  30. return Promise.reject(msg);
  31. });
  32. },
  33. fetchWidgetListFromPerspective: function fetchWidgetListFromPerspective(collectionId, context) {
  34. var _this2 = this;
  35. return context.appController.findCollection(collectionId).then(function (collection) {
  36. var widgets = {
  37. list: collection ? JSON.parse(JSON.stringify(collection)) : collection
  38. };
  39. return _this2.fetchRequiredWidgetAttributes(widgets).then(function () {
  40. return widgets;
  41. });
  42. }).catch(function () {
  43. var msg = stringResources.get('errorLoadingWidgetList');
  44. return Promise.reject(msg);
  45. });
  46. },
  47. fetchRequiredWidgetAttributes: function fetchRequiredWidgetAttributes(widgetList) {
  48. var _this3 = this;
  49. var deferredArray = [];
  50. _.each(widgetList.list, function (w) {
  51. w.options = w.options || {};
  52. w.options.ext = _this3._hasExtensionTemplate(w);
  53. // extension templates are already localized from the perspective level
  54. if (!w.title && w.name) {
  55. w.title = w.options.ext ? w.name : stringResources.get(w.name);
  56. }
  57. if (!w.name) {
  58. w.name = w.title;
  59. }
  60. if (w.options.templatePath && !w.options.template) {
  61. deferredArray.push(_this3._loadTemplate(w));
  62. } else if (w.options.imageLink) {
  63. deferredArray.push(_this3._loadImage(w));
  64. } else {
  65. // ensure the deferred array maps with the widget list
  66. deferredArray.push(true);
  67. }
  68. if (w.url) {
  69. w.getEntries = _this3.fetchWidgetList.bind(_this3, w.url);
  70. }
  71. });
  72. if (deferredArray.length) {
  73. return Promise.all(deferredArray).then(function (isLoaded) {
  74. // filter out any widgets that has failed to load
  75. widgetList.list = _.filter(widgetList.list, function (w, i) {
  76. return isLoaded[i];
  77. });
  78. });
  79. }
  80. return Promise.resolve();
  81. },
  82. _loadImage: function _loadImage(widget) {
  83. return new Promise(function (resolve) {
  84. // eslint-disable-next-line no-undef
  85. var img = new Image();
  86. img.onload = function () {
  87. resolve(true);
  88. };
  89. img.onerror = function () {
  90. resolve(false);
  91. };
  92. img.src = widget.options.imageLink;
  93. });
  94. },
  95. _loadTemplate: function _loadTemplate(widget) {
  96. var _this4 = this;
  97. var cdnUrl = '';
  98. var extensionTemplate = this._hasExtensionTemplate(widget);
  99. if (!extensionTemplate) {
  100. cdnUrl = this.panelAttributes.cdnUrl || '';
  101. }
  102. return new Promise(function (resolve) {
  103. _this4.requireFn(['text!' + cdnUrl + widget.options.templatePath], function (template) {
  104. if (extensionTemplate) {
  105. var xml = $.parseXML(template);
  106. var svgRoot = $(xml).find('svg');
  107. if (svgRoot.length > 0) {
  108. if (typeof widget.options.viewBox === 'undefined') {
  109. // respect the viewbox defined in the svg
  110. var viewbox = svgRoot.attr('viewBox');
  111. if (viewbox) {
  112. widget.options.viewBox = viewbox;
  113. widget.options.previewBox = widget.options.viewBox;
  114. } else {
  115. // create a viewbox based on the size
  116. var width = parseInt(svgRoot.attr('width'), 10) || 100;
  117. var height = parseInt(svgRoot.attr('height'), 10) || 100;
  118. widget.options.viewBox = '0 0 ' + width + ' ' + height;
  119. widget.options.previewBox = '-10 -10 ' + width * 1.2 + ' ' + height * 1.2;
  120. }
  121. }
  122. template = '';
  123. var content = svgRoot.children();
  124. _.each(content, function (node) {
  125. // eslint-disable-next-line no-undef
  126. template += new XMLSerializer().serializeToString(node);
  127. });
  128. }
  129. }
  130. widget.options.template = template;
  131. // resolve as succeeded
  132. resolve(true);
  133. }, function (error) {
  134. _this4.logger.error('Failed to load widget', error);
  135. // resolve as failed - don't reject since we want continue with other successful widgets
  136. resolve(false);
  137. });
  138. });
  139. },
  140. _hasExtensionTemplate: function _hasExtensionTemplate(widget) {
  141. if (widget && widget.options) {
  142. var ext = new RegExp('^v[0-9]+/ext/.+$');
  143. return ext.test(widget.options.templatePath);
  144. }
  145. return false;
  146. },
  147. _getDefaultSpec: function _getDefaultSpec(Widget, widget) {
  148. if (Widget.getDefaultSpec) {
  149. if (widget.options) {
  150. widget.options.dashboardApi = this.dashboardApi;
  151. }
  152. /** getDefaultSpec() is expected to return a promise that will be resolved with the default spec. for the widget. */
  153. return Widget.getDefaultSpec(widget.name, widget.options);
  154. } else {
  155. var copyDefaultSpec = widget.defaultSpec ? JSON.parse(JSON.stringify(widget.defaultSpec)) : {};
  156. return Promise.resolve(copyDefaultSpec);
  157. }
  158. },
  159. _addFillAndBorderToAvatarIfNeeded: function _addFillAndBorderToAvatarIfNeeded(Widget, avatar, spec) {
  160. if (Widget.addFillAndBorderToAvatarIfNeeded) {
  161. Widget.addFillAndBorderToAvatarIfNeeded(avatar, spec);
  162. }
  163. },
  164. addWidgetBySelection: function addWidgetBySelection(widget, ev, options) {
  165. var _this5 = this;
  166. options = options || {};
  167. if (ev.gesture) {
  168. // prevent simulated click if we have a touch gesture.
  169. ev.gesture.preventDefault();
  170. }
  171. var sWidget = widget.widget;
  172. var dndManagerFeature = this.dashboardApi.getFeature('DashboardDnd.internal');
  173. return new Promise(function (resolve) {
  174. var api = _this5.dashboardApi.getCanvas();
  175. var onAddDone = function onAddDone(id) {
  176. if (id) {
  177. api.selectWidget(id, {
  178. isTouch: ev.type === 'tap',
  179. options: options.selectOptions ? options.selectOptions : undefined
  180. });
  181. }
  182. // Computers click very quickly (during functional tests). This call to
  183. // resetDragging() needs to happen *after* the call to startDrag() in
  184. // addWidgetByDrag, or else the drag started there won't be cancelled
  185. // and you can end up with a dragging widget hanging on the mouse after
  186. // clicking on a widget in a panel.
  187. dndManagerFeature.resetDragging();
  188. resolve(id);
  189. };
  190. // we are adding a dashboard fragment
  191. if (widget.fragment) {
  192. // add a copy of the fragment
  193. var id = api.addFragment({ model: JSON.parse(JSON.stringify(widget.fragment)) });
  194. onAddDone(id);
  195. return;
  196. }
  197. _this5.requireFn([sWidget], function (Widget) {
  198. var id = null;
  199. if (!api.hasMaximizedWidget()) {
  200. _this5._getDefaultSpec(Widget, widget).then(function (spec) {
  201. // Remove properties piggy-backed on spec
  202. spec.model.avatarHtml = undefined;
  203. spec.model.visTypeLocked = true; //On create widget of specific type, lock that type in
  204. var content = {
  205. spec: spec.model
  206. };
  207. if (spec.layoutProperties) {
  208. //liveWidget does not have layoutProperties in defaultSpec
  209. content.layout = spec.layoutProperties.style;
  210. }
  211. var transactionApi = _this5.dashboardApi.getFeature('Transaction');
  212. var transactionToken = transactionApi.startTransaction();
  213. api.addContent(content, transactionToken).then(function (content) {
  214. onAddDone(content.getId());
  215. resolve(content.getId());
  216. }).finally(transactionApi.endTransaction.bind(transactionApi, transactionToken));
  217. }).catch(function (e) {
  218. _this5.logger.error(e);
  219. resolve(id);
  220. });
  221. } else {
  222. resolve(id);
  223. }
  224. });
  225. });
  226. },
  227. addWidgetByDrag: function addWidgetByDrag(widget, ev) {
  228. var _this6 = this;
  229. if (widget.fragment || widget.widget) {
  230. ev.stopPropagation();
  231. var dndManagerFeature = this.dashboardApi.getFeature('DashboardDnd.internal');
  232. return new Promise(function (resolve, reject) {
  233. var prepareDrag = function prepareDrag(type, dropInfo, ev, widget, spec, avatarStyle) {
  234. var avatar = $('<div>');
  235. var moveX, moveY;
  236. moveX = moveY = ev.showAvatarImmediately ? 0 : 20;
  237. avatar.addClass('avatar');
  238. avatar.addClass('widget');
  239. // if avatarHtml is provided, it should take precedence
  240. if (spec) {
  241. if (spec.model.avatarHtml) {
  242. avatar.html(spec.model.avatarHtml);
  243. // Don't store the avatar HTML
  244. spec.model.avatarHtml = undefined;
  245. } else if (spec.model.content) {
  246. avatar.html(spec.model.content);
  247. }
  248. avatar.addClass(spec.model.type + 'Widget');
  249. }
  250. if (widget) {
  251. _this6._addFillAndBorderToAvatarIfNeeded(widget, avatar, spec);
  252. }
  253. var style = avatarStyle || spec && spec.layoutProperties && spec.layoutProperties.style;
  254. if (style) {
  255. avatar.css(style);
  256. }
  257. // TODO: having widgetType and widgetSpec seems redundant since this information is is in the dropInfo
  258. // We need to revisit and cleanu the DnD payload
  259. var dragInfo = {
  260. event: ev,
  261. type: type,
  262. data: dropInfo,
  263. widgetSpec: spec || null,
  264. widgetType: spec && spec.model.type || null,
  265. avatar: avatar,
  266. moveXThreshold: moveX,
  267. moveYThreshold: moveY,
  268. showAvatarImmediately: ev.showAvatarImmediately || false
  269. };
  270. return dragInfo;
  271. };
  272. // wW are dragging a fragment
  273. // piggy back on the pin drag payload since it uses a fragment.
  274. // TODO: ideally we should make the fragment drag to be generic and not related to just pins.
  275. var fragment = widget.fragment;
  276. if (fragment) {
  277. var dropInfo = {
  278. operation: 'new',
  279. pinSpec: {
  280. contentType: 'boardFragment',
  281. content: JSON.parse(JSON.stringify(fragment))
  282. }
  283. };
  284. var dragInfo = prepareDrag('pin', dropInfo, ev, null, null, fragment.layout && fragment.layout.style);
  285. dndManagerFeature.startDrag(dragInfo);
  286. resolve(dragInfo);
  287. return;
  288. }
  289. _this6.requireFn([widget.widget], function (Widget) {
  290. _this6._getDefaultSpec(Widget, widget).then(function (spec) {
  291. //On create widget of specific type, lock that type in
  292. spec.model.visTypeLocked = true;
  293. var dropInfo = _.extend(spec, {
  294. operation: 'new'
  295. });
  296. var dragInfo = prepareDrag('widget', dropInfo, ev, Widget, spec);
  297. dndManagerFeature.startDrag(dragInfo);
  298. resolve(dragInfo);
  299. }).catch(reject);
  300. });
  301. });
  302. }
  303. return Promise.reject();
  304. }
  305. });
  306. return WidgetAddUIHelper;
  307. });
  308. //# sourceMappingURL=WidgetAddUIHelper.js.map