WidgetTitleView.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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. 2017, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['../../../lib/@waca/core-client/js/core-client/ui/core/View', '../../../lib/@waca/core-client/js/core-client/utils/BidiUtil', '../../../dashboard/util/TextEditor', '../../../app/util/LocaleUtil', '../../../app/nls/StringResources', 'jquery', 'underscore'], function (View, BidiUtil, TextEditor, LocaleUtil, resources, $, _) {
  9. var localizedProp = function localizedProp(str) {
  10. if ((typeof str === 'undefined' ? 'undefined' : _typeof(str)) === 'object') {
  11. var locale = document.getElementsByTagName('html')[0].getAttribute('lang');
  12. return str[locale] || str.en || str[Object.keys(str)[0]];
  13. }
  14. return str;
  15. };
  16. var WidgetTitleView = View.extend({
  17. init: function init(options) {
  18. this.titleId = options.id + 'Title';
  19. this.canEditTitle = options.canEditTitle;
  20. this.widgetModel = options.widgetModel;
  21. this.widgetChromeEventRouter = options.widgetChromeEventRouter;
  22. this.header = options.header;
  23. this.dashboardApi = options.dashboardApi;
  24. this.registerEvents();
  25. },
  26. registerEvents: function registerEvents() {
  27. this.widgetChromeEventRouter.on('title:chromeSelected', this.onChromeSelected, this);
  28. this.widgetChromeEventRouter.on('title:chromeDeselected', this.onChromeDeselected, this);
  29. this.widgetChromeEventRouter.on('title:containerReady', this.onContainerReady, this);
  30. this.widgetChromeEventRouter.on('title:enterContainer', this.onEnterContainer, this);
  31. this.widgetChromeEventRouter.on('title:updateModel', this.updateModelContent, this);
  32. this.widgetChromeEventRouter.on('widget:clearWidgetArialabel', this.clearWidgetArialabel, this);
  33. this.widgetChromeEventRouter.on('widget:updateWidgetArialabel', this.updateWidgetArialabel, this);
  34. if (this._isSmartTitleEnabled()) {
  35. this.widgetModel.on('change:titleMode', this._showTitle, this);
  36. } else {
  37. this.widgetModel.on('change:showTitle', this._showTitle, this);
  38. }
  39. },
  40. /**
  41. * Based on properties, generate the HTML for the static content
  42. */
  43. getHtmlRender: function getHtmlRender() {
  44. return this.getStyleNode().get(0).outerHTML;
  45. },
  46. _getTextProperties: function _getTextProperties(text) {
  47. return {
  48. style: 'responsive',
  49. text: text
  50. };
  51. },
  52. /**
  53. * Render widget title view
  54. * @param {boolean} [hidden=false] whether to render hidden or not
  55. */
  56. render: function render() {
  57. var hidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  58. var titleHtml = void 0;
  59. var title = '';
  60. if (this.widgetModel && this.widgetModel.get) {
  61. titleHtml = TextEditor.cleanContentElements(this.widgetModel.get('titleHtml'));
  62. title = this.widgetModel.get('name');
  63. }
  64. var sTitle = title ? localizedProp(title) : '';
  65. if (titleHtml) {
  66. this.$el = $(titleHtml);
  67. if (!this.$el.length) {
  68. this.$el = $('<div class="widgetTitle" title="' + sTitle + '">' + titleHtml + '</div>');
  69. }
  70. this._nTitle = this.$el.find('.textArea');
  71. } else {
  72. this.$el = $('<div>', {
  73. 'class': 'widgetTitle',
  74. 'title': sTitle
  75. });
  76. this.$el.attr('aria-labelledby', this.titleId);
  77. this._nTitle = $('<div>', {
  78. 'class': 'textArea'
  79. });
  80. this.$el.prepend(this._nTitle);
  81. }
  82. this.$el.attr('appcues-data-id', 'widgetTitle');
  83. this.$el.toggleClass('titleShown', !!(sTitle && sTitle.length));
  84. var shouldShowTitle = this._shouldShowTitle();
  85. this._nTitle.toggleClass('hidden', hidden && !shouldShowTitle);
  86. },
  87. remove: function remove() {
  88. this.widgetChromeEventRouter.off('title:chromeSelected', this.onChromeSelected, this);
  89. this.widgetChromeEventRouter.off('title:chromeDeselected', this.onChromeDeselected, this);
  90. this.widgetChromeEventRouter.off('title:containerReady', this.onContainerReady, this);
  91. this.widgetChromeEventRouter.off('title:enterContainer', this.onEnterContainer, this);
  92. this.widgetChromeEventRouter.off('title:updateModel', this.updateModelContent, this);
  93. this.widgetChromeEventRouter.off('widget:clearWidgetArialabel', this.clearWidgetArialabel, this);
  94. this.widgetChromeEventRouter.off('widget:updateWidgetArialabel', this.updateWidgetArialabel, this);
  95. if (this._isSmartTitleEnabled()) {
  96. this.widgetModel.off('change:titleMode', this._showTitle, this);
  97. } else {
  98. this.widgetModel.off('change:showTitle', this._showTitle, this);
  99. }
  100. this.widgetModel.off('change:titleHtml', this._onTitleHtmlChange, this);
  101. if (this.textEditor) {
  102. this.textEditor.destroy();
  103. this.textEditor = null;
  104. }
  105. WidgetTitleView.inherited('remove', this, arguments);
  106. },
  107. onContainerReady: function onContainerReady(widget) {
  108. var _this = this;
  109. var truncateFeature = widget && widget.content && widget.content.getFeature && widget.content.getFeature('WidgetTitleTruncate');
  110. var extension = truncateFeature && truncateFeature.getExtensionHooks();
  111. truncateFeature && truncateFeature.setUpdateModelContent(this.updateModelContent.bind(this));
  112. truncateFeature && truncateFeature.setTitleHighlighter(this._highlightTitle.bind(this));
  113. this._handleEditUpdateTruncation = extension && extension.onEditUpdate;
  114. // TODO: Revisit how this class is implemented.
  115. // There could be a chance that some of the functions in the class are called before onContainerReady,
  116. // causing a null exception when accessing 'this.textEditor'. (e.g. updateTitle())
  117. // For now, we check for this.textEditor === null all over this class to make sure when don't blow up.
  118. this.widget = widget;
  119. this.textEditor = new TextEditor({
  120. 'node': this._nTitle,
  121. 'container': this.header,
  122. 'widget': widget,
  123. 'fontSizes': TextEditor.INT_FONTSIZE_CHOICES,
  124. 'toolbarNode': widget.$widgetContainer,
  125. 'shouldResizeViz': true,
  126. 'initialState': {
  127. 'fontSize': '16',
  128. 'color': 'responsiveColor'
  129. },
  130. 'extension': extension
  131. });
  132. if (LocaleUtil.isLocaleRTL()) {
  133. BidiUtil.initElementForBidi(this._nTitle);
  134. }
  135. //Widget title is never responsive (Removed auto size)
  136. this.textEditor.isResponsive = false;
  137. this.widgetModel.on('change:titleHtml', this._onTitleHtmlChange, this);
  138. this.textEditor.initContentEditable('p');
  139. this.textEditor.selectionBindNodeEvents();
  140. this._applyTitle();
  141. this._updateAriaInfo();
  142. // register the textEditor as a properties provider for text format properties
  143. return this.dashboardApi.getCanvasWhenReady().then(function (canvas) {
  144. var content = canvas.getContent(widget.id);
  145. content.getFeature('Properties').registerProvider(_this.textEditor);
  146. content.getFeature('TextEditor').registerProvider({
  147. 'widgetId': widget.id,
  148. 'textEditor': _this.textEditor,
  149. 'titleId': _this.titleId
  150. });
  151. });
  152. },
  153. _updateAriaInfo: function _updateAriaInfo() {
  154. var titleNodeId = this.widgetModel.id + 'Title';
  155. this.$el.find('.note-editable').attr('id', titleNodeId);
  156. },
  157. _onTitleHtmlChange: function _onTitleHtmlChange(options) {
  158. options = options || {};
  159. if (options && (options.value === null || options.value === '' || options.value === undefined)) {
  160. this._setTitleHtmlFromName();
  161. } else {
  162. if (this.textEditor) {
  163. var appliedContentResult = this.textEditor.applyContent(options);
  164. var title = this.textEditor.getTitleFromHtml(this.getHtmlRender());
  165. this.$el.prop('title', title); //update title, so tooltip is also synced
  166. return appliedContentResult;
  167. }
  168. }
  169. },
  170. _applyTitle: function _applyTitle() {
  171. var _this2 = this;
  172. if (!this.textEditor) {
  173. return;
  174. }
  175. var translationService = this.dashboardApi.getFeature('TranslationService');
  176. if (!this.widgetModel.get('name')) {
  177. //We only show placeholder text if smart title is disabled or
  178. //when the smart title feature is enabled and the user is in the custom title
  179. var titleMode = this.widget && this.widget.content && this.widget.content.getPropertyValue('titleMode');
  180. var smartTitleEnabled = this._isSmartTitleEnabled();
  181. var addPlaceHolderTitle = false;
  182. if (smartTitleEnabled) {
  183. addPlaceHolderTitle = titleMode && titleMode === 'customTitle';
  184. } else {
  185. addPlaceHolderTitle = true;
  186. }
  187. if (this.canEditTitle && addPlaceHolderTitle) {
  188. var prop = this._getTextProperties(resources.get('titlePlaceHolder'));
  189. this.textEditor.setText(TextEditor.getTextHTML(prop));
  190. smartTitleEnabled && this.textEditor.toggleEditing(true);
  191. } else {
  192. this.textEditor.setText(' ');
  193. }
  194. } else if (!this.widgetModel.get('titleHtml')) {
  195. //Special case for upgraded dashboard.
  196. if (this.widgetModel['name'] && this.widgetModel['name'].translationTable) {
  197. //update all the locales
  198. _.each(this.widgetModel['name'].translationTable, function (item, locale) {
  199. //update the locale
  200. _this2.widgetModel.set({ 'name': { locale: locale, value: item } });
  201. _this2._setTitleHtmlFromName(locale);
  202. });
  203. }
  204. //and ensure our html is reset to the default locale.
  205. this._setTitleHtmlFromName();
  206. } else if (translationService.isInTranslationMode()) {
  207. this._updateTextEditor();
  208. }
  209. },
  210. /**
  211. * Set the HTML for a specific name properly using the provided locale (or Default if locale is null)
  212. * This is storing HTML directly in the spec. This is not ideal to say the least.
  213. */
  214. _setTitleHtmlFromName: function _setTitleHtmlFromName(locale) {
  215. if (!this.textEditor) {
  216. return;
  217. }
  218. var prop = this._updateTextEditor(locale);
  219. //update the aria labels
  220. if (prop.text) {
  221. this.$el.prop('title', prop.text);
  222. this.$el.find('.ariaLabelNode').html(TextEditor.cleanContentElements(prop.text));
  223. }
  224. var updatedHtml = TextEditor.cleanContentElements(this.getHtmlRender());
  225. //Get the default language if ant
  226. var boardModel = this.dashboardApi.getFeature('internal').getBoardModel();
  227. var titleOptions = boardModel.getLanguageModelOptions();
  228. _.extend(titleOptions, {
  229. silent: true
  230. });
  231. this.widgetModel.set({ titleHtml: { value: updatedHtml, locale: locale } }, titleOptions);
  232. },
  233. _updateTextEditor: function _updateTextEditor(locale) {
  234. var _propValue = this.widgetModel.get('name');
  235. //get the localized value of the name
  236. if (locale && this.widgetModel['name']) {
  237. _propValue = this.widgetModel['name'].getValue(locale);
  238. }
  239. var prop = this._getTextProperties(_propValue);
  240. var _textHTML = TextEditor.getTextHTML(prop);
  241. this.textEditor.setText(_textHTML);
  242. //update so smart title updates don't confuse text editor into thinking edit happened and it needs to save
  243. this.textEditor.updateLastSavedText(_textHTML);
  244. return prop;
  245. },
  246. _showTitle: function _showTitle(options) {
  247. this.updateTitle(options);
  248. //only toggleEdit (highlights all title text) when needed - if smart title is disabled or when its enabled and we are changing
  249. //from noTitle to customTitle
  250. var showTitle = this.widgetModel.showTitle;
  251. var titleMode = this.widget.content.getPropertyValue('titleMode');
  252. if (!showTitle && titleMode === 'customTitle') {
  253. if (options && options.prevValue === 'noTitle') {
  254. showTitle = true;
  255. } else {
  256. showTitle = false;
  257. }
  258. }
  259. if (this.canEditTitle && showTitle) {
  260. if (this.textEditor && this.widget && this.widget.chromeSelected && this.widget.isAuthoringMode) {
  261. this.textEditor.toggleEditing(!(options && options.data && options.data.edit === false), options);
  262. }
  263. }
  264. },
  265. _hasHeader: function _hasHeader() {
  266. if (this.widget && this.widget.$el && this.widget.$el.parent() && this.widget.$el.parent().find) {
  267. return this.widget.$el.parent().find('.widgetHeader').length !== 0;
  268. }
  269. return false;
  270. },
  271. _toggleEdit: function _toggleEdit(toggle) {
  272. if (!this.textEditor) {
  273. return;
  274. }
  275. var eventName = 'showTitle';
  276. var eventValue = false;
  277. if (this._isSmartTitleEnabled()) {
  278. eventName = 'titleMode';
  279. eventValue = 'noTitle';
  280. }
  281. if (this.textEditor.isEditing() && this.textEditor.textIsEmpty() && toggle === false) {
  282. this.widget.content.setPropertyValue(eventName, eventValue);
  283. }
  284. this.textEditor.toggleEditing(toggle);
  285. },
  286. checkAndRecreateHeader: function checkAndRecreateHeader() {
  287. if (!this.widget) {
  288. return;
  289. }
  290. if (this.widget.isWidgetMaximized && this.widget.isWidgetMaximized()) {
  291. return;
  292. }
  293. if (!this._hasHeader()) {
  294. this.widgetChromeEventRouter.trigger('title:resetTitle', {
  295. widgetTitleView: this
  296. });
  297. }
  298. },
  299. _shouldShowTitle: function _shouldShowTitle() {
  300. //ShowTitle-support old (showTitle) and new (titleMode)
  301. if (this.widgetModel.titleMode === undefined) {
  302. return !!this.widgetModel.showTitle;
  303. }
  304. var titleMode = this.widgetModel.titleMode || 'smartTitle';
  305. return titleMode !== 'noTitle';
  306. },
  307. _isWidgetReady: function _isWidgetReady() {
  308. var widgetReady = true;
  309. var visualization = this.widget && this.widget.content && this.widget.content.getFeature('Visualization');
  310. if (visualization && visualization.getSlots() && visualization.getSlots().isMappingComplete() === false) {
  311. widgetReady = false;
  312. }
  313. return widgetReady;
  314. },
  315. updateTitle: function updateTitle(options) {
  316. var widgetReady = this._isWidgetReady();
  317. var shouldShowTitle = this._shouldShowTitle() && widgetReady;
  318. var wasShowingTitle = !this._nTitle.hasClass('hidden');
  319. if (shouldShowTitle) {
  320. this.checkAndRecreateHeader();
  321. this._applyTitle();
  322. this._nTitle.removeClass('hidden');
  323. } else {
  324. this._nTitle.addClass('hidden');
  325. if (this.textEditor) {
  326. this.textEditor.toggleEditing(false, options);
  327. }
  328. }
  329. if (shouldShowTitle !== wasShowingTitle) {
  330. this.widgetChromeEventRouter.trigger('title:resizeViz');
  331. }
  332. },
  333. onChromeSelected: function onChromeSelected() {
  334. WidgetTitleView.inherited('onChromeSelected', this, arguments);
  335. if (this.textEditor && this.canEditTitle) {
  336. this.textEditor.attachEnterEditEvents();
  337. }
  338. },
  339. onChromeDeselected: function onChromeDeselected() {
  340. WidgetTitleView.inherited('onChromeDeselected', this, arguments);
  341. this._toggleEdit(false);
  342. if (this.textEditor) {
  343. this.textEditor.detachEnterEditEvents();
  344. }
  345. },
  346. onChromeOffFocus: function onChromeOffFocus() {
  347. this._toggleEdit(false);
  348. },
  349. getProperties: function getProperties(properties) {
  350. return this.textEditor.getProperties(properties);
  351. },
  352. onEnterContainer: function onEnterContainer() {
  353. if (this.textEditor && this.canEditTitle) {
  354. this.textEditor.toggleEditing(true);
  355. }
  356. },
  357. /**
  358. * Updates the model version of the markup of this widget
  359. * @param updatedHtml An optional parameter of the markup.
  360. * This is to avoid regenerating the markup if it is already known.
  361. */
  362. updateModelContent: function updateModelContent(info) {
  363. if (!this.textEditor) {
  364. return;
  365. }
  366. // update the model to persist the changes. Use transactionId to combine undos
  367. var boardModel = this.dashboardApi.getFeature('internal').getBoardModel();
  368. var data = boardModel.getLanguageModelOptions();
  369. if (info.transactionId) {
  370. _.extend(data, {
  371. payloadData: {
  372. undoRedoTransactionId: info.transactionId,
  373. transactionToken: info.transactionToken
  374. }
  375. });
  376. } else {
  377. _.extend(data, {
  378. silent: true
  379. });
  380. }
  381. //SMEditTest
  382. var title = this.textEditor.getTitleFromHtml(this.getHtmlRender());
  383. var existingTitle = null;
  384. if (this.widgetModel && this.widgetModel.get) {
  385. existingTitle = this.widgetModel.get('name');
  386. }
  387. var sExistingTitle = existingTitle ? localizedProp(existingTitle) : '';
  388. this.$el.prop('title', title); //update title, so tooltip is also synced
  389. var updatedHtml = TextEditor.cleanContentElements(this.getHtmlRender());
  390. this._handleEditUpdateTruncation && this._handleEditUpdateTruncation(sExistingTitle, updatedHtml, title, info.transactionId);
  391. //SMEditTest
  392. this.widgetModel.set({
  393. titleHtml: updatedHtml,
  394. name: title
  395. }, data);
  396. },
  397. //only used in the context of edit Title action - in some cases, we just need to highlight the title
  398. _highlightTitle: function _highlightTitle() {
  399. this.textEditor.toggleEditing(true);
  400. },
  401. updateWidgetArialabel: function updateWidgetArialabel(event) {
  402. if (this._nAriaLabel) {
  403. this._nAriaLabel.text(event.value);
  404. }
  405. },
  406. clearWidgetArialabel: function clearWidgetArialabel() {
  407. if (this._nAriaLabel) {
  408. this._nAriaLabel.empty();
  409. }
  410. },
  411. getStyleNode: function getStyleNode() {
  412. return this.$el;
  413. },
  414. _isSmartTitleEnabled: function _isSmartTitleEnabled() {
  415. var featureChecker = this.dashboardApi.getGlassCoreSvc('.FeatureChecker');
  416. if (featureChecker) {
  417. return !(featureChecker.checkValue && featureChecker.checkValue('dashboard', 'SmartTitle', 'disabled'));
  418. }
  419. return false;
  420. }
  421. });
  422. return WidgetTitleView;
  423. });
  424. //# sourceMappingURL=WidgetTitleView.js.map