Master the Dojo Rich Text Editor

And extend it with plug-ins

The Dojo Rich Text Editor is one of the most popular Dojo widgets. However, although it has many fine features, sometimes the editor isn't powerful enough to meet certain requirements. Luckily, the pluggable architecture allows enhancement using plug-ins. In this article, learn about using the Dojo Rich Text Editor as well as how you can extend it when necessary.

Share:

He Gu Yi (heguyi@cn.ibm.com), Software Engineer, IBM

Photo of He Gu YiHe Gu Yi, a software engineer at the IBM China Development Lab in Shanghai, is working on Web 2.0-related projects. His expertise includes Ajax and the Dojo library.



Zhu Xiao Wen (xwzhu@cn.ibm.com), Software Engineer, IBM

Photo of Zhu Xioa WenZhu Xiao Wen is a software engineer at the IBM China Development Lab in Shanghai. He is working on various new features of the Dojo library. Zhu's interests, and expertise, include Ajax and other Web 2.0 technologies.



11 January 2011

Also available in Chinese Japanese

Introduction

The Dojo Rich Text Editor, Dijit.Editor, is designed to look and work like a word processor. It has a toolbar, HTML output, and a plug-in architecture that supports new commands, new buttons, and other features. And, you don't need to worry about browser incompatibility. The editor supports several browsers, including Internet Explorer (IE) 6/7/8, Firefox3, Chrome, and Safari.

In this article, learn about the basics of the Dojo Rich Text Editor and how to use plug-ins provided by Dojo to enhance performance. You will also create your own plug-in, and learn how to extend the editor toolbar.

Dojo Rich Text Editor

Figure 1 shows the classic Dojo Rich Text Editor. It has a toolbar containing several useful commands, such as Cut, Copy, and Paste. You can edit the content in a WYSIWYG format.

Figure 1. Dojo Rich Text Editor
The screenshot of a classical Dojo Rich Text Editor

Using the editor is quite simple. The first step is to require into the page in which you're using the editor. Do this in the same location where all your dojo.require calls are made—usually a head script tag. You also need to import a CSS file to select a theme, such as Claro. Be sure that the CSS class of the body element is set to claro, as shown in Listing 1.

Listing 1. Import editor class definition
<html>
<head>
	<title>Dojo Rich Text Editor</title>
	<style type="text/css">
		@import "../../../dijit/themes/claro/claro.css";
	</style>
	<script type="text/javascript">
		dojo.require("dijit.Editor");
	</script>
</head>
<body class="claro">

</body>
</html>

There are two approaches to creating an editor: programmatically or declaratively. The two approaches are equal. Listing 2 shows how to create an editor declaratively.

Listing 2. Create an editor declaratively
<div dojoType="dijit.Editor" id="editor1">
    <p>
        This is the content of the editor.
    </p>
</div>

If you explore the DOM tree of the created editor, you'll find that the editor is composed of two parts: a toolbar and an iframe area. The designMode attribute of the iframe's document is set to on, which lets you edit the content of the iframe directly. This is the power source of the editor.

When the design mode is on, browsers let you modify the content by calling built-in commands. For example, you can set a piece of selected text to bold by calling the command execCommand('bold',false, null) in Firefox. The built-in commands in each browser are different. Luckily, the editor does the normalization so you can call the unified API provided by the editor to modify the content.

To use the Dojo Rich Text Editor you click a button icon on the toolbar to trigger an event (for example, click), as shown in Figure 2. The event is captured by the editor or its plug-in. The DOM tree of the iframe document is changed by calling the built-in commands or by modifying the DOM node directly.

Figure 2. Workflow for processing the editing request
Shows click on an icon, then click event captured by the editor, leading to the editing area (iframe)

Plug-ins

The Dojo Rich Text Editor pluggable architecture lets you extend the functions by implementing and registering plug-ins with the editor. A plug-in is something that adds a function to the editor or changes the editor's behavior. Dojo includes several editor plug-ins, and you can also write your own plug-ins.

