Extending DOM elements
During the development of a site or a web application, it is sometimes very practical to want to use your own methods with the elements of the DOM. A very elegant example is jQuery.
In this article I will show you the possible methods to succeed in this extension and then I will try to evaluate them.
Methode n°1 - Extend "Element"
to start we will explain what happens when trying to create a DOM element in Javascript using for example:
var box = document.createElement('DIV');
This creation process is done as follows:
document.createElement('DIV');
|
|_ HTMLDIVElement.prototype
^
|_ HTMLElement.prototype
^
|_ Element.prototype
^
|_ Node.prototype
^
|_ Object.prototype
^
|_ null
We see that if we want to extend the 'DIV' elements of the DOM we have the choice of using the prototype whether from HTMLDIVElement, HTMLElement, Element or Node.
If we want for example to extend the DIV element in this case we could use the prototype
HTMLDIVElement.prototype as follows:
HTMLDIVElement.prototype.toggle = function () { this.style.display = this.style.display === 'none' ? 'block' : 'none'; } // this will trigger the toggle on the selected div document.querySelector('div.classname').toggle(); // this WON'T work document.querySelector('p').toggle();
In this example the line of code "document.querySelector ('p'). Toggle ();" does not give result because the toggle function is only defined for elements of type 'DIV'.
So if we want all the elements to have this function, we could use the "Element.prototype" prototype. It is safer.
Element.prototype.toggle = function () { this.style.display = this.style.display === 'none' ? 'inline-block' : 'none'; }
Thus we could add a method to "all" the elements of the DOM.
ATTENTION: here we have just extended the prototype of "Element". This does not include text and comment type elements.
// < Text.prototype < CharacterData.prototype < Node.prototype document.createTextNode('foo');
So an element of type text has CharacterData as its parent, as long as we want to add our finction toggle also to all the elements it must be applied to the prototype of "Node".
CAUTION: Internet Explorer 7 does not expose its global variables Node, Element, HTMLElement and HTMLParagraphElement 🤨.
We can see that playing directly on the prototypes of the DOM elements is not really the right thing to do.
The solution is to add new properties and methods. For more information on the disadvantages of this method, I advise you to check out this page http://perfectionkills.com/whats-wrong-with-extending-the-dom/.
Methode n°2 - Use of the Objet wrapper
JQuery uses a simple but also very efficient and safe method.
It is about inventing an object to which we can associate properties and methods without having fear of collisions or that a Browser will not support it.
Let's see how it goes:
;(function(){ "use strict"; /* DOM */ // We define our wrapper, which we will call DOM. It is our Constructor. // It receives "el" (one or more DOM elements) as a parameter // we define the elements property which generates an array containing the elements // from the DOM function DOM(el) { this.elements = el.length ? Array.prototype.slice.call(el) : [el]; } // We extend our wrapper to contain the following methods: DOM.prototype = { // The scale method zooms a certain element by having a // zoom factor as parameter scale: function (factor) { this.elements.map(function(element, index) { element.style.height = factor * parseInt(window.getComputedStyle(element).height, 10) + 'px'; element.style.width = factor * parseInt(window.getComputedStyle(element).width, 10) + 'px'; }); return this; // This is important for the sequence of methods // (we want for example El.method1 (..). method2 (..) etc ...) }, // The css method defines the style of an element just like jQuery. // In this case the style parameter is a CSS properties object css: function (styles) { this.elements.map(function(element, index) { for (var prop in styles) { if (styles.hasOwnProperty(prop)) { element.style[prop] = styles[prop]; } } }); return this; }, // The changeBGColor method changes the background color of a DOM element. // parameter: the new color changeBGColor: function (color) { this.elements.map(function(element, index) { element.style.backgroundColor = color; }); return this; }, // This Method is a simplification of addEventListener, // which aims to add events to DOM elements on: function (eventName, callback) { this.elements.map(function(element, index) { element.addEventListener(eventName, function (evt) { if (typeof callback === 'function') { callback(evt)} }, false); }); } } /* EO:DOM */ // We define an instance of our DOM Wrapper. So we could // use our little Library in a very simple way: // directly: dom (ELEMENT (S) OF THE DOM) window.dom = function (element) { return new DOM(element); } // we return our wrapper in case the user wants // use this library as follows: var $ D = new DOM (DOM ELEMENT (S)); return DOM; }());
Thus we were able to create an Object that will allow us to work safely with the
DOM elements to be able to "manipulate" them. Let's see a small example of use
from our library.
Using this HTML code as an example:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>DOM</title> <script src="dom.js"></script> <!-- dom.js ist our DOM library --> <style> html, body { height: 100%; min-height: 1005; } *, *:before, *:after { box-sizing: border-box; } .me { width: 200px; height: 20px; background: red; margin: 10px; } </style> </head> <body> <h1>Example : Extend DOM elements</h1> <div class="me"></div>1</div> <div class="me">2</div> <div class="others">3</div> <div class="me">4</div> <div class="me">5</div> <span class="others">6</span> <div class="me">7</div> </body> </html>
If we want for example:
- change the styles of all ".me" class elements
- change the background color of the ".other" and SPAN
- change the style of the DIV having the class name ".other" and zoom it after a click
Our Javascript code would be as follows:
var cssElements = dom(document.querySelectorAll('.me')); var BGElement = dom(document.querySelector('span.others')); var zoomElement = dom(document.querySelector('div.others')); // all the elements of class '.me' will have the text color white, // centered and a 20px font cssElements.css({ color: '#FFF', textAlign: 'center', fontSize: '20px' }); // We change the background color of the 'other' span to blue. BGElement.changeBGColor('blue'); /// we change the style of the '.other' DIV and we continue with the on method to define // a function that will be called after a click. This method would be 'scale' with // a factor equal to 2 (so we double the dimensions of the DIV with each click) zoomElement.css({ width: '100px', height: '10px', backgroundColor: 'orange', margin: '10px', color: '#FFF', textAlign: 'center', transition: 'all 0.25s ease-in-out 0s' }).on('click', function(e){ e.preventDefault(); zoomElement.scale(2); });
Now if we put everything in a small project 😉:
See the CodePen
</
Conclusion
So I hope I was able to clear up the idea on extending DOM elements. feel free to
ask your questions or add your proposals.
It is important to point out that the small library we built is still missing a lot of "Checks" in case of unexpected errors.