Writing a DOM Library



The DOM API is not only lengthy but also inconvenient. Here, I will encapsulate DOM API in my core library. First, I revise the original code of Writing a Utility Library to the following structure. See comments for details.
(function(global) {
    // XD refers to a function and will be a namespace
    var XD = function(selector, container) {
        return new XD.mth.init(selector, container);
    };

    // utility functions are collected in utils temporarily
    var utils = {
        trim: function(text) {
            return (text || '').replace( /^(\\s|\\u00A0)+|(\\s|\\u00A0)+\$/g, '');
        },
        ...
    };
   
    // this function can extend target with properties of source
    function extend(target, source) {
        utils.each(source, function(value, key) {
            target[key] = value;
        });
    }
  
    // extend XD with utils
    extend(XD, utils);
    // extend is a utility function and takes XD as its namespace
    XD.extend = extend;
    // standardize some property names
    XD.props = {
        'for': 'htmlFor',
        'class': 'className',
        readonly: 'readOnly',
        maxlength: 'maxLength',
        cellspacing: 'cellSpacing',
        rowspan: 'rowSpan',
        colspan: 'colSpan',
        tabindex: 'tabIndex',
        usemap: 'useMap',
        frameborder: 'frameBorder'
    };

    // explained soon...
    XD.mth = XD.prototype = {
       init: function(selector, container) {
            ...
       }
    };
  
    XD.mth.init.prototype = XD.mth;

    global.XD = XD;
})(this);

In the above structure, the XD function encapsulates getElementById and getElementsByTagName. For example, you can pass a string of id values:
// return a collection object. The element in index 0 has an id 'xyz'.
XD('#xyz'); 


// return a collection object.
// The element in index 0 has an id 'xyz' and index 1 has an id 'abc'
.

XD('#xyz, #abc'); 

//
The element in index 0 has an id 'xyz';
// index 1 has an id 'abc'
; index 2 has an id '123'.
XD('#xyz, #abc, #123');

You can pass a string of tag names:
// The returned object collect all img elements. Use indices to access them.
XD('img');

// The returned object collect all img and div elements. Use indices to access them.
XD('img, div');

// The returned object collect all img, div and a elements. Use indices to access them.
XD('img, div, a');

//
The returned object collect all img child nodes of the container element.
XD('img', XD('#container'));

Or, pass a string of tags to create elements. For example:
XD('<img>');       // Create an img element, collected in the returned object.
XD('<img>,<div>'); // Create an img and a div element. both are collected in the returned object.

You can see in the above structure. Calling the function XD will create an instance of XD.mth.init internally. The implementation of XD.mth.init is as follow:
XD.mth = XD.prototype = {
    init: function(selector, container) {
        // selector is a node.
        if(selector.nodeType) {
            this[0] = selector;
            this.length = 1;
            return this;
        }
        // If container isn't undefined (a collection object returned by XD),
        // use its first element to call getElementById or getElementsByTagName
        if(container && container[0]) {
            container = container[0];
        }
        else { // else use document to call that
            container = document;
        }
        // Use an array to collect elements
        var elements = [];
        // multiple items are separated by a ',' character
        XD.each(selector.split(','), function(text) {
            text = XD.trim(text);
            if(text.charAt(0) === '#') { // this is '#id'
                elements.push(container.getElementById(text.substring(1)));
            }
            else if(text.charAt(0) === '<') { // this is '<tagName>'
                elements.push(document.createElement(
                                     text.substring(1, text.length - 1)));
            }
            else { // this is a tag name
                XD.each(container.getElementsByTagName(text), function(element) {
                    elements.push(element);
                });  
            }
        });
        // extend this with collected elements
        XD.extend(this, elements);
        // assign this the length of collected elements
        this.length = elements.length;
    }
};

Remember, if a function after the new operator doesn't return a value, it'll return this by default. In the above, XD.mth and XD.prototype refer to the same object , and then XD.mth.init.prototype refers to XD.mth again. This means that instances created through XD.mth.init can use functions defined on XD.mth. For example, if you want to add new functions to the collection object returned by XD:
XD.mth = XD.prototype = {
    init: function(selector, container) {
        ...
    },
    size: function() {     // the size of the collected objects
        return this.length;
    },
    isEmpty: function() {  // is it empty?
        return this.length === 0;
    }
};

If a document has two img tags, you can collect them, use size to return 2 and isEmpty to return false as follows:
var size = XD('img').size();        // 2
var isEmpty = XD('img').isEmpty();  // false

