The state of browser-based HTML editors

Or, are HTML editors designed by idiots?

For some time now I have been searching for a good embeddable, JavaScript-based HTML editor — an editor that I can use to let users write their posts and comments in the browser using “what-you-see-is-what-you-get” formatting: bold and italic type, bulleted lists, images and so on.

What do I expect of an HTML editor? Well, it has to be open-source, compatible with the major browsers (IE 6 and 7, Safari, Firefox and Opera) on their respective operating systems, and it has to be modular in order to embed it into the existing application framework and design. Those goals are not particularly ambitious in this day and age — so why does such a product not exist?

The main problem is, surprisingly, not browser compatibility or openness. I have evaluated several projects in depth, including TinyMCE and NicEdit, and looked at several others, including FCKeditor, DevEdit and KTML, and — their propensity to crash or produce loopy formatting aside — all of them provide the essentials, and some of them go pretty far with advanced editing features such as table drawing and floating objects. The problem is the lack of modularity and the extra baggage that comes with the “one size fits all” approach.

On looking at a package such as TinyMCE, it is immediately apparent that these projects have been designed to be swallowed whole — that is, they are monolithic frameworks and not simply toolkits. TinyMCE, FCKeditor and NicEdit, to cite the ones I am most familiar with, all provide subsystems for plugins, toolbars, localization, skinning, themes, dialogs, widgets and so on — that’s a lot of baggage when you just want an editor widget. But it’s the fundamental design of this conglomeration of components that’s screwed up.

To explain, let me step back for a second and illustrate how I think an editor should work. To start with, let’s review the traditional mode-view-controller (MVC) design pattern:

For an editor, this translates to the following:

Why MVC? Such a design is needed first of all to separate concerns. The view interacts with the user; the controller controls the UI and translates user interactions into model modifications; the model tells listeners when it has changed. Thus each part of the system is isolated from unnecessary concerns, and is prevented from meddling with the stuff that isn’t any of its business.

A side effect of such clear division is that isolating the interactions means there can be any number of players in the game, all of them unaware of each other. For example, a well-designed MVC setup can let you attach multiple views to the same data model. And by view, I mean anything that has a visual representation: A live word count widget, for instance, is just an editor view that doesn’t edit anything, but merely shows a different representation of the same underlying data.

(A digression: MVC actually breaks down in the browser if you rely on the DOM as your data model, because the DOM is implicitly connected to the view. Thus you cannot have, say, a “raw HTML tags” view of the document alongside a full-fledged WYSIWYG editor. Unfortunately, this is probably unavoidable because the only practicable way of implementing HTML editing in a browser today is to use the browser’s own editing support. Maybe you could store an internal DOM document and synchronize the trees, but that sounds a bit icky. That said, I haven’t actually tried.)

To support the complete MVC cycle, the editor (what I’ve called the “editor object” in the diagram above, to distinguish from the editor component as a whole) must be programmable and internally consistent with the DOM. To manipulate the document programmatically from JavaScript — for example, to set a subset of the document to a specific font — you grab a piece of the DOM, manipulate it and let the DOM event machinery trickle the changes up to the controller, which then updates the view. That’s basic MVC.

Since the editor object also represents state other than HTML data — caret position, scroll position and selection being three — the editor object also has to be scriptable, so that you can manipulate and observe that state. And to simplify certain operations, it should provide enough of a veneer to make common editor operations easily decomposable: “select whole word”, “delete word, “move three words to the right”, “set as bold,” that kind of thing.

MVC, then, helps you build the required ecosystem of controllers and views — toolbars, word counters, spell checkers, colour pickers and the like — in which each actually extends the MVC pattern:

Take toolbars, for example: Traditionally a strip of icons above the editor, some icons representing actions (indent text, copy, paste), a few representing both action and state (a “B” icon both lets you turn text bold, and is also highlighted to show when the current selection is set in boldface), and icons are either enabled or disabled depending on whether their functionality is available in the current context.

To implement a toolbar, then, you create a toolbar controller and a toolbar view. The controller tells the editor that it wants to listen for notifications; these notifications are used to update the toolbar buttons. If the current text is bold, highlight the “B” icon; if there’s something in the clipboard, enable the “paste” button. Similarly, clicking a toolbar button sends a notification back to the controller, which then sends a message to the editor object.

All right. This is all exceedingly basic stuff, so why I am lecturing you about it and calling people names?

Because nobody has so far bothered to implement an MVC-based HTML editor.

