YUI 2.8: Rich Text Editor

Exclusive offer: get 50% off this eBook here
YUI 2.8: Learning the Library

YUI 2.8: Learning the Library — Save 50%

Develop your next-generation web applications with the YUI JavaScript development library

$26.99    $13.50
by Dan Wellman Daniel Barreiro | July 2010 | AJAX Open Source Web Development

In this article by Daniel Barreiro and Dan Wellman, authors of the book YUI 2.8: Learning the Library, we see the Rich Text Editor, which allows our users to create enhanced text as one of the most popular forms of user interaction over the Internet is user-created content.

(For more resources on YUI, see here.)

Long gone are the days when we struggled to highlight a word in an e-mail message for lack of underlining or boldfacing. The rich graphic environment that the web provides has extended to anything we do on it; plain text is no longer fashionable.

YUI includes a Rich Text Editor (RTE) component in two varieties, the basic YA-HOO.widget.SimpleEditor and the full YAHOO.widget.Editor. Both editors are very simple to include in a web page and they enable our visitors to enter richly formatted documents which we can easily read and use in our applications. Beyond that, the RTE is highly customizable and allows us to tailor the editor we show the user in any way we want.

In this article we’ll see:

  • What each of the two editors offers
  • How to create either of them
  • Ways to retrieve the text entered
  • How to add toolbar commands

 

The Two Editors

Nothing comes for free, features take bandwidth so the RTE component has two versions, SimpleEditor which provides the basic editing functionality and Editor which is a subclass of SimpleEditor and adds several features at a cost of close to 40% more size plus several more dependencies which we might have already loaded and might not add to the total.

A look at their toolbars can help us to see the differences:

YUI 2.8: Rich Text Editor

The above is the standard toolbar of SimpleEditor. The toolbar allows selection of fonts, sizes and styles, select the color both for the text and the background, create lists and insert links and pictures.

YUI 2.8: Rich Text Editor

The full editor adds to the top toolbar sub and superscript, remove formatting, show source, undo and redo and to the bottom toolbar, text alignment, &ltHn> paragraph styles and indenting commands. The full editor requires, beyond the common dependencies for both, Button and Menu so that the regular HTML &ltselect> boxes can be replaced by a fancier one:

YUI 2.8: Rich Text Editor

Finally, while in the SimpleEditor, when we insert an image or a link, RTE will simply call window.prompt() to show a standard input box asking for the URL for the image or the link destination, the full editor can show a more elaborate dialog box such as the following for the Insert Image command:

YUI 2.8: Rich Text Editor

A simple e-mail editor

It is high time we did some coding, however I hope nobody gets frustrated at how little we’ll do because, even though the RTE is quite a complex component and does wonderful things, there is amazingly little we have to do to get one up and running. This is what our page will look like:

YUI 2.8: Rich Text Editor

This is the HTML for the example:

<form method="get" action="#" id="form1">
<div class="fieldset"><label for="to">To:</label>
<input type="text" name="to" id="to"/></div>
<div class="fieldset"><label for="from">From:</label>
<input type="text" name="from" id="from" value="me" /></div>
<div class="fieldset"><label for="subject">Subject:</label>
<input type="text" name="subject" id="subject"/></div>
<textarea id="msgBody" name="msgBody" rows="20" cols="75">
Lorem ipsum dolor sit amet, and so on
</textarea>
<input type="submit" value=" Send Message " />
</form>

This simple code, assisted by a little CSS would produce something pretty much like the image above, except for the editing toolbar. This is by design, RTE uses Progressive Enhancement to turn the &lttextarea> into a fully featured editing window so, if you don’t have JavaScript enabled, you’ll still be able to get your text edited, though it will be plain text.

The form should have its method set to “post”, since the body of the message might be quite long and exceed the browser limit for a “get” request, but using “get” in this demo will allow us to see in the location bar of the browser what would actually get transmitted to the server.

Our page will require the following dependencies: yahoo-dom-event.js, element-min.js and simpleeditor-min.js along its CSS file, simpleeditor.css. In a <script> tag right before the closing </body> we will have:

YAHOO.util.Event.onDOMReady(function () {
var myEditor = new YAHOO.widget.SimpleEditor('msgBody', {
height: '300px',
width: '740px',
handleSubmit: true
});
myEditor.get('toolbar').titlebar = false;
myEditor.render();
});