Some plug-ins, such as Undo, Redo, Cut, and Copy, are enabled by default. Most plug-ins have an associated toolbar button, such as the FindReplace plug-in. Some plug-ins, such as EnterKeyHandling, affect the editor's behavior without changing the toolbar. The editor loads and initializes a plug-in as follows.

  1. Import necessary CSS files.

    Some plug-ins may have CSS files associated with them. The CSS files define the UI layout of the plug-in, so you need to first import the CSS files into the document.

  2. Declare the plug-in in the extraPlugins list.

    The plug-in should be declared in the extraPlugins list so the editor can create the plug-in instance from the declaration, as shown in Listing 3.

    Listing 3. Declare the plug-in
    <div dojoType="dijit.Editor" id="editor1" extraPlugins="['FindReplace']">
        <p>
            This is the content of the editor.
        </p>
    </div>

    The example in Listing 3 declares a plug-in named FindReplace.

  3. Mix the declared plug-ins into a complete plug-in list.

    The editor will mix the declared plug-in, along with the default plug-ins, into a complete plug-in list. At this stage, the editor holds a list of plug-in names. The actual plug-ins will be created in the following steps.

  4. Create the toolbar of the editor.

    The editor simply creates an instance of dijit.Toolbar and appends it to the child of its DOM node, as shown in Listing 4.

    Listing 4. Create the toolbar of the editor
    if(!this.toolbar){
    	// if you haven't been assigned a toolbar, create one
    	this.toolbar = new dijit.Toolbar({
    		dir: this.dir,
    		lang: this.lang
    	});
    	this.header.appendChild(this.toolbar.domNode);
    }
  5. Create the plug-in instances according to the list.

    All the magic happens here. The editor enumerates the plug-in name list and calls addPlugin on these names one by one. In the addPlugin method, the plug-ins will be created and their setEditor and setToolbar methods will be called to initialize their context.

    Listing 5. Create the plug-in instances
    postCreate: function(){
    	...
    	// Create the plug-in one by one
    	dojo.forEach(this.plugins, this.addPlugin, this);
    	...
    }
    addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
    	...
    	// Get the plug-in instance that is referenced by paremeter o.plugin.
    	var o={"args":args,"plugin":null,"editor":this};
    	dojo.publish(dijit._scopeName + ".Editor.getPlugin",[o]);
    	// Set the plug-in's context
    	plugin.setEditor(this); 
    	if(dojo.isFunction(plugin.setToolbar)){
    		plugin.setToolbar(this.toolbar);
    	}
    	...
    }

All the plug-ins that were declared are created and initialized.


Create your own plug-in

If the plug-ins provided by the Dojo toolkit don't meet your needs, you can build one of your own. The Dojo Rich Text Editor lets you extend the functions of the editor. You can create a new plug-in class that inherits from dijit._editor._Plugin and add it to the editor's plug-in list.

Lifecycle of an editor plug-in

dijit._editor._Plugin, a base class for a plug-in to the editor, is usually a single button on the Toolbar and some associated code. It defines the lifecycle of the plug-in.

A plug-in's lifecycle is started when it is added to the editor by the addPlugin function, whereby a Dojo topic will be published so the plug-in can construct itself. The plug-in's setEditor and setToolbar functions are then called, respectively, to install the plug-in into the editor. By default, the setEditor function:

  1. Creates a local reference to the editor.
  2. Creates the command button.
  3. Connects the execCommand method of the editor to the button.
  4. Connects the updateState method of the plug-in to the editor.

It's quite simple. The default behavior of setToolbar is even simpler: it adds the command button created previously to the toolbar.

At this point, the initialization is complete and the plug-in lifecycle enters an "endless" event-driven phase until someone destroys the whole editor and ends the lifecycle, which will call the destroy method of the plug-in.

To summarize, the lifecycle functions are:

  • Editor.addPlugin
    • constructor of plugin
    • plugin.setEditor
    • plugin.setToolbar
  • Event-driven phase. Constantly call plugin.updateState.
  • Editor.destroy
    • plugin.destroy

To better learn the details, you'll write a find/replace plug-in called FindReplace in the following sections. FindReplace searches the text in the editor for given keywords and then highlights them if found. It also supports replacing the keywords with something new. To implement the plug-in you will devise a special toolbar that's shown when the command button is clicked.

Inherit dijit._editor._Plugin

The first step is to establish an empty code framework by inheriting the base class dijit._editor._Plugin, as shown in Listing 6.

Listing 6. Basic code framework of a new plug-in
dojo.provide("dojox.editor.plugins.FindReplace");

dojo.require("dijit._editor._Plugin");

dojo.declare("dojox.editor.plugins.FindReplace", dijit._editor._Plugin, {
	// When you click the command button, you show a toolbar, 
	// instead of executing some editor commands.
	useDefaultCommand: false,
	
	// You can just use the original method.	
	// setEditor: function(editor){},
	
	setToolbar: function(editorToolbar){
		this.inherited(arguments);
		
		//TODO: Create your additional find/replace toolbar here, 
		// and append it after the editor toolbar.
	},
	
	updateState: function(){
		// You don't need to handle anything when editor state is updated. 
		// So just leave this empty.
	},
	
	destroy: function(){
		this.inherited(arguments);
		
		//TODO: Remember to destroy the toolbar you created.
	}
});

