'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