Then, we add several practical functions:
XD.mth = XD.prototype = {
    ...
    each: function(callback) {
        return XD.each(this, callback);
    },
    html: function(value) {
        if(value === undefined) {
            return this[0] && this[0].nodeType === 1 ?
                     this[0].innerHTML : null;
        }
        else {
            return XD.each(this, function(element) {
                if(element.nodeType === 1) {
                    element.innerHTML = value;
                }
            });
        }
    },
    attr: function(name, value) {
        name = XD.props[name] || name;
        if(value === undefined) {
            return this[0] && this[0].nodeType !== 3 && this[0].nodeType !== 8 ?
                     this[0][name] : undefined;
        }
        else {
            return XD.each(this, function(element) {
                if(element.nodeType !== 3 && element.nodeType !== 8) {
                    element[name] = value;
                }
            });
        }      
    },
    val: function(value) {
        // currently, this function only deals with input elements
        if(value === undefined) {
            return this[0] && this[0].nodeName === 'INPUT' ?
                    this[0].value : null;
        }
        else {
            return XD.each(this, function(element) {
                if(element.nodeName === 'INPUT') {
                    element.value = value;
                }
            });
        }
    },
    append: function(childs) {
        if(typeof childs === 'string' || childs.nodeType) {
            childs = XD(childs);
        }
        if(this.length === 1) { // only one parent node
            var parent = this[0];
            XD.each(childs, function(child) {
                parent.appendChild(child);
            });
        }
        else if(this.length > 1){ // multiple parent nodes
            XD.each(this, function(parent) {
                childs.each(function(child) {
                    // copy child nodes
                    var container = document.createElement('div');
                    container.appendChild(child);
                    container.innerHTML = container.innerHTML;
                    parent.appendChild(container.firstChild);
                });
            });
        }
        return this;
    },
    remove: function() { // remove from parent nodes
        return XD.each(this, function(element) {
            element.parentNode.removeChild(element);
        });
    }
};

The each function allows you specify a callback function called when iterating collected elements. The first parameter of the callback function accepts the iterated element. The keyword this refers to the element too. For example:
XD('img').each(function(element) {
    var src1 = element.src;
    var src2 = this.src;
    ...
});

Setting innerHTML properties for more than one elements may be as follow:
XD('div').html('<b>text</b>');

If you have multiple div elements, the above code will set their innerHTML properties the specified text. If you don't specify a text, the html function returns the innerHTML property of the first div element in the collection object.
var html = XD('div').html();

Similarly, the attr function is used to set or return an attribute.
// set the src property of all img elements
XD('img').attr('src', 'caterpillar.jpg');


// return the src property of the first img element.
XD('img').attr('src');

The val function allows you to get or set the value property of an input element.
// set all input values to 'default'
XD('input').val('default'); 

// return the value of the first input element
XD('input').val();          

The append function may be used to append a child node. If there are multiple elements in the collection object, source nodes will be copied. The remove function removes all collected elements from their parent nodes. For example:
// create img elements and append to all div elements
XD('div').append('<img>');


// remove all div elements from the DOM tree
XD('div').remove(); 

The above attr or html function returns the collection object itself. (If the first parameter of XD.each accepts this of the enclosing context, XD.each returns it after completed.) So, you may chain operations as follow:
XD('<img>,<img>')
    .attr('src', 'images/caterpillar_small.jpg')
    .each(function(element) {
        // ... do something
    });

If somebodies want to extend this library someday, they may write in their own js file as follow:
(function(XD) {
    XD.mth.get = function(index) {
        // this refers to the collection object
        return this[index];
    };
})(this.XD);

Then he can use so:
XD('img').get(0);  // return the first img element

If you've ever used jQuery, you must be familiar with the above coding style. In fact, I am simply imitating jQuery. I store the code in a file gossip-0.2.js. You can download it to see details.

If you are interested in how jQuery is written, this simplified source code may be a start. I'll use this file to simplify my examples afterword. For example, I can simplify the first example in Updating the DOM Tree as follows:
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript" src="js/gossip-0.2.js"></script>
        <script type="text/javascript">
            window.onload = function() {
                XD('#add')[0].onclick = function() {
                    var imgXD = XD('<img>').attr('src', XD('#src').val());
                    imgXD[0].onclick = function() {
                        XD(this).remove();
                    };
                    XD('#images').append(imgXD);
                };
            };
        </script>
    </head>
    <body>
        <input id="src" type="text"><button id="add">Add</button>
        <div id="images"></div>
    </body>
</html>

Or simplify its second example as follows:
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="content-type">
        <script type="text/javascript" src="js/gossip-0.2.js"></script>
        <script type="text/javascript">
            window.onload = function() {
                var container1XD = XD('#container1');
                var container2XD = XD('#container2');
                
                XD('#image')[0].onclick = function() {
                    if(this.parentNode === container1XD[0]) {
                        container2XD.append(this);
                    }
                    else {
                        container1XD.append(this);
                    }
                };
            };
        </script>
    </head>
    <body>
        Container 1: <div id="container1">
        <img id="image" src=
      "https://openhome.cc/Gossip/images/caterpillar_small.jpg"/>
                </div><br>
        Container 2: <div id="container2"></div>
    </body>
</html>

This library still needs more work and I'll improve it afterword.