This is all the code we need turn that &lttextarea> into an RTE; we simply create an instance of SimpleEditor giving the id of the &lttextarea> and a series of options. In this case we set the size of the editor and tell it that it should take care of submitting the data on the RTE along the rest of the form. What the RTE does when this option is true is to set a listener for the form submission and dump the contents of the editor window back into the &lttextarea> so it gets submitted along the rest of the form.

The RTE normally shows a title bar over the toolbar; we don't want this in our application and we eliminate it simply by setting the titlebar property in the toolbar configuration attribute to false. Alternatively, we could have set it to any HTML string we wanted shown on that area

Finally, we simply render the editor. That is all we need to do; the RTE will take care of all editing chores and when the form is about to be submitted, it will take care of sending its data along with the rest.

Filtering the data

The RTE will not send the data unfiltered, it will process the HTML in its editing area to make sure it is clean, safe, and compliant. Why would we expect our data to contain anything invalid? If all text was written from within the RTE, there would be no problem at all as the RTE won't generate anything wrong, but that is not always the case. Plenty of text will be cut from somewhere else and pasted into the RTE, and that text brings with it plenty of existing markup.

To clean up the text, the RTE will consider the idiosyncrasies of a few user agents and the settings of a couple of configuration attributes.

The filterWord configuration attribute will make sure that the extra markup introduced by text pasted into the editor from MS Word does not get through.

The markup configuration attribute has four possible settings:

  • semantic: This is the default setting; it will favor semantic tags in contrast to styling tags, for example, it will change &ltb> into &ltstrong>, &ltti&g into &ltem> and &ltfont> into &ltspan style="font: ….
  • css: It will favor CSS style attributes, for example, changing &ltb> into &ltspan style="font-weight:bold">.
  • default: It does the minimum amount of changes required for safety and compliance.
  • xhtml: Among other changes, it makes sure all tags are closed such as &ltbr />, &ltimg />, and &ltinput />.

 

The default setting, which is not the default, offers the least filtering that will be done in all cases; it will make sure tags have their matching closing tags, extra whitespace is stripped off, and the tags and attributes are in lower case. It will also drop several tags that don't fit in an HTML fragment, such as &lthtml> or &ltbody>, that are unsafe, such as &ltscript> or &ltiframe>, or would involve actions, such as &ltform> or form input elements. The list of invalid tags is stored in property .invalidHTML and can be freely changed.

More validation

We can further validate what the RTE sends in the form; instead of letting the RTE handle the data submission automatically, we can handle it ourselves by simply changing the previous code to this:

YAHOO.util.Event.onDOMReady(function () {
var Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event;

var myEditor = new YAHOO.widget.SimpleEditor('msgBody', {
height: '300px',
width: '740px'
});
myEditor.get('toolbar').titlebar = false;
myEditor.render();

Event.on('form1', 'submit', function (ev) {
var html = myEditor.getEditorHTML();
html = myEditor.cleanHTML(html);
if (html.search(/<strong/gi) > -1) {
alert("Don't shout at me!");
Event.stopEvent(ev);
}
this.msgBody.innerHTML = html;
});
});

We have dropped the handleSubmit configuration attribute when creating the SimpleEditor instance as we want to handle it ourselves.

We listen to the submit event for the form and in the listener we read the actual rich text from the RTE via .getEditorHTML(). We may or may not want to clean it; in this example, we do so by calling .cleanHTML(). In fact, if we call .cleanHTML() with no arguments we will get the cleaned-up rich text; we don't need to call .getEditorHTML() first. Then we can do any validation that we want on that string and any of the other values. We use the Event utility .stopEvent() method to prevent the form from submitting if an error is found, but if everything checks fine, we save the HTML we recovered from the RTE into the &lttextarea>, just as if we had the handleSubmit configuration attribute set, except that now we actually control what goes there.

In the case of text in boldface, it would seem easy to filter it out by simply adding this line:

myEditor.invalidHTML.strong = true;

However, this erases the tag and all the content in between, probably not what we wanted. Likewise, we could have set .invalidHTML.em to true to drop italics, but other elements are not so easy. RTE replaces a &ltu> (long deprecated) by &ltspan style="text-decoration:underline;"> which is impossible to drop in this way. Besides, these replacements depend on the setting of the markup configuration attribute.

