David Banks

Web Developer

Build your own DOM utility (Part 1)

26 April 2015HTMLJavaScript

This post shows how to write your own simple DOM utility, which may eliminate the need to include a big library just for it's convenient DOM functionality. This topic will be split across several posts. This is part 1.

When I was first started learning front-end development, I often went straight to jQuery when I had to manipulate the DOM. I've found that it is simple enough to build your own DOM utility, unless you are relying on a library such as jQuery to support old browsers. Plus, I enjoy programming in JavaScript so writing my own DOM utility was a fun exercise for me!

Let's call our utility $DOM. We want to create a 'class' to wrap standard DOM elements so that they have useful methods available on them. Start by defining the prototype of our $DOM objects. For now we will leave it empty.



$DOM_proto = {};

The main $DOM function

We want our main $DOM function to allow creation of new elements as well as wrapping standard DOM elements, as well as accepting selectors and returning an array of $DOM objects corresponding to the elements matching a certain selector.



function $DOM(arg) {

    // Throw error for invalid argument
    if (!arg || !(typeof arg === 'string' || arg instanceof Element))
        throw new Error('Invalid argument provided');

    var newTagPattern = /^<([^\/]+)\/?>$/;

    // Create new $DOM object
    var $DOMobj = Object.create($DOM_proto);

    if (arg instanceof Element) {

        // Just wrap the element in with our $DOM methods
        $DOMobj._element = arg;
        return $DOMobj;

    }

    // Convert arg to string, trim off white space
    arg = ('' + arg).trim();

    // Arguments like '<tag>' should create a new empty element

    if (newTagPattern.test(arg)) {

        // Create a new element
        $DOMobj._element = document.createElement(arg.match(newTagPattern)[1]);
        return $DOMobj;

    }

    // Assume a selector was provided. Try to select all elements
    // matching the selector. Throw error if selector is invalid.

    var elem, $doms = [], i, n;

    try {

        elem = document.querySelectorAll(arg);
        n = elem.length;
        $doms = [];

        for (i = 0; i < n; i++)
            $doms.push($DOM(elem[i]));

        return $doms;

    } catch (exception) {

        throw new Error('Invalid selector: ' + arg);

    }

    return null;

};

This gives us the following use cases for the $DOM function:



// New empty div element
var newDiv = $DOM('<div>');

// Select all divs - leave out the tag brackets
var divs = $DOM('div');

// Select all elements of class 'box'
var boxes = $DOM('.box');

Define prototype methods

Notice our $DOM() function returns objects with a single property called _element which stores the raw DOM element, and the prototype is given by $DOM_proto (or an array of such objects). To add convenience methods to each object, we need to specify them on the prototype. Let's get started with some simple methods.

Note: We want to make setter functions chainable. This is a popular way of writing code and is seen is libraries like jQuery and D3.js. Basically, we want to be able to do cool things like this:



var myDiv = $DOM('<div>')
    .addClass('editable')
    .attr('contentEditable', '')
    .text('This paragraph is editable!');

Method: get()

This method returns the raw DOM element. The _element property should be treated as private, so we need to provide an accessor function.



$DOM_proto.get = function get() {
    return this._element;
};

Method: text()

This method gets or sets the text content of the current element. The setter form is chainable.



$DOM_proto.text = function text() {

    // Get text
    if (arguments.length === 0)
        return this._element.textContent;

    // Set text, return this to make method chainable
    this._element.textContent = '' + arguments[0];
    return this;

};

Method: html()

This method gets or sets the inner HTML of the current element. The setter form is chainable.



$DOM_proto.html = function html() {

    // Get inner HTML
    if (arguments.length === 0)
        return this._element.innerHTML;

    // Set innerHTML, return this to make method chainable
    this._element.innerHTML = arguments[0];
    return this;

};

Method: attr()

This method gets or sets attributes of the current element. The setter is chainable.



$DOM_proto.attr = function attr() {

    // Throw error when no argument provided
    if (arguments.length === 0)
        throw new Error('No attribute provided');

    // Throw error when invalid argument provided
    if (!arguments[0])
        throw new Error('Invalid attribute provided');

    // Get attribute
    if (arguments.length === 1)
        return this._element.getAttribute('' + arguments[0]);

    // 2 arguments - set attribute. Return this (chainable)
    this._element.setAttribute(arguments[0], arguments[1]);
    return this;

};

Method: prop()

This method gets or sets properties on the current element. For example, to change an input value you would use this method rather than the attr() method. The setter is chainable.



$DOM_proto.prop = function prop() {

    // Throw error if no argument provided
    if (arguments.length === 0)
        throw new Error('No property specified');

    // Throw error if invalid property name provided
    if (!arguments[0] || typeof arguments[0] !== 'string')
        throw new Error('Invalid property specified')

    // Get property value
    if (arguments.length === 1)
        return this._element[arguments[0]];

    // 2 arguments - set property. Return this (chainable)
    this._element[arguments[0]] = arguments[1];
    return this;

};

I hope you enjoyed this post! See Part 2 for the implementations of more useful methods.