|
1 /** |
|
2 * jquery.tinymce.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 /*global tinymce:true, jQuery */ |
|
12 |
|
13 (function($) { |
|
14 var undef, |
|
15 lazyLoading, |
|
16 patchApplied, |
|
17 delayedInits = [], |
|
18 win = window; |
|
19 |
|
20 $.fn.tinymce = function(settings) { |
|
21 var self = this, url, base, lang, suffix = ""; |
|
22 |
|
23 // No match then just ignore the call |
|
24 if (!self.length) { |
|
25 return self; |
|
26 } |
|
27 |
|
28 // Get editor instance |
|
29 if (!settings) { |
|
30 return window.tinymce ? tinymce.get(self[0].id) : null; |
|
31 } |
|
32 |
|
33 self.css('visibility', 'hidden'); // Hide textarea to avoid flicker |
|
34 |
|
35 function init() { |
|
36 var editors = [], initCount = 0; |
|
37 |
|
38 // Apply patches to the jQuery object, only once |
|
39 if (!patchApplied) { |
|
40 applyPatch(); |
|
41 patchApplied = true; |
|
42 } |
|
43 |
|
44 // Create an editor instance for each matched node |
|
45 self.each(function(i, node) { |
|
46 var ed, id = node.id, oninit = settings.oninit; |
|
47 |
|
48 // Generate unique id for target element if needed |
|
49 if (!id) { |
|
50 node.id = id = tinymce.DOM.uniqueId(); |
|
51 } |
|
52 |
|
53 // Only init the editor once |
|
54 if (tinymce.get(id)) { |
|
55 return; |
|
56 } |
|
57 |
|
58 // Create editor instance and render it |
|
59 ed = new tinymce.Editor(id, settings, tinymce.EditorManager); |
|
60 editors.push(ed); |
|
61 |
|
62 ed.on('init', function() { |
|
63 var scope, func = oninit; |
|
64 |
|
65 self.css('visibility', ''); |
|
66 |
|
67 // Run this if the oninit setting is defined |
|
68 // this logic will fire the oninit callback ones each |
|
69 // matched editor instance is initialized |
|
70 if (oninit) { |
|
71 // Fire the oninit event ones each editor instance is initialized |
|
72 if (++initCount == editors.length) { |
|
73 if (typeof func === "string") { |
|
74 scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, "")); |
|
75 func = tinymce.resolve(func); |
|
76 } |
|
77 |
|
78 // Call the oninit function with the object |
|
79 func.apply(scope || tinymce, editors); |
|
80 } |
|
81 } |
|
82 }); |
|
83 }); |
|
84 |
|
85 // Render the editor instances in a separate loop since we |
|
86 // need to have the full editors array used in the onInit calls |
|
87 $.each(editors, function(i, ed) { |
|
88 ed.render(); |
|
89 }); |
|
90 } |
|
91 |
|
92 // Load TinyMCE on demand, if we need to |
|
93 if (!win.tinymce && !lazyLoading && (url = settings.script_url)) { |
|
94 lazyLoading = 1; |
|
95 base = url.substring(0, url.lastIndexOf("/")); |
|
96 |
|
97 // Check if it's a dev/src version they want to load then |
|
98 // make sure that all plugins, themes etc are loaded in source mode as well |
|
99 if (url.indexOf('.min') != -1) { |
|
100 suffix = ".min"; |
|
101 } |
|
102 |
|
103 // Setup tinyMCEPreInit object this will later be used by the TinyMCE |
|
104 // core script to locate other resources like CSS files, dialogs etc |
|
105 // You can also predefined a tinyMCEPreInit object and then it will use that instead |
|
106 win.tinymce = win.tinyMCEPreInit || { |
|
107 base: base, |
|
108 suffix: suffix |
|
109 }; |
|
110 |
|
111 // url contains gzip then we assume it's a compressor |
|
112 if (url.indexOf('gzip') != -1) { |
|
113 lang = settings.language || "en"; |
|
114 url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) + |
|
115 "&themes=" + escape(settings.theme || 'modern') + "&plugins=" + |
|
116 escape(settings.plugins || '') + "&languages=" + (lang || ''); |
|
117 |
|
118 // Check if compressor script is already loaded otherwise setup a basic one |
|
119 if (!win.tinyMCE_GZ) { |
|
120 win.tinyMCE_GZ = { |
|
121 start: function() { |
|
122 function load(url) { |
|
123 tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(url)); |
|
124 } |
|
125 |
|
126 // Add core languages |
|
127 load("langs/" + lang + ".js"); |
|
128 |
|
129 // Add themes with languages |
|
130 load("themes/" + settings.theme + "/theme" + suffix + ".js"); |
|
131 load("themes/" + settings.theme + "/langs/" + lang + ".js"); |
|
132 |
|
133 // Add plugins with languages |
|
134 $.each(settings.plugins.split(","), function(i, name) { |
|
135 if (name) { |
|
136 load("plugins/" + name + "/plugin" + suffix + ".js"); |
|
137 load("plugins/" + name + "/langs/" + lang + ".js"); |
|
138 } |
|
139 }); |
|
140 }, |
|
141 |
|
142 end: function() { |
|
143 } |
|
144 }; |
|
145 } |
|
146 } |
|
147 |
|
148 var script = document.createElement('script'); |
|
149 script.type = 'text/javascript'; |
|
150 script.onload = script.onreadystatechange = function(e) { |
|
151 e = e || window.event; |
|
152 |
|
153 if (lazyLoading !== 2 && (e.type == 'load' || /complete|loaded/.test(script.readyState))) { |
|
154 tinymce.dom.Event.domLoaded = 1; |
|
155 lazyLoading = 2; |
|
156 |
|
157 // Execute callback after mainscript has been loaded and before the initialization occurs |
|
158 if (settings.script_loaded) { |
|
159 settings.script_loaded(); |
|
160 } |
|
161 |
|
162 init(); |
|
163 |
|
164 $.each(delayedInits, function(i, init) { |
|
165 init(); |
|
166 }); |
|
167 } |
|
168 }; |
|
169 script.src = url; |
|
170 document.body.appendChild(script); |
|
171 } else { |
|
172 // Delay the init call until tinymce is loaded |
|
173 if (lazyLoading === 1) { |
|
174 delayedInits.push(init); |
|
175 } else { |
|
176 init(); |
|
177 } |
|
178 } |
|
179 |
|
180 return self; |
|
181 }; |
|
182 |
|
183 // Add :tinymce psuedo selector this will select elements that has been converted into editor instances |
|
184 // it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements. |
|
185 $.extend($.expr[":"], { |
|
186 tinymce: function(e) { |
|
187 var editor; |
|
188 |
|
189 if (e.id && "tinymce" in window) { |
|
190 editor = tinymce.get(e.id); |
|
191 |
|
192 if (editor && editor.editorManager === tinymce) { |
|
193 return true; |
|
194 } |
|
195 } |
|
196 |
|
197 return false; |
|
198 } |
|
199 }); |
|
200 |
|
201 // This function patches internal jQuery functions so that if |
|
202 // you for example remove an div element containing an editor it's |
|
203 // automatically destroyed by the TinyMCE API |
|
204 function applyPatch() { |
|
205 // Removes any child editor instances by looking for editor wrapper elements |
|
206 function removeEditors(name) { |
|
207 // If the function is remove |
|
208 if (name === "remove") { |
|
209 this.each(function(i, node) { |
|
210 var ed = tinyMCEInstance(node); |
|
211 |
|
212 if (ed) { |
|
213 ed.remove(); |
|
214 } |
|
215 }); |
|
216 } |
|
217 |
|
218 this.find("span.mceEditor,div.mceEditor").each(function(i, node) { |
|
219 var ed = tinymce.get(node.id.replace(/_parent$/, "")); |
|
220 |
|
221 if (ed) { |
|
222 ed.remove(); |
|
223 } |
|
224 }); |
|
225 } |
|
226 |
|
227 // Loads or saves contents from/to textarea if the value |
|
228 // argument is defined it will set the TinyMCE internal contents |
|
229 function loadOrSave(value) { |
|
230 var self = this, ed; |
|
231 |
|
232 // Handle set value |
|
233 /*jshint eqnull:true */ |
|
234 if (value != null) { |
|
235 removeEditors.call(self); |
|
236 |
|
237 // Saves the contents before get/set value of textarea/div |
|
238 self.each(function(i, node) { |
|
239 var ed; |
|
240 |
|
241 if ((ed = tinymce.get(node.id))) { |
|
242 ed.setContent(value); |
|
243 } |
|
244 }); |
|
245 } else if (self.length > 0) { |
|
246 // Handle get value |
|
247 if ((ed = tinymce.get(self[0].id))) { |
|
248 return ed.getContent(); |
|
249 } |
|
250 } |
|
251 } |
|
252 |
|
253 // Returns tinymce instance for the specified element or null if it wasn't found |
|
254 function tinyMCEInstance(element) { |
|
255 var ed = null; |
|
256 |
|
257 if (element && element.id && win.tinymce) { |
|
258 ed = tinymce.get(element.id); |
|
259 } |
|
260 |
|
261 return ed; |
|
262 } |
|
263 |
|
264 // Checks if the specified set contains tinymce instances |
|
265 function containsTinyMCE(matchedSet) { |
|
266 return !!((matchedSet) && (matchedSet.length) && (win.tinymce) && (matchedSet.is(":tinymce"))); |
|
267 } |
|
268 |
|
269 // Patch various jQuery functions |
|
270 var jQueryFn = {}; |
|
271 |
|
272 // Patch some setter/getter functions these will |
|
273 // now be able to set/get the contents of editor instances for |
|
274 // example $('#editorid').html('Content'); will update the TinyMCE iframe instance |
|
275 $.each(["text", "html", "val"], function(i, name) { |
|
276 var origFn = jQueryFn[name] = $.fn[name], |
|
277 textProc = (name === "text"); |
|
278 |
|
279 $.fn[name] = function(value) { |
|
280 var self = this; |
|
281 |
|
282 if (!containsTinyMCE(self)) { |
|
283 return origFn.apply(self, arguments); |
|
284 } |
|
285 |
|
286 if (value !== undef) { |
|
287 loadOrSave.call(self.filter(":tinymce"), value); |
|
288 origFn.apply(self.not(":tinymce"), arguments); |
|
289 |
|
290 return self; // return original set for chaining |
|
291 } else { |
|
292 var ret = ""; |
|
293 var args = arguments; |
|
294 |
|
295 (textProc ? self : self.eq(0)).each(function(i, node) { |
|
296 var ed = tinyMCEInstance(node); |
|
297 |
|
298 if (ed) { |
|
299 ret += textProc ? ed.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, "") : ed.getContent({save: true}); |
|
300 } else { |
|
301 ret += origFn.apply($(node), args); |
|
302 } |
|
303 }); |
|
304 |
|
305 return ret; |
|
306 } |
|
307 }; |
|
308 }); |
|
309 |
|
310 // Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe |
|
311 $.each(["append", "prepend"], function(i, name) { |
|
312 var origFn = jQueryFn[name] = $.fn[name], |
|
313 prepend = (name === "prepend"); |
|
314 |
|
315 $.fn[name] = function(value) { |
|
316 var self = this; |
|
317 |
|
318 if (!containsTinyMCE(self)) { |
|
319 return origFn.apply(self, arguments); |
|
320 } |
|
321 |
|
322 if (value !== undef) { |
|
323 if (typeof value === "string") { |
|
324 self.filter(":tinymce").each(function(i, node) { |
|
325 var ed = tinyMCEInstance(node); |
|
326 |
|
327 if (ed) { |
|
328 ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value); |
|
329 } |
|
330 }); |
|
331 } |
|
332 |
|
333 origFn.apply(self.not(":tinymce"), arguments); |
|
334 |
|
335 return self; // return original set for chaining |
|
336 } |
|
337 }; |
|
338 }); |
|
339 |
|
340 // Makes sure that the editor instance gets properly destroyed when the parent element is removed |
|
341 $.each(["remove", "replaceWith", "replaceAll", "empty"], function(i, name) { |
|
342 var origFn = jQueryFn[name] = $.fn[name]; |
|
343 |
|
344 $.fn[name] = function() { |
|
345 removeEditors.call(this, name); |
|
346 |
|
347 return origFn.apply(this, arguments); |
|
348 }; |
|
349 }); |
|
350 |
|
351 jQueryFn.attr = $.fn.attr; |
|
352 |
|
353 // Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents |
|
354 $.fn.attr = function(name, value) { |
|
355 var self = this, args = arguments; |
|
356 |
|
357 if ((!name) || (name !== "value") || (!containsTinyMCE(self))) { |
|
358 if (value !== undef) { |
|
359 return jQueryFn.attr.apply(self, args); |
|
360 } else { |
|
361 return jQueryFn.attr.apply(self, args); |
|
362 } |
|
363 } |
|
364 |
|
365 if (value !== undef) { |
|
366 loadOrSave.call(self.filter(":tinymce"), value); |
|
367 jQueryFn.attr.apply(self.not(":tinymce"), args); |
|
368 |
|
369 return self; // return original set for chaining |
|
370 } else { |
|
371 var node = self[0], ed = tinyMCEInstance(node); |
|
372 |
|
373 return ed ? ed.getContent({save: true}) : jQueryFn.attr.apply($(node), args); |
|
374 } |
|
375 }; |
|
376 } |
|
377 })(jQuery); |