This example has also served us to see how data can be read from the RTE and cleaned if desired. Data can also be sent to the RTE by using .setEditorHTML(), but not before the editorContentLoaded event is fired, as the RTE would not be ready to receive it.

In the example, we wanted to manipulate the editor contents, so we read it and saved it back into the &lttextarea> in separate steps; otherwise, we could have used the .saveHTML() method to send the data back to the &lttextarea> directly. In fact, this is what the RTE itself does when we set handleSubmit to true.

YUI 2.8: Learning the Library Develop your next-generation web applications with the YUI JavaScript development library
Published: July 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

(For more resources on YUI, see here.)

Improving the looks

Moving from the SimpleEditor to the full editor requires very few changes. We need to add a few dependencies—Container Core, Button, and Menu, and their corresponding CSS files, and load editor-min.js instead of simpleeditor-min.js. We would then create an instance of YAHOO.widget.Editor instead of YAHOO. widget.SimpleEditor as we did so far. As usual, the most trusted source for this is the Dependency Configurator, which will probably produce a shorter list of dependencies by aggregating or combining whatever it can.

However, we might want less than the full editor but still want to improve the looks. We can do so by going half way—load Container Core, Button, and Menu as for the full editor, but still load simpleeditor-min.js and create an instance of SimpleEditor (we wouldn't be able to create an instance of Editor since we haven't loaded it). Then, we need to add the following right before rendering the editor:

myEditor.get('toolbar').buttonType = 'advanced';

This option would bring the fancy dropdown with the font names each in its own font; this is nothing much. The Insert Image or Insert Link options would still show a plain window.prompt() dialog box.

Changing the toolbar

Customization is one of the great advantages of YUI's RTE in contrast to other editors. In our previous example we refused to accept text in boldface. It doesn't make much sense for us to have the big boldface button in the toolbar if we are not going to accept its outcome. Remember, we have no control over what gets pasted from other sources so removing the button, or any other command, cannot prevent text in boldface appearing in the text we retrieve from the RTE; we still do need to filter it afterwards.

We have already changed a couple of things in the toolbar, we have dropped the title bar and we changed the buttons to the advanced version. Going from the SimpleEditor to the full editor also changes the toolbar significantly though it is not just a matter of changing the looks; Editor also adds the code to execute the commands those buttons represent.

The template for the toolbar is represented by an object that describes its features. As we've seen so far, it can be reached by using the .get() method inherited from Element. We can change the object that describes the toolbar before we call .render() to show the editor.

We can also change the toolbar once it has been rendered. As the toolbar can take a while to set up, it has to be done after the toolbarLoaded event fires. Then, the .toolbar property of the Editor can be used, not the toolbar configuration attribute, which is only the template for the toolbar before it actually gets rendered.

For example, these two are equivalent:

// change the template before it gets rendered
myEditor.get('toolbar').titlebar = false;

// change the actual toolbar once it has been rendered
myEditor.on('toolbarLoaded', function() {
this.toolbar.set('titlebar', false);
});

The toolbar configuration attribute (myEditor.get('toolbar')) corresponds to the template. Later, once rendered, this will become the .toolbar property (myEditor.toolbar). What in the template are plain properties (.titlebar), in the actual toolbar become configuration attributes (.get('titlebar')). This is a bit confusing.

On a first level, the toolbar template has these properties, which then turn into configuration attributes:

  • .titlebar: Contains either the text to be shown on the title bar, or false to hide it.
  • .collapse: Whether it should show a minimize icon on the top right corner of the title bar to collapse the toolbar. It requires .titlebar to be true.
  • .buttonType: Can be advanced or basic to change the type of element to be used for the buttons.
  • .grouplabels: Whether it should show the labels for the button groups.
  • .disabled: Disables the toolbar.
  • .draggable: Whether the toolbar can be dragged. It simply sets the toolbar as a draggable element; it is up to the developer to set suitable drop targets and actions when they are reached. As a curiosity, the editable area is a valid drop target, which will accept dragged elements, such as the text on the title bar itself.
  • .buttons: The actual collection of buttons on the bar.

 

The .buttons collection is the most interesting of all because it describes all the buttons and separators in the toolbar. It is easy to change them in the template; for example, to drop the boldface button, we can do:

