Creating a Simple HTML Editor with Javascript

javascript
Published on October 8, 2018

This tutorial shows how you can create a simple WYSIWYG HTML editor with Javascript. Consider it as a mini version of the famous TinyMCE HTML editor.

Demo

Features in the editor :

  1. Supports making text bold or underline via "Bold" and "Underline" menu options.
  2. Unordered lists and images can be inserted via "List" and "Picture" menu options.
    (For simplicity, only this image will be inserted while adding an image)
  3. On selecting some text, it highlights the appropriate menu option. For example, clicking on a bold text will highlight the "Bold" menu option. Similarly selecting some part of a list will highlight the "List" menu opton.
    If there are multiple selections (via the Ctrl key), then only common commands will be highlighted. For example, selecting both a bold text (1) and bold text inside a list (2), will highlight only the "Bold" menu option and not the "List" menu option.
  4. While deciding the appropriate menu options to highlight, only those selections will be taken into account where start position and end position involve same type of element. For example a selection that involves starting from normal text and ends up in a bold text will not be considered while deciding the menu option to highlight (more on this later).

Download codes for the above demo

Using a contentEditable Element

A normal <textarea> element cannot be used for a HTML editor, because HTML cannot be rendered inside a textarea. So to allow the editor to accommodate HTML tags, we use the contentEditable attribute on a <div>.

contentEditable attribute indicates that the contents of the element can be edited.

<div contenteditable="true" spellcheck="false"></div>

Setting spellcheck="false" will disallow spelling check on the editor.

Setting Various Edit Options

The document.execCommand method can be used to execute various commands on a editable region. There are commands that to make text as bold, underline, change font size and family, add foreground or background color, insert links, HTML or images, and a lot more. Basically there are commands for most of the features that you can imagine for a HTML editor. There is also an "Undo" command to undo the last executed command !

See the complete list of commands here.

Since this tutorial is focussed on providing only 4 edit options - "Bold", "Underline", "List" & "Picture", only these commands will be discussed here.

  • Bold
    document.execCommand('bold')

    This command will toggle bold for the selected text, or the insertion point. All browsers insert a <b> tag — excpet IE which inserts a <strong> tag.

  • Underline
    document.execCommand('underline')

    This command will toggle underline for the selected text, or the insertion point. <u> tag is inserted here.

  • List
    document.execCommand('insertUnorderedList')

    This command will create a bullet list selected text, or the insertion point. <ul> tag is inserted here.

  • Picture
    document.execCommand('insertImage', false, 'http://url-of-the-picture')

    This command will create an image at the insertion point. <img> tag is inserted here.

  • To insert an image, you need to give the url of the picture as the third parameter to document.execCommand.

    In real-life cases (just like in professional HTML editors), the user will see a dialog to choose or upload an image first. Once an image is chosen, the command can be executed.

All commands will be executed when an appropriate menu option is clicked.

// execute command when "Bold" menu is clicked
document.querySelector('#bold-menu').addEventListener('click', function() {
    document.execCommand('bold');
});

Highlighting Menus (1) - Selection and Range Objects

The main challenge in creating the text editor is to highlight menu options when something is selected in the editor. For example when you click on a bold text, you would expect the "Bold" menu to be highlighted (like in normal text editors). To understand how this would work, you need to know a bit about Selection and Range APIs.

  • Selection

    When the user selects some text (by dragging the mouse from an initial point to a final point) in the editor, you will need to know the underlying elements that the user has selected. A Selection object represents a range of text that is selected by the user. To know the selection in the current document, you can use the window.getSelection() method.

    Selection API on MDN

  • Range

    The user is free to make multiple selections in the editor (by using the Control key). Each such selection represents a Range object.

    Basically the Selection object contains an list of Range objects.

    Range API on MDN

Highlighting Menus (2) - Start and End Containers of a Range

One important thing is to note that the starting point and final point of a Range may be different DOM elements. For example, a user may select some text starting from normal text and end selection in a bold text. In this case the starting point lies inside a Text Node, and the final point lies inside a <b> (or <strong>) Element.

The startContainer and endContainer properties of a Range object gives the starting DOM element and final DOM element of the selected range.

In this tutorial, to keep things simple, we will ignore the range in which starting element and final elements are different. This means that in the below image the first selected range is considered while the second one is not considered in the decision as to which menu button is to be highlighted.

Highlighting Menus (3) - Finding Parent Tags of a Range

To highlight the appropriate menu option we will need to find all parent tags of the selected range, till the editor element.

Example 1 - This will highlight only the "Bold" menu :

<div contenteditable="true">
    <b>selected text</b>
</div>

Example 2 - This will highlight both the "Bold" and "List" menu :

<div contenteditable="true">
    <ul>
        <li><b>selected text</b></li>
    </ul>
</div>

If there are multiple ranges then we find parent tags of all of them. The common tags from all of them will be the final set of highlighted menus.

Steps to Highlight a Menu

  1. Get the selection object
  2. Get all ranges in the selection
  3. For each range :
    Check whether start and end containers are the same
    If yes, then find all parent tags upto the editor container
  4. Now get the common parent tags for all range objects. Highlight those menus
// Editor container 
var editor_element = document.querySelector('#editor-text');

// No of ranges
var num_ranges = window.getSelection().rangeCount;
   
// Will hold parent tags of a range    
var range_parent_tags;
    
// Will hold parent tags of all ranges
var all_ranges_parent_tags = [];

var start_element,
    end_element,
    cur_element,

// For all ranges
for(var i=0; i<num_ranges; i++) {
    // Start container of range
    start_element = window.getSelection().getRangeAt(i).startContainer;
    
    // End container of range
    end_element = window.getSelection().getRangeAt(i).endContainer;
    
    // Will hold parent tags of the range
    range_parent_tags = [];

    // If starting container and end container are the same
    if(start_element.isEqualNode(end_element)) {
        cur_element = start_element.parentNode;
        
        // Get all parent tags till editor container
        while(!editor_element.isEqualNode(cur_element)) {
            range_parent_tags.push(cur_element.nodeName);
            cur_element = cur_element.parentNode;
        }
    }

    // Push tags of current range 
    all_ranges_parent_tags.push(range_parent_tags);
}

// then select common tags from entries in "all_ranges_parent_tags"

Final Words

This tutorial gives an introduction how Javascript text editors work. Professional text editors, such as TinyMCE and CKEditor are much more complex, although they too are mostly based on Selection and Range APIs.

ContentEditable — The Good, the Bad and the Ugly is a interesting article written by a CKEditor lead developer on the challenges faced while creating a text editor.

Useful Resources

In this Tutorial