1 | /** |
---|
2 | * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. |
---|
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license |
---|
4 | */ |
---|
5 | |
---|
6 | /** |
---|
7 | * @fileOverview Defines the {@link CKEDITOR_Adapters.jQuery jQuery Adapter}. |
---|
8 | */ |
---|
9 | |
---|
10 | /** |
---|
11 | * @class CKEDITOR_Adapters.jQuery |
---|
12 | * @singleton |
---|
13 | * |
---|
14 | * The jQuery Adapter allows for easy use of basic CKEditor functions and access to the internal API. |
---|
15 | * To find more information about the jQuery Adapter, go to the [jQuery Adapter section](#!/guide/dev_jquery) |
---|
16 | * of the Developer's Guide or see the "Create Editors with jQuery" sample. |
---|
17 | * |
---|
18 | * @aside guide dev_jquery |
---|
19 | */ |
---|
20 | |
---|
21 | (function( $ ) { |
---|
22 | /** |
---|
23 | * Allows CKEditor to override `jQuery.fn.val()`. When set to `true`, the `val()` function |
---|
24 | * used on textarea elements replaced with CKEditor uses the CKEditor API. |
---|
25 | * |
---|
26 | * This configuration option is global and is executed during the loading of the jQuery Adapter. |
---|
27 | * It cannot be customized across editor instances. |
---|
28 | * |
---|
29 | * <script> |
---|
30 | * CKEDITOR.config.jqueryOverrideVal = true; |
---|
31 | * </script> |
---|
32 | * |
---|
33 | * <!-- Important: The jQuery Adapter is loaded *after* setting jqueryOverrideVal. --> |
---|
34 | * <script src="/ckeditor/adapters/jquery.js"></script> |
---|
35 | * |
---|
36 | * <script> |
---|
37 | * $( 'textarea' ).ckeditor(); |
---|
38 | * // ... |
---|
39 | * $( 'textarea' ).val( 'New content' ); |
---|
40 | * </script> |
---|
41 | * |
---|
42 | * @cfg {Boolean} [jqueryOverrideVal=true] |
---|
43 | * @member CKEDITOR.config |
---|
44 | */ |
---|
45 | CKEDITOR.config.jqueryOverrideVal = typeof CKEDITOR.config.jqueryOverrideVal == 'undefined' ? |
---|
46 | true |
---|
47 | : |
---|
48 | CKEDITOR.config.jqueryOverrideVal; |
---|
49 | |
---|
50 | if ( typeof $ == 'undefined' ) |
---|
51 | return; |
---|
52 | |
---|
53 | // jQuery object methods. |
---|
54 | $.extend( $.fn, { |
---|
55 | /** |
---|
56 | * Returns an existing CKEditor instance for the first matched element. |
---|
57 | * Allows to easily use the internal API. Does not return a jQuery object. |
---|
58 | * |
---|
59 | * Raises an exception if the editor does not exist or is not ready yet. |
---|
60 | * |
---|
61 | * @returns CKEDITOR.editor |
---|
62 | * @deprecated Use {@link #editor editor property} instead. |
---|
63 | */ |
---|
64 | ckeditorGet: function() { |
---|
65 | var instance = this.eq( 0 ).data( 'ckeditorInstance' ); |
---|
66 | |
---|
67 | if ( !instance ) |
---|
68 | throw 'CKEditor is not initialized yet, use ckeditor() with a callback.'; |
---|
69 | |
---|
70 | return instance; |
---|
71 | }, |
---|
72 | |
---|
73 | /** |
---|
74 | * A jQuery function which triggers the creation of CKEditor with `<textarea>` and |
---|
75 | * {@link CKEDITOR.dtd#$editable editable} elements. |
---|
76 | * Every `<textarea>` element will be converted to a framed editor, while any other |
---|
77 | * supported element will be converted to an inline editor. |
---|
78 | * This method binds the callback to the `instanceReady` event of all instances. |
---|
79 | * If the editor has already been created, the callback is fired straightaway. |
---|
80 | * You can also create multiple editors at once by using `$( '.className' ).ckeditor();`. |
---|
81 | * |
---|
82 | * **Note**: jQuery chaining and mixed parameter order is allowed. |
---|
83 | * |
---|
84 | * @param {Function} callback |
---|
85 | * Function to be run on the editor instance. Callback takes the source element as a parameter. |
---|
86 | * |
---|
87 | * $( 'textarea' ).ckeditor( function( textarea ) { |
---|
88 | * // Callback function code. |
---|
89 | * } ); |
---|
90 | * |
---|
91 | * @param {Object} config |
---|
92 | * Configuration options for new instance(s) if not already created. |
---|
93 | * |
---|
94 | * $( 'textarea' ).ckeditor( { |
---|
95 | * uiColor: '#9AB8F3' |
---|
96 | * } ); |
---|
97 | * |
---|
98 | * @returns jQuery.fn |
---|
99 | */ |
---|
100 | ckeditor: function( callback, config ) { |
---|
101 | if ( !CKEDITOR.env.isCompatible ) |
---|
102 | throw new Error( 'The environment is incompatible.' ); |
---|
103 | |
---|
104 | // Reverse the order of arguments if the first one isn't a function. |
---|
105 | if ( !$.isFunction( callback ) ) { |
---|
106 | var tmp = config; |
---|
107 | config = callback; |
---|
108 | callback = tmp; |
---|
109 | } |
---|
110 | |
---|
111 | // An array of instanceReady callback promises. |
---|
112 | var promises = []; |
---|
113 | |
---|
114 | config = config || {}; |
---|
115 | |
---|
116 | // Iterate over the collection. |
---|
117 | this.each( function() { |
---|
118 | var $element = $( this ), |
---|
119 | editor = $element.data( 'ckeditorInstance' ), |
---|
120 | instanceLock = $element.data( '_ckeditorInstanceLock' ), |
---|
121 | element = this, |
---|
122 | dfd = new $.Deferred(); |
---|
123 | |
---|
124 | promises.push( dfd.promise() ); |
---|
125 | |
---|
126 | if ( editor && !instanceLock ) { |
---|
127 | if ( callback ) |
---|
128 | callback.apply( editor, [ this ] ); |
---|
129 | |
---|
130 | dfd.resolve(); |
---|
131 | } else if ( !instanceLock ) { |
---|
132 | // CREATE NEW INSTANCE |
---|
133 | |
---|
134 | // Handle config.autoUpdateElement inside this plugin if desired. |
---|
135 | if ( config.autoUpdateElement |
---|
136 | || ( typeof config.autoUpdateElement == 'undefined' && CKEDITOR.config.autoUpdateElement ) ) { |
---|
137 | config.autoUpdateElementJquery = true; |
---|
138 | } |
---|
139 | |
---|
140 | // Always disable config.autoUpdateElement. |
---|
141 | config.autoUpdateElement = false; |
---|
142 | $element.data( '_ckeditorInstanceLock', true ); |
---|
143 | |
---|
144 | // Set instance reference in element's data. |
---|
145 | if ( $( this ).is( 'textarea' ) ) |
---|
146 | editor = CKEDITOR.replace( element, config ); |
---|
147 | else |
---|
148 | editor = CKEDITOR.inline( element, config ); |
---|
149 | |
---|
150 | $element.data( 'ckeditorInstance', editor ); |
---|
151 | |
---|
152 | // Register callback. |
---|
153 | editor.on( 'instanceReady', function( evt ) { |
---|
154 | var editor = evt.editor; |
---|
155 | |
---|
156 | setTimeout( function() { |
---|
157 | // Delay bit more if editor is still not ready. |
---|
158 | if ( !editor.element ) { |
---|
159 | setTimeout( arguments.callee, 100 ); |
---|
160 | return; |
---|
161 | } |
---|
162 | |
---|
163 | // Remove this listener. Triggered when new instance is ready. |
---|
164 | evt.removeListener(); |
---|
165 | |
---|
166 | /** |
---|
167 | * Forwards the CKEditor {@link CKEDITOR.editor#event-dataReady dataReady event} as a jQuery event. |
---|
168 | * |
---|
169 | * @event dataReady |
---|
170 | * @param {CKEDITOR.editor} editor Editor instance. |
---|
171 | */ |
---|
172 | editor.on( 'dataReady', function() { |
---|
173 | $element.trigger( 'dataReady.ckeditor', [ editor ] ); |
---|
174 | } ); |
---|
175 | |
---|
176 | /** |
---|
177 | * Forwards the CKEditor {@link CKEDITOR.editor#event-setData setData event} as a jQuery event. |
---|
178 | * |
---|
179 | * @event setData |
---|
180 | * @param {CKEDITOR.editor} editor Editor instance. |
---|
181 | * @param data |
---|
182 | * @param {String} data.dataValue The data that will be used. |
---|
183 | */ |
---|
184 | editor.on( 'setData', function( evt ) { |
---|
185 | $element.trigger( 'setData.ckeditor', [ editor, evt.data ] ); |
---|
186 | } ); |
---|
187 | |
---|
188 | /** |
---|
189 | * Forwards the CKEditor {@link CKEDITOR.editor#event-getData getData event} as a jQuery event. |
---|
190 | * |
---|
191 | * @event getData |
---|
192 | * @param {CKEDITOR.editor} editor Editor instance. |
---|
193 | * @param data |
---|
194 | * @param {String} data.dataValue The data that will be returned. |
---|
195 | */ |
---|
196 | editor.on( 'getData', function( evt ) { |
---|
197 | $element.trigger( 'getData.ckeditor', [ editor, evt.data ] ); |
---|
198 | }, 999 ); |
---|
199 | |
---|
200 | /** |
---|
201 | * Forwards the CKEditor {@link CKEDITOR.editor#event-destroy destroy event} as a jQuery event. |
---|
202 | * |
---|
203 | * @event destroy |
---|
204 | * @param {CKEDITOR.editor} editor Editor instance. |
---|
205 | */ |
---|
206 | editor.on( 'destroy', function() { |
---|
207 | $element.trigger( 'destroy.ckeditor', [ editor ] ); |
---|
208 | } ); |
---|
209 | |
---|
210 | // Overwrite save button to call jQuery submit instead of javascript submit. |
---|
211 | // Otherwise jQuery.forms does not work properly |
---|
212 | editor.on( 'save', function() { |
---|
213 | $( element.form ).submit(); |
---|
214 | return false; |
---|
215 | }, null, null, 20 ); |
---|
216 | |
---|
217 | // Integrate with form submit. |
---|
218 | if ( editor.config.autoUpdateElementJquery && $element.is( 'textarea' ) && $( element.form ).length ) { |
---|
219 | var onSubmit = function() { |
---|
220 | $element.ckeditor( function() { |
---|
221 | editor.updateElement(); |
---|
222 | } ); |
---|
223 | }; |
---|
224 | |
---|
225 | // Bind to submit event. |
---|
226 | $( element.form ).submit( onSubmit ); |
---|
227 | |
---|
228 | // Bind to form-pre-serialize from jQuery Forms plugin. |
---|
229 | $( element.form ).bind( 'form-pre-serialize', onSubmit ); |
---|
230 | |
---|
231 | // Unbind when editor destroyed. |
---|
232 | $element.bind( 'destroy.ckeditor', function() { |
---|
233 | $( element.form ).unbind( 'submit', onSubmit ); |
---|
234 | $( element.form ).unbind( 'form-pre-serialize', onSubmit ); |
---|
235 | } ); |
---|
236 | } |
---|
237 | |
---|
238 | // Garbage collect on destroy. |
---|
239 | editor.on( 'destroy', function() { |
---|
240 | $element.removeData( 'ckeditorInstance' ); |
---|
241 | } ); |
---|
242 | |
---|
243 | // Remove lock. |
---|
244 | $element.removeData( '_ckeditorInstanceLock' ); |
---|
245 | |
---|
246 | /** |
---|
247 | * Forwards the CKEditor {@link CKEDITOR.editor#event-instanceReady instanceReady event} as a jQuery event. |
---|
248 | * |
---|
249 | * @event instanceReady |
---|
250 | * @param {CKEDITOR.editor} editor Editor instance. |
---|
251 | */ |
---|
252 | $element.trigger( 'instanceReady.ckeditor', [ editor ] ); |
---|
253 | |
---|
254 | // Run given (first) code. |
---|
255 | if ( callback ) |
---|
256 | callback.apply( editor, [ element ] ); |
---|
257 | |
---|
258 | dfd.resolve(); |
---|
259 | }, 0 ); |
---|
260 | }, null, null, 9999 ); |
---|
261 | } else { |
---|
262 | // Editor is already during creation process, bind our code to the event. |
---|
263 | editor.once( 'instanceReady', function( evt ) { |
---|
264 | setTimeout( function() { |
---|
265 | // Delay bit more if editor is still not ready. |
---|
266 | if ( !editor.element ) { |
---|
267 | setTimeout( arguments.callee, 100 ); |
---|
268 | return; |
---|
269 | } |
---|
270 | |
---|
271 | // Run given code. |
---|
272 | if ( editor.element.$ == element && callback ) |
---|
273 | callback.apply( editor, [ element ] ); |
---|
274 | |
---|
275 | dfd.resolve(); |
---|
276 | }, 0 ); |
---|
277 | }, null, null, 9999 ); |
---|
278 | } |
---|
279 | } ); |
---|
280 | |
---|
281 | /** |
---|
282 | * The [jQuery Promise object]((http://api.jquery.com/promise/)) that handles the asynchronous constructor. |
---|
283 | * This promise will be resolved after **all** of the constructors. |
---|
284 | * |
---|
285 | * @property {Function} promise |
---|
286 | */ |
---|
287 | var dfd = new $.Deferred(); |
---|
288 | |
---|
289 | this.promise = dfd.promise(); |
---|
290 | |
---|
291 | $.when.apply( this, promises ).then( function() { |
---|
292 | dfd.resolve(); |
---|
293 | } ); |
---|
294 | |
---|
295 | /** |
---|
296 | * Existing CKEditor instance. Allows to easily use the internal API. |
---|
297 | * |
---|
298 | * **Note**: This is not a jQuery object. |
---|
299 | * |
---|
300 | * var editor = $( 'textarea' ).ckeditor().editor; |
---|
301 | * |
---|
302 | * @property {CKEDITOR.editor} editor |
---|
303 | */ |
---|
304 | this.editor = this.eq( 0 ).data( 'ckeditorInstance' ); |
---|
305 | |
---|
306 | return this; |
---|
307 | } |
---|
308 | } ); |
---|
309 | |
---|
310 | /** |
---|
311 | * Overwritten jQuery `val()` method for `<textarea>` elements that have bound CKEditor instances. |
---|
312 | * This method gets or sets editor content by using the {@link CKEDITOR.editor#method-getData editor.getData()} |
---|
313 | * or {@link CKEDITOR.editor#method-setData editor.setData()} methods. To handle |
---|
314 | * the {@link CKEDITOR.editor#method-setData editor.setData()} callback (as `setData` is asynchronous), |
---|
315 | * `val( 'some data' )` will return a [jQuery Promise object](http://api.jquery.com/promise/). |
---|
316 | * |
---|
317 | * @method val |
---|
318 | * @returns String|Number|Array|jQuery.fn|function(jQuery Promise) |
---|
319 | */ |
---|
320 | if ( CKEDITOR.config.jqueryOverrideVal ) { |
---|
321 | $.fn.val = CKEDITOR.tools.override( $.fn.val, function( oldValMethod ) { |
---|
322 | return function( value ) { |
---|
323 | // Setter, i.e. .val( "some data" ); |
---|
324 | if ( arguments.length ) { |
---|
325 | var _this = this, |
---|
326 | promises = [], //use promise to handle setData callback |
---|
327 | |
---|
328 | result = this.each( function() { |
---|
329 | var $elem = $( this ), |
---|
330 | editor = $elem.data( 'ckeditorInstance' ); |
---|
331 | |
---|
332 | // Handle .val for CKEditor. |
---|
333 | if ( $elem.is( 'textarea' ) && editor ) { |
---|
334 | var dfd = new $.Deferred(); |
---|
335 | |
---|
336 | editor.setData( value, function() { |
---|
337 | dfd.resolve(); |
---|
338 | } ); |
---|
339 | |
---|
340 | promises.push( dfd.promise() ); |
---|
341 | return true; |
---|
342 | } |
---|
343 | // Call default .val function for rest of elements |
---|
344 | else |
---|
345 | return oldValMethod.call( $elem, value ); |
---|
346 | } ); |
---|
347 | |
---|
348 | // If there is no promise return default result (jQuery object of chaining). |
---|
349 | if ( !promises.length ) |
---|
350 | return result; |
---|
351 | // Create one promise which will be resolved when all of promises will be done. |
---|
352 | else { |
---|
353 | var dfd = new $.Deferred(); |
---|
354 | |
---|
355 | $.when.apply( this, promises ).done( function() { |
---|
356 | dfd.resolveWith( _this ); |
---|
357 | } ); |
---|
358 | |
---|
359 | return dfd.promise(); |
---|
360 | } |
---|
361 | } |
---|
362 | // Getter .val(); |
---|
363 | else { |
---|
364 | var $elem = $( this ).eq( 0 ), |
---|
365 | editor = $elem.data( 'ckeditorInstance' ); |
---|
366 | |
---|
367 | if ( $elem.is( 'textarea' ) && editor ) |
---|
368 | return editor.getData(); |
---|
369 | else |
---|
370 | return oldValMethod.call( $elem ); |
---|
371 | } |
---|
372 | }; |
---|
373 | } ); |
---|
374 | } |
---|
375 | })( window.jQuery ); |
---|