Skip to content

Commit

Permalink
Merge branch 't/10281' into major
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Jasiun committed Jun 28, 2013
2 parents 9ae4973 + e6d4fa2 commit 2999484
Show file tree
Hide file tree
Showing 5 changed files with 495 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ CKEditor 4 Changelog

## CKEditor 4.2

* [#10281](http://dev.ckeditor.com/ticket/10281): jQuery adapter.
* [#10042](http://dev.ckeditor.com/ticket/10042): Introduced config.title to change the human-readable title of the editor.
* [#10370](http://dev.ckeditor.com/ticket/10370): Inconsistency in data events between framed and inline editors.

Expand Down
371 changes: 371 additions & 0 deletions adapters/jquery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.html or http://ckeditor.com/license
*/

/**
* @fileOverview Defines the {@link CKEDITOR_Adapters.jQuery jQuery adapter}.
*/

/**
* @class CKEDITOR_Adapters.jQuery
* @singleton
*
* jQuery adapter provides easy use of basic CKEditor functions and access to internal API.
* To find more information about jQuery adapter go to [guide page](#!/guide/dev_jquery) or see the sample.
*
* @aside guide dev_jquery
*/

(function( $ ) {
/**
* Allows CKEditor to override `jQuery.fn.val()`, making possible to use the val()
* function on textareas, as usual, having it synchronized with CKEditor.
*
* This configuration option is global and executed during the jQuery Adapter loading.
* It can't be customized across editor instances.
*
* <script>
* CKEDITOR.config.jqueryOverrideVal = true;
* </script>
*
* <!-- Important: The JQuery adapter is loaded *after* setting jqueryOverrideVal -->
* <script src="/ckeditor/adapters/jquery.js"></script>
*
* <script>
* $( 'textarea' ).ckeditor();
* // ...
* $( 'textarea' ).val( 'New content' );
* </script>
*
* @cfg {Boolean} [jqueryOverrideVal=true]
* @member CKEDITOR.config
*/
CKEDITOR.config.jqueryOverrideVal = typeof CKEDITOR.config.jqueryOverrideVal == 'undefined' ?
true
:
CKEDITOR.config.jqueryOverrideVal;

if ( typeof $ == 'undefined' )
return;

// jQuery object methods.
$.extend( $.fn, {
/**
* Return existing CKEditor instance for first matched element.
* Allows to easily use internal API. Doesn't return jQuery object.
*
* Raised exception if editor doesn't exist or isn't ready yet.
*
* @returns CKEDITOR.editor
* @deprecated Use {@link #editor editor property} instead.
*/
ckeditorGet: function() {
var instance = this.eq( 0 ).data( 'ckeditorInstance' );

if ( !instance )
throw 'CKEditor not yet initialized, use ckeditor() with callback.';

return instance;
},

/**
* jQuery function which triggers creation of CKEditor with `<textarea>` and {@link CKEDITOR.dtd#$editable editable elements}.
* Every `<textarea>` element will be converted to framed editor and any other supported element to inline editor.
* This method binds callback to `instanceReady` event of all instances.
* If editor is already created, then callback is fired right away.
* You can also create multiple editors at once using `$( '.className' ).ckeditor();`
*
* **Note**: jQuery chaining and mixed parameter order allowed.
*
* @param {Function} callback
* Function to be run on editor instance. Callback takes source element as parameter.
*
* $( 'textarea' ).ckeditor( function( textarea ) {
* // callback function code
* } );
*
* @param {Object} config
* Configuration options for new instance(s) if not already created.
*
* $( 'textarea' ).ckeditor( {
* uiColor: '#9AB8F3'
* } );
*
* @returns jQuery.fn
*/
ckeditor: function( callback, config ) {
if ( !CKEDITOR.env.isCompatible )
throw new Error( 'Environment is incompatible.' );

// Reverse the order of arguments if the first one isn't a function.
if ( !$.isFunction( callback ) ) {
var tmp = config;
config = callback;
callback = tmp;
}

// An array of instanceReady callback promises.
var promises = [];

config = config || {};

// Iterate over the collection.
this.each( function() {
var $element = $( this ),
editor = $element.data( 'ckeditorInstance' ),
instanceLock = $element.data( '_ckeditorInstanceLock' ),
element = this,
dfd = new $.Deferred();

promises.push( dfd.promise() );

if ( editor && !instanceLock ) {
if ( callback )
callback.apply( editor, [ this ] );

dfd.resolve();
} else if ( !instanceLock ) {
// CREATE NEW INSTANCE

// Handle config.autoUpdateElement inside this plugin if desired.
if ( config.autoUpdateElement
|| ( typeof config.autoUpdateElement == 'undefined' && CKEDITOR.config.autoUpdateElement ) ) {
config.autoUpdateElementJquery = true;
}

// Always disable config.autoUpdateElement.
config.autoUpdateElement = false;
$element.data( '_ckeditorInstanceLock', true );

// Set instance reference in element's data.
if ( $( this ).is( 'textarea' ) )
editor = CKEDITOR.replace( element, config );
else
editor = CKEDITOR.inline( element, config );

$element.data( 'ckeditorInstance', editor );

// Register callback.
editor.on( 'instanceReady', function( evt ) {
var editor = evt.editor;

setTimeout( function() {
// Delay bit more if editor is still not ready.
if ( !editor.element ) {
setTimeout( arguments.callee, 100 );
return;
}

// Remove this listener. Triggered when new instance is ready.
evt.removeListener();

/**
* Forwarded editor's {@link CKEDITOR.editor#event-dataReady dataReady event} as a jQuery event.
*
* @event dataReady
* @param {CKEDITOR.editor} editor Editor's instance.
*/
editor.on( 'dataReady', function() {
$element.trigger( 'dataReady.ckeditor', [ editor ] );
} );

/**
* Forwarded editor's {@link CKEDITOR.editor#event-setData setData event} as a jQuery event.
*
* @event setData
* @param {CKEDITOR.editor} editor Editor's instance.
* @param data
* @param {String} data.dataValue The data that will be used.
*/
editor.on( 'setData', function( evt ) {
$element.trigger( 'setData.ckeditor', [ editor, evt.data ] );
} );

/**
* Forwarded editor's {@link CKEDITOR.editor#event-getData getData event} as a jQuery event.
*
* @event getData
* @param {CKEDITOR.editor} editor Editor's instance.
* @param data
* @param {String} data.dataValue The data that will be returned.
*/
editor.on( 'getData', function( evt ) {
$element.trigger( 'getData.ckeditor', [ editor, evt.data ] );
}, 999 );

/**
* Forwarded editor's {@link CKEDITOR.editor#event-destroy destroy event} as a jQuery event.
*
* @event destroy
* @param {CKEDITOR.editor} editor Editor's instance.
*/
editor.on( 'destroy', function() {
$element.trigger( 'destroy.ckeditor', [ editor ] );
} );

// Overwrite save button to call jQuery submit instead of javascript submit.
// Otherwise jQuery.forms does not work properly
editor.on( 'save', function() {
$( element.form ).submit();
return false;
}, null, null, 20 );

// Integrate with form submit.
if ( editor.config.autoUpdateElementJquery && $element.is( 'textarea' ) && $( element.form ).length ) {
var onSubmit = function() {
$element.ckeditor( function() {
editor.updateElement();
} );
};

// Bind to submit event.
$( element.form ).submit( onSubmit );

// Bind to form-pre-serialize from jQuery Forms plugin.
$( element.form ).bind( 'form-pre-serialize', onSubmit );

// Unbind when editor destroyed.
$element.bind( 'destroy.ckeditor', function() {
$( element.form ).unbind( 'submit', onSubmit );
$( element.form ).unbind( 'form-pre-serialize', onSubmit );
} );
}

// Garbage collect on destroy.
editor.on( 'destroy', function() {
$element.removeData( 'ckeditorInstance' );
} );

// Remove lock.
$element.removeData( '_ckeditorInstanceLock' );

/**
* Forwarded editor's {@link CKEDITOR.editor#event-instanceReady instanceReady event} as a jQuery event.
*
* @event instanceReady
* @param {CKEDITOR.editor} editor Editor's instance.
*/
$element.trigger( 'instanceReady.ckeditor', [ editor ] );

// Run given (first) code.
if ( callback )
callback.apply( editor, [ element ] );

dfd.resolve();
}, 0 );
}, null, null, 9999 );
} else {
// Editor is already during creation process, bind our code to the event.
editor.once( 'instanceReady', function( evt ) {
setTimeout( function() {
// Delay bit more if editor is still not ready.
if ( !editor.element ) {
setTimeout( arguments.callee, 100 );
return;
}

// Run given code.
if ( editor.element.$ == element && callback )
callback.apply( editor, [ element ] );

dfd.resolve();
}, 0 );
}, null, null, 9999 );
}
} );

/**
* jQuery promise object to handle asynchronous constructor.
* This promise will be resolved after **all** of the constructors.
*
* @property {Function} promise
*/
var dfd = new $.Deferred();

this.promise = dfd.promise();

$.when.apply( this, promises ).then( function() {
dfd.resolve();
} );

/**
* Existing CKEditor instance. Allows to easily use internal API.
*
* **Note**: This is not a jQuery object.
*
* var editor = $( 'textarea' ).ckeditor().editor;
*
* @property {CKEDITOR.editor} editor
*/
this.editor = this.eq( 0 ).data( 'ckeditorInstance' );

return this;
}
} );

/**
* Overwritten jQuery `val()` method for `<textarea>` elements which have CKEditor instances bound.
* Method gets or sets editor's content using {@link CKEDITOR.editor#method-getData editor.getData()}
* or {@link CKEDITOR.editor#method-setData editor.setData()}. To handle
* {@link CKEDITOR.editor#method-setData editor.setData()} callback (setData is asynchronous)
* `val( 'some data' )` will return [jQuery promise](http://api.jquery.com/promise/).
*
* @method val
* @returns String|Number|Array|jQuery.fn|function(jQuery promise)
*/
if ( CKEDITOR.config.jqueryOverrideVal ) {
$.fn.val = CKEDITOR.tools.override( $.fn.val, function( oldValMethod ) {
return function( value ) {
// Setter, i.e. .val( "some data" );
if ( arguments.length ) {
var _this = this,
promises = [], //use promise to handle setData callback

result = this.each( function() {
var $elem = $( this ),
editor = $elem.data( 'ckeditorInstance' );

// Handle .val for CKEditor.
if ( $elem.is( 'textarea' ) && editor ) {
var dfd = new $.Deferred();

editor.setData( value, function() {
dfd.resolve();
} );

promises.push( dfd.promise() );
}
// Call default .val function for rest of elements
else
return oldValMethod.call( $elem, value );
} );

// If there is no promise return default result (jQuery object of chaining).
if ( !promises.length )
return result;
// Create one promise which will be resolved when all of promises will be done.
else {
var dfd = new $.Deferred();

$.when.apply( this, promises ).done( function() {
dfd.resolveWith( _this );
} );

return dfd.promise();
}
}
// Getter .val();
else {
var $elem = $( this ).eq( 0 ),
editor = $elem.data( 'ckeditorInstance' );

if ( $elem.is( 'textarea' ) && editor )
return editor.getData();
else
return oldValMethod.call( $elem );
}
};
} );
}
})( window.jQuery );
Loading

0 comments on commit 2999484

Please sign in to comment.