DOM - node Overview
in Dev on Java Script
DOM
The Document Object Model (a.k.a. the DOM) Is a Hierarchy/Tree of JavaScript Node Objects
When you write an HTML document, you encapsulate HTML content inside other HTML content. By doing this, you set up a hierarchy that can be expressed as a tree.
<body>
<section>
<div>
<table>
<tr>
<th></th>
<td></td>
</tr>
</table>
</div>
</section>
</body>
The preceding HTML code, when parsed by a browser, creates a document that contains nodes structured in a tree format (i.e., DOM).
HTML documents get parsed by a browser and converted into a tree structure of node objects representing a live document. The purpose of the DOM is to provide a programmatic interface for scripting (removing, adding, replacing, eventing, and modifying) this live document using JavaScript.
node type
- DOCUMENT_NODE (e.g., window.document)
- ELEMENT_NODE (e.g., <body>, , <p>,
- ATTRIBUTE_NODE (e.g., class=”funEdges”)
- TEXT_NODE (e.g., text characters in an HTML document including carriage returns and whitespace)
- DOCUMENT_FRAGMENT_NODE (e.g., document.createDocumentFragment())
- DOCUMENT_TYPE_NODE (e.g., <!DOCTYPE html>)
- etc…
Subnode Objects Inherit From the Node Object
- Object < Node < Element < HTMLElement < (e.g., HTML*Element)
- Object < Node < Attr (this is deprecated in DOM4)
- Object < Node < CharacterData < Text
- Object < Node < Document < HTMLDocument
- Object < Node < DocumentFragment
It’s important to remember not only that all node types inherit from Node, but also that the chain of inheritance can be long.w For example, all HTMLAnchorElement nodes inherit properties and methods from HTMLElement, Element, Node, and Object objects.
all nodes inherit a set of baseline properties and methods from their constructor as well as properties from the prototype chain.
Using JavaScript Methods to Create Element and Text Nodes
- createElement()
let elementNode = document.createElement('div');
console.log(elementNode);
// <div></div>
- createTextNode()
let textNode = document.createTextNode('Hi');
console.log(textNode);
// Hi
Using JavaScript Strings to Create and Add Element and Text Nodes to the DOM
innerHTML : The innerHTML property will convert HTML elements found in the string to actual DOM nodes. invokes a heavy and expensive HTML parser, whereas text node generation is trivial; thus, use innerHTML and friends sparingly.
textContent can only be used to construct text nodes. If you pass textContent a string containing HTML elements, it will simply spit it out as text. textContent gets the content of all elements, including script and style elements, but innerText does not.
innerText is aware of style and will not return the text of hidden elements, whereas textContent will.
<!DOCTYPE html>
<body>
<div id="A"></div>
<span id="B"></span>
<div id="C"></div>
<div id="D"></div>
<div id="E"></div>
</body>
<script>
//create a strong element and text node and add it to the DOM
document.getElementById('A').innerHTML = '<strong>Hi</strong>';
/* create a div element and text node to replace <span id="B"></div> (notice span#B is replaced) */
document.getElementById('B').outerHTML = '<div id="B"class="new">Whats Shaking</div>'
//create a text node and update the div#C with the text node
document.getElementById('C').textContent = 'dude';
//NON standard extensions below (i.e., innerText and outerText)
//create a text node and update the div#D with the text node
document.getElementById('D').innerText = 'Keep it';
/* create a text node and replace the div#E with the text node (notice div#E is gone) */ document.getElementById('E').outerText = 'real!';
</script>
console.log(document.body.innerhtml);
<div id="A"><strong>Hi</strong></div>
<div id="B" class="new">Whats Shaking</div>
<span id="C">dude</span>
<div id="D">Keep it</div>
real!
- insertAdjacentHTML() method : which only works on Element nodes, is a good deal more precise than the previously mentioned methods. Using this method, it’s possible to insert nodes before the beginning tag, after the beginning tag, before the end tag, and after the end tag. and insertAdjacentHTML options beforebegin and afterend will only work if the node is in the DOM tree and has a parent element.
<!DOCTYPE html>
<html lang="en">
<body>
<i id="elm">how</i>
<script>
var elm = document.getElementById('elm');
elm.insertAdjacentHTML('beforebegin', '<span>Hey-</span>');
elm.insertAdjacentHTML('afterbegin', '<span>dude-</span>');
elm.insertAdjacentHTML('beforeend', '<span>-are</span>');
elm.insertAdjacentHTML('afterend', '<span>-you?</span>');
console.log(document.body.innerHTML);
<span>Hey-</span><i id="A"><span>dude-</span>how<span>-are</span></i> <span>-you?</span>
</script>
</body>
</html>
- document.write() : can also be used to simultaneously create and add nodes to the DOM. However, it’s typically not used unless its usage is required to accomplish third-party scripting tasks. Basically, the write() method will output to the page the values passed to it during page loading/parsing. You should be aware that using the write() method will stall/block the parsing of the HTML document being loaded.
Extracting Parts of the DOM Tree as JavaScript Strings
<!DOCTYPE html>
<html lang="en">
<body>
<div id="A"><i>Hi</i></div>
<div id="B">Dude<strong> !</strong></div>
<script>
console.log(document.getElementById('A').innerHTML);
// <i>Hi</i>
console.log(document.getElementById('A').outerHTML);
// <div id="A">Hi</div>
console.log(document.getElementById('B').textContent);
console.log(document.getElementById('B').innerText);
console.log(document.getElementById('B').outerText);
// Dude !'
// Dude !'
// Dude !'
</script>
</body>
</html>
Using appendChild() and insertBefore() to Add Node Objects to the DOM
- appendChild() : method will append a node (or multiple nodes) to the end of the child node(s) of the node the method is called on. If there is no child node(s), the node being appended is appended as the first child.
<!DOCTYPE html>
<body>
<p>Hi</p>
<script>
//create a blink element node and text node
const elementNode = document.createElement('strong');
const textNode = document.createTextNode(' Dude');
//append these nodes to the DOM
document.querySelector('p').appendChild(elementNode);
document.querySelector('strong').appendChild(textNode);
console.log(document.body.innerHTML);
// <p> Hi
// <strong> Dude</strong>
// </p>
</script>
</body>
</html>
- insertBefore(the node to be inserted , the reference node in the document before which you would like the node inserted) : When it becomes necessary to control the location of insertion beyond appending nodes to the end of a child list of nodes, we can use insertBefore(). (If you do not pass a second parameter to the insertBefore() method, then it functions just like appendChild().)
<!DOCTYPE html>
<body>
<ul>
<li>2</li>
<li>3</li>
</ul>
<script>
//create a text node
const text1 = document.createTextNode('1');
//create li element node
const li = document.createElement('li');
// append the text to the li
li.appendChild(text1);
//select the ul in the document
const ul = document.querySelector('ul');
// add the li element we created above to the DOM,
// notice I call on <ul> and pass reference to <li>2</li> using ul.firstChild
ul.insertBefore(li,ul.firstChild);
console.log(document.body.innerHTML);
// <ul>
// <li>1</li>
// <li>2</li>
// <li>3</li>
// </ul>
</script>
</body>
</html>
sing removeChild() and replaceChild() to Remove and Replace Nodes
- removeChild() : Removing a node from the DOM is a bit of a multistep process. First you have to select the node you want to remove. Then you need to gain access to its parent element, typically by using the parentNode property. It’s on the parent node that you invoke the removeChild() method, passing it the reference to the node to be removed.
<!DOCTYPE html>
<body>
<div id="A">Hi</div>
<div id="B">Dude</div>
<script>
//remove element node
const divA = document.getElementById('A');
divA.parentNode.removeChild(divA);
//remove text node
const divB = document.getElementById('B').firstChild;
divB.parentNode.removeChild(divB);
//log the new DOM updates, which should only show the remaining empty div#B
console.log(document.body.innerHTML);
// <div id="B"></div>
</script>
</body>
</html>
- replaceChild() : Replacing an element or text node is not unlike removing one.
<!DOCTYPE html>
<body>
<div id="A">Hi</div>
<div id="B">Dude</div>
<script>
//replace element node
const divA = document.getElementById('A');
const newSpan = document.createElement('span');
newSpan.textContent = 'Howdy';
divA.parentNode.replaceChild(newSpan,divA);
//replace text node
const divB = document.getElementById('B').firstChild;
const newText = document.createTextNode('buddy');
divB.parentNode.replaceChild(newText, divB);
//log the new DOM updates
console.log(document.body.innerHTML);
// <span> Howdy</span>
// <div>buddy</div>
</script>
</body>
</html>
Depending on what you are removing or replacing, simply providing the innerHTML, outerHTML, and textContent properties with an empty string might be easier and faster. Be careful, however, as memory leaks in the browser might come back to haunt you.
Using cloneNode()
- cloneNode() : to duplicate a single node or a node and all its child nodes.
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<li>Hi</li>
<li>there</li>
</ul>
<script>
const cloneUL = document.querySelector('ul').cloneNode();
console.log(cloneUL.constructor);
// HTMLUListElement()
console.log(cloneUL.innerHTML);
//(an empty string) as only the ul was cloned
</script>
</body>
</html>
To clone a node and all its child nodes, you pass the cloneNode() method a parameter of true.
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<li>Hi</li>
<li>there</li>
</ul>
<script>
const cloneUL = document.querySelector('ul').cloneNode(ture);
console.log(cloneUL.constructor);
// HTMLUListElement()
console.log(cloneUL.innerHTML);
// <li>Hi</li>
// <li>there</li>
</script>
</body>
</html>
When cloning an Element node, all of its attributes and their values (including in-line events) are cloned as well. Anything added with ad dEventListener() or node.onclick is not cloned.
Grokking Node Collections (i.e., NodeList and HTMLCollection)
NodeList :[e.g., document.querySelector All(‘*’)]
HTMLCollection : (e.g., document.scripts)
Getting a List/Collection of All Immediate Child Nodes
Using the childNodes property produces an array-like list [i.e., NodeList] of the im‐ mediate child nodes.
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<li>Hi</li>
<li>there</li>
</ul>
<script>
const ulElementChildNodes = document.querySelector('ul').childNodes;
console.log(ulElementChildNodes);
// an array like list of all nodes inside of the ul
// NodeList(5)
// 0: text
// 1: li
// 2: text
// 3: li
// 4: text
// length: 5
// Call forEach as if it's a method of NodeLists so we can loop over the NodeList. Done because NodeLists are arraylike, but do not directly inherit from Array
Array.prototype.forEach.call(ulElementChildNodes,function(item){
console.log(item); //logs each item in the array
// <li>Hi</li>
// text
// ...
// <li>there</li>
// text
// ...
});
</script>
</body>
</html>
Converting a NodeList or HTMLCollection to a JavaScript Array
isArray() : Node lists and HTML collections are array-like but are not true JavaScript arrays, which inherit array methods. programmatically confirm this using isArray().
<!DOCTYPE html>
<html lang="en">
<body>
<a href="#"></a>
<script>
console.log(Array.isArray(document.links));
/* returns false, it's an HTMLCollection not an Array */
console.log(Array.isArray(document.querySelectorAll('a')));
/* returns false, it's a NodeList not an Array */
</script>
</body>
</html>
Converting a node list and HTML collection list to a true JavaScript array can provide several benefits.
it gives us the ability to create a snapshot of the list that is not tied to the live DOM, considering that NodeList and HTMLCollection are live lists.
converting a list to a JavaScript array gives access to the methods provided by the Array object (e.g., forEach, pop, map, reduce, and so on.).
- Converting a node list and HTML collection list to a true JavaScript array
<!DOCTYPE html>
<html lang="en">
<body>
<a href="#"></a>
<script>
console.log(Array.isArray(Array.prototype.slice.call(document.links)));
//returns true
console.log(Array.isArray(Array.prototype.slice.call(document.querySelectorAll('a'))));
//returns true
</script>
</body>
</html>
To convert an array-like list to a true JavaScript array we pass the array-like list to call() or apply(), in which the call() or apply() is calling a method that returns an unaltered true JavaScript array. In the following code, I use the .slice() method, which doesn’t really slice anything; I am just using it to convert the list to a JavaScript Array since the slice() returns an array.
Traversing Nodes in the DOM
From a node reference (i.e., document.querySelector(‘ul’)), it’s possible to get a dif‐ ferent node reference by traversing the DOM using the following properties:
- parentNode :
- firstChild :
- lastChild :
- nextSibling :
- previousSibling :
<!DOCTYPE html>
<html lang="en">
<body>
<ul><!-- comment -->
<li id="A"></li>
<li id="B"></li>
<!-- comment -->
</ul>
<script>
//cache selection of the ul
const ul = document.querySelector('ul');
//What is the parentNode of the ul?
console.log(ul.parentNode.nodeName);
// body
//What is the first child of the ul?
console.log(ul.firstChild.nodeName);
// comment
//What is the last child of the ul?
console.log(ul.lastChild.nodeName);
/* text not comment, because there is a line break */
//What is the nextSibling of the first li?
console.log(ul.querySelector('#A').nextSibling.nodeName);
//text
//What is the previousSibling of the last li?
console.log(ul.querySelector('#B').previousSibling.nodeName);
// text
</script>
</body>
</html>
If you are familiar with the DOM, you should not be surprised that traversing the DOM includes traversing not just element nodes, but also text and comment nodes, and this is not exactly ideal. Using the following properties we can traverse the DOM, while ignoring text and comment nodes:
- firstElementChild
- lastElementChild
- nextElementChild
- previousElementChild • children
- parentElement
- childElementCount : calculating the number of child elements a node contains.
<!DOCTYPE html>
<html lang="en">
<body>
<ul><!-- comment -->
<li id="A"></li>
<li id="B"></li>
<!-- comment -->
</ul>
<script>
//cache selection of the ul
var ul = document.querySelector('ul');
//What is the first child of the ul?
console.log(ul.firstElementChild.nodeName);
// li
//What is the last child of the ul?
console.log(ul.lastElementChild.nodeName);
// li
//What is the nextSibling of the first li?
console.log(ul.querySelector('#A').nextElementSibling.nodeName);
// li
//What is the previousSibling of the last li?
console.log(ul.querySelector('#B').previousElementSibling.nodeName);
// li
//What are the element only child nodes of the ul?
console.log(ul.children);
//HTMLCollection(2)
//all child nodes including text nodes
//What is the parent element of the first li?
console.log(ul.firstElementChild.parentElement);
// ul
</script>
</body>
</html>
Verifying a Node Position in the DOM Tree with contains()
- contains() :
<!DOCTYPE html>
<html lang="en">
<body>
<script>
// is <body> inside <html lang="en"> ?
const inside = document.querySelector('html').contains(document.querySelector('body'));
console.log(inside);
//true
const same = document.querySelector('body').contains(document.querySelector('body'));
console.log(same);
// true
// contains() will return true if the node selected and the node passed in are identical.
</script>
</body>
</html>
Determining Whether Two Nodes Are Identical
- isEqualNode()
<!DOCTYPE html>
<html lang="en">
<body>
<input type="text">
<input type="text">
<textarea>foo</textarea>
<textarea>bar</textarea>
<script>
var input = document.querySelectorAll('input');
console.log(input[0].isEqualNode(input[1]));
//logs true, because they are exactly identical
var textarea = document.querySelectorAll('textarea');
console.log(textarea[0].isEqualNode(textarea[1]));
//logs false, because the child text node is not the same
console.log(textarea[0] === textarea[1])
// false
</script>
</body>
</html>
If you don’t care about two nodes being exactly equal, and instead you want to know whether two node references refer to the same node, you can simply check them using the === operator (i.e., document.body === document.body). This will tell you if they are identical but not equal.