myEditor.get('toolbar').buttons[2].buttons.splice(0,1);

It is basically a matter of looking at a dump of the toolbar template and using plain JavaScript to manipulate the object literal. Likewise, to add a button to the end, we could do this:

toolbar.buttons.push({type:'separator'});

toolbar.buttons.push({
group:'mine',
label:'my button',
buttons:[{
type: "push",
label: "Bold CTRL + SHIFT + B",
value: "bold"
}]
});

This makes a boldface button appear at the end, in a separate button group called "mine" and labeled my button. This new button just added will act just like our original boldface button, though it is in a different location.

YUI 2.8: Rich Text Editor

Once the RTE is rendered we can add or remove buttons and groups by using the methods provided by the Toolbar object:

  • .addButton(config, after): Adds the button described in config, after the given button in the same group
  • .addButtonGroup(config): Adds a button group
  • .addButtonToGroup(config, group, after): Adds the button described in config, in the named group, after the referenced button
  • .addSeparator(): Adds a small gap in between groups or buttons
  • .destroyButton(id): Deletes the button
  • .getButtons() and .getButtonByXxx(): Methods to find all buttons or specific buttons by different search criteria

 

The config arguments in all cases are object literals like the one shown in the example earlier. For groups, .group and .label properties are important; for buttons we have:

  • type: Any one of push, menu, color, select, or spin.
  • value: A unique string value assigned to the button.
  • title: A string to be assigned to the title HTML attribute (tooltip).
  • label: A text to show in the button (unless replaced by an image). It will be used as the title if none is given.
  • menu: The same as for a Button of the menu type.
  • disabled: Sets the disabled, grayed-out state.

 

Adding a new toolbar button

So far we have played with moving around an existing button, which already has a predefined action associated with it. Now, we'll see how to add new functionality to the toolbar. We'll add a timestamping function, a button that will add a date to the text.

First, we have to add the button, as we've already seen:

var toolbar = myEditor.get('toolbar');

toolbar.buttons.push({type:'separator'});

toolbar.buttons.push({
group:'added',
label:'Timestamping',
buttons:[{
type: "push",
label: "Timestamp",
value: "timestamp"
}]
});

To add the button we first grab a reference to the template and then add first a separator, then a group containing a simple push button.

The button just added will have no image on the button face. To add one, we make use of the styles the RTE will generate for it. In the head of the page, we add:

<style type="text/css">
.yui-skin-sam .yui-toolbar-container .yui-toolbar-timestamp
span.yui-toolbar-icon {
background: transparent url( icons/cal.png )
no-repeat center center;
left: 5px;
}
</style>

The relevant part of this CSS style is the third selector, .yui-toolbar-timestamp. The RTE will use the name given to the button as part of its class name so, for a button named xxxx, it will give it a class name of yui-toolbar-xxxx, which lets us associate the button with an image. We set this image as the non-repeating, centered background. Depending on the dimensions of the image we might need to align it as we've done here with the left setting.

The RTE will assign the button other class names to signal other possible states of the button like hovering, checked, or disabled, which we may use to further tailor the looks of our buttons.

So far, our new button looks nice but doesn't do much at all. We need to associate an action with it and to do that we can use what the RTE calls dynamic events. They are not listed in the API docs because they are created dynamically. For every button named xxxx, the toolbar will have an xxxxClick event created dynamically. So, all we need to do is listen for that event, but not before we are sure the toolbar has been loaded and is available:

myEditor.on('toolbarLoaded', function () {
this.toolbar.on('timestampClick', function () {
myEditor.execCommand('inserthtml',
' [' + (new Date()) + '] ');
});
});

RTE's .execCommand() method allows us to manipulate the text in the editing window. It will call the numerous .cmd_xxxx() methods listed in the API docs, but it will do some pre and post processing such as firing events, pushing the change into the undo stack, setting the .editorDirty property (which tells us that the text has been changed) and starting the node change process; in other words, don't bypass the .execCommand() method.

We could fill pages and pages with the list of the commands available but they are quite self-explanatory, for example, bold, insertunorderedlist, or createlink, and they simply do what the corresponding toolbar button does. Do remember to look in the API docs page for both SimpleEditor and Editor since the later adds a good number of commands of its own.

