Widget.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. 'use strict';
  2. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2013, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['./LayoutBaseView', 'jquery', 'underscore', './WidgetTitleView', '../../model/LayoutModel', '../../widgets/PropertiesUtil', '../../../app/nls/StringResources', '../../../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/core/Events', '../../../app/util/EventChainLocal'], function (BaseView, $, _, WidgetTitleView, LayoutModel, PropertiesUtil, StringResources, Deferred, KeyCodes, Events, EventChainLocal) {
  9. // NOSONAR
  10. var Widget = BaseView.extend({
  11. init: function init(options) {
  12. Widget.inherited('init', this, arguments);
  13. this._layoutEvents = new Events();
  14. this._widgetReady = new Deferred();
  15. this._widgetRenderComplete = new Deferred();
  16. this.canvasNotifier = options.canvasNotifier;
  17. this.widgetModel = options.widgetModel;
  18. this.widgetRegistry = options.widgetRegistry;
  19. this.visPreferredSize = null;
  20. this.icons = {};
  21. this._savedTransform = null;
  22. this.$widgetContentWrapper = this.$el.find('.widgetContentWrapper');
  23. this._registerWidgetChromeEvents();
  24. this._createHeader();
  25. this._createAttachedView();
  26. this._createFilterGroupOverlay();
  27. this.isTouchDevice = 'ontouchstart' in document.documentElement;
  28. this.additionalWidgetData = options.additionalWidgetData;
  29. // Add appcues-data-id attribute to widget
  30. this.$el.attr('appcues-data-id', 'widget');
  31. // attach keydown event handler (done here to ensure dom is ready)
  32. $(this.domNode).on('keydown.widgetKeydown', this.onKeyDown.bind(this));
  33. },
  34. renderContent: function renderContent() {
  35. var _this = this;
  36. // todo lifecycle_cleanup -- the widget creation/rendering is too complex. It needs to be simplified
  37. // The widget can be created here instead of using the widget loader to do that
  38. // Also make sure we render once.
  39. // Render is called as we switch between consume/authoring mode by the layout controller
  40. // we don't want to render the widget multiple times
  41. if (!this._isRendered && this.widgetModel) {
  42. this._isRendered = true;
  43. this.registerViewFeatures().then(function () {
  44. // Notify the widget loader, create and render the widget
  45. if (_this.canvasNotifier) {
  46. _this._establishCanvasConnection(_this.additionalWidgetData);
  47. }
  48. _this.renderWidget();
  49. });
  50. }
  51. this.layoutController.markViewAsReady(this);
  52. return Promise.resolve();
  53. // Can't wait for the widget render compelete because the glass wait for the layout render to be resolved before showing
  54. // the view.. and as long as the view is hidden, the widget will not render (because they are invisible)
  55. // return this.whenRenderComplete().then(() => {
  56. // });
  57. },
  58. renderWidget: function renderWidget() {
  59. var _this2 = this;
  60. return new Promise(function (resolve, reject) {
  61. if (!_this2.layoutController.widgetLoader.isLoaded(_this2.id)) {
  62. var transactionId = _this2.additionalWidgetData && _this2.additionalWidgetData.addPayloadData && _this2.additionalWidgetData.addPayloadData.undoRedoTransactionId;
  63. _this2.layoutController.widgetLoader.loadWidget(_this2.id, _this2.widgetModel.toJSON(), _this2.domNode, _this2._prepareAsyncCallback(function (loadInfo) {
  64. if (loadInfo && loadInfo.widget) {
  65. if (loadInfo.widget.onContainerReady) {
  66. loadInfo.widget.onContainerReady({
  67. model: _this2.widgetModel,
  68. widgetChromeEventRouter: _this2.widgetChromeEventRouter,
  69. isAuthoringMode: _this2.layoutController.isAuthoring,
  70. additionalWidgetData: _this2.additionalWidgetData,
  71. layoutAPI: _this2.getAPI()
  72. });
  73. }
  74. _this2._triggerChromeEvents(loadInfo.widget, _this2.widgetChromeEventRouter);
  75. }
  76. }), _this2._prepareAsyncCallback(function (error) {
  77. if (error.errorCode === (_this2.layoutController.widgetLoader && _this2.layoutController.widgetLoader.ERROR.WIDGET_ALREADY_LOADED)) {
  78. //We've hit a rare timing issue where the two loads ended up being requested before one finished. Trigger the onLoad behavior.
  79. _this2._widgetAlreadyLoadedCallback();
  80. resolve();
  81. } else if (error.exception) {
  82. reject(error.exception);
  83. } else {
  84. reject(new Error(error.errorCode));
  85. }
  86. }), transactionId).then(function () {
  87. resolve();
  88. });
  89. } else {
  90. _this2._widgetAlreadyLoadedCallback();
  91. resolve();
  92. }
  93. });
  94. },
  95. _widgetAlreadyLoadedCallback: function _widgetAlreadyLoadedCallback() {
  96. var widget = this.layoutController.widgetLoader.getWidget(this.id);
  97. if (widget) {
  98. if (widget.onContainerReady) {
  99. widget.onContainerReady({
  100. model: this.widgetModel,
  101. widgetChromeEventRouter: this.widgetChromeEventRouter,
  102. isAuthoringMode: this.layoutController.isAuthoring,
  103. layoutAPI: this.getAPI()
  104. });
  105. }
  106. this._triggerChromeEvents(widget);
  107. }
  108. },
  109. _triggerChromeEvents: function _triggerChromeEvents(widget) {
  110. if (this.widgetChromeEventRouter) {
  111. // Notify the chrome that the widget is ready
  112. this.widgetChromeEventRouter.trigger('widget:ready', {
  113. // TODO -- to be removed after refactoring
  114. widget: widget,
  115. //TODO - end to be removeDone
  116. widgetAPI: widget.getAPI()
  117. });
  118. this._triggerRenderComplete(widget);
  119. }
  120. },
  121. _triggerRenderComplete: function _triggerRenderComplete(widget) {
  122. if (widget.whenRenderComplete) {
  123. widget.whenRenderComplete().then(this._prepareAsyncCallback(function () {
  124. // Notify the chrome that the widget has finished rendering
  125. this.widgetChromeEventRouter.trigger('widget:renderComplete', {
  126. widget: widget
  127. });
  128. this.eventRouter.trigger('widget:renderComplete', {
  129. widget: widget
  130. });
  131. }.bind(this)));
  132. } else {
  133. this.widgetChromeEventRouter.trigger('widget:renderComplete', {
  134. widget: widget
  135. });
  136. this.eventRouter.trigger('widget:renderComplete', {
  137. widget: widget
  138. });
  139. }
  140. },
  141. /**
  142. * Execute a callback. The method will only execute the callback if the object is not destroyed
  143. * Typpically used in a promise callback because we might have destroyed the object before the promise was resolved.
  144. * @param {Function} callback
  145. */
  146. _prepareAsyncCallback: function _prepareAsyncCallback(callback) {
  147. return function () {
  148. if (!this._destroyed) {
  149. return callback.apply(undefined, arguments);
  150. }
  151. return Promise.reject('The live widget object was destroyed');
  152. }.bind(this);
  153. },
  154. getAPI: function getAPI() {
  155. var api = Widget.inherited('getAPI', this, arguments);
  156. if (this.widgetTitleView) {
  157. api.updateTitle = this.widgetTitleView.updateTitle.bind(this.widgetTitleView);
  158. api.setTitleBadge = this.setTitleBadge.bind(this);
  159. api.onTitleBadgeClick = this.onTitleBadgeClick.bind(this);
  160. api.offTitleBadgeClick = this.offTitleBadgeClick.bind(this);
  161. api.offFocus = this.widgetTitleView.onChromeOffFocus.bind(this.widgetTitleView);
  162. }
  163. return api;
  164. },
  165. setTitleBadge: function setTitleBadge(value, label) {
  166. var _this3 = this;
  167. this.$badgeContainer.empty();
  168. if (value) {
  169. var $badge = $('<div>', {
  170. 'tabindex': 0,
  171. 'class': 'titleBadge',
  172. 'aria-label': label,
  173. 'appcues-data-id': 'titleBadge'
  174. });
  175. $badge.text(value);
  176. this.$badgeContainer.append($badge);
  177. $badge.on('clicktap keydown', function (event) {
  178. if (event.type !== 'keydown' || event.which === 13) {
  179. _this3._layoutEvents.trigger('titlebadge:click', { event: event });
  180. }
  181. });
  182. }
  183. },
  184. onTitleBadgeClick: function onTitleBadgeClick(handler, scope) {
  185. this._layoutEvents.on('titlebadge:click', handler, scope);
  186. },
  187. offTitleBadgeClick: function offTitleBadgeClick(handler, scope) {
  188. this._layoutEvents.off('titlebadge:click', handler, scope);
  189. },
  190. /**
  191. * Detect a focus out so that we can invoke the onExistContainer event.
  192. * This method detect lossing focus either by tabbing or clicking outside of the container
  193. *
  194. */
  195. onFocusOut: function onFocusOut() /*event*/{
  196. var isFocusIn = false;
  197. var focusIn = function (evt) {
  198. if (evt.target !== this.domNode) {
  199. isFocusIn = true;
  200. }
  201. }.bind(this);
  202. $(this.domNode).on('focusin.widgetFocus', focusIn);
  203. setTimeout(function () {
  204. if (!isFocusIn) {
  205. this._exitContainer();
  206. }
  207. }.bind(this), 10);
  208. },
  209. onKeyDown: function onKeyDown(event) {
  210. // F10 or (Ctrl key + X) will maximize the selected widget
  211. // Only data widget support maximize mode..
  212. if (event.keyCode === KeyCodes.F10 || event.ctrlKey && event.keyCode === KeyCodes.X) {
  213. this.onMaximize();
  214. return false;
  215. }
  216. if (event.keyCode === KeyCodes.F12 && !event.shiftKey || event.ctrlKey && event.keyCode === KeyCodes.NUM1) {
  217. // Press F12 Key + Without Shift Key or (Ctrl + 1) to enter into the widget container
  218. this.isEnterContainer = true;
  219. this.widgetChromeEventRouter.trigger('widget:onEnterContainer');
  220. $(this.domNode).on('focusout.widgetFocus', this.onFocusOut.bind(this));
  221. event.stopPropagation();
  222. event.preventDefault();
  223. return false;
  224. }
  225. if (event.keyCode === KeyCodes.F12 && event.shiftKey || event.ctrlKey && event.keyCode === KeyCodes.NUM0) {
  226. // Press F12 Key + Shift or (Ctrl+0) to exit from the widget container
  227. this._exitContainer();
  228. // just dump focus back on the widget
  229. $(this.domNode).focus();
  230. event.stopPropagation();
  231. event.preventDefault();
  232. return false;
  233. }
  234. if ((event.keyCode === KeyCodes.SPACE || event.keyCode === KeyCodes.ENTER) && $.contains(this.domNode, event.target)) {
  235. var eventChainLocal = new EventChainLocal(event);
  236. eventChainLocal.setProperty('preventWidgetSelection', true);
  237. return true;
  238. }
  239. var isArrowKey = event.keyCode === KeyCodes.LEFT_ARROW || event.keyCode === KeyCodes.RIGHT_ARROW || event.keyCode === KeyCodes.UP_ARROW || event.keyCode === KeyCodes.DOWN_ARROW;
  240. if (isArrowKey && $.contains(this.domNode, event.target)) {
  241. var _eventChainLocal = new EventChainLocal(event);
  242. _eventChainLocal.setProperty('preventMoveAction', true);
  243. return true;
  244. }
  245. },
  246. _exitContainer: function _exitContainer() {
  247. if (this.isEnterContainer) {
  248. this.widgetChromeEventRouter.trigger('widget:onExitContainer');
  249. this.isEnterContainer = false;
  250. $(this.domNode).off('focusout');
  251. }
  252. },
  253. /**
  254. * Trigger an event using the chrome event router
  255. * @param eventName - Event name
  256. * @param paylad - Event payload
  257. */
  258. notifyWidget: function notifyWidget(eventName, payload) {
  259. this.widgetChromeEventRouter.trigger(eventName, payload);
  260. },
  261. widgetReady: function widgetReady() {
  262. return this._widgetReady.promise;
  263. },
  264. whenRenderComplete: function whenRenderComplete() {
  265. return this._widgetRenderComplete.promise;
  266. },
  267. _registerWidgetChromeEvents: function _registerWidgetChromeEvents() {
  268. this.widgetChromeEventRouter_disposeEvents.push(this.widgetChromeEventRouter.on('widget:updateLayout', this.updateModel, this), this.widgetChromeEventRouter.on('layout:getPageFillColor', this.getPageFillColor, this), this.widgetChromeEventRouter.on('widget:setPreferredSize', this.setPreferredSize, this), this.widgetChromeEventRouter.on('widget:getSize', this.getSize, this), this.widgetChromeEventRouter.on('widget:showError', this.sizeToErrorContainer, this), this.widgetChromeEventRouter.on('widget:clearError', this.resizeToModel, this), this.widgetChromeEventRouter.on('widget:clearMoreDataIndicator', this.clearMoreDataIndicator, this), this.widgetChromeEventRouter.on('widget:updateDescription', this.updateDescription, this), this.widgetChromeEventRouter.on('widget:addIcon', this.addIcon, this), this.widgetChromeEventRouter.on('widget:onShowAttachedView', this.onShowAttachedView, this), this.widgetChromeEventRouter.on('widget:onHideAttachedView', this.onHideAttachedView, this), this.widgetChromeEventRouter.on('widget:setLayoutProperties', this.setLayoutProperties, this), this.widgetChromeEventRouter.on('title:resizeViz', this.onResize, this), this.widgetChromeEventRouter.on('title:resetTitle', this.recreateHeader, this), this.widgetChromeEventRouter.on('widget:ready', this.onWidgetReady, this), this.widgetChromeEventRouter.on('widget:renderComplete', this.onWidgetRenderComplete, this));
  269. },
  270. onWidgetReady: function onWidgetReady(info) {
  271. this.widget = info.widget;
  272. this.widgetAPI = info.widgetAPI;
  273. this._widgetReady.resolve(info.widgetAPI);
  274. },
  275. onWidgetRenderComplete: function onWidgetRenderComplete() {
  276. this._widgetRenderComplete.resolve();
  277. },
  278. getPageFillColor: function getPageFillColor(payload) {
  279. var parent = this.model.getParent();
  280. var fillColor = null;
  281. while (parent && !fillColor) {
  282. if (parent.fillColor && parent.fillColor !== 'transparent') {
  283. fillColor = parent.fillColor;
  284. }
  285. parent = parent.getParent();
  286. }
  287. payload.fillColor = fillColor;
  288. },
  289. /**
  290. * This is an event handler for sizing the widget dom node to fit the error container properly;
  291. * But doing so without storing the size in the model.
  292. * @param payload - An object containing the height and width of the error container
  293. */
  294. sizeToErrorContainer: function sizeToErrorContainer(payload) {
  295. var _this4 = this;
  296. if (_.isUndefined(this._errorStyle)) {
  297. this._errorStyle = {};
  298. }
  299. _.each(payload, function (value, key) {
  300. _this4._errorStyle[key] = value;
  301. });
  302. if (_.isEmpty(this._errorStyle)) {
  303. this.logger.warn('no error style to apply');
  304. } else {
  305. this.$el.css(this._errorStyle);
  306. }
  307. },
  308. /**
  309. * Resizes the widget layout DOM node to the size stored in the model
  310. */
  311. resizeToModel: function resizeToModel() {
  312. var style = this.model.style;
  313. var styleToApply = {};
  314. if (this._errorStyle) {
  315. styleToApply = _.clone(this._errorStyle);
  316. _.each(styleToApply, function (value, key) {
  317. styleToApply[key] = '';
  318. });
  319. delete this._errorStyle;
  320. }
  321. if (style) {
  322. styleToApply.height = style.height;
  323. styleToApply.width = style.width;
  324. if (this.model.style.opacity) {
  325. styleToApply.opacity = this.model.style.opacity;
  326. }
  327. }
  328. if (!_.isEmpty(styleToApply)) {
  329. this.$el.css(styleToApply);
  330. }
  331. },
  332. /**
  333. * Update the layout view model with the given preferred size
  334. * @param payload - event payload.
  335. */
  336. setPreferredSize: function setPreferredSize(payload) {
  337. var iPreferredHeight = payload.preferredSize.height + 'px';
  338. var iPreferredWidth = payload.preferredSize.width + 'px';
  339. var bPreferredSizeChanged = this.visPreferredSize && !_.isEqual(this.visPreferredSize, payload.preferredSize);
  340. var style = this.model.style;
  341. var bHasSizeInModel = !!(style && style.height && style.width);
  342. if (!bHasSizeInModel || bPreferredSizeChanged && !this._isWidgetInDropZone()) {
  343. if (payload.options.undoRedoTransactionId) {
  344. this._updateModel({
  345. undoRedoTransactionId: payload.options.undoRedoTransactionId,
  346. transactionToken: payload.options && payload.options.transactionToken,
  347. triggerResize: false
  348. }, this, {
  349. height: iPreferredHeight,
  350. width: iPreferredWidth
  351. });
  352. } else {
  353. this._updateModel({
  354. triggerResize: false
  355. }, null, {
  356. height: iPreferredHeight,
  357. width: iPreferredWidth
  358. });
  359. }
  360. } else {
  361. // Update DOM node with saved style. Useful when loading a board.
  362. this.$el.css({
  363. height: this.model.style.height,
  364. width: this.model.style.width
  365. });
  366. }
  367. this.visPreferredSize = payload.preferredSize;
  368. },
  369. /**
  370. * Return the bounds of the visualization within the widget layout view DOM node
  371. * This is an asynchronous function and it returns the bounds by resolving the deferred object
  372. * in the payload argument
  373. *
  374. * @param payload - Contains the deferred object that is resolved with the bounds
  375. * @return undefined
  376. */
  377. getSize: function getSize(payload) {
  378. var $widgetContent = this.$el.find('.widgetContent');
  379. var iWidth;
  380. var iHeight;
  381. var oModelStyle = this.model.style;
  382. //when the parent of an element is hidden, children without dimensions explicitly set are returned as 0.
  383. if (this._getHiddenLayoutParents().length > 0) {
  384. // element has a hidden parent, get the dims from the model
  385. // possible values stored on the model that will still work:
  386. // - percentage (ends in %)
  387. // - pixels (ends in px)
  388. // - no ending (pixels assumed)
  389. iWidth = oModelStyle.width;
  390. iHeight = oModelStyle.height;
  391. } else {
  392. iWidth = $widgetContent.innerWidth();
  393. iHeight = $widgetContent.innerHeight();
  394. }
  395. // The payload should contain a deferred object that will be resolved with the size
  396. if (payload.deferred) {
  397. payload.deferred.resolve({
  398. top: oModelStyle.top || 0,
  399. left: oModelStyle.left || 0,
  400. width: iWidth,
  401. height: iHeight
  402. });
  403. }
  404. },
  405. /*
  406. * Checks whether the widget has a parent which is hidden via display:none.
  407. * @return collection of jquery selectors
  408. */
  409. _getHiddenLayoutParents: function _getHiddenLayoutParents() {
  410. return this.$el.parents().filter(function () {
  411. return $(this).css('display') === 'none';
  412. });
  413. },
  414. /**
  415. * Checks whether the widget is snapped in a template drop zone or not.
  416. * @return boolean - true if widget is snapped and false otherwise.
  417. */
  418. _isWidgetInDropZone: function _isWidgetInDropZone() {
  419. var current = this.model;
  420. var parent = this.model.getParent();
  421. var bIsInDropZone = false;
  422. while (parent && parent.type !== 'genericPage') {
  423. current = parent;
  424. parent = parent.getParent();
  425. }
  426. if (parent && current.relatedLayouts) {
  427. var relatedLayout = parent.findChildItem(parent.items, current.relatedLayouts);
  428. if (relatedLayout.type === 'templateDropZone') {
  429. bIsInDropZone = true;
  430. }
  431. }
  432. return bIsInDropZone;
  433. },
  434. // This allows us to pass in a transaction id so that there isn't more than one frame added to the undo/redo stack
  435. updateModel: function updateModel(payload) {
  436. this.model.updateModel({
  437. updateArray: [{
  438. id: this.id,
  439. style: {
  440. height: $(this.domNode).outerHeight() + 'px',
  441. width: $(this.domNode).outerWidth() + 'px'
  442. }
  443. }]
  444. }, this, payload.payloadData);
  445. },
  446. /**
  447. * TODO: Combine this with updateModel function
  448. * @param payload The event payload
  449. * @param sender The event's sender
  450. * @param style object with at least height and width
  451. */
  452. _updateModel: function _updateModel(payload, sender, style) {
  453. if (style) {
  454. var newStyle = {
  455. height: style.height,
  456. width: style.width
  457. };
  458. if (style.opacity !== undefined) {
  459. newStyle.opacity = style.opacity;
  460. }
  461. this.model.updateModel({
  462. updateArray: [{
  463. id: this.model.id,
  464. style: newStyle
  465. }]
  466. }, sender, payload);
  467. }
  468. },
  469. /**
  470. * Tell the widget module that the the layout container is ready.
  471. *
  472. * @param additionalWidgetData - additional data to be passed to the widget module.
  473. */
  474. _establishCanvasConnection: function _establishCanvasConnection(additionalWidgetData) {
  475. this.widgetOwnsTitle = this.widgetRegistry ? this.widgetRegistry.ownTitle : true;
  476. this.canvasNotifier.trigger('widget:addDone', {
  477. id: this.id,
  478. widgetChromeEventRouter: this.widgetChromeEventRouter,
  479. isAuthoringMode: this.isAuthoringMode,
  480. domNode: this.domNode,
  481. additionalWidgetData: additionalWidgetData,
  482. layoutAPI: this.getAPI(),
  483. // TODO: should be removed.
  484. // nobody should be calling the widget layout instance directly.
  485. // Consumers should be consuming the layoutAPI instead.
  486. //
  487. // for now we'll keep it in because storytelling still references it
  488. widget: this
  489. });
  490. },
  491. updateDescription: function updateDescription(payload) {
  492. if (this._nWidgetDescription) {
  493. this._nWidgetDescription.text(payload.value);
  494. } else {
  495. this.$el.attr('aria-label', payload.value);
  496. }
  497. },
  498. _getDescriptionDomNodeId: function _getDescriptionDomNodeId() {
  499. return this.domNode.id + 'Description';
  500. },
  501. recreateHeader: function recreateHeader(ev) {
  502. // Check and recreate the header
  503. this._createHeader(ev.widgetTitleView);
  504. },
  505. _createHeader: function _createHeader(widgetTitleViewInstance) {
  506. if (this.widgetRegistry) {
  507. this.createContentNode = this.widgetRegistry.createContentNode;
  508. this.supportsTitle = this.createContentNode && !this.widgetRegistry.disableTitle;
  509. this.createDescriptionNode = this.createContentNode || !!this.widgetRegistry.createDescriptionNode;
  510. } else {
  511. this.createContentNode = this.supportsTitle = this.createDescriptionNode = true;
  512. }
  513. var ariaLabelledBy = void 0;
  514. if (this.createDescriptionNode) {
  515. this._nWidgetDescription = $('<div>', {
  516. 'id': this._getDescriptionDomNodeId(),
  517. 'class': 'ariaLabelNode hidden'
  518. });
  519. this.$el.append(this._nWidgetDescription);
  520. ariaLabelledBy = this._getDescriptionDomNodeId();
  521. }
  522. if (this.createContentNode) {
  523. var nContainer = $('<div>', {
  524. 'class': 'widgetHeader'
  525. });
  526. if (this.supportsTitle) {
  527. var interactions = this.dashboardApi.getAppConfig('interactions') || {};
  528. var canEditTitle = interactions.editTitle === undefined || interactions.editTitle === true || interactions.editTitle === 'true';
  529. if (widgetTitleViewInstance) {
  530. this.widgetTitleView = widgetTitleViewInstance;
  531. } else {
  532. this.widgetTitleView = new WidgetTitleView({
  533. id: this.domNode.id,
  534. header: nContainer,
  535. canEditTitle: canEditTitle,
  536. widgetModel: this.widgetModel,
  537. widgetChromeEventRouter: this.widgetChromeEventRouter,
  538. dashboardApi: this.dashboardApi
  539. });
  540. var hidden = !!(this.widgetModel && !this.widgetModel.showTitle);
  541. this.widgetTitleView.render(hidden);
  542. }
  543. this.$el.addClass('titleSupported');
  544. ariaLabelledBy += ' ' + this.widgetTitleView.titleId;
  545. nContainer.prepend(this.widgetTitleView.getStyleNode());
  546. var nIcons = $('<div>', {
  547. 'class': 'widgetIcons'
  548. });
  549. this.$badgeContainer = $('<div>', {
  550. 'class': 'badgeContainer'
  551. });
  552. nContainer.append(this.$badgeContainer);
  553. nContainer.append(nIcons);
  554. }
  555. this.$widgetContentWrapper.prepend(nContainer);
  556. }
  557. if (ariaLabelledBy) {
  558. this.$el.attr('aria-labelledby', ariaLabelledBy);
  559. }
  560. },
  561. _createAttachedView: function _createAttachedView() {
  562. var viewId = this.domNode.id + '_attachedView';
  563. var nContainer = $('<div>', {
  564. id: viewId,
  565. 'class': 'attachedView'
  566. });
  567. this.$widgetContentWrapper.append(nContainer);
  568. this.$attachedView = nContainer;
  569. },
  570. _createFilterGroupOverlay: function _createFilterGroupOverlay() {
  571. var viewId = this.domNode.id + '_eventGroupOverlay';
  572. var nContainer = $('<div>', {
  573. id: viewId,
  574. 'class': 'eventGroupOverlay'
  575. });
  576. var nContent = $('<div>', {
  577. 'class': 'eventGroupOverlayContent'
  578. });
  579. nContainer.append(nContent);
  580. this.$widgetContentWrapper.append(nContainer);
  581. },
  582. onShowAttachedView: function onShowAttachedView(view) {
  583. this.$attachedView.html(view.$el).addClass('expanded');
  584. },
  585. onHideAttachedView: function onHideAttachedView() /*$html*/{
  586. this.$attachedView.removeClass('expanded').empty();
  587. },
  588. destroy: function destroy(event) {
  589. // clean up keydown event handler
  590. $(this.domNode).off('keydown.widgetKeydown');
  591. $(this.domNode).off('.widgetFocus');
  592. this._destroyAuthoringHelper();
  593. this.nHandle = null;
  594. // Notify the canvas to unload/destroy the widget module
  595. if (this.canvasNotifier) {
  596. this.canvasNotifier.trigger('widget:removeDone', _.extend(event || {}, {
  597. id: this.model.id,
  598. widget: this
  599. }));
  600. }
  601. if (this.widgetTitleView) {
  602. this.widgetTitleView.remove();
  603. this.widgetTitleView = null;
  604. }
  605. this._destroyed = true;
  606. Widget.inherited('destroy', this, arguments);
  607. },
  608. // Notify the widget
  609. _triggerWidgetChromeEvent: function _triggerWidgetChromeEvent(sMsg, options) {
  610. if (this.widgetChromeEventRouter) {
  611. this.widgetChromeEventRouter.trigger(sMsg, options);
  612. }
  613. },
  614. onResize: function onResize() {
  615. Widget.inherited('onResize', this, arguments);
  616. this._triggerWidgetChromeEvent('widget:onResize', _typeof(arguments[0]) === 'object' ? arguments[0] : {});
  617. },
  618. onSelect: function onSelect() {
  619. this._triggerWidgetChromeEvent('widgetchrome:selected');
  620. },
  621. onDeselect: function onDeselect() {
  622. this._triggerWidgetChromeEvent('widgetchrome:deselected');
  623. },
  624. /**
  625. * @param options - options to be passed and sent in the eventRouter payload.
  626. */
  627. onShow: function onShow(options) {
  628. Widget.inherited('onShow', this, arguments);
  629. this._triggerWidgetChromeEvent('widget:onShow', options);
  630. },
  631. onHide: function onHide() {
  632. Widget.inherited('onHide', this, arguments);
  633. this._triggerWidgetChromeEvent('widget:onHide');
  634. },
  635. onMaximize: function onMaximize() {
  636. this._triggerWidgetChromeEvent('widget:onMaximize');
  637. },
  638. setLayoutProperties: function setLayoutProperties(payload) {
  639. this.$el.toggleClass('noRotate', !!payload.noRotate);
  640. this.$el.toggleClass('mobilePannable', !!payload.mobilePannable);
  641. this.$el.toggleClass('pannable', !!payload.pannable);
  642. this.$el.toggleClass('maximizable', !!payload.maximizable);
  643. },
  644. addIcon: function addIcon(payload) {
  645. if (this.icons[payload.name]) {
  646. this.icons[payload.name].widgetIcon.detach();
  647. }
  648. //We can specify a location in the payload where we put the icon.
  649. if (payload.location) {
  650. payload.location.append(payload.widgetIcon);
  651. } else {
  652. this.$el.find('.widgetIcons').append(payload.widgetIcon);
  653. }
  654. this.icons[payload.name] = payload;
  655. },
  656. getContextToolbarItems: function getContextToolbarItems() /*hideFn*/{
  657. var toolbarItems = [];
  658. if (this.widgetAPI && !this.widgetAPI.getError()) {
  659. var consumeItems = this.widgetAPI.getContextToolbarItems();
  660. toolbarItems = toolbarItems.concat(consumeItems);
  661. }
  662. return {
  663. items: toolbarItems
  664. };
  665. },
  666. /**
  667. * Get the propertyUIControl spec for this layout & widget
  668. *
  669. * @returns {Promise} - Promise that will resolve with the property UI control spec
  670. */
  671. getProperties: function getProperties() {
  672. var _this5 = this;
  673. return this.widgetReady() //Ensure that the widget is present before we try to get the properties.
  674. .then(function () {
  675. return _this5.widgetAPI.getProperties();
  676. }).then(function (items) {
  677. return _this5._getLayoutProperties().then(function (layoutProperties) {
  678. return $.merge(items, layoutProperties);
  679. });
  680. });
  681. },
  682. _getLayoutProperties: function _getLayoutProperties() {
  683. var _this6 = this;
  684. var properties = this._getStyleProperties();
  685. return PropertiesUtil.processProperties(properties, this.model.style, this.dashboardApi).then(function () {
  686. if (_this6.supportsTitle) {
  687. properties = _this6.widgetTitleView.getProperties(properties);
  688. }
  689. return properties;
  690. });
  691. },
  692. _getLayoutOpacity: function _getLayoutOpacity() {
  693. if (this.model.style.opacity || this.model.style.opacity === 0) {
  694. return this.model.style.opacity;
  695. } else {
  696. return '1';
  697. }
  698. },
  699. _getStyleProperties: function _getStyleProperties() {
  700. this._registerOnPropertyChangeStyle();
  701. var props = [];
  702. props.push(this._getOpacityProperty());
  703. return props;
  704. },
  705. _getOpacityProperty: function _getOpacityProperty() {
  706. return {
  707. name: 'opacity',
  708. label: StringResources.get('propOpacity'),
  709. module: '../ui/UiSlider',
  710. sliderType: 'percentage',
  711. description: StringResources.get('propOpacityDescription'),
  712. connect: [true, false],
  713. defaultValue: 100,
  714. range: {
  715. 'min': 0,
  716. 'max': 100
  717. },
  718. step: 1,
  719. format: {
  720. 'decimals': 0,
  721. 'postfix': '%'
  722. },
  723. tabName: StringResources.get('tabName_general'),
  724. sectionName: StringResources.get('sectionName_appearance'),
  725. noInputView: true
  726. };
  727. },
  728. _registerOnPropertyChangeStyle: function _registerOnPropertyChangeStyle() {
  729. this.model.style.onPropertyChange = function (name, value) {
  730. var style = $.extend(true, {}, this.model.style);
  731. style[name] = value;
  732. this._updateModel({
  733. undoRedoTransactionId: _.uniqueId('changeLayoutStyle_' + name),
  734. triggerResize: false
  735. }, this, style);
  736. }.bind(this);
  737. },
  738. clearMoreDataIndicator: function clearMoreDataIndicator() {
  739. //Consume Mode View case
  740. var $moredataNode = this.$el.find('.widgetHeader .dataWidgetHasMoreData');
  741. if ($moredataNode && $moredataNode.length > 0) {
  742. $moredataNode.remove();
  743. } else {
  744. //DataSlots View case, in which the 'has more data message' is showing outside of the widget header
  745. //and it's in a dialogBlocker instead
  746. var $pageViewContent = this.$el.closest('.pageViewContent');
  747. if ($pageViewContent && $pageViewContent.length > 0) {
  748. $moredataNode = $pageViewContent.find('.dialogBlocker .dataWidgetHasMoreData');
  749. if ($moredataNode && $moredataNode.length > 0) {
  750. $moredataNode.remove();
  751. }
  752. }
  753. }
  754. },
  755. getWidgetAPI: function getWidgetAPI() {
  756. return this.widgetAPI;
  757. }
  758. });
  759. return Widget;
  760. });
  761. //# sourceMappingURL=Widget.js.map