JavaScript: DOM Manipulations

DOM

Document Object Model

DOM - Document Object Model
A tree of nodes. A tree-like representation of the content of a webpage.
selectors
div, .display, #container
relationship properties
>, +
relational selectors
firstElementChild, lastElementChild, previousElementSibling
eg. container.firstElementChild = first child of #container.

DOM methods

Query Selectors
element.querySelector(selector)
element.querySelectorAll(selector)
querySelector()
Return the first match of selector
querySelectorAll()
Return a "nodelist" containing references to all matches of selector
  • Nodelist looks like an array, it behaves somewhat like an array, but it's a nodelist, not an array.
  • The biggest difference is that some array methods are not applicable to a nodelist.
  • If you do want to operate an array method that can't be used for a nodelist, you can first change the nodelist into an actual array using Array.from() or the spread operator (...).
Create Elements
document.createElement(tagName, [options])

document.createElement("div")       // Example
createElement()
It creates the element in memory, it doesn't put it in DOM until instructed.
Do manipulation to the element suchs as adding id, class, text, styles, etc before placing it to the DOM.
Append Elements
parentNode.appendChild(childNode)
parentNode.insertBefore(newNode, referenceNode)
appendChild()
Appends childNode as the last child of parentNode
insertBefore()
Insert newNode into parentNode before referenceNode
Remove Elements
parentNode.removeChild(child)
removeChild()
Removes child from parentNode on the DOM and returns a reference to child.

Altering elements

Altering Elements
const myDiv = document.createElement("div")
Reference to an element, "myDiv" in example above, allow us to alter element's properties, for example:
  • adding, removing, altering attributes
  • changing classes
  • adding inline style information
Adding inline style
myDiv.style.color = "blue";
myDiv.style.cssText = "color: blue; background: white;";
myDiv.setAttribute("style", "color: blue; background: white;");
For CSS rule in kebab-case like "background-color", we can't use it as is after a period. It needs to be changed into camelCase, or use bracket notation instead of dot notation. It can be used as is using quotation.
myDiv.style.backgroundColor = "blue";
myDiv.style["background-color"] = "blue";
myDiv.style.cssText = "background-color: blue;";
Read more: DOM Enlightenment: 6.2 Getting, setting, & removing individual inline CSS properties
Editing attributes
myDiv.setAttribute(id',"myDiv");
myDiv.getAttribute(id');
myDiv.removeAttribute(id');
setAttribute()
Create or update the specified attribute with the specified value.
getAttribute()
Returns value of the specified attribute.
getAttribute()
Removes the specified attribute.
Read more: MDN: HTML attribute reference
Working with classes
myDiv.classList.add("className");
myDiv.classList.remove("className");
myDiv.classList.toggle("className");
classList.add()
Add a class with the specified name.
classList.remove()
Remove a class with the specified name.
classList.toggle()
If the element doesn't have class with the specified name yet, add it. If it does, remove it.
It's often standard and cleaner to toggle a css style rather than adding and removing inline CSS.
Adding text content
myDiv.textContent = "Howdi!";

// before:  <div id="myDiv"></div>
// after:   <div id="myDiv">Howdy!</div>
Creates a "text node" and insert it in the element.
Adding HTML content
myDiv.innerHTML = "<span>Howdi!</span>";

// before:  <div id="myDiv"></div>
// after:   <div id="myDiv"><span>Howdi!</span>!</div>
Renders HTML inside the element.
textContent is more preferable than innerHTML for adding text. innerHTML should be used only when needed and with caution because can create security risk if misused. Check out: Script injection.

Placing <script> element

<script>
<script src="scriptFileName.js" defer></script>
  • Location: Inside the head or body. MDN prefers head.
  • src: the path to the JavaScript
  • defer: instructs the browser to load the JavaSript after the page has finished parsing the HTML.
  • Closing tag: must be included. Even though it looks like an empty element, it is not.
  • The script can be kept in external js file and addressed using src, or just written between the bracket.

DOM Exercise

Task
Have a div container with id "container".
Using script:
  • a <p> with red text that says “Hey I’m red!”
  • an <h3> with blue text that says “I’m a blue h3!”
  • a <div> with a black border and pink background color with the following elements inside of it:
    • another <h1> that says “I’m in a div”
    • a <p> that says “ME TOO!”
    • Hint for this one: after creating the <div> with createElement, append the <h1> and <p> to it before adding it to the container.
Exercise container
Code:
<div id="container"></div>
<script defer>
    const container = document.querySelector("#container");

    const para1 = document.createElement("p");
    para1.style.color = "red";
    para1.textContent = "Hey, I'm red!";
    container.appendChild(para1);

    const header3 = document.createElement("h3");
    header3.style.color = "blue";
    header3.textContent = "I'm a blue h3!";
    container.appendChild(header3);

    const div1 = document.createElement("div");
    div1.style.cssText = "border: solid 1px black; background-color: pink;";

    const header1 = document.createElement("h1");
    header1.textContent = "I'm in a div";
    div1.appendChild(header1);

    const para2 = document.createElement("p");
    para2.textContent = "ME TOO!";
    div1.appendChild(para2);

    container.appendChild(div1);
</script>

Events

Event
Examples:
  • click
  • dblclick
  • keydown
  • keyup
More events: w3schools: HTML DOM Events
Handler
A function that runs in case of an event.
3 methods to assign event handlers:
  • HTML attribute
  • DOM property
  • Add event listener
Method 1: Event as an HTML attribute
HTML
<!-- Function call -->
<button onclick="funcName()">Click me</button>

<!-- Method call -->
<button onclick="alert('Hi')">Click me</button>
Example:
<!-- HTML -->
<button onclick="alert('Hello from Button 1')">Button 1</button>

Method 2: Event as a DOM Property
HTML
<button id="idName">Click me</button>
JavaScript
const elName = document.querySelector("idName");

<!-- Function name -->
elName.onclick = funcName;

<!-- Function described on spot -->
elName.onclick = () => {alert("Hi")};
Example:
<!-- HTML -->
<button id="button2">Button 2</button>

// JavaScript
const button2 = document.querySelector("#button2");
button2.onclick = () => {alert("Hello from Button 2")};


Method 3: Using event listener
This is a main topic by itself. It's covered in the next section.

Event Listener

addEventListener()
HTML
<button id="idName">Click me</button>
JavaScript
const elName = document.querySelector("idName");

<!-- Function name -->
elName.addEventListener("click", funcName);
elName.removeEventListener("click", funcName);

<!-- Function described on spot -->
elName.addEventListener("click", () => {alert("Hi")});
Example:
<!-- HTML -->
<button id="button3">Button 3</button>

// JavaScript
const button3 = document.querySelector("#button3");
button3.addEventListener("click", () => {alert("Hello from Button 3");});


removeEventListener()
  • removeEventListener() can remove functionName that is previously added using addEventListener().
  • It only works when function is addressed using a name in the addition. Removal is done by addressing this name.
  • removeEventListener() can't remove function description. Because it's not an entity.
Event listener with function callback that refers itself
JavaScript
elName.addEventListener("click", function (e) {codeblock});
elName.addEventListener("click", (e) => {codeblock});
  • function (e) here is a callback from addEventListener.
  • e is for "event": e here is an object that references the event itself. Within this object we have access to many properties and functions. For examples:
    • which mouse button or key was pressed
    • information about the event's target (the DOM node/element that was clicked)
  • this = e.target = How to address event's target element elName
Example:
<!-- HTML -->
<button id="button4">Button 4</button>

// JavaScript
const button3 = document.querySelector("#button4");
button4.addEventListener("click", () => {alert("Hello from Button 4");});
button4.addEventListener("click", (e) => {alert(`This is an ${e}`);});
button4.addEventListener("click", function (e) {
    alert("Your button will turn pink.");
    e.target.style.background = 'pink';
    this.textContent = "Pink Button";
});

// Note that this = e.target = addresing button4



Selector All

Using querySelectorAll() to create a group of nodes

Group of nodes
HTML
<div id="divName">
    <button id="button1">Click 1</button>
    <button id="button2">Click 2</button>
    <button id="button3">Click 3</button>
</div>
JavaScript
const groupName = divName.querySelectorAll("button");

groupName.forEach((member) => {
    member.addEventListener("click", (e) => {
        alert(member.id);
        e.target.style.background = "pink";
    });
});
Example:
<!-- HTML -->
<div id="container2" style="border: 3px solid silver; padding: 1rem;">
    <p>Inside container2</p>
    <button id="button5">Button 5</button>
    <button id="button6">Button 6</button>
    <button id="button7">Button 7</button>
</div>
<p>Outside container2</p>
<button id="button8">Button 8</button>

// JavaScript
const group = container2.querySelectorAll("button");
group.forEach(btn) = > {
    btn.addEventListener("click", (e) => {
        alert(`You pressed ${btn.id}`);
        e.target.style.background = "pink";
    });
});

Inside container2

Outside container2


Event capture, propagation, and bubbling

Check out WesBos video on the topic: Youtube: JS Event Capture, Propagation, and Bubbling

An element is contained inside their parents elements. It could be directly the body, or could be deeply nested within a structure of elements. When we clicked an element in a webpage, really we actually also clicked on all those ancestor elements wrapped around it. We don't usually see this effect because in most use, we don't attach an event listener to those wrapping elements. This is bubbling concept. The browser see the click not just in the very element, but it is bubbling up. It ripples all the way to the outter most element. To the top of the document.

one
two
three
<body>
    <div class="one">
        <div class="two">
            <div class="three">
            </div>
        </div>
    </div>
</body>
<script>
    const divs = document.querySelectorAll("div");
    function logText(e) {
        console.log(this.classList.value);
    }
    divs.forEach(div => div.addEventListener("click", logText));
</script>

How does it actually work in Javascript/modern browser?

Capturing in
The browser is first doing what is called capture. When you click on the element, it will ripple down from the outter most element inward all the way to the bottom or inner most element, capturing all the events.
body > div 1 > div 2 > div 3
At that moment, the events are not fired yet. Just captured.
Console log: (none)
Bubbling out
From the bottom, it will start bubbling out, firing the event from innermost element outward.
div 3 < div 2 < div 1 < body
This is the default behaviour. This is expressed (commonly omitted) in the third argument of the addEventListener function as {capture: false}. It doesn't need to be written down because already assumed as default.
el.addEventListener('click', fn, {capture: false})
el.addEventListener('click', fn)
Console log:
  • three
  • two
  • one

Manipulations

Capture: true
If we want to reverse the order of event firing, we can make the event actually fired as capturing actually happening, from outward in.
el.addEventListener('click', fn, {capture: true})
Console log:
  • one
  • two
  • three
Once: true
An event can be listened and fired as many times as as being triggered. This default (commonly omitted) is expressed in the third parameter as {once: false}. If we only want to listen it once, we can set it to true
el.addEventListener('click', fn, {once: true})
once is a new property. Other way to make an event only listened once is by removing the event listener: removeEventListener
Stop Propagation
Propagation refers to reading the nesting containers involved. When we only want to read just one, use el.stopPropagation(). It will stop once a container is read. It can be used in conjunction with capture: true to make the read container is the widest scope one.
Examples - check the youtube video
<script>
    const divs = document.querySelectorAll("div");

    function logText(e) {
        console.log(this.classList.value);
        e.stopPropagation();
    }

    divs.forEach(div => div.addEventListener("click", logText, {
        capture: true, 
        once: true
    }));
</script>