// Register this plug-in so it can construct itself when the editor publishes a topic.
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin", null, function(o){
	if(!o.plugin && o.args.name.toLowerCase() === "findreplace"){
		o.plugin = new dojox.editor.plugins.FindReplace({});
	}
});

Extend the toolbar

The second task is to make a button on the editor toolbar. The setEditor method will automatically do this for you; all you need to do is use a different label and a different icon. The simplest way is shown in Listing 7.

Listing 7. Create a ToggleButton in the Editor toolbar
...
dojo.declare("dojox.editor.plugins.FindReplace", dijit._editor._Plugin, {
	...
	// You'd like to use a toggle button to show/hide your own find/replace toolbar.
	buttonClass: dijit.form.ToggleButton,
	
	// As long as you provide a command, the CSS class of the button icon will be 
	// generated automatically. For this one, it will be "dijitEditorFindReplace".
	command: "findReplace",

	// You can also use localization here.
	getLabel: function(){
		return "Find and Replace";
	},
	...
});
...

When you have a background image for the CSS class dijitEditorFindReplace you can see the example button, as shown in Figure 3.

Figure 3. New button for example plug-in
A new button on the toolbar showing a find/replace icon for the new plug-in

The next step is to create the find/replace toolbar and bind it to the command button. You need a field for search strings, another field for replace strings, a find button, and a replace button. If you want to make it more powerful, you can add many other things to make it look more professional, such as a Replace All button or check boxes for Case Sensitive Search and Backwards Search. You can write a simple separate widget to hold all this stuff, as shown Listing 8.

Listing 8. Create a find/replace toolbar
dojo.declare("dojox.editor.plugins.FindReplacePane", [dijit._Widget, dijit._Templated], {
	// With templates, you don't need to create all the stuff manually.
	templateString: dojo.cache("dojox.editor.plugins", "FindReplacePane.html"),
	
	// There are widgets in template. Tell the parser to parse them.
	widgetsInTemplate: true
});

In the FindReplacePane.html you can directly use dijit.Toolbar and other form widgets. The example in Listing 9 combines a label and a text field (or a check box) together to form a new widget to keep the code simpler.

Listing 9. Content of find/replace toolbar
<div><div dojotype="dijit.Toolbar" dojoattachpoint="frToolbar">
  <div dojoattachpoint="findField" dojotype="dojox.editor.plugins._FindReplaceTextBox" 
    label="Find:"></div>
  <div dojoattachpoint="replaceField" dojotype="dojox.editor.plugins._FindReplaceTextBox" 
    label="Replace with:"></div>
  <div dojotype="dojox.editor.plugins._ToolbarLineBreak"></div>
  <div dojoattachpoint="findButton" dojotype="dijit.form.Button" 
    label="Find" showLable="true"></div>
  <div dojoattachpoint="replaceButton" dojotype="dijit.form.Button" 
    label="Replace" showLable="true"></div>
  <div dojoattachpoint="replaceAllButton" dojotype="dijit.form.Button" 
    label="Replace All" showLable="true"></div>
  <div dojoattachpoint="matchCase" dojotype="dojox.editor.plugins._FindReplaceCheckBox" 
    label="Match case"></div>
  <div dojoattachpoint="backwards" dojotype="dojox.editor.plugins._FindReplaceCheckBox" 
    label="Backwards"></div>
</div></div>

With dojoattachpoint you can easily access the widgets as a property of the FindReplacePane. dojox.editor.plugins._ToolbarLineBreak is a very useful widget for a multi-line toolbar.

Now that you have both the button and the toolbar it's time to connect them together. All you have to do is place the toolbar at a proper position and define what to do when the toggle button is clicked. Listing 10 shows an example.

Listing 10. Connect the button and toolbar
// This initialization work should be done in setToolbar, 
// because you want to be sure that the editor toolbar is ready.
setToolbar: function(editorToolbar){
	// Super class will add the command button to the editor toolbar for you.
	this.inherited(arguments);
	
	// Create your find/replace toolbar, place it after the editor toolbar,
	// hide it, and start it up.
	var frtb = this.frToolbar = new dojox.editor.plugins.FindReplacePane();
	frtb.placeAt(toolbar.domNode, "after");
	dojo.style(frtb.domNode, "display", "none");
	frtb.startup();
	
	// Toggle it when your toggle button is clicked...
	this.connect(this.button, "onChange", "_toggleFindReplace");
	
	...
},
_toggleFindReplace: function(toShow){
	// Remember the original height.
	var height = dojo.marginBox(this.editor.domNode).h
	
	dojo.style(this.frToolbar.domNode, "display", toShow ? "block" : "none");
	
	// Resize the editor to maintain the height.
	this.editor.resize({h: height});
}

