WYSIWYG with contenteditable

The long and hard way to build a WYSIWYG!

Giuseppe Aiello
Fleka Developers
Published in
6 min readOct 15, 2016

--

First of all, to understand my pain, read this article “Why contenteditable is terrible” by Nick Santos, Software Engineer @Medium.

And, if that wouldn’t be enough, you can have a clearer vision about contenteditable reading “ContentEditable — The Good, the Bad and the Ugly” by Piotrek Koszuliński, CKEditor lead developer.

Once you have read the articles above, you could then ask !WHY! Tell me only WHY!? Well, the because is that the alternatives are not working well for the projects we develop for our Client @fleka. We tried several WYSIWYG editors, among all CKEditor and Redactor, but when these tools go in the hands of the final users (aka our Clients), the problems start popping out; they - of course - are not able to manage the HTML content, and they - of course - copy and paste text from any kind of source (ie:Word) carrying with it a lot of strange tags and HTML elements.

Don’t take me wrong, CKEditor is a very powerful, open sourced, community driven project and you can really do a LOT of things with it; I was simply trying to find a more ‘elegant’ and easy solution for our AngularJS projects, building an AngularJS component that could have been easy implemented in all our web apps. CKEditor proved to be hard to include in our minification process (powered by Grunt) and, not to be forgotten, in fleka we always try to aim for the best possible UX: using CKEditor it’s not easy at all for us to customize it to perfectly fit inside our custom design.

Redactor, on the other way, besides its price, it proved to be useful for our projects, easy to customize and integrate with custom code, and it fits pretty well in our designs, but it creates really a lot of automatic and useless (at least for me) HTML elements that, in my opinion, tends to make the work of the content editors harder because it’s very easy to paste text in the wrong elements, without you even noticing it if you don’t check the HTML code/view available inside the Redactor editor.

So, let’s make this very clear, you DON’T have to create your personal WYSIWYG editor, most of all if you don’t have the possibility to dedicate months to it: contenteditable is not a standard and it creates several different problems in different browsers, so it’s a path that you don’t want to take, unless you know what you’re doing but, also in that case, think about it twice please! ;)

Discovering contenteditable

When I wanted to dig more into this topic, I started to take a look to the HTML code of the Medium editor, and I did the same thing with the Redactor’s one. And, after the first days of meeting contenteditable=”true”, I started to realize that things were not so straight forward as they looked. By default, the browsers start to add <span> and acting in ways that are creating a real mess and confusion inside the HTML code, so there is a lot of work to be done “behind the curtains” to correct all these default behaviours.

Here some examples with Redactor:

Imperavi Redactor ‘make it bold’ example

Here you can see that Redactor is leaving the 2 <strong> nodes completely separated, and it’s not trying to convert the word in one single node. I personally don’t like this default behavior at all.

Now let’s see how CKEditor handles the same situation:

CKEditor ‘make it bold’ example

CKEditor is definitely making a better job, removing one of the 2 <strong> elements and appending the second node inside the first one. But, still, it leaves that word as 2 separated text nodes; I think this is way better in terms of HTML than the Redactor’s result, but again I would like to have only one element with only one text node for this specific situation.

Finally, after the first days of development, here is the example with our custom solution:

Fleka ‘make it bold’ example

The editor is smart enough to remove the second <b> element, append the text node into the first one and “normalize” it to a single element with a single text node inside. But we want to use <strong>, and I will not annoy you regarding the reasons of using it in place of <b> because you can easily perform a Google search about this topic.

Custom DOM editor

When you set an HTML element, let’s say a <div>, as contenteditable, you expect that several common actions work right away, but the sad truth is that you instead need to fix a lot of these behaviours, to be able to produce a good semantic out of the inserted text when paste, formatting and new lines are created in the contenteditable itself. That’s why I started creating a custom DOM editor that has to, among other things:
- take care of converting the elements to the correct ones (without adding style and span here and there);
- intercept the keyboard events to correctly run custom methods;
- take care of undo/redo actions through a custom Undo Manager.

- Semantic code Manager

By default the contenteditable element will use <b> and <i> because it’s not meant to produce semantic code and that’s why we need to take care ourselves about converting the elements to <strong> and <i> (and viceversa, meaning that it has to be allowed to remove the formatting on a text that is already bolded for example).
After that, a good content editor has to take care at least of the lists <ul>( ordered and unordered of course), pay attention to not allow weird nested elements, formatting the allowed nested elements in a specific way and so on…

- Keyboard events Manager

Other events that have to be handled manually are the shortcuts of the keyboard, to convert the selected text bold for example, or again to prevent the default Undo and run the custom Undo Manager. This is also important to intercept the pasting action and clear the text when this is copied from an external source (ie: Word) maintaining the basic formatting of the text (bold, italic, links, etc) that the editor has to support out-of-the-box.

- History (Undo/Redo) Manager

After taking care of semantic HTML, it came the moment when I realized that I needed a custom history (Undo/Redo) management. Hurray! This is because when you handle DOM nodes by yourself, you’re messing up the default history that the browser is building for you, meaning that when you undo one action it’s very possible that the browser will undo an appendChild() command but will not remove one node that you deleted using removeChild() in the same action…
A very good article about it is “Detect, Undo And Redo DOM Changes With Mutation Observer” by Addy Osmani, Engineer at Google, that suggests the use of the Mutation Observer.

Next Steps…

The next steps (that I’m going to try to cover in a future post) will be:
- layout: allow the possibility to float specific elements;
- image management: upload/store/retrieve images to use them inside the editor;

If you’re curious about this, drop me a line here.

--

--

CTO | 15+ years of experience | Cloud and Frontend enthusiastic | Flutter developer | I strive to foster a culture of innovation and continuous learning