Skip to content

Commit

Permalink
Merge pull request #4463 from ckeditor/t/4461
Browse files Browse the repository at this point in the history
Prevent editor creation on detached element
  • Loading branch information
f1ames authored Apr 23, 2021
2 parents bc88d4e + 46cd968 commit 1c01491
Show file tree
Hide file tree
Showing 21 changed files with 813 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ CKEditor 4 Changelog

New Features:

* [#4461](https://github.com/ckeditor/ckeditor4/issues/4461): Introduced possibility to delay editor initialization while it is in detached DOM element.

Fixed Issues:

* [#4604](https://github.com/ckeditor/ckeditor4/issues/4604): Fixed: [`CKEDITOR.plugins.clipboard.dataTransfer#getTypes()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_clipboard_dataTransfer.html#method-getTypes) returns no types.
Expand Down
16 changes: 12 additions & 4 deletions core/creators/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@
return null;
}

var editor = new CKEDITOR.editor( instanceConfig, element, CKEDITOR.ELEMENT_MODE_INLINE ),
textarea = element.is( 'textarea' ) ? element : null;
// (#4461)
if ( CKEDITOR.editor.shouldDelayEditorCreation( element, instanceConfig ) ) {
CKEDITOR.editor.initializeDelayedEditorCreation( element, instanceConfig, 'inline' );
return null;
}

var textarea = element.is( 'textarea' ) ? element : null,
editorData = textarea ? textarea.getValue() : element.getHtml(),
editor = new CKEDITOR.editor( instanceConfig, element, CKEDITOR.ELEMENT_MODE_INLINE );

if ( textarea ) {
editor.setData( textarea.getValue(), null, true );
editor.setData( editorData, null, true );

//Change element from textarea to div
element = CKEDITOR.dom.element.createFromHtml(
Expand All @@ -63,7 +70,7 @@

// Initial editor data is simply loaded from the page element content to make
// data retrieval possible immediately after the editor creation.
editor.setData( element.getHtml(), null, true );
editor.setData( editorData, null, true );
}

// Once the editor is loaded, start the UI.
Expand Down Expand Up @@ -156,6 +163,7 @@
CKEDITOR.domReady( function() {
!CKEDITOR.disableAutoInline && CKEDITOR.inlineAll();
} );

} )();

/**
Expand Down
6 changes: 6 additions & 0 deletions core/creators/themedui.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ CKEDITOR.replaceClass = 'ckeditor';
return null;
}

// (#4461)
if ( CKEDITOR.editor.shouldDelayEditorCreation( element, config ) ) {
CKEDITOR.editor.initializeDelayedEditorCreation( element, config, 'replace' );
return null;
}

// Create the editor instance.
var editor = new CKEDITOR.editor( config, element, mode );

Expand Down
155 changes: 155 additions & 0 deletions core/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,103 @@

return element;
};

/**
* Initializes delayed editor creation based on provided configuration.
*
* If the {@link CKEDITOR.config#delayIfDetached_callback} function is declared, it will be invoked with a single argument:
*
* * A callback, that should be called to create editor.
*
* Otherwise, it periodically (with `setInterval()` calls) checks if element is attached to DOM and creates editor automatically.
*
* ```js
* CKEDITOR.inline( detachedEditorElement, {
* delayIfDetached: true,
* delayIfDetached_callback: registerCallback
* } );
* ```
*
* @private
* @since 4.17.0
* @static
* @member CKEDITOR.editor
* @param {CKEDITOR.dom.element} element The DOM element on which editor should be initialized.
* @param {Object} config The specific configuration to apply to the editor instance. Configuration set here will override the global CKEditor settings.
* @param {String} editorCreationMethod Creator function that should be used to initialize editor (inline/replace).
*/
CKEDITOR.editor.initializeDelayedEditorCreation = function( element, config, editorCreationMethod ) {
if ( config.delayIfDetached_callback ) {
CKEDITOR.warn( 'editor-delayed-creation', {
method: 'callback'
} );

config.delayIfDetached_callback( function() {
CKEDITOR[ editorCreationMethod ]( element, config );

CKEDITOR.warn( 'editor-delayed-creation-success', {
method: 'callback'
} );
} );
} else {
var interval = config.delayIfDetached_interval === undefined ? CKEDITOR.config.delayIfDetached_interval : config.delayIfDetached_interval,
intervalId;

CKEDITOR.warn( 'editor-delayed-creation', {
method: 'interval - ' + interval + ' ms'
} );

intervalId = setInterval( function() {
if ( !element.isDetached() ) {
clearInterval( intervalId );

CKEDITOR[ editorCreationMethod ]( element, config );

CKEDITOR.warn( 'editor-delayed-creation-success', {
method: 'interval - ' + interval + ' ms'
} );
}
}, interval );
}
};

