[Solved] CKEditor Autosave Draft Functionality - 1.4KB, No Plugin
Like using a plugin but easier.
[Solved] CKEditor Autosave Draft Functionality - 1.4KB, No Plugin
Articles in Client-Side | By August R. Garcia
Published | Last Update
Like using a plugin but easier.
1,065 view, 1 RAM, and 1 comment
Automatically saving user drafts of comments and other data started by users on a website is useful for obvious reasons.
Code to Autosave Drafts with CKEditor
When using CKEditor, it seems that this functionality is not built-in by default. While there are plugins that can do this, such as this AutoSave Plugin for CKEditor, the download is 96.4 KB compressed as a .zip and the main uncompressed plugin.js file is 16.8 KB by itself. For basic draft saving functionality, here is 1.4 KB of minified code that does basically what you want.
function init_ckeditors(){ckes=[],CKEDITOR.on("instanceReady",function(e){init_ckeditor(e.editor),ckes.push(e.editor)}),window.onbeforeunload=function(){for(iii=0;iii<ckes.length;++iii)1==ckes[iii].has_been_modified&&ckes[iii].save_draft()}}function init_ckeditor(e){e.form=get_cke_parent_form(e.id),e.key=get_ckeditor_draft_key(e.form),e.has_been_modified=!1,e.save_draft=save_ckeditor_draft;let t=localStorage.getItem(e.key);null!==t&&e.setData(t),e.on("blur",function(){this.save_draft()}),e.on("focus",function(){this.save_draft()}),e.form&&e.form.addEventListener("submit",function(t){localStorage.removeItem(e.key)})}function save_ckeditor_draft(){this.has_been_modified=!0,localStorage[this.key]=this.getData()}function get_ckeditor_draft_key(e){return"CKEditor Draft - URL w/o Params: "+window.location.href.split("?")[0]+" - Form Identifiers: "+get_form_identifiers(e)}function get_cke_parent_form(e){let t=document.querySelector("."+e+".cke.cke_editor_body[role='application']");for(;null!=t&&"FORM"!==t.nodeName;)t=t.parentNode;return t}function get_form_identifiers(e){if(!e)return"CKEditor Has No Form";let t=e.querySelectorAll("*[name"),i=[];for(let e=0;e<t.length;++e)i.push({tagName:t[e].tagName,name:t[e].getAttribute("name")});return JSON.stringify({name:e.getAttribute("name"),action:e.getAttribute("action"),method:e.getAttribute("method"),fields:i})}
And then to run the initialization for the draft-saving functionality, use:
// Call the init function to run the code.
init_ckeditors();
Full Commented Code for Autosaving CKEditor Drafts
Here's the same code as above commented and in non-minified form, which is 6.8 KB total.
// =============================================== //
// ===== ===== CKEditor Init Functions ===== ===== //
// =============================================== //
// Intialize CKEditor Instances
function init_ckeditors() {
ckes = [];
CKEDITOR.on('instanceReady', function( evt ){
init_ckeditor(evt.editor);
ckes.push(evt.editor);
});
// When the page unloads, save all drafts that were modified
window.onbeforeunload = function(){
for (iii = 0; iii < ckes.length; ++iii) {
if (ckes[iii].has_been_modified == true)
ckes[iii].save_draft();
}
}
}
// Initializes a single CKEditor's draft functionality. Called from init_ckeditors()
function init_ckeditor(cke) {
// Add various attributes and methods to the CKEditor instance
cke.form = get_cke_parent_form( cke.id );
cke.key = get_ckeditor_draft_key( cke.form );
cke.has_been_modified = false ;
cke.save_draft = save_ckeditor_draft ;
// If there is an existing draft, put it into the editor
let draft = localStorage.getItem(cke.key);
if (draft !== null) {
cke.setData( draft );
}
// If the editor loses or gains focus, then save the draft
// Drafts are also saved when the user closes the page via the onbeforeunload
// function in init_ckeditor()
// Documentation on binding functions to CKEditor:
// https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html
cke.on( 'blur' , function() { this.save_draft(); } );
cke.on( 'focus' , function() { this.save_draft(); } );
// Saving the draft on every change is potentially excessive/performance intensive
// but can be done by uncommenting this line, if desired.
//cke.on( 'change', function() { this.save_draft(); } );
// ===================== //
// ===== ON SUBMIT ===== //
// ===================== //
// Set on-submit function to clear saved drafts on submit of the parent form,
// if a parent form exists.
//
// Presumably, most if not all CKEditor instances will be within <form> elements
// Assuming that a form was found, bind an onsubmit function that clears saved
// drafts on submit.
// If the editor is not--for whatever reason--within a <form> tag,
// no onsubmit function is bound.
//
// Note: If the form is submitted, but the form submit fails on the server-side, then
// the draft will still be removed from localStorage.
// When the back button is hit, the draft should still be restored by the
// browser (at least in Chrome).
if (cke.form) {
cke.form.addEventListener("submit", function(event) {
localStorage.removeItem(cke.key);
});
}
}
// Additional method bound to CKEditor instances.
// Uses a unique key to store drafts for different URLs as well as multiple
// instances on the same page.
function save_ckeditor_draft() {
this.has_been_modified = true ;
localStorage[this.key] = this.getData();
}
// Get the key used in localStorage to reference a particular editor's saved draft.
// To create a unique draft ID, this function uses:
// * The URL of the current page, without params.
// --> This allows the cache to ignore ?page=2
// and so on, for example if a forum thread spans multiple threads.
// * If applicable, data from the <form> tag and it's child elements. Specifically:
// * The <form> tag's name="", action="", and method="" attributes.
// * The tag name and name="" attributes of all <input>, <textarea>, and other
// elements within the
// <form> tag that have a name="" attribute (e.g., <select> tags and so on).
// --> These allow the cache to make a reasonably accurate guess about whether; ex: a form
// on ?page=2 should be treated as the same form shown on ?page=1/3/69/420
// Note: The CKEditor instance's ID cannot be used reliably, because it is autogenerated and
// is based on the number of CKEditors on a page. If, for example, there are separate
// forms to edit every forum comment that exists on a page, these will be
// inconsistent between pages that have different numbers of comments.
// Format is: "CKEditor Draft - [URL without Params] - [<form> and child element info]"
// Ignores any query parameters,
function get_ckeditor_draft_key(form) {
let url_no_params = window.location.href.split('?')[0];
let form_info = get_form_identifiers(form);
return "CKEditor Draft - URL w/o Params: " + url_no_params + " - Form Identifiers: " + form_info;
}
// Helper function.
// Select the CKEditor by its ID and then loop up through its parent node until a <form>
// is found or until all parent nodes have been checked.
function get_cke_parent_form( cke_id ) {
// CKEditor instances' ID attribute aren't actually used as id="" HTML attributes;
// they are used as an additional class instead.
let node = document.querySelector("."+cke_id+".cke.cke_editor_body[role='application']");
while ( node != null && node.nodeName !== "FORM" ) {
node = node.parentNode;
} return node;
}
// As discussed in regards to get_ckeditor_draft_key(), it is common for the same form to be
// shown on multiple pages of a website. For example, a forum thread may have
// Consider the following
function get_form_identifiers( form ) {
if (!form) return "CKEditor Has No Form";
let fields = form.querySelectorAll("*[name");
let field_objs = [];
for (let iii = 0; iii < fields.length; ++iii) {
field_objs.push({
"tagName" : fields[iii].tagName ,
"name" : fields[iii].getAttribute("name")
});
}
return JSON.stringify({
"name" : form.getAttribute("name" ) ,
"action" : form.getAttribute("action") ,
"method" : form.getAttribute("method") ,
"fields" : field_objs
});
}
In conclusion, very nice. Feel free to use and/or modify this code for your project or other purposes.








