/** * EditorManager.js * * Copyright, Moxiecode Systems AB * Released under LGPL License. * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ (function(tinymce) { /** * @class tinymce */ // Shorten names var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode, Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; // Setup some URLs where the editor API is located and where the document is tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); if (!/[\/\\]$/.test(tinymce.documentBaseURL)) tinymce.documentBaseURL += '/'; tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); /** * Absolute baseURI for the installation path of TinyMCE. * * @property baseURI * @type tinymce.util.URI */ tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); // Add before unload listener // This was required since IE was leaking memory if you added and removed beforeunload listeners // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event tinymce.onBeforeUnload = new Dispatcher(tinymce); // Must be on window or IE will leak if the editor is placed in frame or iframe Event.add(window, 'beforeunload', function(e) { tinymce.onBeforeUnload.dispatch(tinymce, e); }); /** * Fires when a new editor instance is added to the tinymce collection. * * @event onAddEditor * @param {tinymce} sender TinyMCE root class/namespace. * @param {tinymce.Editor} editor Editor instance. * @example * tinyMCE.execCommand("mceAddControl", false, "some_textarea"); * tinyMCE.onAddEditor.add(function(mgr,ed) { * console.debug('A new editor is available' + ed.id); * }); */ tinymce.onAddEditor = new Dispatcher(tinymce); /** * Fires when an editor instance is removed from the tinymce collection. * * @event onRemoveEditor * @param {tinymce} sender TinyMCE root class/namespace. * @param {tinymce.Editor} editor Editor instance. */ tinymce.onRemoveEditor = new Dispatcher(tinymce); tinymce.EditorManager = extend(tinymce, { /** * Collection of editor instances. * * @property editors * @type Object * @example * for (edId in tinyMCE.editors) * tinyMCE.editors[edId].save(); */ editors : [], /** * Collection of language pack data. * * @property i18n * @type Object */ i18n : {}, /** * Currently active editor instance. * * @property activeEditor * @type tinymce.Editor * @example * tinyMCE.activeEditor.selection.getContent(); * tinymce.EditorManager.activeEditor.selection.getContent(); */ activeEditor : null, /** * Initializes a set of editors. This method will create a bunch of editors based in the input. * * @method init * @param {Object} s Settings object to be passed to each editor instance. * @example * // Initializes a editor using the longer method * tinymce.EditorManager.init({ * some_settings : 'some value' * }); * * // Initializes a editor instance using the shorter version * tinyMCE.init({ * some_settings : 'some value' * }); */ init : function(s) { var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; function createId(elm) { var id = elm.id; // Use element id, or unique name or generate a unique id if (!id) { id = elm.name; if (id && !DOM.get(id)) { id = elm.name; } else { // Generate unique name id = DOM.uniqueId(); } elm.setAttribute('id', id); } return id; }; function execCallback(se, n, s) { var f = se[n]; if (!f) return; if (tinymce.is(f, 'string')) { s = f.replace(/\.\w+$/, ''); s = s ? tinymce.resolve(s) : 0; f = tinymce.resolve(f); } return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); }; function hasClass(n, c) { return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); }; t.settings = s; // Legacy call Event.bind(window, 'ready', function() { var l, co; execCallback(s, 'onpageload'); switch (s.mode) { case "exact": l = s.elements || ''; if(l.length > 0) { each(explode(l), function(v) { if (DOM.get(v)) { ed = new tinymce.Editor(v, s); el.push(ed); ed.render(1); } else { each(document.forms, function(f) { each(f.elements, function(e) { if (e.name === v) { v = 'mce_editor_' + instanceCounter++; DOM.setAttrib(e, 'id', v); ed = new tinymce.Editor(v, s); el.push(ed); ed.render(1); } }); }); } }); } break; case "textareas": case "specific_textareas": each(DOM.select('textarea'), function(elm) { if (s.editor_deselector && hasClass(elm, s.editor_deselector)) return; if (!s.editor_selector || hasClass(elm, s.editor_selector)) { ed = new tinymce.Editor(createId(elm), s); el.push(ed); ed.render(1); } }); break; default: if (s.types) { // Process type specific selector each(s.types, function(type) { each(DOM.select(type.selector), function(elm) { var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); el.push(editor); editor.render(1); }); }); } else if (s.selector) { // Process global selector each(DOM.select(s.selector), function(elm) { var editor = new tinymce.Editor(createId(elm), s); el.push(editor); editor.render(1); }); } } // Call onInit when all editors are initialized if (s.oninit) { l = co = 0; each(el, function(ed) { co++; if (!ed.initialized) { // Wait for it ed.onInit.add(function() { l++; // All done if (l == co) execCallback(s, 'oninit'); }); } else l++; // All done if (l == co) execCallback(s, 'oninit'); }); } }); }, /** * Returns a editor instance by id. * * @method get * @param {String/Number} id Editor instance id or index to return. * @return {tinymce.Editor} Editor instance to return. * @example * // Adds an onclick event to an editor by id (shorter version) * tinyMCE.get('mytextbox').onClick.add(function(ed, e) { * ed.windowManager.alert('Hello world!'); * }); * * // Adds an onclick event to an editor by id (longer version) * tinymce.EditorManager.get('mytextbox').onClick.add(function(ed, e) { * ed.windowManager.alert('Hello world!'); * }); */ get : function(id) { if (id === undef) return this.editors; if (!this.editors.hasOwnProperty(id)) return undef; return this.editors[id]; }, /** * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. * * @method getInstanceById * @param {String} id Editor instance id to return. * @return {tinymce.Editor} Editor instance to return. * @deprecated Use get method instead. * @see #get */ getInstanceById : function(id) { return this.get(id); }, /** * Adds an editor instance to the editor collection. This will also set it as the active editor. * * @method add * @param {tinymce.Editor} editor Editor instance to add to the collection. * @return {tinymce.Editor} The same instance that got passed in. */ add : function(editor) { var self = this, editors = self.editors; // Add named and index editor instance editors[editor.id] = editor; editors.push(editor); self._setActive(editor); self.onAddEditor.dispatch(self, editor); // #ifdef jquery // Patch the tinymce.Editor instance with jQuery adapter logic if (tinymce.adapter) tinymce.adapter.patchEditor(editor); // #endif return editor; }, /** * Removes a editor instance from the collection. * * @method remove * @param {tinymce.Editor} e Editor instance to remove. * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. */ remove : function(editor) { var t = this, i, editors = t.editors; // Not in the collection if (!editors[editor.id]) return null; delete editors[editor.id]; for (i = 0; i < editors.length; i++) { if (editors[i] == editor) { editors.splice(i, 1); break; } } // Select another editor since the active one was removed if (t.activeEditor == editor) t._setActive(editors[0]); editor.destroy(); t.onRemoveEditor.dispatch(t, editor); return editor; }, /** * Executes a specific command on the currently active editor. * * @method execCommand * @param {String} c Command to perform for example Bold. * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. * @param {String} v Optional value parameter like for example an URL to a link. * @return {Boolean} true/false if the command was executed or not. */ execCommand : function(c, u, v) { var t = this, ed = t.get(v), w; function clr() { ed.destroy(); w.detachEvent('onunload', clr); w = w.tinyMCE = w.tinymce = null; // IE leak }; // Manager commands switch (c) { case "mceFocus": ed.focus(); return true; case "mceAddEditor": case "mceAddControl": if (!t.get(v)) new tinymce.Editor(v, t.settings).render(); return true; case "mceAddFrameControl": w = v.window; // Add tinyMCE global instance and tinymce namespace to specified window w.tinyMCE = tinyMCE; w.tinymce = tinymce; tinymce.DOM.doc = w.document; tinymce.DOM.win = w; ed = new tinymce.Editor(v.element_id, v); ed.render(); // Fix IE memory leaks if (tinymce.isIE && ! tinymce.isIE11) { w.attachEvent('onunload', clr); } v.page_window = null; return true; case "mceRemoveEditor": case "mceRemoveControl": if (ed) ed.remove(); return true; case 'mceToggleEditor': if (!ed) { t.execCommand('mceAddControl', 0, v); return true; } if (ed.isHidden()) ed.show(); else ed.hide(); return true; } // Run command on active editor if (t.activeEditor) return t.activeEditor.execCommand(c, u, v); return false; }, /** * Executes a command on a specific editor by id. This method was added for compatibility with the 2.x branch. * * @deprecated Use the execCommand method of a editor instance instead. * @method execInstanceCommand * @param {String} id Editor id to perform the command on. * @param {String} c Command to perform for example Bold. * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. * @param {String} v Optional value parameter like for example an URL to a link. * @return {Boolean} true/false if the command was executed or not. */ execInstanceCommand : function(id, c, u, v) { var ed = this.get(id); if (ed) return ed.execCommand(c, u, v); return false; }, /** * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. * * @method triggerSave * @example * // Saves all contents * tinyMCE.triggerSave(); */ triggerSave : function() { each(this.editors, function(e) { e.save(); }); }, /** * Adds a language pack, this gets called by the loaded language files like en.js. * * @method addI18n * @param {String} p Prefix for the language items. For example en.myplugin * @param {Object} o Name/Value collection with items to add to the language group. */ addI18n : function(p, o) { var lo, i18n = this.i18n; if (!tinymce.is(p, 'string')) { each(p, function(o, lc) { each(o, function(o, g) { each(o, function(o, k) { if (g === 'common') i18n[lc + '.' + k] = o; else i18n[lc + '.' + g + '.' + k] = o; }); }); }); } else { each(o, function(o, k) { i18n[p + '.' + k] = o; }); } }, // Private methods _setActive : function(editor) { this.selectedInstance = this.activeEditor = editor; } }); })(tinymce); /** * Alternative name for tinymce added for 2.x compatibility. * * @member * @property tinyMCE * @type tinymce * @example * // To initialize editor instances * tinyMCE.init({ * ... * }); */ /** * Alternative name for tinymce added for compatibility. * * @member tinymce * @property EditorManager * @type tinymce * @example * // To initialize editor instances * tinymce.EditorManager.get('editor'); */