I just want to help others that may have issues integrating CKEditor5 with Meteor.
1) Installed using npm:
meteor npm install --save @ckeditor/ckeditor5-build-classic
2) Used CSS to change height on the editor:
.ck-editor__editable {
min-height: 350px;
}
3) Used the onRendered funtion to create the editor:
Here, assigning the editor to the variable theEditor will not work, it will always be undefined for some reason. Also, assigning the editor to the Template instance (a ReactiveDict) will not work, again it will be undfined. THE only thing that worked for me is to assigne the editor to the window object like below.
import ClassicEditor from '@ckeditor/ckeditor5-build-classic/build/ckeditor';
Template.templateName.onRendered(() => {
let theEditor;
ClassicEditor
.create( document.querySelector( '#noteContent' ) )
.then( editor => {
console.log('editor: ', editor); // undefined
theEditor = editor; // doesn't work, undefined
Template.instance().state.set('editor', editor); // undefined
window.myEditor = editor; // works
})
.catch( error => {
console.error( error.stack );
});
...
});
4) In order to get the data back from the window object, I did this:
Template.templateName.events({
'click #update-note': (event, instance) => {
...
const note = window.myEditor.getData(); // works.
...
},
I hope this helps.
UPDATE:
This post by me at the bottom has corrections/modifications after the feedback from the maintainers.
Hi! Thanks for sharing.
The theEditor variable must work within the scope of onRender()'s callback. It will not be available outside this callback but inside it will definitely be accessible.
I don't know Meteor and I don't know what you want to achieve, but assigning the editor instance to window.myEditor is not safe. E.g. you may want to have two editors on one page and this solution won't work.
In general, it seems to be a good idea to connect the new editor instance with a state of your template, but the following line looks wrong:
Template.instance().state.set('editor', editor);
Again, I don't know Meteor, but instance() call can't possibly know which template is now being rendered because editor initialization is asynchronous. So perhaps that's why it doesn't work.
I'm afraid that you need to dig a bit deeper into Meteor to find the proper way of managing template states.
I'll leave this issue open. Perhaps someone else will stumble upon it and share some ideas.
PS. There's a separate topic about CKEditor 5 integration with other frameworks (Angular, React, etc.): https://github.com/ckeditor/ckeditor5/issues/599.
Thanks.
UPDATED:
If I change the code to this, from another location, I can see that the state is set for TEST, and I can set the state inside the .then promise, but the 'editor' state is never set (not just undefined, the variable is not added to the hash table). An exception is thrown when trying to add the editor to my template state.
Template.templateName.onRendered(() => {
let template = this;
template.state.set('TEST', 'WORKS'); // works
ClassicEditor
.create( document.querySelector( '#noteContent' ) )
.then( editor => {
console.log('editor', editor) // see object
window.myEditor = editor; // works
template.state.set('right', 'left'); // I can evaluate 'right' and it's set to 'left'
template.state.set('editor', editor); // throws exception show below
template.state.set('left', 'right'); // 'left' is never added to state after exception above
})
.catch( error => {
console.error( error.stack );
});
...
});
ADDED EXCEPTION:
It's actually even stranger, I can set the template state within the .then promise code, but not the editor to the state, there is an exception thrown for this line:
template.state.set('editor', editor);
"RangeError: Maximum call stack size exceeded
at Function._.each._.forEach (http://localhost:3105/packages/underscore.js?hash=cde485f60699ff9aced3305f70189e39c665183c:146:43)
at Object.EJSON.clone (http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:530:5)
at Object.EJSON.clone (http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:517:22)
at http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:531:22
at Function._.each._.forEach (http://localhost:3105/packages/underscore.js?hash=cde485f60699ff9aced3305f70189e39c665183c:157:22)
at Object.EJSON.clone (http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:530:5)
at http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:531:22
at Function._.each._.forEach (http://localhost:3105/packages/underscore.js?hash=cde485f60699ff9aced3305f70189e39c665183c:157:22)
at Object.EJSON.clone (http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:530:5)
at http://localhost:3105/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969:531:22"
Could there be an issue with underscore?
Hi, @formspoint.
"RangeError: Maximum call stack size exceeded"
That's probably because Meteor accepts plain object in its collections to easily sync server and client. You won't be able to add here an object that has circular references, it doesn't make sense too (as far as Meteor uses collections with easy-to-transfer data as I guess).
If you want to get the editor's state after each change you can write:
editor.document.on( 'changesDone', () => {
template.state.set( 'editor', editor.getData() );
} );
if I undestand these states correctly.
Note, that the name of that event will change to the 'change' soon.
Hi @ma2ciek
I closed issue by mistake while typing. Please, read my updated previous comment.
Thanks for the help. This is my updated code, but there are a few more problems I talk more about in the subsequent post.
Creating the editor:
import ClassicEditor from '@ckeditor/ckeditor5-build-classic/build/ckeditor';
Template.templateName.onRendered(function() {
ClassicEditor
.create( document.querySelector( '#noteContent' ) )
.then( editor => {
if (editor) {
let template = this;
template.state.set('editor', editor.getData());
}
})
.catch( error => {
console.error( error.stack );
});
});
Helper for the template:
note () {
let note = note.findOne({_id: Meteor.userId()}, {note: 1});
return note;
}
Template:
<textarea id="noteContent" name="content">{{note}}</textarea>
Event to get the data from the instance:
Template.theTemplate.events({
'click #update-note': (event, instance) => {
event.preventDefault();
const note = instance.state.get('editor');
});
Oh, my mistake. I meant editor.document.on( 'changesDone', cb ), not editor.on( 'changesDone', cb ). I'm sorry for the confusion.
Above, the only time I have editor is inside the .then promise.
IMO, that's the right place as you have access to the editor.
I'm closing it due to a lack of activity.