Figure 4 shows what your example looks like now.

Figure 4. Find/replace toolbar
Tool bar with the words Find and Replace and text boxes next to each with checkboxes for match case and search backwards.

Handle events and implement functions

Now for the real work of making the Find and Replace buttons work. You can set them up in the setToolbar method, as shown in Listing 11.

Listing 11. Handle the events
setToolbar: function(editorToolbar){
	...
	var tb = this._frToolbar = ...
	
	// Connect methods to the onClick events of the buttons.
	this.connect(tb.findButton, "onClick", "_find");
	this.connect(tb.replaceButton, "onClick", "_replace");
	this.connect(tb.replaceAllButton, "onClick", "_replaceAll");
	
	// Make the ENTER key work for the "Find" text field.
	this.connect(tb.findField, "onKeyDown", function(evt){
		if(evt.keyCode == dojo.keys.ENTER){
			this._find();
			dojo.stopEvent(evt);
		}
	});
	
	...
}

That was fairly straightforward. The next step is to implement the _find function. For non-IE browsers, you can simply use window.find. For IE, things get a little complicated. You need to write an adapter function to eliminate the browser difference, as shown in Listing 12.

Listing 12. Implementation of Find
// An adapter function to make all browsers look the same.
_findText: function(txt, isCaseSensitive, isBackwards){
	if(!txt){ return false; }
	var ed = this.editor, win = ed.window, doc = ed.document;
	var found = false;
	if(win.find){
		found = win.find(txt, isCaseSensitive, isBackwards, 
			false, false, false, false);
	}else if(doc.selection){
		/* IE */
		// Focus to restore position/selection, 
		// then shift to search from current position.
		ed.focus();
		var txtRg = doc.body.createTextRange();
		var curPos = doc.selection ? doc.selection.createRange() : null;
		if(curPos){
			txtRg.setEndPoint(isBackwards ? "EndToStart" : "StartToEnd", 
				curPos);
		}
		var flags = isCaseSensitive ? 4 : 0;
		if(backwards){
			flags = flags | 1;
		}
		found = txtRg.findText(txt, txtRg.text.length, flags);
		if(found){
			txtRg.select();
		}
	}
	return found;
},
_find: function(){
	var tb = this._frToolbar;
	return this._findText(tb.findField.get("value"), 
		tb.matchCase.get("value"), tb.backwards.get("value"));
}

Run the page again to see the effect, as shown in Figure 5.

Figure 5. Find text
Finding the word list in the browser window.

You now have a useful Find function on most modern browsers.

Edit content programmatically

The Dojo Rich Text Editor provides a unified API, execCommand, that you can use to call the built-in commands. There are many commands available, such as Undo, Redo, Cut, Copy, Paste, Bold, and Italic. For a complete list of built-in commands, refer to the dijit.Editor documentation (see Resources).

For the example plug-in, you need to insert some text into the editor to replace the selected text. Of course, there is a built-in command for this—inserthtml. All you must do is:

  1. Check whether the selected text matches the text in the find field.
  2. If yes, call editor.execCommand("inserthtml", replaceText);

Listing 13 shows the code.

Listing 13. Replace implementation
        _replace: function(){
	var tb = this._frToolbar;
	var txt = tb.findField.get("value");
	if(!txt){ return false; }
	
	var repTxt = tb.replaceField.get("value") || "";
	var isCaseSensitive = tb.caseSensitive.get("value");
	var isBackwards = tb.backwards.get("value");
	var ed = this.editor;
	ed.focus();
	
	//Replace the current selected text if it matches the find field.
	var selected = dojo.withGlobal(ed.window, "getSelectedText", 
		dijit._editor.selection, [null]);
	if(selected && (isCaseSensitive ? 
		(selected === txt) : (selected.toLowerCase() === txt.toLowerCase()))){
		ed.execCommand("inserthtml", repTxt);
		return true;
	}
	return false;
}

You should call getSelectedText with the iframe window in the editor instead of the window of the whole page. Figure 6 shows the result.

Figure 6. Replace text
Finding the word list in the browser window and replacing it with the word queue.

You now know the essential details for writing an editor plug-in. Perhaps you can continue with the Replace All function as an exercise on your own.


Conclusion

In this article, you learned about the pluggable architecture of the Dojo Rich Text Editor. The plug-in mechanism lets you extend the functions of the editor. With the Dojo Rich Text Editor you can deploy a powerful text editor in an application. You can easily use and customize the editor to meet your specific requirements.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=606957
ArticleTitle=Master the Dojo Rich Text Editor
publish-date=01112011