category: builds-integration
{@snippet builds/saving-data/build-autosave-source}
CKEditor 5 allows you to retrieve the data from and save it to your server (or to your system in general) in various ways. In this guide you can learn about the available options along with their pros and cons.
This is the classic way of integrating the editor. It is typically used in simpler CMSes, forums, comment sections, etc.
This approach is only available in the {@link builds/guides/overview#classic-editor Classic editor} and only if the editor was used to replace a <textarea> element:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CKEditor 5 - Classic editor</title>
<script src="https://cdn.ckeditor.com/ckeditor5/{@var ckeditor5-version}/classic/ckeditor.js"></script>
</head>
<body>
<h1>Classic editor</h1>
<form action="[URL]" method="post">
<textarea name="content" id="editor">
<p>This is some sample content.</p>
</textarea>
<p><input type="submit" value="Submit"></p>
</form>
<script>
ClassicEditor
.create( document.querySelector( '#editor' ) )
.catch( error => {
console.error( error );
} );
</script>
</body>
</html>
Classic editor will automatically update the value of the <textarea> element once the user submits the form. You do not need any additional JavaScript code to send the editor data to the server.
In your HTTP server, you can now read the editor data from the content variable of the POST request. For instance, in PHP, you can get it in this way:
<?php
$editor_data = $_POST[ 'content' ];
?>
Please note that the replaced `<textarea>` element is updated automatically by CKEditor straight before the submission. If you need to access the `<textarea>` value programatically with JavaScript (e.g. in the `onsubmit` handler to validate the entered data), there is a chance that the `<textarea>` element would still store the original data. In order to update the value of the replaced `<textarea>`, use the {@link module:editor-classic/classiceditor~ClassicEditor#updateSourceElement `editor.updateSourceElement()`} method.
If you need to get the actual data from CKEditor at any moment using JavaScript, use the {@link module:editor-classic/classiceditor~ClassicEditor#getData `editor.getData()`} method as described in the next section.
When you:
you can retrieve the data from the editor by using the {@link module:editor-classic/classiceditor~ClassicEditor#getData editor.getData()} method.
For that, you need to store the reference to the editor because — unlike in CKEditor 4 — there is no global CKEDITOR.instances property. You can do that in multiple ways, for example by assigning the editor to a variable defined outside the then()'s callback:
let editor;
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( newEditor => {
editor = newEditor;
} )
.catch( error => {
console.error( error );
} );
// Assuming there is a <button id="submit">Submit</button> in your application.
document.querySelector( '#submit' ).addEventListener( 'click', () => {
const editorData = editor.getData();
// ...
} );
The {@link module:autosave/autosave~Autosave} feature allows you to automatically save the data (e.g. send it to the server) when needed (when the user changed the content).
This plugin is unavailable in any of the builds by default so you need to {@link builds/guides/integration/installing-plugins install it}.
Assuming that you implemented a saveData() function that sends the data to your server and returns a promise which is resolved once the data is successfully saved, configuring the autosave feature is as simple as:
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [
Autosave,
// ... other plugins
],
autosave: {
save( editor ) {
return saveData( editor.getData() );
}
},
// ... other configuration options
} );
The autosave feature listens to the {@link module:engine/model/document~Document#event:change:data editor.model.document#change:data} event, throttles it and executes the config.autosave.save() function.
It also listens to the native window#beforeunload event and blocks it in the following cases:
save() function did not resolve its promise or it was not called yet due to throttling).This automatically secures you from the user leaving the page before the content is saved or some ongoing actions like image upload did not finish.
This demo shows a simple integration of the editor with a fake HTTP server (which needs 1000ms to save the content).
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [
Autosave,
// ... other plugins
],
autosave: {
save( editor ) {
return saveData( editor.getData() );
}
}
} )
.then( editor => {
window.editor = editor;
displayStatus( editor );
} )
.catch( err => {
console.error( err.stack );
} );
// Save the data to a fake HTTP server (emulated here with a setTimeout()).
function saveData( data ) {
return new Promise( resolve => {
setTimeout( () => {
console.log( 'Saved', data );
resolve();
}, HTTP_SERVER_LAG );
} );
}
// Update the "Status: Saving..." info.
function displayStatus( editor ) {
const pendingActions = editor.plugins.get( 'PendingActions' );
const statusIndicator = document.querySelector( '#editor-status' );
pendingActions.on( 'change:hasAny', ( evt, propertyName, newValue ) => {
if ( newValue ) {
statusIndicator.classList.add( 'busy' );
} else {
statusIndicator.classList.remove( 'busy' );
}
} );
}
How to understand this demo:
save()'s promise was not resolved).{@snippet builds/saving-data/autosave}
An additional concern when integrating the editor in your website is that the user may mistakenly leave before saving the data. This problem is automatically handled by the autosave feature described above, but if you do not use it and instead chose different integration methods, you should consider handling these two scenarios:
To handle the former situation you can listen to the native window#beforeunload event. The latter situation can be handled by using CKEditor 5 {@link module:core/pendingactions~PendingActions} plugin.
The example below shows how all these mechanisms can be used together to enable or disable a "Save" button and block the user from leaving the page without saving the data.
The {@link module:core/pendingactions~PendingActions} plugin is unavailable in any of the builds by default so you need to {@link builds/guides/integration/installing-plugins install it}.
// Note: We need to build the editor from source.
// We cannot use existing builds in this case.
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import PendingActions from '@ckeditor/ckeditor5-core/src/pendingactions';
let isDirty = false;
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [
PendingActions,
// ... other plugins
]
} )
.then( editor => {
window.editor = editor;
handleStatusChanges( editor );
handleSaveButton( editor );
handleBeforeunload( editor );
} )
.catch( err => {
console.error( err.stack );
} );
// Handle clicking the "Save" button by sending the data to a
// fake HTTP server (emulated here with setTimeout()).
function handleSaveButton( editor ) {
const saveButton = document.querySelector( '#save' );
const pendingActions = editor.plugins.get( 'PendingActions' );
saveButton.addEventListener( 'click', evt => {
const data = editor.getData();
// Register the action of saving the data as a "pending action".
// All asynchronous actions related to the editor are tracked like this,
// so later on you only need to check `pendingActions.hasAny` to check
// whether the editor is busy or not.
const action = pendingActions.add( 'Saving changes' );
evt.preventDefault();
// Save the data to a fake HTTP server.
setTimeout( () => {
pendingActions.remove( action );
// Reset isDirty only if the data did not change in the meantime.
if ( data == editor.getData() ) {
isDirty = false;
}
updateStatus( editor );
}, HTTP_SERVER_LAG );
} );
}
// Listen to new changes (to enable the "Save" button) and to
// pending actions (to show the spinner animation when the editor is busy).
function handleStatusChanges( editor ) {
editor.plugins.get( 'PendingActions' ).on( 'change:hasAny', () => updateStatus( editor ) );
editor.model.document.on( 'change:data', () => {
isDirty = true;
updateStatus( editor );
} );
}
// If the user tries to leave the page before the data is saved, ask
// them whether they are sure they want to proceed.
function handleBeforeunload( editor ) {
const pendingActions = editor.plugins.get( 'PendingActions' );
window.addEventListener( 'beforeunload', evt => {
if ( pendingActions.hasAny ) {
evt.preventDefault();
}
} );
}
function updateStatus( editor ) {
const saveButton = document.querySelector( '#save' );
// Disables the "Save" button when the data on the server is up to date.
if ( isDirty ) {
saveButton.classList.add( 'active' );
} else {
saveButton.classList.remove( 'active' );
}
// Shows the spinner animation.
if ( editor.plugins.get( 'PendingActions' ).hasAny ) {
saveButton.classList.add( 'saving' );
} else {
saveButton.classList.remove( 'saving' );
}
}
How to understand this demo:
{@snippet builds/saving-data/manualsave}