|
1 /** |
|
2 * EditorManager.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class used as a factory for manager for tinymce.Editor instances. |
|
13 * |
|
14 * @example |
|
15 * tinymce.EditorManager.init({}); |
|
16 * |
|
17 * @class tinymce.EditorManager |
|
18 * @mixes tinymce.util.Observable |
|
19 * @static |
|
20 */ |
|
21 define("tinymce/EditorManager", [ |
|
22 "tinymce/Editor", |
|
23 "tinymce/dom/DomQuery", |
|
24 "tinymce/dom/DOMUtils", |
|
25 "tinymce/util/URI", |
|
26 "tinymce/Env", |
|
27 "tinymce/util/Tools", |
|
28 "tinymce/util/Observable", |
|
29 "tinymce/util/I18n", |
|
30 "tinymce/FocusManager" |
|
31 ], function(Editor, DomQuery, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) { |
|
32 var DOM = DOMUtils.DOM; |
|
33 var explode = Tools.explode, each = Tools.each, extend = Tools.extend; |
|
34 var instanceCounter = 0, beforeUnloadDelegate, EditorManager; |
|
35 |
|
36 function removeEditorFromList(editor) { |
|
37 var editors = EditorManager.editors, removedFromList; |
|
38 |
|
39 delete editors[editor.id]; |
|
40 |
|
41 for (var i = 0; i < editors.length; i++) { |
|
42 if (editors[i] == editor) { |
|
43 editors.splice(i, 1); |
|
44 removedFromList = true; |
|
45 break; |
|
46 } |
|
47 } |
|
48 |
|
49 // Select another editor since the active one was removed |
|
50 if (EditorManager.activeEditor == editor) { |
|
51 EditorManager.activeEditor = editors[0]; |
|
52 } |
|
53 |
|
54 // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor |
|
55 if (EditorManager.focusedEditor == editor) { |
|
56 EditorManager.focusedEditor = null; |
|
57 } |
|
58 |
|
59 return removedFromList; |
|
60 } |
|
61 |
|
62 function purgeDestroyedEditor(editor) { |
|
63 // User has manually destroyed the editor lets clean up the mess |
|
64 if (editor && !(editor.getContainer() || editor.getBody()).parentNode) { |
|
65 removeEditorFromList(editor); |
|
66 editor.unbindAllNativeEvents(); |
|
67 editor.destroy(true); |
|
68 editor = null; |
|
69 } |
|
70 |
|
71 return editor; |
|
72 } |
|
73 |
|
74 EditorManager = { |
|
75 /** |
|
76 * Dom query instance. |
|
77 * |
|
78 * @property $ |
|
79 * @type tinymce.dom.DomQuery |
|
80 */ |
|
81 $: DomQuery, |
|
82 |
|
83 /** |
|
84 * Major version of TinyMCE build. |
|
85 * |
|
86 * @property majorVersion |
|
87 * @type String |
|
88 */ |
|
89 majorVersion: '@@majorVersion@@', |
|
90 |
|
91 /** |
|
92 * Minor version of TinyMCE build. |
|
93 * |
|
94 * @property minorVersion |
|
95 * @type String |
|
96 */ |
|
97 minorVersion: '@@minorVersion@@', |
|
98 |
|
99 /** |
|
100 * Release date of TinyMCE build. |
|
101 * |
|
102 * @property releaseDate |
|
103 * @type String |
|
104 */ |
|
105 releaseDate: '@@releaseDate@@', |
|
106 |
|
107 /** |
|
108 * Collection of editor instances. |
|
109 * |
|
110 * @property editors |
|
111 * @type Object |
|
112 * @example |
|
113 * for (edId in tinymce.editors) |
|
114 * tinymce.editors[edId].save(); |
|
115 */ |
|
116 editors: [], |
|
117 |
|
118 /** |
|
119 * Collection of language pack data. |
|
120 * |
|
121 * @property i18n |
|
122 * @type Object |
|
123 */ |
|
124 i18n: I18n, |
|
125 |
|
126 /** |
|
127 * Currently active editor instance. |
|
128 * |
|
129 * @property activeEditor |
|
130 * @type tinymce.Editor |
|
131 * @example |
|
132 * tinyMCE.activeEditor.selection.getContent(); |
|
133 * tinymce.EditorManager.activeEditor.selection.getContent(); |
|
134 */ |
|
135 activeEditor: null, |
|
136 |
|
137 setup: function() { |
|
138 var self = this, baseURL, documentBaseURL, suffix = "", preInit, src; |
|
139 |
|
140 // Get base URL for the current document |
|
141 documentBaseURL = document.location.href; |
|
142 |
|
143 // Check if the URL is a document based format like: http://site/dir/file and file:/// |
|
144 // leave other formats like applewebdata://... intact |
|
145 if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) { |
|
146 documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); |
|
147 |
|
148 if (!/[\/\\]$/.test(documentBaseURL)) { |
|
149 documentBaseURL += '/'; |
|
150 } |
|
151 } |
|
152 |
|
153 // If tinymce is defined and has a base use that or use the old tinyMCEPreInit |
|
154 preInit = window.tinymce || window.tinyMCEPreInit; |
|
155 if (preInit) { |
|
156 baseURL = preInit.base || preInit.baseURL; |
|
157 suffix = preInit.suffix; |
|
158 } else { |
|
159 // Get base where the tinymce script is located |
|
160 var scripts = document.getElementsByTagName('script'); |
|
161 for (var i = 0; i < scripts.length; i++) { |
|
162 src = scripts[i].src; |
|
163 |
|
164 // Script types supported: |
|
165 // tinymce.js tinymce.min.js tinymce.dev.js |
|
166 // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js |
|
167 // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js |
|
168 if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) { |
|
169 if (src.indexOf('.min') != -1) { |
|
170 suffix = '.min'; |
|
171 } |
|
172 |
|
173 baseURL = src.substring(0, src.lastIndexOf('/')); |
|
174 break; |
|
175 } |
|
176 } |
|
177 |
|
178 // We didn't find any baseURL by looking at the script elements |
|
179 // Try to use the document.currentScript as a fallback |
|
180 if (!baseURL && document.currentScript) { |
|
181 src = document.currentScript.src; |
|
182 |
|
183 if (src.indexOf('.min') != -1) { |
|
184 suffix = '.min'; |
|
185 } |
|
186 |
|
187 baseURL = src.substring(0, src.lastIndexOf('/')); |
|
188 } |
|
189 } |
|
190 |
|
191 /** |
|
192 * Base URL where the root directory if TinyMCE is located. |
|
193 * |
|
194 * @property baseURL |
|
195 * @type String |
|
196 */ |
|
197 self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL); |
|
198 |
|
199 /** |
|
200 * Document base URL where the current document is located. |
|
201 * |
|
202 * @property documentBaseURL |
|
203 * @type String |
|
204 */ |
|
205 self.documentBaseURL = documentBaseURL; |
|
206 |
|
207 /** |
|
208 * Absolute baseURI for the installation path of TinyMCE. |
|
209 * |
|
210 * @property baseURI |
|
211 * @type tinymce.util.URI |
|
212 */ |
|
213 self.baseURI = new URI(self.baseURL); |
|
214 |
|
215 /** |
|
216 * Current suffix to add to each plugin/theme that gets loaded for example ".min". |
|
217 * |
|
218 * @property suffix |
|
219 * @type String |
|
220 */ |
|
221 self.suffix = suffix; |
|
222 |
|
223 self.focusManager = new FocusManager(self); |
|
224 }, |
|
225 |
|
226 /** |
|
227 * Initializes a set of editors. This method will create editors based on various settings. |
|
228 * |
|
229 * @method init |
|
230 * @param {Object} settings Settings object to be passed to each editor instance. |
|
231 * @example |
|
232 * // Initializes a editor using the longer method |
|
233 * tinymce.EditorManager.init({ |
|
234 * some_settings : 'some value' |
|
235 * }); |
|
236 * |
|
237 * // Initializes a editor instance using the shorter version |
|
238 * tinyMCE.init({ |
|
239 * some_settings : 'some value' |
|
240 * }); |
|
241 */ |
|
242 init: function(settings) { |
|
243 var self = this, editors = []; |
|
244 |
|
245 function createId(elm) { |
|
246 var id = elm.id; |
|
247 |
|
248 // Use element id, or unique name or generate a unique id |
|
249 if (!id) { |
|
250 id = elm.name; |
|
251 |
|
252 if (id && !DOM.get(id)) { |
|
253 id = elm.name; |
|
254 } else { |
|
255 // Generate unique name |
|
256 id = DOM.uniqueId(); |
|
257 } |
|
258 |
|
259 elm.setAttribute('id', id); |
|
260 } |
|
261 |
|
262 return id; |
|
263 } |
|
264 |
|
265 function createEditor(id, settings, targetElm) { |
|
266 if (!purgeDestroyedEditor(self.get(id))) { |
|
267 var editor = new Editor(id, settings, self); |
|
268 |
|
269 editor.targetElm = editor.targetElm || targetElm; |
|
270 editors.push(editor); |
|
271 editor.render(); |
|
272 } |
|
273 } |
|
274 |
|
275 function execCallback(name) { |
|
276 var callback = settings[name]; |
|
277 |
|
278 if (!callback) { |
|
279 return; |
|
280 } |
|
281 |
|
282 return callback.apply(self, Array.prototype.slice.call(arguments, 2)); |
|
283 } |
|
284 |
|
285 function hasClass(elm, className) { |
|
286 return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className); |
|
287 } |
|
288 |
|
289 function readyHandler() { |
|
290 var l, co; |
|
291 |
|
292 DOM.unbind(window, 'ready', readyHandler); |
|
293 |
|
294 execCallback('onpageload'); |
|
295 |
|
296 if (settings.types) { |
|
297 // Process type specific selector |
|
298 each(settings.types, function(type) { |
|
299 each(DOM.select(type.selector), function(elm) { |
|
300 createEditor(createId(elm), extend({}, settings, type), elm); |
|
301 }); |
|
302 }); |
|
303 |
|
304 return; |
|
305 } else if (settings.selector) { |
|
306 // Process global selector |
|
307 each(DOM.select(settings.selector), function(elm) { |
|
308 createEditor(createId(elm), settings, elm); |
|
309 }); |
|
310 |
|
311 return; |
|
312 } else if (settings.target) { |
|
313 createEditor(createId(settings.target), settings); |
|
314 } |
|
315 |
|
316 // Fallback to old setting |
|
317 switch (settings.mode) { |
|
318 case "exact": |
|
319 l = settings.elements || ''; |
|
320 |
|
321 if (l.length > 0) { |
|
322 each(explode(l), function(id) { |
|
323 var elm; |
|
324 |
|
325 if ((elm = DOM.get(id))) { |
|
326 createEditor(id, settings, elm); |
|
327 } else { |
|
328 each(document.forms, function(f) { |
|
329 each(f.elements, function(e) { |
|
330 if (e.name === id) { |
|
331 id = 'mce_editor_' + instanceCounter++; |
|
332 DOM.setAttrib(e, 'id', id); |
|
333 createEditor(id, settings, e); |
|
334 } |
|
335 }); |
|
336 }); |
|
337 } |
|
338 }); |
|
339 } |
|
340 break; |
|
341 |
|
342 case "textareas": |
|
343 case "specific_textareas": |
|
344 each(DOM.select('textarea'), function(elm) { |
|
345 if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) { |
|
346 return; |
|
347 } |
|
348 |
|
349 if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) { |
|
350 createEditor(createId(elm), settings, elm); |
|
351 } |
|
352 }); |
|
353 break; |
|
354 } |
|
355 |
|
356 // Call onInit when all editors are initialized |
|
357 if (settings.oninit) { |
|
358 l = co = 0; |
|
359 |
|
360 each(editors, function(ed) { |
|
361 co++; |
|
362 |
|
363 if (!ed.initialized) { |
|
364 // Wait for it |
|
365 ed.on('init', function() { |
|
366 l++; |
|
367 |
|
368 // All done |
|
369 if (l == co) { |
|
370 execCallback('oninit'); |
|
371 } |
|
372 }); |
|
373 } else { |
|
374 l++; |
|
375 } |
|
376 |
|
377 // All done |
|
378 if (l == co) { |
|
379 execCallback('oninit'); |
|
380 } |
|
381 }); |
|
382 } |
|
383 } |
|
384 |
|
385 self.settings = settings; |
|
386 |
|
387 DOM.bind(window, 'ready', readyHandler); |
|
388 }, |
|
389 |
|
390 /** |
|
391 * Returns a editor instance by id. |
|
392 * |
|
393 * @method get |
|
394 * @param {String/Number} id Editor instance id or index to return. |
|
395 * @return {tinymce.Editor} Editor instance to return. |
|
396 * @example |
|
397 * // Adds an onclick event to an editor by id (shorter version) |
|
398 * tinymce.get('mytextbox').on('click', function(e) { |
|
399 * ed.windowManager.alert('Hello world!'); |
|
400 * }); |
|
401 * |
|
402 * // Adds an onclick event to an editor by id (longer version) |
|
403 * tinymce.EditorManager.get('mytextbox').on('click', function(e) { |
|
404 * ed.windowManager.alert('Hello world!'); |
|
405 * }); |
|
406 */ |
|
407 get: function(id) { |
|
408 if (!arguments.length) { |
|
409 return this.editors; |
|
410 } |
|
411 |
|
412 return id in this.editors ? this.editors[id] : null; |
|
413 }, |
|
414 |
|
415 /** |
|
416 * Adds an editor instance to the editor collection. This will also set it as the active editor. |
|
417 * |
|
418 * @method add |
|
419 * @param {tinymce.Editor} editor Editor instance to add to the collection. |
|
420 * @return {tinymce.Editor} The same instance that got passed in. |
|
421 */ |
|
422 add: function(editor) { |
|
423 var self = this, editors = self.editors; |
|
424 |
|
425 // Add named and index editor instance |
|
426 editors[editor.id] = editor; |
|
427 editors.push(editor); |
|
428 |
|
429 // Doesn't call setActive method since we don't want |
|
430 // to fire a bunch of activate/deactivate calls while initializing |
|
431 self.activeEditor = editor; |
|
432 |
|
433 /** |
|
434 * Fires when an editor is added to the EditorManager collection. |
|
435 * |
|
436 * @event AddEditor |
|
437 * @param {Object} e Event arguments. |
|
438 */ |
|
439 self.fire('AddEditor', {editor: editor}); |
|
440 |
|
441 if (!beforeUnloadDelegate) { |
|
442 beforeUnloadDelegate = function() { |
|
443 self.fire('BeforeUnload'); |
|
444 }; |
|
445 |
|
446 DOM.bind(window, 'beforeunload', beforeUnloadDelegate); |
|
447 } |
|
448 |
|
449 return editor; |
|
450 }, |
|
451 |
|
452 /** |
|
453 * Creates an editor instance and adds it to the EditorManager collection. |
|
454 * |
|
455 * @method createEditor |
|
456 * @param {String} id Instance id to use for editor. |
|
457 * @param {Object} settings Editor instance settings. |
|
458 * @return {tinymce.Editor} Editor instance that got created. |
|
459 */ |
|
460 createEditor: function(id, settings) { |
|
461 return this.add(new Editor(id, settings, this)); |
|
462 }, |
|
463 |
|
464 /** |
|
465 * Removes a editor or editors form page. |
|
466 * |
|
467 * @example |
|
468 * // Remove all editors bound to divs |
|
469 * tinymce.remove('div'); |
|
470 * |
|
471 * // Remove all editors bound to textareas |
|
472 * tinymce.remove('textarea'); |
|
473 * |
|
474 * // Remove all editors |
|
475 * tinymce.remove(); |
|
476 * |
|
477 * // Remove specific instance by id |
|
478 * tinymce.remove('#id'); |
|
479 * |
|
480 * @method remove |
|
481 * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove. |
|
482 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. |
|
483 */ |
|
484 remove: function(selector) { |
|
485 var self = this, i, editors = self.editors, editor; |
|
486 |
|
487 // Remove all editors |
|
488 if (!selector) { |
|
489 for (i = editors.length - 1; i >= 0; i--) { |
|
490 self.remove(editors[i]); |
|
491 } |
|
492 |
|
493 return; |
|
494 } |
|
495 |
|
496 // Remove editors by selector |
|
497 if (typeof selector == "string") { |
|
498 selector = selector.selector || selector; |
|
499 |
|
500 each(DOM.select(selector), function(elm) { |
|
501 editor = editors[elm.id]; |
|
502 |
|
503 if (editor) { |
|
504 self.remove(editor); |
|
505 } |
|
506 }); |
|
507 |
|
508 return; |
|
509 } |
|
510 |
|
511 // Remove specific editor |
|
512 editor = selector; |
|
513 |
|
514 // Not in the collection |
|
515 if (!editors[editor.id]) { |
|
516 return null; |
|
517 } |
|
518 |
|
519 /** |
|
520 * Fires when an editor is removed from EditorManager collection. |
|
521 * |
|
522 * @event RemoveEditor |
|
523 * @param {Object} e Event arguments. |
|
524 */ |
|
525 if (removeEditorFromList(editor)) { |
|
526 self.fire('RemoveEditor', {editor: editor}); |
|
527 } |
|
528 |
|
529 if (!editors.length) { |
|
530 DOM.unbind(window, 'beforeunload', beforeUnloadDelegate); |
|
531 } |
|
532 |
|
533 editor.remove(); |
|
534 |
|
535 return editor; |
|
536 }, |
|
537 |
|
538 /** |
|
539 * Executes a specific command on the currently active editor. |
|
540 * |
|
541 * @method execCommand |
|
542 * @param {String} c Command to perform for example Bold. |
|
543 * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. |
|
544 * @param {String} v Optional value parameter like for example an URL to a link. |
|
545 * @return {Boolean} true/false if the command was executed or not. |
|
546 */ |
|
547 execCommand: function(cmd, ui, value) { |
|
548 var self = this, editor = self.get(value); |
|
549 |
|
550 // Manager commands |
|
551 switch (cmd) { |
|
552 case "mceAddEditor": |
|
553 if (!self.get(value)) { |
|
554 new Editor(value, self.settings, self).render(); |
|
555 } |
|
556 |
|
557 return true; |
|
558 |
|
559 case "mceRemoveEditor": |
|
560 if (editor) { |
|
561 editor.remove(); |
|
562 } |
|
563 |
|
564 return true; |
|
565 |
|
566 case 'mceToggleEditor': |
|
567 if (!editor) { |
|
568 self.execCommand('mceAddEditor', 0, value); |
|
569 return true; |
|
570 } |
|
571 |
|
572 if (editor.isHidden()) { |
|
573 editor.show(); |
|
574 } else { |
|
575 editor.hide(); |
|
576 } |
|
577 |
|
578 return true; |
|
579 } |
|
580 |
|
581 // Run command on active editor |
|
582 if (self.activeEditor) { |
|
583 return self.activeEditor.execCommand(cmd, ui, value); |
|
584 } |
|
585 |
|
586 return false; |
|
587 }, |
|
588 |
|
589 /** |
|
590 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. |
|
591 * |
|
592 * @method triggerSave |
|
593 * @example |
|
594 * // Saves all contents |
|
595 * tinyMCE.triggerSave(); |
|
596 */ |
|
597 triggerSave: function() { |
|
598 each(this.editors, function(editor) { |
|
599 editor.save(); |
|
600 }); |
|
601 }, |
|
602 |
|
603 /** |
|
604 * Adds a language pack, this gets called by the loaded language files like en.js. |
|
605 * |
|
606 * @method addI18n |
|
607 * @param {String} code Optional language code. |
|
608 * @param {Object} items Name/value object with translations. |
|
609 */ |
|
610 addI18n: function(code, items) { |
|
611 I18n.add(code, items); |
|
612 }, |
|
613 |
|
614 /** |
|
615 * Translates the specified string using the language pack items. |
|
616 * |
|
617 * @method translate |
|
618 * @param {String/Array/Object} text String to translate |
|
619 * @return {String} Translated string. |
|
620 */ |
|
621 translate: function(text) { |
|
622 return I18n.translate(text); |
|
623 }, |
|
624 |
|
625 /** |
|
626 * Sets the active editor instance and fires the deactivate/activate events. |
|
627 * |
|
628 * @method setActive |
|
629 * @param {tinymce.Editor} editor Editor instance to set as the active instance. |
|
630 */ |
|
631 setActive: function(editor) { |
|
632 var activeEditor = this.activeEditor; |
|
633 |
|
634 if (this.activeEditor != editor) { |
|
635 if (activeEditor) { |
|
636 activeEditor.fire('deactivate', {relatedTarget: editor}); |
|
637 } |
|
638 |
|
639 editor.fire('activate', {relatedTarget: activeEditor}); |
|
640 } |
|
641 |
|
642 this.activeEditor = editor; |
|
643 } |
|
644 }; |
|
645 |
|
646 extend(EditorManager, Observable); |
|
647 |
|
648 EditorManager.setup(); |
|
649 |
|
650 // Export EditorManager as tinymce/tinymce in global namespace |
|
651 window.tinymce = window.tinyMCE = EditorManager; |
|
652 |
|
653 return EditorManager; |
|
654 }); |