The most relevant effect of the node change process is the firing of the beforeNodeChange and afterNodeChange events, the first of which will cancel the change if false is returned from its listener. For example, the boldface button could listen to the afterNodeChange event so that it changes its state to active when the cursor is over text already in boldface. It doesn't do, so changing the state of the boldface button is hardwired into the code, which is faster, but that would be the way to do it for an add-on.

Adding CSS styles

It might look better if the timestamp is added in a different color from the rest of the text. Instead of simply having it enclosed in square brackets as in the previous sample, we might want to insert the following HTML:

myEditor.execCommand('inserthtml',
'<span class="timestamp">' + (new Date()) + '<\/span>');

If we were to add the style definition for timestamp to our page, we would find it has no effect whatsoever on the editor. This is because the editing window is actually a separate document in an &ltiframe>. within our page. Being a separate document, it has its own stylesheet different from that of our page.

To add extra styles into the editing window, we can use the extracss. configuration attribute, like this:

var myEditor = new YAHOO.widget.Editor('body', {
height: '300px',
width: '740px',
extracss: '.timestamp {margin: 0 1em;background-color:cyan;}'
});

This is not to be confused with the CSS styles required for the buttons added to the toolbar. The toolbar does reside in the same page as the rest of the application, it is just the editing area of the RTE that is actually a separate &ltiframe> and needs CSS styles set as previously shown.

Changing the available fonts

The list of available fonts in the RTE font names dropdown is quite limited. From the toolbar dump in the examples, it becomes obvious why the names of the fonts are hardcoded. Many people expect that the font names dropdown will show all the fonts installed on each machine. This is not possible; the browser environment forbids any code in it to access such information in the host machine. Besides, there is hardly any certainty that such fonts would be available on other machines.

However, if in an intranet scenario an application needs the RTE to show a particular font that is certain to be available on all the machines, then it can be easily added to the list of fonts in the toolbar template, like this:

myEditor.get('toolbar').buttons[0].buttons[0].menu.push(
{text:'GreekC'});

Summary

The Rich Text Editor is an easy-to-use editor that can be added to any project that might need user input. It offers a couple of versions so you can decide where on the cost (bandwidth)/benefit (features) curve you want to be.

The RTE is also highly customizable and we've seen how to add tools to its toolbar and their associated actions, and how to customize the looks of the editor window.

Its rich set of events plus the dynamic events let you add code to capture everything that happens in the RTE and respond accordingly.

The flexibility of the RTE is reflected in the home page for the component at the YUI site, where the list of examples shows the many ways in which it can be improved or tailored for specific needs.


Further resources on this subject:

YUI 2.8: Learning the Library Develop your next-generation web applications with the YUI JavaScript development library
Published: July 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Dan Wellman

Dan Wellman is an author and frontend engineer living on the South Coast of the UK and working in London. By day he works for Skype and has a blast writing application-grade JavaScript. By night he writes books and tutorials focused mainly on frontend web development. He is also a staff writer for the Tuts+ arm of the Envato network, and occasionally writes for .Net magazine. He's the proud father of four amazing children, and the grateful husband of a wonderful wife.

Daniel Barreiro

Daniel Barreiro, screen-name Satyam, has been a very active member in the YUI forums, providing support, writing articles in the YUI Blog, and maintaining examples and further articles in his own site. A YUI Contributor, he is one of the few external developers to have a component included in the YUI 2 library.

Books From Packt

jQuery 1.4 Reference Guide
jQuery 1.4 Reference Guide

Oracle Application Express 3.2 - The Essentials and More
Oracle Application Express 3.2 - The Essentials and More

Yahoo User Interface 2.X Cookbook: RAW
Yahoo User Interface 2.X Cookbook: RAW

Tcl 8.5 Network Programming
Tcl 8.5 Network Programming

Moodle 1.9 for Teaching Special Education Children (5-10): Beginner's Guide
Moodle 1.9 for Teaching Special Education Children (5-10): Beginner's Guide

Liferay Portal 6 Enterprise Intranets
Liferay Portal 6 Enterprise Intranets

Grok 1.0 Web Development
Grok 1.0 Web Development

Firebug 1.5: Editing, Debugging, and Monitoring Web Pages
Firebug 1.5: Editing, Debugging, and Monitoring Web Pages

Your rating: None Average: 2.7 (3 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
c
S
r
F
t
p
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software