EditorManager.js 14 KB

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