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...
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.
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.
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.