Introduction
There are plenty of web-based rich text editors around. To list some:
Before I begin, I'd like to state that I do not want to be understood incorrectly. All the above rich content editors do an excellent job: They convert the boring textarea to a fully editable item. And their development team put great effort to improve/enhance/support them. I fully respect their work. This article is by no means a complementary method. I just wanted to look at things from a different aspect. That's it.
There are many many other rich content editors, which offer different features. All these editors utilize ContentEditable
mode of the browsers to create the so called "what you see is what you get" effect.
However, enabling edition of rich text content is not a standard. So each vendor has their own implementation.
Moreover, the support for ContentEditable
mode is limited: Only Mozilla-based browsers and Internet Explorer currently support it. So say when a third browser comes out and says "hey you guys out there! I have my own way of implementing ContentEditable
mode as well!", there is nobody to enforce them to do it in a certain way. Because there isn't any declared standard for rich content creation.
What happens then? We will triple our effort to fork our code to support both of those browsers. Say all of a sudden, ten browsers pop out with ten different API implementations. You will have two choices then: Either you will spend ten times more effort and create a code spaghetti to handle all possible cases or you will ignore some (or most) of the minority browsers.
But this should not be the case:
Actually, there is a standard way of editing HTML documents which World Wide Web Consortium recommends:
a la Document Object Model!
If we somehow achieve rich content editability via manipulating DOM, we can add rich content support to any DOM-supporting browser; not only Mozilla and IE.
I know that we cannot support every single browser on earth. But, IMHO, at least equal opportunities should be given to all the browsers that fully support DOM.
But, Is This Really Necessary?
The necessity of such a product is another issue. As mentioned above, the vast majority of the web use IE and Mozilla-family browsers which implement content-editability to an excellent extent.
Another point of discussion may be the suitability and necessity of WYSIWYG on a web page. Or to state in other words, will it be better to use textareas along with BBCode
and leave the template-management and WYSIWYG stuff to application packages like Macromedia Contribute.
According to my web stats, Opera users constitute less than 1% of my traffic. (8-10 percent Mozilla, which is good :) ; around 90 percent IE and 1-2 percent all the rest).
So people will be happy with the WYSIWYG editors that work in Mozilla-based browsers and IE (which make up about 96-99% of my incoming web traffic).
Will I be happy? Certainly not. But that's me and just me. :)
Anyway, one goal of Code Project is manifesting crazy-looking thoughts, isn't it?
May be an idea will pull it from a techy-minded perspective to a more reasonable ground where average web user may utilize.
I hear you saying "enough philosophy, let's see the work done". So here we go...
Implementation
The best way to test an article editor is to write an article with it; so that I may get an idea of which features I may add, what can be done to ease use of the editor, kill bugs, etc. So I'm writing this article from the Editor (sorry I couldn't find a more imaginative name for it. :) )
Believe me or not, I'm writing this article from within Opera: my second favorite browser. My first is Mozilla; I've never considered Internet Explorer at all, shame on me. :)
Editor uses several js classes to function:
DraggableLayer
: The class is used to add drag-drop support and DOM integration. ToolTip
: This class is used to pop tiny tooltip on hovering over the node anchors (the images with a white plus on them). DOMManager
: The object is used to seek parent-child relations as well as remove empty text nodes which cause problem in Opera and Mozilla. WindowObject
: This class is used to get the inner dimensions and scroll ofsets of the browser window. TextFormatter
: The object is used to remove extra markup that is used for editing purposes only. Moreover, it makes the tags lowercase, so the markup will be more or less standards-compatible. Editor
: Editor is the model for the article editing application GUI (the view).
Using the Editor
, you may do a lot of formatting to your article, add images, add links and still create valid XHTML-strict code. I tried to make the GUI simple, but I am not a usability expert. I'm open to any ideas/sketches/mock-ups, etc. that may enhance the usability further.
The Editor Object
For those who are new to the MVC (Model View Controller) paradigm: The Editor
object is a Model
for the editor. By separating the Model from the view; we can change the view as much as we like, change the layout, change the style, etc. without the need of changing the js code behind.
Here is a highly truncated prototype of the Editor
.
_this=_Editor.prototype;
function _Editor() {
}
_this.setActiveNodeName=function(strValue){...};
_this.setActiveNode=function(obj){...};
_this.getActiveNode=function(){...};
_this.setCurrentAction=function(intValue){...};
_this.isBlockLevel=function(strName){...}
_this.isInline=function(strName){...}
_this.isActiveNodeBlockLevel=function(){...}
_this.getActiveNodeName=function(){...};
_this.getCurrentAction=function(){...};
_this.init=function(){
this.controlPane={
};
};
_this.getExtendedNodeDescription=function(strNode){...};
_this._RadH1_click=function(evt){...};
_this._RadH2_click=function(evt){...};
_this._RadH3_click=function(evt){...};
_this._RadH4_click=function(evt){...};
_this._RadH5_click=function(evt){...};
_this._RadH6_click=function(evt){...};
_this._RadP_click=function(evt){...};
_this._RadPre_click=function(evt){...};
_this._RadStrong_click=function(evt){...};
_this._RadEm_click=function(evt){...};
_this._RadNormal_click=function(evt){...};
_this._btnChangeType_click=function(evt){...};
_this.toggleCommitAction=function(){...};
_this._btnEdit_click=function(evt) {...};
_this._blnPreview_click=function(evt){...};
_this._btnAddAfter_click=function(evt) {...};
_this._btnAddBefore_click=function(evt){...};
_this._btnMoveAfter_click=function(evt){...};
_this._btnMoveBefore_click=function(evt){...};
_this._btnCopyHere_click=function(evt){...};
_this._btnCopyBefore_click=function(evt){...};
_this._btnCopyAfter_click=function(evt){...};
_this._btnDelete_click=function(evt){...}
_this._btnCancel_click=function(evt){...}
_this.cancelAction=function(){...};
_this._resetGUI=function(){...};
_this.createProperNode=function(strName,strText){...};
_this.getProperNodeValue=function(objNode){...};
_this.getProperNode=function(objNode){...};
_this._btnCommit_click=function(evt){
switch(Editor.getCurrentAction()){
case Constant.Editor.Action.ADD_AFTER:
Editor.addAfter();
break;
case Constant.Editor.Action.ADD_BEFORE:
Editor.addBefore();
break;
case Constant.Editor.Action.MOVE_AFTER:
Editor.moveAfter();
break;
case Constant.Editor.Action.MOVE_BEFORE:
Editor.moveBefore();
break;
case Constant.Editor.Action.DUPLICATE:
Editor.duplicate();
break;
case Constant.Editor.Action.DELETE_NODE:
Editor.deleteNode();
break;
case Constant.Editor.Action.EDIT_TEXT:
Editor.editText();
break;
case Constant.Editor.Action.CHANGE_TYPE:
Editor.changeType();
break;
default:
break;
}
Editor.cancelAction();
};
_this.addAfter=function(){...};
_this.addBefore=function(){...};
_this.moveAfter=function(){...};
_this.moveBefore=function(){...};
_this.duplicate=function(){...};
_this.deleteNode=function(){...};
_this.editText=function(){...};
_this.changeType=function(){...};
_this._appendControlsToFamily=function(nodeToInsert){...};
_this.organizeEditPane=function(){...};
_this.appendControls=function(theNode){...};
_this._edit_click=function(evt){...};
The HTML
The HTML for the Editor page is as follows:
<div id="EditorArea" class="""textEditor">""
<h2>Introduction</h2>
</div>
<div id="EditorControls">
<h3 id="EditorInfo">Informational Heading</h3>
<div style="padding:10px;" id="EditorControlButtons">
<h4>transform</h4>
<div><input type="button" id="btnChangeType" value="change type" /></div>
<h4>alter</h4>
<div>
<input type="button" id="btnEdit" value="edit text" />
<input type="button" id="btnAddBefore" value="add before" />
<input type="button" id="btnAddAfter" value="add after" />
</div>
<h4>move</h4>
<div>
<input type="button" id="btnMoveBefore" value="move before / move up" />
<input type="button" id="btnMoveAfter" value="move after / move down" />
</div>
<h4>copy</h4>
<div>
<input type="button" id="btnCopyHere" value="duplicate element" />
</div>
<h4>remove</h4>
<div><input type="button" id="btnDelete" value="remove element" /></div>
</div>
<div style="padding:10px;">
<ul id="ListTagsBlockLevel">
... radio buttons ...
</ul>
<ul id="ListTagsInline">
.... radio buttons ...
</ul>
<textarea rows="10" cols="30" id="TxtContent"></textarea>
</div>
<div style="padding:10px;text-align:right;">
<input type="button" value="cancel" id="btnCancel" />
<input type="button" value="commit" id="btnCommit" />
</div>
</div>
The EditorArea
layer includes editable content, whereas EditorControls
layer includes GUI controls.
To initialize the editor, we call its init()
method on page load.
window.onload=function() {
Editor.init();
document.getElementById("BtnArticleSource"
).onclick=BtnArticleSource_click;
};
function BtnArticleSource_click(evt){
alert("This action may take some time and your browser may_
hang for a few seconds.\nPlease be patient.");
var src=new EventObject(evt).getSource();
document.getElementById("ArticleSource").value=
TextFormatter.properHTML(
Editor.controlPane.panel.EditorArea.getObject().innerHTML);
}
BtnArticleSource_click
method is triggered when clicking the "retrieve article's HTML" button. This will fill the textbox
at the bottom of the page with the article's HTML.
You may further investigate the code. I tried to write the code and the markup as clean as possible. Feel free to express any queries and comments.
Tips and Usage
Creation and modification of the article is done on node basis. If you are working in the edit mode, you will recognize the node anchors ( and ). is used to add/edit/modify inline elements (strong text, emphasized text, normal text, links and images) whereas is used to add/edit/modify block-level elements (heading level 1-6, paragraphs and preformatted text).
Clicking on a node anchor pops up available actions for that particular node. The pop-up is draggable DHTML layer (my following article will be on creating a simple drag&drop layer I suppose).
Double-clicking anywhere on the article toggles between edit and preview modes.
As far as my experience is concerned: getting used to the Editor
takes some time. Because, you need to look the entire document as a whole instead of bunches of paragraphs and text to copy, paste and drag around. But you become more creative as you get used to it. In fact, the "duplicate element" functionality happens to be a good helper at times. In addition, bubbling block level elements up and down is fun.
Actions available for all nodes (inline and block-level):
- change type: Changes the type of the node, for instance, you may convert a text element to a link or to an image
- add before: Adds an element before that node
- add after: Adds an element after that node
- move before/move up: Moves the node upwards in the node hierarchy
- move after/move down: Moves the node downwards in the node hierarchy
- duplicate: Creates an identical node and positions it just after the selected node
- remove: Removes the selected node entirely. Caution! This operation cannot be undone
- cut node: Removes the node and copies the node contents to a temporary variance
- copy node: Copies the node content to a temporary variance; it does not remove the original node
- paste before: Pastes the copied or cut node just before the selected node
- paste after: Pastes the copied or cut node just after the selected node
Actions only available for inline nodes only:
- edit: Changes the contents of the node (the text and (source, link, width, etc. if available)).
Available types for inline nodes:
- strong text: bold text.
- emphasized text: italic text
- normal text: normal text
- link: A link
- image:.
Available types for block-level nodes:
- heading level 1: First level heading
- heading level 2: Second level heading
- heading level 3: Third level heading
- heading level 4: Fourth level heading
- heading level 5: Fifth level heading
- heading level 6: Sixth level heading
- paragraph: A paragraph
- preformatted text: Preformatted text (which is generally used for formatting code)
Things That Can Be Done to Improve the Editor
From now on, I will post new versions to the Editor to sardalya (http://www.sarmal.com/sardalya/). Those interested may follow the changes there. Here is a list of things to do that come to mind at first glance. Though I would like to remind you that I am open to any suggestions, positive&negative criticisms, so that I may enhance the Editor further.
- Ability to insert lists and nested lists
- Add support for other tags (like
<q>
, <blockquote>
, <hr />
) - Add some descriptive icons to the pop-up interface
- Add tab-support for preformatted text, which is a great aid when indenting code (Opera will not support it, but it will transform gracefully - may be add a button to copy tab character (from a hidden field may be) for Opera and other non-supporting browsers)
- Some more mouse interaction (such as dragging an anchor and dropping it to another anchor will move the node before the second one, shift-dragging will copy it, etc)
- Allow selection and modification of multiple nodes (i.e., making two paragraphs bold, converting three headings to paragraphs, etc.)
- Allow creation and modification of nested elements (such as
<strong><em>
strong and emphasized text</em></strong>
) - Assign keyboard shortcuts to certain operations
- Ability to add predefined smiley images, bullet images, avatars, etc.
Note to the Reader
I've included only the necessary parts of s@rdalya API that I used in this project not to deviate from the subject and to keep the code small. You may find the entire API at: http://www.sarmal.com/sardalya/.
However, the API on web currently does not include the Editor. Editor will be added to the next stable release.
Also note that this is a development release and it is not optimized for web. An optimized version along with usage examples and documentation will be available soon.
History
- 03-10-2005 - Article created
- 18-10-2005 - Version 1.1.0 is released (former version was 1.0.0) - Article updated accordingly