/** * Private class used by {@link Ext.froala.Editor} and {@link Ext.froala.EditorField}. * If you use this mixin, your class must override component methods handed here: * - doAddListener -> handleAddListener * - doRemoveListener -> handleRemoveListener * - updateValue * See source for Ext.froala.Editor or EditorField for examples. */Ext.define('Ext.froala.Mixin', { extend: 'Ext.Mixin', twoWayBindable: ['value'], defaultBindProperty: 'value', config: { /** * @cfg {String} activationKey The Froala activation key. If specified, this * take precedence over the activation key configured in your application's * `app.json`. */ activationKey: undefined, /** * @cfg {Object} defaultEditor The default Froala editor configs passed to the Froala * constructor. This value is merged with the {@link #editor} config you specify. This can * only be specified at time of creation and cannot be set later. */ defaultEditor: { iconsTemplate: 'font_awesome_5' }, /** * @cfg {String} value * The text content of the editor. */ value: '', /** * A Froala config object as documented at * https://www.froala.com/wysiwyg-editor/docs/options * This config is set once, upon creation of the FroalaEditor and cannot be updated later. * The most commonly provided value is `toolbarButtons`, which defaults to * editor: { * toolbarButtons: { * 'moreText': { * 'buttons': ['bold', 'italic', 'underline', 'strikeThrough', * 'subscript', 'superscript', 'fontFamily', * 'fontSize', 'textColor', 'backgroundColor', 'inlineClass', * 'inlineStyle', 'clearFormatting' * ] * }, * 'moreParagraph': { * 'buttons': ['alignLeft', 'alignCenter', 'formatOLSimple', 'alignRight', * 'alignJustify', 'formatOL', 'formatUL', 'paragraphFormat', 'paragraphStyle', * 'lineHeight', 'outdent', 'indent', 'quote' * ] * }, * 'moreRich': { * 'buttons': ['insertLink', 'insertImage', 'insertVideo', 'insertTable', * 'emoticons', 'fontAwesome', 'specialCharacters', 'embedly', * 'insertFile', 'insertHR' * ] * }, * 'moreMisc': { * 'buttons': ['undo', 'redo', 'fullscreen', 'print', 'getPDF', 'spellChecker', * 'selectAll', 'html', 'help' * ], * 'align': 'right', * 'buttonsVisible': 2 * } * } * } */ editor: {} }, ariaRole: 'texteditor', /** * @property {Boolean} isFroalaEditor * Identifies this class and its subclasses. * @readonly */ isFroalaEditor: true, /** * @property {Boolean} isReady * Flags whether the Froala editor instance has been initialized. Initialization * happens automatically when the component is created, but takes several milliseconds. * Upon initialization, the {@link #event-ready} event is fired. * @readonly */ isReady: false, onFroalaContentChanged: function() { var me = this; if (Ext.isFunction(me.publishValue)) { me.publishValue(); } else { me.setValue(me.getEditor().html.get()); } }, createFroalaEditor: function(config, froalaEl) { var me = this, defaultConfig = me.getDefaultEditor(), options, froalaEditor, value, key = Ext.manifest.froala, froalaEditorDomElement = froalaEl || me.getFroalaEditorDomElement(), bufferedChangedEvent = Ext.Function.createBuffered(me.onFroalaContentChanged, 50, me); // bufferedChangedEvent avoids running the change event more often than necessary. options = Ext.merge(me.getEditor(), defaultConfig); if (config.events) { options.events = config.events; } key = me.getActivationKey() || (key && key['activation-key']); if (key) { options.key = key; } froalaEditor = new FroalaEditor(froalaEditorDomElement, options, function() { value = config.value || me.getValue(); froalaEditor.component = me; me.monitorConfiguredListeners(); froalaEditor.isReady = true; me.fireEvent('ready', me, froalaEditor); froalaEditor.events.on('contentChanged', bufferedChangedEvent); froalaEditor.html.set(value); if (!me.activeErrorsTpl && me.xtype === 'froalaeditorfield') { me.activeErrorsTpl = me.htmlActiveErrorsTpl; me.setActiveError(config.activeError); } me.initialValue = me.originalValue = me.lastValue = value; }); froalaEditor.isReady = false; return froalaEditor; }, updateValue: function(value) { var me = this, editor = me.getEditor(), editorValue; if (editor && editor.isReady) { me.fireEvent('change', me, value); editorValue = editor.snapshot.get().html || editor.html.get(); // The value won't change if it came from // onFroalaContentChanged. Otherwise, someone // ran setValue() on the component and the // editor's html has to reflect that. if (value !== editorValue) { editor.html.set(value); if (Ext.isFunction(me.publishValue)) { me.publishValue(); } } } }, updateDisabled: function(disabled) { var editor = this.getEditor(); if (editor) { editor.edit[disabled ? 'off' : 'on'](); } }, privates: { /** * Set up Froalaq events specified in the listeners:{} block. * For non-Froala events, using listeners:{} works fine. But * for Froala events, we have to wait until aftr the Froala * instance is created before we can add listeners to it. This * method is run one time, immediately after the Froala * instance has been created. */ monitorConfiguredListeners: function(froalaEditor) { // Assert: froalaListenersConfig has been inialized with // the original Froala event names (which may be camel-case). // By the time we get here, the ExtJS framework takes the // names from listeners:{} and puts then into hasListeners, // which are all lower case. froalaListenersConfigNames maps // the lowercase name to the original name var me = this, originalName, eventNames = Object.keys(me.hasListeners); eventNames.forEach(function(event) { if (me.isFroalaEvent(event)) { originalName = me.froalaListenersConfigNames[event]; me.setupListener(originalName); } }); }, froalaNamePrefixRe: /froala\./, /** * @param {String} event - The event name being checked. * @returns {Boolean} true if the event is a Froala event. */ isFroalaEvent: function(event) { return !!event.match(this.froalaNamePrefixRe); }, translateFroalaEventName: function(event) { return event.replace(this.froalaNamePrefixRe, ''); }, setupListener: function(event) { var me = this, froalaEditor = me.getEditor(), translatedFroalaEventName, froalaEventsBeingMonitored; // This method is called from two places: // - When a listener is added procedurally, via on(), after the // Froala editor instance exists. // - One time immediately after the Froala editor instance is // created, in order to add the items in listeners:{} // // Upon entry, the event has already been added to this Observable. // But for Froala events, we need to add a listener to the Froala // instance. If it's not a Froala event, just ignore it and the // event will get fired in the normal way. if (!me.isFroalaEvent(event)) { return; } // Add the event to Froala, passing an event handler. The handler // simply fires the event via Observable. That means we only need // to setup the Froala event once. The flow is: Froala detects the // event, Froala calls the one event handler which then fires the // event, and Observable takes care of informing all listeners. froalaEventsBeingMonitored = me.getFroalaEventsBeingMonitored(); if (!froalaEventsBeingMonitored[event]) { translatedFroalaEventName = me.translateFroalaEventName(event); froalaEditor.events.on(translatedFroalaEventName, createHandler(event, me)); froalaEventsBeingMonitored[event] = true; } function createHandler(name) { // Return the froala event handler. The event handler simply // fires the component event via fireEventArgs(), using the // name in closure scope. This component is passed as the // first argument, followed by the Froala arguments. return function() { var args = Array.prototype.slice.call(arguments); args.unshift(me); me.fireEventArgs(name, args); }; } }, handleAddListener: function(ename) { var me = this, froalaEditor, isBeingRunFromListenersConfig; if (!me.isFroalaEvent(ename)) { return; } // This method is called by the overridden doAddListener // from Editor or EditorField. It's run whenever a listener // is being added, either via a listeners:{} config or via // view.on(). If this is called after initialization (and // froalaEditor.isReady is true), then set up the Foala // listener with the editor instance. But if it's run via // the component config, and therefore, before the editor // exists, then we need to remember the event name and add // it later, after the editor is created. That's done in // monitorConfiguredListeners(). froalaEditor = me.getEditor(); isBeingRunFromListenersConfig = !(froalaEditor && froalaEditor.isReady); if (isBeingRunFromListenersConfig) { // ename may be camel-case. But when ExtJS initialized listeners:{} // and updates hasListeners, those events are changed to lowercase. // Therefore, save the lowercase name and associate it with the original // name. me.froalaListenersConfigNames[ename.toLowerCase()] = ename; } else { me.setupListener(ename); } }, froalaListenersConfigNames: {}, handleRemoveListener: function(ename) { var me = this, froalaEditor = me.getEditor(); if (!(froalaEditor && froalaEditor.isReady)) { return; } // If this is an event we're monitoring on the Froala instance, // but there are no longer any listeners, then tell Froala to stop // monitored. if (me.isFroalaEvent(ename)) { if (!me.hasListeners[ename]) { // TODO: A future release of Froala will have an "off()" event, // used to remove an event listener. When that's addded, use // this code to clean up listeners. This is un-tested code. // froalaEditor.events.off(ename); delete me.getFroalaEventsBeingMonitored()[ename]; } } }, getFroalaEventsBeingMonitored: function() { return (this.froalaEventsBeingMonitored = this.froalaEventsBeingMonitored || {}); } }, getFroalaEditorDomElement: function() { Ext.raise('getFroalaEditorDomElement must be overridden in the class using froala/Mixins'); }});