Skip to content

Instantly share code, notes, and snippets.

@Reinmar
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Reinmar/9665f1630cd6287b4c2b to your computer and use it in GitHub Desktop.
Save Reinmar/9665f1630cd6287b4c2b to your computer and use it in GitHub Desktop.

Listening on DOM events in CKEditor

Based on http://dev.ckeditor.com/ticket/13019.

The background about native and CKEditor DOM events:

  1. Events bubbling (propagation). If I click on a link inside a paragraph which is inside editor, then the click event will be fired on that link first, then on the paragraph, then on its parent (e.g. <body> element) which may be the editor.editable() element itself. Then the event is fired on the <html> element and finally on the document. The elements tree looks a little bit different in inline editor, but the idea is the same - events bubble up the tree (yes, the root is at the top...).
  2. It is possible to listen to native DOM events using CKEditor API. Nearly every CKEDITOR.dom.* object (elements, editable, document, window) has the on() method. Underneath there's magic but the result is as follows - when a native DOM event is fired on a DOM object a corresponding CKEditor's event is fired on that object's wrapper. And as native event bubbles, a series of CKEditor events is fired on ascendant objects.
  3. It is possible to prevent native DOM events' default action (preventDefault()) and to stop their propagation (stopPropagation()). Once event's propagation is stopped on e.g. paragraph, the event won't be fired on the editable element or on the document.
  4. Canceling CKEditor's event (by using evt.cancel()) does both things - it prevents the native event's default action as well as stop event's propagation.
  5. Some native DOM events do not bubble, some bubble to specific objects only (the focus event for instance). But most bubble well.
  6. CKEditor listeners have priorities. A default one is 10. A listener with priority 1 will be executed before that with priority 10. When there are two listeners with the same priority the first added will be the first to be executed.

Now, there are hundreds listeners in CKEditor. Some are added on the inner or outer documents, some on editable, some on different elements themselves (although, very rarely on elements inside the editable). When one of the listeners handles the event and want to prevent the default action it usually cancels the event. As I wrote above - this also stops this event's propagation. It is very important, because it means that no other listener will be fired after this event is handled (handled events are canceled to assure that there's no conflict between listeners).

When talking about keydown events it is important to know that most listeners are added on the editable element with the default priority. The editor#key facade event is based on editable's keydown event with priority 10 as well. If one of editor#key listeners or any other editable#keydown listener handles the event, then there's a huge chance that your own listener will not be fired. Why? Because these default listeners are added early in CKEditor's code, so as I mentioned in point 6., those listeners will be executed before yours.

(Note: On different browsers there are different listeners added which handle different keys. For example, on Webkit and Blink, due to these browsers terrible bugs, backspace and delete keys are handled in many cases, while this wasn't necessary on other browsers.)

So how to listen to DOM events to be sure that my listeners will be executed? There's no perfect solution (ok, I'm not perfectly honest - there's events capturing, the reverse of bubbling, but CKEditor's API does not support it because it didn't work on old IEs and this is a whole story itself), but usually it's enough to listen on the down-most element (e.g. editable) with a high priority (e.g. 1).

editor.on( 'pluginsLoaded', function( event ){
	editor.on( 'contentDom', function() {
		var editable = editor.editable();

		editable.attachListener( editable, 'keydown', function( e ) {
			console.log( 'KEYDOWN EVENT' );
		}, null, null, 1 );
	} );
} );

So popular mistakes are:

  • listening on top-most objects like document to which events do not bubble when canceled earlier in the tree,
  • listening with the default priority.
@fredck
Copy link

fredck commented Mar 30, 2015

I think the title should be "Listening to DOM..."

You may try to break the document flow into smaller sections, so it is easier to read.

Anyway, cool explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment