HTML DOM

# HTML DOM

When a web page is loaded, the browser creates a Document Object Model (DOM) of the page.

# What is DOM

DOM is the programming interface for web documents. It represents the page so that a program (usually in JavaScript) can change the document structure, style, and content.

Document

  • Entire web page (HTML or XML)

Object

  • Every part of web page is converted to an object

Model

  • Tree structure used to represent relationships between these objects

The DOM itself is not part of JavaScript language; it is a Web API that can be used to access and modified web pages.

# DOM data types

# Node types

  1. Element Nodes: These represent HTML or XML tags in the document. For example, <div>, <span>, <a> etc.

  2. Text Nodes: These contain textual data and are always leaf nodes, meaning they do not have children.

  3. Attribute Nodes: These represent attributes of elements, like class, id, href etc.

  4. Document Node: This is the root node that represents the entire document.

  5. Comment Nodes: These represent comments in the HTML/XML document.

  6. Document Fragment Nodes: These are essentially lightweight Document nodes that hold a part of the Document tree. They are useful for offscreen manipulations.

# Node properties

Each node in the DOM tree can be identified by a node type. JavaScript uses integer number to determine the node types.

For example, 9 is the value of document node and 1 is for element node.

We can use nodeType property on any node to get the type of node.

node.nodeType; // 1

Other important node properties include nodeName and nodeValue.

The values of these properties depend on the node type. For example, when the node type is the element node, the nodeName is the element's tag name and nodeValue is null.

if (node.nodeType === Node.ELEMENT_NODE) {
    console.log(node.nodeName); // "button" for a <button> element
    console.log(node.nodeValue); // null
}

# Node vs. Element

It's easy to confuse these two.

Node is the generic name of any object in the DOM tree.

Element is a specific type of Node with nodeType of 1.

node_vs_element

# Selecting elements

# Select element by id

Looking for the first element with the specific attribute id.

If element found, returns an Element, else return null.

const element = document.getElementById(id);

For example

<html>
    <head>
        ...
    </head>
    <body>
        <p id="message">A paragraph</p>
    </body>
</html>

The document contains a <p> element that has an id attribute with value message.

const p = document.getElementById("message");
console.log(p); // <p id="message">A paragraph</p>

# Select elements by name

Every element on an HTML document may have a name attribute:

<input type="radio" name="language" value="JavaScript" />
<input type="radio" name="language" value="TypeScript" />

Unlike id, name can be non-unique. Multiple HTML elements can have the same name value.

To find all elements with a specific name, we can use

const elements = document.getElementsByName(name);

This will return a NodeList of elements.


# Select elements by tag name

The getElementsByTagName() method accepts a tag name and returns a live HTMLCollection of elements with the matching tag name in the order which they appear in the document.

<p>
    <h1>Heading 1</h1>
    <h2>Heading 2</h2>
    <h2>Heading 2.2</h2>
</p>
const elements = document.getElementsByTagName("h2");

# Select elements by class names

The getElementsByClassName() method takes a class name and returns an array-like live HTMLCollection of elements with matching class="name".

<div class="container">content...</div>
<div class="container">content...</div>
const elements = document.getElementsByClassName("container");

# Select elements by CSS selectors

We are going to cover querySelector() and querySelectorAll() method here as well as the ways to construct the CSS selectors.

const element = parentNode.querySelector(selector); // return single Element
const elements = parentNode.querySelectorAll(selector); // return NodeList of Elements

To convert a NodeList to an array, we can sue Array.from() method.

const nodeList = document.querySelectorAll(selector);
const elements = Array.from(nodeList);

# CSS Selectors

  1. Universal selector *: match all
  2. Type selector tagName: matching html tag name
  3. Class selector .className: matching class name
  4. ID selector #id: matching id (should be unique)
  5. Attribute selector [attribute]
    • [attribute=value] exact match
    • [attribute^=pre] prefix match
    • [attribute$=post] suffix match
    • [attribute~=substring] substring match

# Grouping selectors

  • selector1, selector2, selector 3
  • Use comma to separate, the selector list will match any element with at least one of the selectors in the group.

# Selector combinator

  1. Descendent combinator selector selector

    • Use space, i.e. p a will match all <a> elements inside <p> element.
    • let links = document.querySelector("p a");
      
  2. Child combinator selector > selector

    • Finds all elements that are direct children of the first element.
  3. General sibling combinator selector ~ selector

    • Finds siblings that share the same parent.
    • // this will match all <a> elements follows <p>, immediately or not
      let links = document.querySelectorAll("p ~ a");
      
  4. Adjacent sibling combinator selector + selector

    • Matches elements directly follows the preceding element
    • For example, h1 + a matches all <a> elements that directly follow an h1.
    • let links = document.querySelectorAll("h1 + a");
      

# Pseudo

  1. Pseudo-classes The : pseudo matches elements based on their states:

element:state

For example, the li:nth-child(2) selects the second <li> element in a list:

let listItem = document.querySelectorAll("li:nth-child(2)");

Other commonly used pseudo-selectors

  • :nth-last-child(2) count from the last child backwards.
  • :first-child and :last_child first and last child of a parent element.
  • :not(selector) excludes elements matching specified selector.
  • :hover, :active, :focus match based on user interaction.
  • :empty match elements that have no children.
  • :root highest-level parent element, usually <html>.
  1. Pseudo-elements The :: represent entities that are not included in the document.

For example, p::first-line matches the first line of all p elements:

let links = document.querySelector("p::first-line");

# Traversing elements

# Find parent element

const parent = node.parentNode;

The parentNode is read-only.

The document and DocumentFragment do not have a parent node. (null)

# Finding sibling elements

To find the next sibling of an element, use nextElementSibling attribute.

To find the previous sibling of an element, use previousElementSibling attribute.

const nextSibling = currentNode.nextElementSibling;
const prevSibling = currentNode.previousElementSibling;

# Finding child elements

To find the first child element of a specified element, use firstChild property.

To find the last child element, use lastChild property.

To find all the children, use childNodes property which returns a NodeList of all child elements with any node type.

To find all the child element nodes, use children property which returns a live HTMLCollection of all child nodes with Element type.

const firstChild = currentNode.firstChild;
const lastChild = currentNode.lastChild;
const childrenNodes = currentNode.childNodes;
const childrenElements = currentNode.children;

# Manipulating elements

# Create an element

To create an HTML element, we use document.createElement method.

const element = document.createElement(htmlTag);

The document.createElement() accepts an HTML tag name and returns a new Node with Element type.

For example, let's create a new <div> element.

let div = document.createElement("div");
div.innerHTML = "<p>Hello World</p>";
div.id = "content";
div.className = "greeting";

// after creating the div, we need to attach it to an existing element
document.body.appendChild(div);
  • Use appendChild() method to add a node to the end of the list of child nodes of a specified parent node.
  • The appendChild() can be used to move an existing child node to the new position within the document.

# Modify innerHTML

The innerHTML is a property of the Element that allows us to get or set the HTML markup contained within the element.

const content = element.innerHTML;

If we get the innerHTML of an element, the browser will serialize the HTML fragment of that element's descendants. The content reflects the latest HTML source, including all changes that have been made since the page was loaded.

For example, Suppose that you have the following markup:

<ul id="menu">
    <li>Home</li>
    <li>Services</li>
</ul>

The following example uses the innerHTML property to get the content of the <ul> element:

let menu = document.getElementById('menu');
console.log(menu.innerHTML);

// output
<li>Home</li>
<li>Services</li>

To set the value of innerHTML property,

element.innerHTML = newHTML;

// clear the body
document.body.innerHTML = "";

!!! Security risks: HTML5 specifies that a <script> tag inserted with innerHTML should not execute. However, cross-site scripting (XSS) can still occur if we do not have control over what we insert with innerHTML. For example,

const main = document.getElementById("main");

const externalHTML = `<img src='1' onerror='alert("Error loading image")'>`;
// shows the alert
main.innerHTML = externalHTML;

Here we try to insert an <img> tag but it will error out since the src is invalid. Then the onerror function will be run. Instead of a simple alert, hackers could include malicious code there and initiate an attack.

Therefore, we should not set any content that we have no control to the innerHTML. If we want to insert plain text into document, we can use textContent property instead which will be parsed as raw text instead of html.

# Reading and setting text

We can use either textContent or innerText property.

textContent returns the concatenation of the textContent of every child node.

innerText takes the CSS style into account (something with display:none will not return) and returns only human-readable text (comment not returned).

const text = node.textContent;
const text = node.innerText;

Setting textContent will overwrite all the node's children and replace with a single text node.

node.textContent = newText;

# Inserting nodes

  • Insert one or more nodes before the element, use element.before(node1, node2, ...).
  • Insert one or more nodes after the element, use element.after(node1, node2, ...).
  • Insert one or more nodes after the last child of a parent node, use parentNode.append(...nodes).
  • Insert one or more nodes before the first child of a parent node, use parentNode.prepend(...nodes).
  • Insert a node before another node as a child node of a parent node, use parentNode.insertBefore(newNode, existingNode).
  • Insert a node after another node as a child node of a parent node, use parentNode.insertAfter(newNode, existingNode). Using existingNode.after(newNode) accomplishes the same thing.

# DocumentFragment interface

The lightweight version of the Document that is not part of the active DOM tree. This means, any changes made to the document fragment is not reflected in the current document and won't affect performance.

The typical use case is to compose DOM nodes first using DocumentFragment and insert it into the active DOM tree to get better performance.

const fragment = new DocumentFragment();
// or
const fragment = document.createDocumentFragment();

// then construct DOM nodes
let li = document.createElement("li");
li.innerText = "Hello";
fragment.appendChild(li);
document.body.appendChild(fragment);

# Managing attributes

When the web browser loads a page, it generates the corresponding DOM objects based on the DOM nodes of the document.

For example, if a page contains an input element:

<input type="text" id="username" />

The web browser will generate an HTMLInputElement object which has the corresponding properties type and id.

The browser will only convert standard attributes to object properties.

Attribute method

  • element.getAttribute(name)
  • element.setAttribute(name, value)
  • element.hasAttribute(name)
  • element.removeAttribute(name)

Data attribute If we were to add a custom attribute for an element, we should try to prefix it with data-, e.g. data-testid, data-status. All attributes that start with data- are reserved for developer use cases.

To access data-* attributes, we can use the dataset property.

For example, we have the following div element with custom attributes:

<div id="main" data-progress="pending" data-value="10%"></div>

Then we can access the attribute via the dataset property.

const bar = document.querySelector('#main');
console.log(bar.dataset);

// output
[object DOMStringMap] {
    progress: "pending",
    value: "10%",
}

# Working with styling

# Setting inline styles

To get the inline style of an element, we can use the style property.

element.style;

// we can modify the inline style
element.style.color = "orange";

The style property returns a read-only CSSStyleDeclaration object that contains a list of CSS properties.

To completely override the existing inline style, we can set the cssText property of the style object.

element.style.cssText = "color:red;background-color:yellow";
// or
element.setAttribute("style", "color:red;background-color:yellow");
// or adding new styles
element.style.cssText += "color:red;background-color:yellow";

# Getting computed styles

CSS properties may change upon user's interaction with the element. For example, when user hover overs an element, we attach pseudo element :hover on the element so that the element's style may change for the hover state. We can get the modified style with getComputedStyle(element, pseudoElement) method of the window object.

const element = document.querySelector("button");
const style = window.getComputedStyle(element, ":hover");

The method would return a live style object which is an instance of the CSSStyleDeclaration object.

# Working with CSS classes

className returns a space-separated list of classes of an element as a string.

<div id="hi" class="hello world" />;
const element = document.querySelector("#hi");
console.log(element.className); // hello world
// we can add to this className by
element.className += newClassName; // prefix newClassName with space

The classList is a read-only property of an element that returns a live collection of CSS classes. We can modified the CSS classes of an element with this interface.

const classes = element.classList;

classList methods

  1. add(className) and remove(className)
  2. replace(oldClass, newClass)
  3. contains(className)
  4. toggle(className): If class list contains the className, remove it; otherwise, add it