/**
* Whether editor creation should be delayed.
*
* @private
* @since 4.17.0
* @static
* @member CKEDITOR.editor
* @param {CKEDITOR.dom.element} element The DOM element on which editor should be initialized.
* @param {Object} config Editor configuration.
* @returns {Boolean} True if creation should be delayed.
*/
CKEDITOR.editor.shouldDelayEditorCreation = function( element, config ) {
CKEDITOR.editor.mergeDelayedCreationConfigs( config );
return config && config.delayIfDetached && element.isDetached();
};

/**
* Merges user provided configuration options for delayed creation with {@link CKEDITOR.config default config}.
*
* User provided options are the preferred ones.
*
* @private
* @since 4.17.0
* @static
* @member CKEDITOR.editor
* @param {Object} userConfig Config provided by the user to create editor.
*/
CKEDITOR.editor.mergeDelayedCreationConfigs = function( userConfig ) {
if ( !userConfig ) {
return;
}

userConfig.delayIfDetached = typeof userConfig.delayIfDetached === 'boolean' ? userConfig.delayIfDetached : CKEDITOR.config.delayIfDetached;
userConfig.delayIfDetached_interval = isNaN( userConfig.delayIfDetached_interval ) ? CKEDITOR.config.delayIfDetached_interval : userConfig.delayIfDetached_interval;
userConfig.delayIfDetached_callback = userConfig.delayIfDetached_callback || CKEDITOR.config.delayIfDetached_callback;
};

} )();

/**
Expand Down Expand Up @@ -2228,3 +2325,61 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3;
* @event contentDomInvalidated
* @param {CKEDITOR.editor} editor This editor instance.
*/

/**
* If set to `true`, editor will be only created when its root element is attached to DOM.
* In case the element is detached, the editor will wait for the element to be attached and initialized then.
*
* For more control over the entire process refer to {@link CKEDITOR.config#delayIfDetached_callback}
* and {@link CKEDITOR.config#delayIfDetached_interval} configuration options.
*
* config.delayIfDetached = true;
*
* @since 4.17.0
* @cfg {Boolean} [delayIfDetached=false]
* @member CKEDITOR.config
*/
CKEDITOR.config.delayIfDetached = false;

/**
* Function used to initialize delayed editor creation.
*
* It accepts a single `callback` argument. A `callback` argument is another function that triggers editor creation.
* This allows to store the editor creation function (`callback`) and invoke it whenever necessary instead of periodically
* check if element is attached to DOM to improve performance.
*
* Used only if {@link CKEDITOR.config#delayIfDetached} is set to `true`.
*
* **Note**: This function (`callback`) should be called only if editor target element is reattached to DOM.
*
* If this option is defined, editor will not run the default {@link CKEDITOR.config#delayIfDetached_interval interval checks}.
*
* // Store the reference to the editor creation function.
* var resumeEditorCreation;
*
* config.delayIfDetached_callback = function( createEditor ) {
* resumeEditorCreation = createEditor;
* };
*
* // Create editor calling `resumeEditorCreation()` whenever you choose (e.g. on button click).
* resumeEditorCreation();
*
* @since 4.17.0
* @cfg {Function} [delayIfDetached_callback = undefined]
* @member CKEDITOR.config
*/
CKEDITOR.config.delayIfDetached_callback = undefined;

/**
* The amount of time (in milliseconds) between consecutive checks whether editor's target element is attached to DOM.
*
* Used only if {@link CKEDITOR.config#delayIfDetached} is set to `true` and
* {@link CKEDITOR.config#delayIfDetached_callback delayIfDetached_callback} not set.
*
* config.delayIfDetached_interval = 2000; // Try to create editor every 2 seconds.
*
* @since 4.17.0
* @cfg {Number} [delayIfDetached_interval=50]
* @member CKEDITOR.config
*/
CKEDITOR.config.delayIfDetached_interval = 50;
Loading

0 comments on commit 1c01491

Please sign in to comment.