Welcome!


You've found the demo page for Carota, a rich text editor implemented entirely in JavaScript that uses HTML5 Canvas for rendering. Most in-browser editors use a built-in feature called contentEditable to do most of the hard work, but this has some limitations and drawbacks that are just too much for more sensitive people, like me, to bear, so I decided to start from scratch. (Anyway, it's fun to write your own editor!)


The source code is released under the very permissive MIT license, so you can do pretty much anything you want with it.


At runtime Carota has no external dependencies. You just pull in the carota-min.js file using the SCRIPT tag and away you go. Or else get node and use:


npm install carota


to get the full source, including this demo site. By the way, Carota itself is displaying all this text, meaning that you can play with the editor right now! Try Ctrl+A and then Backspace to clear this document and see how the JSON view on the right changes as you make further edits. Press Ctrl+Z to undo your changes.


Click the links below for more information...

Why might you need this?


For many people's needs, the contentEditable attribute in modern browsers is quite adequate. But if you need more control over rich text editing it is sadly a dead end.


The word processor in Google Docs does not use contentEditable. Nor does Apple's Pages in iCloud. Instead they have their own JavaScript-based rich text rendering and editing code. Sadly they are not open source licensed!


In some ways, contentEditable is too powerful. It allows your users to edit every possible feature of HTML implemented by the browser. I often find that I only want to support a simple form of rich text that is totally under my control. And the API for representing a range within the text is quite complex because it has to deal with the tree-like nature of the DOM. I wish there was a simple way to work in pure character positions (i.e. mere integers).


In other ways, contentEditable is frustratingly inadequate. There are built-in commands for applying formatting changes to the current selection, but they suck, especially setting the font size. There are bugs and quirks that vary between browsers. Taming contentEditable can become a full-time job in itself.


Bear in mind that Carota does not implement all the same features as HTML's contentEditable. If you're okay with the limitations of contentEditable then by all means you should stick with it.

How does it work?


Several representations of text play a part in Carota.


JSON is the cornerstone of persistence in JavaScript. It is used here to represent rich text, which is just an array of objects. Each object is called a run, and has a property called text and various other optional properties to specify the formatting for the text in the run. The text can include inline objects of your own invention, which you can tell Carota how to represent. (That's how the smiley icons work in this demo - try inserting one!)


Characters are objects representing the characters in the text as a single continuous stream of objects. This is an abstraction over the JSON format to make it easy to split the text into words. An inline object acts like a single character.


Words are objects representing the words in the text, the output of the word-breaking process. A word has two parts: the non-space characters, and the trailing space characters. Either part may be empty, but not both. (A new line is always emitted as a separate word.) Of course, a word can contain regions of the text with different formatting. All edits to the document involve modifying the list of words. Words store their own dimensions (width, ascent and descent).


Static representation is the result of word-wrapping. Every time the word list is modified, wrapping must be performed, but it's pretty fast because we already know the dimensions of each word. The static representation is a list of Line objects, and each line contains a list of PositionedWord objects, each of which contains a list of PositionedCharacter objects. These objects form a uniform hierarchy of nodes, and at the root is the Document. Everything is already positioned, so drawing to the canvas is fast, and implementing the editor is pretty easy. Also any custom inline objects are converted into handlers that know how to render them.


Those are the main core representations involved. In addition a subset of HTML can be loaded into the editor via a parser that converts it into the native JSON format.

How to use it


First, the bad news: if you need to support IE8 and earlier, then Carota is not for you. Right now it requires Canvas. It may be extended in the future to support other ways of rendering.


This demo page has been deliberately set up to make it easy to learn from, the old fashioned way: using the View source command. Everything is in a single index.html file.


It begins with some basic CSS styling, mostly for positioning. Then there is the HTML, most of which is for the "toolbar". Carota does not have a built-in toolbar, but it's very easy to wire up your own controls to it. This page does it in about 30 lines of code.


The text for these pages is held in some hidden DIVs, which get parsed into the native JSON format and then loaded into the editor.


Finally there is a PRE element that displays the JSON saved from the editor, every time you make an edit.


The parts of the code that interact with Carota (there isn't much) works by calling carota.editor.create to cause the editor to be created. Then methods are called on the resulting object to load this text, to subscribe to events (selectionChanged, contentChanged), to performUndo (and discover if we canUndo), to query or modify the selectedRange and to insert inline elements (the smiley button).


There are also a handful of calls to functions in carota.dom which is just a very minimal helper library for conveniently wiring up events, etc. In your own code you'll probably want to use a more full-featured alternative like jQuery, etc. Carota deliberately avoids depending on any such general DOM-manipulation library so it can integrate with anything.