Building your own Java Script Editor


Introduction

A few months ago, I needed to build my own editor which can accept certain tags and attributes. I thought it would be very interesting to share how I built my editor with a "browser independent" feature. To see a demo, click here.

Using The Code

The Design Mode, was the first thing that popped to my mind, then I began to find out which will be the appropriate container "IFrame or div". Then I decided that I will use "div" and its contentEditable attribute -an attribute that makes the entire document editable, like a dedicated HTML editor- will be set to true. Then comes the second step, applying the tags to the selected content.

 The Tools.js file contains the basic miscellaneous methods, needed to build the editor. Here is the important methods of the Tools.js. The code below gets the selected Range object that will be used in the editor. Then it checks if the selected range is within our editor div range. If it's not, then return null.  

// 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 method below check if the selected range is part of the parent node or not. It works on all browsers except for Internet Explorer.

// 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;
    }

The method below checks if  a child element (in our case this is the selected range parent element)  is part of the given parent node or not.

// 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;
    }

The Editor Class

Basically the Editor class is a part of the RBMEditor namespace. The Editor Class constructor retrieves the div element by its name and set its contentEditable attribute to true also it initializes the editor view to the design mode and retrieves the control code holder -a text area.

Editor: function(ctrl,code){
private.EditorCount++;
this.controlContent = RBM.GetElement(ctrl);
this.controlContent.contentEditable = 'true';
this.controlCode = RBM.GetElement(code);
this.editorView = private.EditorView.Design;

}

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.

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 GetHTML method will parse the HTML code and remove the unsupported tags and attributes from it.

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;
            }

Using The Editor

Steps for using the Editor:

  • Initialize an object from the Editor class by giving it id of the div you want to use.

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

Now to call a command simply type the following

editor.ExecuteCommand(RBMEditor.EditorCommands.BOLD);

You could change the BOLD Command with any of the commands listed in the EditorCommands, found in the code (e.g ITALIC,UNDERLINE,PARAGRAPH etc...)

Share Your Thoughts

Finally, I hope this post would help you start building your own editor. For any concern 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 a separate post for how to enhance the editor with such commands.  Also please share your thoughts about any modifications or features.


Similar Articles