| 1 | /** |
|---|
| 2 | * @license Copyright (c) 2003-2016, 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 ); |
|---|