Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Building Your Own JavaScript Editor

4.60/5 (3 votes)
25 Mar 2009CPOL3 min read 33K   498  
A step by step guide for implementing your JavaScript editor

Introduction

A few months ago, I had a situation in which I needed to build my own editor that will accept certain tags and attributes. I thought it would be very interesting to share how I built my editor in a browser independent way. To see a demo, click here.

Using the Code 

The first thing that came to my mind then was what will be the container of my designer, an IFrame, a div, or what. Then I decided that I will make it a div and I will set it's contentEditable attribute to true.

The second thing was OK now my div could be edited by the end user in the design time, how will I apply the tags to the selected content. Below you will find a utility library I have built that has the basic miscellaneous methods we will need to build the editor. In the next part, I will show and explain the Editor class. For now, let's start up with the important methods in the utility library.   

JavaScript
// This function is used to get the selected Range.
GetSelectedRange : function(controlContent){
var selectedRange = null;
if(document.selection)
    selectedRange = document.selection.createRange();
else if(window.selection)
    selectedRange = window.selection.createRange();
if(selectedRange == null){
    if(window.getSelection() != null)
    {
        if (this.Exists(window.getSelection().getRangeAt))
            selectedRange = window.getSelection().getRangeAt(0);
        else { // Safari!
            var range = document.createRange();
            range.setStart(window.getSelection().anchorNode,
                        window.getSelection().anchorOffset);
            range.setEnd(window.getSelection().focusNode,
                        window.getSelection().focusOffset);
            selectedRange = range;
            }      
    }
}
var t = null;    

if(selectedRange != null && this.BrowserType.IE)
{
     t = this.CheckParentById(controlContent.id,selectedRange.parentElement());
}
else{
     t = this.RangeCompareNode(selectedRange,controlContent);
}
if(!t  && controlContent != null)
     selectedRange = null;
     return selectedRange;
}	 

The above code gets the selected range object in a browser independent way in order to work on it later in the editor. Then it checks if the selected range is within our editor div range. If it's not, then return null otherwise return the range. Here we check if the range is within a parent node or not for browsers other than Internet Explorer.

JavaScript
// This function is used to get if the selected range in the
//required parent in firefox.
  RangeCompareNode : function(range,node){
    var nodeRange = node.ownerDocument.createRange();
    try {
      nodeRange.selectNode(node);
    }
    catch (e) {
      nodeRange.selectNodeContents(node);
    }
    var nodeIsBefore =
  range.compareBoundaryPoints(Range.START_TO_START, nodeRange) == 1;
    var nodeIsAfter = range.compareBoundaryPoints(Range.END_TO_END, nodeRange) == -1;

    if (nodeIsBefore && !nodeIsAfter)
      return false;
    if (!nodeIsBefore && nodeIsAfter)
      return false;
    if (nodeIsBefore && nodeIsAfter)
      return true;

    return false;
  }

Here we check if the range is within a parent node or not for Internet Explorer browser:

JavaScript
// This function is used to Check whether a parent element
// contains the child element or not using the parentId.
 CheckParentById : function(parentId,child){
     while(child != null){
         if(child.id == parentId) return true;
         child = child.parentNode;
     }
     return false;
 }

Basically the editor is initialized inside RBMEditor namespace. I will highlight some important methods in the editor. The Execute Command method first gets the current selected range then extracts the HTML from the range. After that it checks if the effect is already applied or not and if it is, it removes the effect using the ExecuteUndoCommand method. If the effect isn't applied, then we create a new element from the command. Then the SetHTMLFromSelection method will replace the current selected range with the new command element in the selected HTML.

JavaScript
ExecuteCommand: function(sCommand,id,value,name,isControl){
              var selection = RBM.GetSelectedRange(this.controlContent);

              if(selection == null)
                  return;
               var html = this.GetHTMLFromSelection(selection);

               var undo = this.ExecuteUndoCommand(sCommand,new String(html));

               if(!undo){
                  var element = this.CreateNewNode(sCommand,id,value,html);
                  if(element != null)
                      this.SetHTMLFromSelection(selection,element);
               }
          }

The GET HTML method will parse the HTML and code and remove the unsupported tags and attributes from the HTML code.

JavaScript
GetHTML: function(sCode){
              //return sCode;
              var ts = new String(sCode);

              var parts = ts.split("<");
              var subpart = '';
              var i = 0;
              var j = 0;
              var totalStr = '';
              var tagName  = '';
              var readTag = true;
              var readSub = true;
              for(i = 0; i < parts.length;i++)
              {
                  if(parts[i] == '')
                      continue;

                  subpart = '';
                  tagName = '';
                  readTag = true;
                  readSub = true;

                  for(j = 0; j < parts[i].length; j++)
                  {
                      if(parts[i].substr(j,1) == '>')
                           readSub = false;
                      if(parts[i].substr(j,1) == ' ' || parts[i].substr(j,1) == '>')
                          readTag = false;
                      if(readSub == true)
                          subpart = subpart + parts[i].substr(j,1);
                      if(readTag == true)
                          tagName = tagName + parts[i].substr(j,1);
                  }

                  if(this.IsSupportedTag(tagName) == false)
                  {
                      parts[i] = parts[i].replace(subpart + '>',' ');
                      parts[i] = parts[i].replace('/' + tagName + '>',' ');
                  }
                  else
                  {
                      parts[i] = '<' + parts[i];
                      parts[i] = this.RemoveUnknownAttributes
              (subpart.replace(tagName,''),parts[i]);
                  }
              }

              var retValue = '';
              for(i = 0; i < parts.length;i++)
              {
                  if(parts[i] != '')
                      retValue = retValue +  parts[i];
              }
              var tnewValue = new String(retValue);
              return tnewValue;
          }

So how to use the editor. First you need to initalize an object from the Editor class and give it the div id that you want to make it as an editor but to initialize it you need to put it this way:

JavaScript
var editor;
   RBM.AddListener(window,'load',RbmInit);
   function RbmInit(){
   editor = new RBMEditor.Editor('oDiv')
   }

Now to call a command simply type the following, you could change the BOLD Command with any of the commands listed in the EditorCommands which you could find in the code or any other added command in it.

JavaScript
editor.ExecuteCommand(RBMEditor.EditorCommands.BOLD); 

Finally hope that this post would help you to start building your own editor. Please feel free to ask me if you have any questions about more advanced techniques or more features like how to add a link, image, etc. I will try later to make separate posts for how to enhance the editor with such commands.  

History 

  • 25th March, 2009: Initial post 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)