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