Hash Brown (2 years ago) 🐏 ⨉ 1Posted by August R. Garcia 2 years ago
Edit History
• [2019-11-23 10:25 PST] August R. Garcia (2 years ago)• [2019-11-23 10:25 PST] August R. Garcia (2 years ago)
• [2019-11-23 10:25 PST] August R. Garcia (2 years ago)
🕓 Posted at 23 November, 2019 10:25 AM PST
- C U [Solved] Implementing an "Estimated Time to Crack" Notification for Registration Forms
- C U [Solved] Enabling Shades/Tints of All Colors in CKEditor
- C U [Solved] Onclick Delay to Confirm Form Submission when Button is Clicked
- C U [Solved] CSS Effect for Custom File Input on Dragover/DragEnter
- C U [Solved] Automatically apply color scale to HTML table with JavaScript
- C U [Solved] Adding an "active" class to <nav>/navigation menus in Laravel
- C U [Solved] How to automatically replace newlines with commas when data is pasted into an HTML <input>
- C U [Solved] Downloading arbitrary tables as TSV using JavaScript
- C U What is the difference between Java and Javascript?
- C U Client-side input validation question -- is it possible to make a CKEditor editor's input required?
August Garcia is some guy who used to sell Viagra on the Internet. He made this website to LARP as a sysadmin while posting about garbage like user-agent spoofing, spintax, the only good keyboard, virtual assitants from Pakistan, links with the rel="nofollow" attribute, proxies, sin, the developer console, literally every link building method, and other junk.
Available at arg@256kilobytes.com, via Twitter, or arg.256kilobytes.com. Open to business inquiries based on availability.
PS: If you're wondering, this code is not yet live on 256 Kilobytes; will be in the next patch.
Edit: There's a bug in the original code related to an onblur (?) function overwriting the deleted cache key if it is handled after the onsubmit event, which can happen. Will post updated code at some point, unless I forget.
Hash Brown (2 years ago) 🐏 ⨉ 1Posted by August R. Garcia 2 years ago
Edit History
• [2019-11-23 10:27 PST] August R. Garcia (2 years ago)🕓 Posted at 23 November, 2019 10:27 AM PST
Sir, I can do you a nice SEO.
Post a New Comment
To leave a comment, login to your account or create an account.
Do you like having a good time?
Register an Account
You can also login to an existing account or reset your password. All use of this site is subject to the terms of service and privacy policy.
Read Quality Articles
Read some quality articles. If you can manage to not get banned for like five minutes, you can even post your own articles.
View Articles →
Argue with People on the Internet
Use your account to explain why people are wrong on the Internet forum.
View Forum →
Vandalize the Wiki
Or don't. I'm not your dad.
View Wiki →
Ask and/or Answer Questions
If someone asks a terrible question, post a LMGTFY link.
View Answers →
Make Some Money
Hire freelancers and/or advertise your goods and/or services. Hire people directly. We're not a middleman or your dad. Manage your own business transactions.