At least, nobody as far as I can see. Among the projects I have looked at, NicEdit comes closest to resembling MVC, if you really squint and maybe turn the brightness of your screen way down. You can actually download cut-down versions of the code that strip away extraneous functionality like dialogs. Unfortunately, it is still a jumbled mess that commingles its concerns. For example, the core editor class knows about plugins, panels and buttons, and interacts with each of these directly instead of relying on notifications or well-defined APIs.

TinyMCE, for its part, doesn’t care at all — for instance, there is no way, that I can find, to create two editors on the same page that have different “themes”. It’s so screwed up that it thinks a theme should include localization, toolbars, graphics, dialogs, skins and — most insane of all — code. Each theme is like an application in itself — there’s a “simple” theme with just a few toolbar buttons, but to get a colour picker toolbar button you must use the “advanced” theme, which has tons of other features that might not interest you. This means that to add anything into their system in practice, you have to extend (or worse, copy) the advanced theme. Words like “decoupling” are not in these guys’ dictionary.

These editors are so focused on being easy to set up, that when you want to tinker — remove stuff, add stuff, taking things apart and see how they can fit together a little differently — you are bound to meet the wall. WordPress users will have noticed that even WordPress has decided to circumvent TinyMCE, rather than customize it, for their HTML editor toolbar. Their widgets are not intergrated into the main toolbar, and they don’t attempt to use TinyMCE’s crummy dialog UI either; instead their own buttons are relegated to a separate “Add media” toolbar:

So if these frameworks are so awful, how should a good framework look — in cold, hard code? It’s actually not difficult:

var editor = new PerfectEditor();
editor.attach(document.getElementById("comment_textarea"));
editor.document.observeChanges(function() {
  var boldButton = document.getElementById("bold_button");
  if (editor.getSelection().getStyle("font-weight") == "bold") {
    boldButton.addClassName("active");
  } else {
    boldButton.removeClassName("active");
  }
}); 

Certainly some people will pooh-pooh such an approach and call it unnecessarily complex — “TinyMCE does this stuff for me automatically”. Well, sure — but do you really have the option of not doing it automatically? That’s the crux of my argument, that the monolithic approach eliminates choice and prevents customization.

But the above example just gives you the raw metal, which can then be glossed over with helper classes (or plugins, if you will):

var editor = new PerfectEditor();
editor.attach(document.getElementById("comment_textarea"));
var toolbar = new PerfectToolbar();
toolbar.setButtonSet(PerfectToolbar.ButtonSets.BASIC);
toolbar.createForEditor(editor, PerfectToolbar.Position.ABOVE); 

That’s nicely separated and layered. But let’s whittle away the boilerplate and move everything into a one-line helper to satisfy the “up and running in 15 minutes” PHP-in-21-days guys:

PerfectEditor.setup("comment_textarea", {
  toolbar: PerfectToolbar.ButtonSets.BASIC,
  toolbarPosition: PerfectToolbar.Position.ABOVE}); 

There, that’s pretty concise. And you still have the option of decoupling everything completely if, say, you want to scratch that standard toolbar and go crazy with your own design.

To demonstrate how these projects fail in practice, consider what would take to implement an “insert image” button. Let’s imagine, blatantly ignoring reality, that we are Flickr, and we want the button to bring up a nice sidebar panel with a selection of your photos, a search field, and perhaps a way to upload new photos. All of this needs to build on Flickr’s existing internal templating system, CSS and so on. Flickr also already has a system for drawing buttons, so we want a Flickr-toolbar, not a “TinyMCE-style” toolbar.

With NicEdit, TinyMCE and FCKeditor, you would create a plugin or theme or something. But in a perfect world, the JavaScript code would look something like this:

var photoPicker = new PhotoPicker();
photoPicker.setNSID("123456");
var toolbar = new Toolbar();
toolbar.createForEditor(editor, PerfectToolbar.Position.ABOVE);
toolbar.addButton({
  label: "Insert Photo",
  icon: "/images/insert_photo.png"}, function() {
    photoPicker.show();
  }
});

I will let you imagine what the actual PhotoPicker class will look like, except for the code that inserts the image:

insertButton.observe("click", function() {
  var imageElement = document.createElement("img");
  imageElement.src = this.photoUrl;
  imageElement.observe("dblclick", this.editPhotoSettings.bind(this));
  editor.document.insertElement(editor.getSelection(), imageElement);
}.bind(this)); 

I think that looks swell.

Advertisements

One Comment

  1. Joshua Peek
    Posted October 16, 2008 at 10:05 pm | Permalink

    You may want to checkout http://github.com/josh/wysihat/


%d bloggers like this: