I am trying to process an insert event from the CKEditor 5.
editor.document.on("change", (eventInfo, type, data) => {
switch (type) {
case "insert":
console.log(type, data);
break;
}
});
When typing in the editor the call back is called. The data
argument in the event callback looks like approximately like this:
{
range: {
start: {
root: { ... },
path: [0, 14]
},
end: {
root: { ... },
path: [0, 15]
}
}
}
I don't see a convenient way to figure out what text was actually inserted. I can call data.range.root.getNodeByPath(data.range.start.path);
which seems to get me the text node that the text was inserted in. Should we then look at the text node's data
field? Should we assume that the last item in the path is always an offset for the start and end of the range and use that to substring? I think the insert event is also fired for inserting non-text type things (e.g. element). How would we know that this is indeed a text type of an event?
Is there something I am missing, or is there just a different way to do this all together?
An event is triggered when we paste some content in to CKEditor or paste from dialog (paste as Word, plain text). An event is triggered when editor content is focus or blur. Maximize and minimize We can catch the editor when maximized or minimized by the event given below.
A dedicated Save plugin for CKEditor 4 is available, too. It provides the button, which fires the save event, but it currently works only for classic editor placed inside the <form> element. The following samples are available for getting and saving data in CKEditor 4:
CKEditor is a pure JavaScript component and it does not offer anything more than JavaScript methods and events to access the data so that you could save it on the server. The CKEditor JavaScript API makes it easy to retrieve and control the data.
Whenever a change is made in the editor, CKEditor 4 fires the change event. This makes additional features like auto-saving really easy to develop. The following example shows how to listen to the change event and print the total number of bytes to the console: A dedicated Save plugin for CKEditor 4 is available, too.
First, let me describe how you would do it currently (Jan 2018). Please, keep in mind that CKEditor 5 is now undergoing a big refactoring and things will change. At the end, I will describe how it will look like after we finish this refactoring. You may skip to the later part if you don't mind waiting some more time for the refactoring to come to an end.
EDIT: The 1.0.0-beta.1
was released on 15th of March, so you can jump to the "Since March 2018" section.
1.0.0-alpha.2
)(If you need to learn more about some class API or an event, please check out the docs.)
Your best bet would be simply to iterate through the inserted range.
let data = '';
for ( const child of data.range.getItems() ) {
if ( child.is( 'textProxy' ) ) {
data += child.data;
}
}
Note, that a TextProxy
instance is always returned when you iterate through the range, even if the whole Text
node is included in the range.
(You can read more about stringifying a range in CKEditor5 & Angular2 - Getting exact position of caret on click inside editor to grab data.)
Keep in mind, that InsertOperation
may insert multiple nodes of a different kind. Mostly, these are just singular characters or elements, but more nodes can be provided. That's why there is no additional data.item
or similar property in data
. There could be data.items
but those would just be same as Array.from( data.range.getItems() )
.
Document#change
You haven't mentioned what you want to do with this information afterwards. Getting the range's content is easy, but if you'd like to somehow react to these changes and change the model, then you need to be careful. When the change
event is fired, there might be already more changes enqueued. For example:
If you know exactly what set of features you will use, you may just stick with what I proposed. Just remember that any change you do on the model should be done in a Document#enqueueChanges()
block (otherwise, it won't be rendered).
If you would like to have this solution bulletproof, you probably would have to do this:
data.range
children, if you found a TextProxy
, create a LiveRange
spanning over that node.enqueueChanges()
block, iterate through stored LiveRange
s and through their children.TextProxy
instance.destroy()
all the LiveRange
s afterwards.As you can see this seems unnecessarily complicated. There are some drawbacks of providing an open and flexible framework, like CKE5, and having in mind all the edge cases is one of them. However it is true, that it could be simpler, that's why we started refactoring in the first place.
1.0.0-beta.1
)The big change coming in 1.0.0-beta.1 will be the introduction of the model.Differ
class, revamped events structure and a new API for big part of the model.
First of all, Document#event:change
will be fired after all enqueueChange
blocks have finished. This means that you won't have to be worried whether another change won't mess up with the change that you are reacting to in your callback.
Also, engine.Document#registerPostFixer()
method will be added and you will be able to use it to register callbacks. change
event still will be available, but there will be slight differences between change
event and registerPostFixer
(we will cover them in a guide and docs).
Second, you will have access to a model.Differ
instance, which will store a diff between the model state before the first change and the model state at the moment when you want to react to the changes. You will iterate through all diff items and check what exactly and where has changed.
Other than that, a lot of other changes will be conducted in the refactoring and below code snippet will also reflect them. So, in the new world, it will look like this:
editor.document.registerPostFixer( writer => {
const changes = editor.document.differ.getChanges();
for ( const entry of changes ) {
if ( entry.type == 'insert' && entry.name == '$text' ) {
// Use `writer` to do your logic here.
// `entry` also contains `length` and `position` properties.
}
}
} );
In terms of code, it might be a bit more of it than in the first snippet, but:
The writer
is an object that will be used to do changes on the model (instead of Document#batch
API). It will have methods like insertText()
, insertElement()
, remove()
, etc.
You can check model.Differ
API and tests already as they are already available on master
branch. (The internal code will change, but API will stay as it is.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With