'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2015, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['jquery', './StaticWidget', '../../../lib/@waca/core-client/js/core-client/utils/Utils', '../../../lib/@waca/core-client/js/core-client/utils/BidiUtil', '../../../dashboard/util/TextEditor', '../../../app/nls/StringResources', 'underscore', '../../../app/util/EventChainLocal', './TextPlaceholderView', './IconPlaceholderView', '../../../lib/@waca/dashboard-common/dist/api/PropertiesProviderAPI', '../../../lib/@waca/dashboard-common/dist/core/APIFactory'], function ($, StaticWidget, Utils, BidiUtil, TextEditor, resources, _, EventChainLocal, TextPlaceholderView, IconPlaceholderView, PropertiesProviderAPI, APIFactory) { var getDefaultSpec = function getDefaultSpec(name, options) { if (!options) { options = { style: 'responsive', placeholder: { showIcon: false, text: resources.get('textPlaceHolder') } }; } var widgetContent = '
' + TextEditor.getTextHTML(options) + '
'; var spec = { model: { type: 'text', name: resources.get('textPlaceHolder'), content: widgetContent }, layoutProperties: { style: { width: '200px', height: '50px' } } }; if (options.style === 'responsive') { spec.model.isResponsive = true; } if (options.placeholder) { spec.model.placeholder = options.placeholder; // build avatarHtml from placeholder text so avatar won't be blank if (!options.text) { spec.model.avatarHtml = '
' + TextEditor.getTextHTML(_.extend({ text: options.placeholder.text }, options)) + '
'; } } return Promise.resolve(spec); }; /** * The text widget object is a widget for displaying text strings on the canvas. In authoring mode, the text can be edited inline, * but in consumption mode becomes a read-only element of the canvas. */ var TextWidget = null; TextWidget = StaticWidget.extend({ _didCleanseContent: false, init: function init(options) { TextWidget.inherited('init', this, arguments); if (options && options.initialConfigJSON && options.initialConfigJSON.placeholder) { var placeholder = options.initialConfigJSON.placeholder; var PlaceholderView = placeholder.iconType ? IconPlaceholderView : TextPlaceholderView; this.placeholder = new PlaceholderView(_.extend({ el: this.$el }, placeholder)); this.$el.on('primaryaction', this.onSelect.bind(this)); } }, destroy: function destroy() { this.textEditor.toggleEditing(false); this.model.off('change:content', this.textEditor.applyContent, this.textEditor); this.content.off('change:property', this._onPropertyChange, this); this.$el.off('primaryaction', this.onSelect.bind(this)); this.textEditor.selectionUnbindNodeEvents(); this.textEditor.destroy(); TextWidget.inherited('destroy', this, arguments); }, _onPropertyChange: function _onPropertyChange() { this.updateModelContent(); }, onContainerReady: function onContainerReady() { var _this = this; TextWidget.inherited('onContainerReady', this, arguments); this.$el.append(this._cleanContent(this.model.get('content'))); var colorDefault = { 'color': 'responsiveColor' }; var defaults = this.initialConfigJSON && this.initialConfigJSON.defaults ? _.extend(this.initialConfigJSON.defaults, colorDefault) : colorDefault; this.textEditor = new TextEditor({ 'node': this.getWidgetStyleNode(), 'container': this.$widgetContainer, 'widget': this, 'toolbarNode': this.$widgetContainer, 'isResponsive': this.model.isResponsive, 'supportsLists': true, 'initialState': defaults }); BidiUtil.initElementForBidi(this.getWidgetStyleNode().get(0)); this.model.on('change:content', this.textEditor.applyContent, this.textEditor); this.content.on('change:property', this._onPropertyChange, this); this.addColorProperties(['content']); // The model is ready; if we cleansed the content on load, do a forced/silent save. if (this._didCleanseContent) { this._didCleanseContent = false; this._silentUpdate(); } this.textEditor.initContentEditable('p > span'); this.textEditor.selectionBindNodeEvents(); if (this.placeholder) { this.placeholder.render(); this.updatePlaceholder(); } this._updateAriaInfo(); //Setting the model to the current HTML so we properly deal with upgrading from old dashboards this.model.set({ content: this.getHtmlRender() }, { silent: true, payloadData: { skipUndoRedo: true } }); // todo lifecycle_cleanup -- this should be turned into a feature. var propertiesAPI = APIFactory.createAPI(this, [PropertiesProviderAPI]); this.content.getFeature('Properties').registerProvider(propertiesAPI); // TODO: replace this with a better fix after release // if custom fonts are in the save TextWidget, they will not resize properly on first load // need the setTimeout to allow the fonts to download // 900ms was the lowest timing that still worked on a slow internet connection return new Promise(function (resolve) { setTimeout(function () { _this.fillText(); resolve(); }, 900); }); }, _updateAriaInfo: function _updateAriaInfo() { this.updateDescription(this.getLabel()); var editableNodeId = this.model.id + 'Editable'; var editableNode = this.$el.find('.note-editable').get(0); if (editableNode) { editableNode.setAttribute('id', editableNodeId); this.$widgetContainer.attr('aria-labelledby', function () { return $(this).attr('aria-labelledby') + ' ' + editableNodeId; }); } }, onShow: function onShow() { this.fillText(); }, _shouldShowPlaceholder: function _shouldShowPlaceholder() { if (this.initialConfigJSON.placeholder.iconType === 'list') { return !this.textEditor.hasText(); } else { return !this.textEditor.hasContent(); } }, updatePlaceholder: function updatePlaceholder() { if (this._shouldShowPlaceholder()) { this._showPlaceholder(); } else { this._hidePlaceholder(); } }, _showPlaceholder: function _showPlaceholder() { this.$widgetContainer.addClass('placeholder'); this.textEditor.hide(); this.placeholder.show(); }, _hidePlaceholder: function _hidePlaceholder() { var immediateEditing = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this.$widgetContainer.removeClass('placeholder'); this.placeholder.hide(); this.textEditor.show(immediateEditing); }, onSelect: function onSelect(event) { var isDescendant = function isDescendant(node, className) { while (node) { if (node.classList && _.contains(node.classList, className)) { return true; } node = node.parentNode; } return false; }; if (this.isAuthoringMode) { if (this.placeholder && this.placeholder.isShowing()) { // Prevent widget ODT from displaying/updating new EventChainLocal(event).setProperty('preventDefaultContextBar', true); this._hidePlaceholder(true); } else if (this.placeholder && this._shouldShowPlaceholder() && isDescendant(event.target, 'moveHandle')) { this._showPlaceholder(); } else if (this.textEditor.isEditing()) { new EventChainLocal(event).setProperty('preventDefaultContextBar', true); } } }, onChromeSelected: function onChromeSelected(isGroupSelect) { TextWidget.inherited('onChromeSelected', this, arguments); if (this.isAuthoringMode && !isGroupSelect) { this.textEditor.attachEnterEditEvents(); } }, onChromeDeselected: function onChromeDeselected() { TextWidget.inherited('onChromeDeselected', this, arguments); if (this.placeholder && !this.placeholder.isShowing() && this._shouldShowPlaceholder()) { this._showPlaceholder(); } if (this.isAuthoringMode) { this.textEditor.toggleEditing(false); this.textEditor.detachEnterEditEvents(); } }, onAuthoringMode: function onAuthoringMode() { TextWidget.inherited('onAuthoringMode', this, arguments); }, onConsumeMode: function onConsumeMode() { TextWidget.inherited('onConsumeMode', this, arguments); this.textEditor.toggleEditing(false); }, onEnterContainer: function onEnterContainer() { this.textEditor.toggleEditing(true); if (this.placeholder && this.placeholder.isShowing()) { this._hidePlaceholder(true); } }, _cleanContent: function _cleanContent(content) { var cleansedContent = TextEditor.cleanContentElements(content); if (!cleansedContent || !this.isValidHtmlContent(cleansedContent)) { // TODO: Re-evaluate this, as our placeholders are now externalized. What is truly needed here? // Provide a fallback to default spec when content is unusable var textOptions = { 'text': resources.get('textPlaceHolder'), 'style': 'responsive' }; cleansedContent = '
' + TextEditor.getTextHTML(textOptions) + '
'; } if (cleansedContent !== content) { this._didCleanseContent = true; } return cleansedContent; }, _silentUpdate: function _silentUpdate() { this.onPropertyUpdate({ transactionId: null // Want a silent save (no undo) }); }, /** * Implementation of {@link PropertiesProviderAPI#getPropertyLayoutList} * TODO: need to add unit test */ getPropertyLayoutList: function getPropertyLayoutList() { var propertyLayoutList = TextWidget.inherited('getPropertyLayoutList', this, arguments) || []; var textEditorPropertyLayoutList = this.textEditor.getPropertyLayoutList(); if (textEditorPropertyLayoutList) { propertyLayoutList.push.apply(propertyLayoutList, textEditorPropertyLayoutList); } return propertyLayoutList; }, getPropertyList: function getPropertyList() { var propertyList = []; var textEditorPropertyList = this.textEditor.getPropertyList(); if (textEditorPropertyList) { propertyList.push.apply(propertyList, textEditorPropertyList); } return propertyList; }, selectAll: function selectAll() { document.getSelection().selectAllChildren(this.getWidgetStyleNode().get(0)); }, /** * Based on properties, generate the HTML for the static content */ getHtmlRender: function getHtmlRender() { return this.getWidgetStyleNode().get(0) ? this.getWidgetStyleNode().get(0).outerHTML : ''; }, fillText: function fillText() { if (!this.placeholder || !this.placeholder.isShowing()) { this.textEditor.fillText(); } else if (this.placeholder.fillText && this.placeholder.isShowing()) { this.placeholder.fillText(); } }, /* * Helpers. */ resize: function resize() { this.fillText(); if (this.placeholder && this.placeholder.resize) { this.placeholder.resize(); } }, registerEventGroup: function registerEventGroup() { // Text widgets are not added to event groups }, /** * Updates the model version of the markup of this widget * @param updatedHtml An optional parameter of the markup. * This is to avoid regenerating the markup if it is already known. */ updateModelContent: function updateModelContent(updatedHtml, transactionId) { // regenerate the HTML if not passed if (!updatedHtml) { updatedHtml = TextEditor.cleanContentElements(this.getHtmlRender()); } else { updatedHtml = TextEditor.cleanContentElements(updatedHtml); } // update the model to persist the changes. Use transactionId to combine undos var data = {}; if (transactionId) { _.extend(data, { payloadData: { undoRedoTransactionId: transactionId } }); } else { _.extend(data, { silent: true }); } var widgetTitle = this.textEditor.getTitleFromHtml(this.getHtmlRender()); this.set({ content: updatedHtml, name: widgetTitle, isResponsive: this.textEditor.isResponsive }, data); }, getLabel: function getLabel() { return resources.get('textWidgetTitle'); } }); TextWidget.getDefaultSpec = getDefaultSpec; return TextWidget; }); //# sourceMappingURL=TextWidget.js.map