Manipulating the DOM: Creating elements, setting attributes, appending children

Manipulating the DOM: creating elements

In this slide deck, we are going to see what it means to "create elements" on the web page, thereby directly manipulating the DOM using the document.createElement() method.

Manipulating the DOM: document.createElement()

Definition:

Creates an HTML element on the page specified by tagName, or an HTMLUnknownElement if tagName is not recognized. Make it a habit, however, of creating recognized elements only!

Syntax:


const element = document.createElement(nodeName);
                

Parameters:

nodeName (type String): Required. The name of the element you want to create.

Return value: a new Element.

Code Example:


function createNode(element) {
    return document.createElement(element);
}
                

The above code is a helper function taken from the Monsters API App.

You may have noticed that there are no quotes around the element argument passed in to document.createElement(). There ARE quotes around the value of element when the createNode() function is called:


let li = createNode('li');
                

You can also remove elements from the DOM with the .remove() method.

To learn more, please visit ChildNode.remove() on MDN.

Manipulating the DOM: Element.setAttribute()

Definition:

This method sets the value of an attribute on the specified element. If the attribute already exists, the value is updated. Otherwise, a new attribute is added with the specified name and value.

Syntax:


Element.setAttribute(name, value);
                

Parameters:

name: A DOMString specifying the name of the attribute whose value is to be set. The attribute name is automatically converted to all lower-case when setAttribute() is called on an HTML element residing in an HTML document.

value: A DOMString containing the value to assign to the attribute. Any non-string value specified is automatically converted into a string.

Return value: undefined.

Exceptions:

InvalidCharacterError: The specified attribute name contains one or more characters which are not valid in attribute names.

You can also get attributes with the .getAttribute() method and remove attributes with the .removeAttribute() method.

To learn more, please refer to the Related Resources slide at the end of this slide deck.

Code Example:


img.setAttribute('src', `${imageUrl}`);
                

This code was taken from the Week 10 Homework's .md file. This is a good example of how the value of the src attribute can be dynamic too.

Manipulating the DOM: Node.appendChild()

Node.appendChild() is a method of the Node interface.

The Node.appendChild() method adds a node to the end of the list of children of a specified parent node. If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position.

This means that a node can't be in two points of the document simultaneously. So if the node already has a parent, the node is first removed, then appended at the new position.

Syntax:


element.appendChild(aChild)
                

Parameters:

aChild:

The node to append to the given parent node (commonly an element).

Return value:

The returned value is the appended child (aChild).

Code Example:


document.body.appendChild(downloadLink.download);
                

The above code is taken from our Note App Project 7.

Code Example:


function append(parent, el) {
    parent.appendChild(el);
}
                

This code is taken from our Monsters Search API Project 8 application.

In essence, the createElement() and appendChild() methods contribute to the extension of the DOM Tree.

Manipulating the DOM: the Node Interface

What is the Node interface? It is an interface that is implemented by a large number of objects, including document, and element, for example.

A node, then, in the context of the DOM, means any object that implements the Node interface. Usually it is an element object representing an HTML element.

Manipulating the DOM: HTML parsing

In the slide deck An Introduction to JavaScript and the DOM, I discuss the HTML Parser, which is implemented by the browser, and is responsible for the encoding, pre-parsing, tokenization, and tree formation of the DOM.

The WHATWG (Web Hypertext Application Technology Working Group) best describes this process in their HTML Living Standard:

Overview of the parsing model: a stream of code points is input into the HTML parsing process, and passed through a tokenization stage, followed by a tree construction stage. The output is a Document object.

Manipulating The DOM: tokenization

In Introduction to JavaScript and the DOM, I discuss tokenization.

Tokenization refers to the process by which a stream of code points are transformed from HTML markup (what you create in the Code Editor) into individual tokens such as a "begin tag", "opening tag", "end tag", etc.

Manipulating The DOM: tokenization

Manipulating the DOM: code points

code point: refers to the numbers assigned to characters that are needed for a specific purpose (i.e., HTML text), and are grouped into a specific character set (also called a repertoire). You may also know character set as "charset". The associated html tag is called meta charset. The character set used in html pages is "utf-8".

To display an HTML page correctly, a web browser must know which character set to use. That's why the meta charset tag is so important.

The default character set for HTML5 is UTF-8.

UTF-8 (Unicode) covers almost all of the characters and symbols in the world.

In essence, words and sentences in text are created from such characters associated with specific code points. Generally, however, the default these days is UTF-8.

To learn more, please refer to the Related Resources slide at the end of this slide deck.

Manipulating The DOM: construction of the DOM Tree

When a token is produced, it must immediately be handled by the tree construction stage.

The input to the tree construction stage is a sequence of tokens from the tokenization stage.

The tree construction stage is associated with a DOM Document object when a parser is created.

The output of this stage consists of dynamically modifying or extending that Document's DOM Tree.

In essence, the DOM represents HTML as a tree structure of tags.

Manipulating The DOM: construction of the DOM Tree

Manipulating the DOM: Document.createTextNode()

All viewable HTML text in a web page (except text in form elements or custom embedded objects) is in text nodes.

Web pages consist of a number of different types of nodes. Some have child nodes and some don't.

To learn more about the different node types, please visit Node.nodeType on MDN.

Manipulating the DOM: document.createTextNode() breakdown

Definition:

Creates a Text Node with the specified text. HTML elements often consist of both an element node and a text node.

Let's say you want to create a header element that contained text. You would have to create both an h1 element and a text node.

Syntax:


document.createTextNode(text);
                

Parameters:

text (required): of type String. It is the text of the Text Node.

Return value: A Text Node object with the created Text Node.

As we went over earlier, first we would use the .createElement() method to create an Element Node using the specified element name.

After creating the Element Node, we would create a Text Node with the specified text for the Element Node. In this case, we would create a Text Node for the h1 Element Node we just created using document.createTextNode().

Finally, after creating the text node for the h1 Element Node, we would use the Element.appendChild() method (or the Element.insertBefore() method) to append the Node Element to a Parent Element.

Example Code:


const para = document.createElement("p");
const paraTextNode = document.createTextNode(`I was working in the lab, late one night
When my eyes beheld an eerie sight
For my monster from his slab, began to rise
And suddenly to my surprise...`);
para.appendChild(paraTextNode);
document.body.appendChild(para);
                

In more modern browsers, you could achieve the same using either the .innerHTML property or the .textContent property.

Manipulating the DOM: textNode vs innerHTML

When you set the .innerHTML property of an Element Node, i.e., li, it creates the appropriate nodes and makes them child nodes of the element that you set the .innerHTML property on.

If there is text in the .innerHTML you set, then text nodes will be created to hold that text.

Code Example:


const wrapperContainer = document.querySelector(".wrapper-container");
const newSection = document.createElement("section");
// append newSection to wrapperContainer
wrapperContainer.appendChild(newSection);
// create p tag
const newPara = document.createElement("p");
newSection.appendChild(newPara);
const newContent = document.createTextNode(
"I just created my first HTML elements!"
);
newPara.appendChild(newContent);
                

Manipulating the DOM: when textNode is preferable over innerHTML

Usually one would not want to manipulate text nodes directly using createTextNode(), i.e., since the same can be achieved using .innerHTML.

On the other hand, you may want to display some text without the security risks that it might contain other markup that the browser would parse and interpret if you used .innerHTML.

So, you create a Text Node and set the value of its text, and the browser won't interpret any HTML in it.

Modern browsers can also use the .textContent property to solve the same problem as well.

To learn more, please visit What Is A Text Node, Its Uses? //document.createTextNode() on stackoverflow.

Manipulating The DOM: .textContent vs .innerHTML

We've already seen .innerHTML in action, and we have seen and used examples of .textContent along the way. They basically do the same thing - replace what is between the opening and closing tag of an element.

.textContent only does that with text content.

On the other hand, .innerHTML takes both text and markup into consideration.

Manipulating the DOM in action


function addElement() {
    document.body.setAttribute("class", "Site");
    document.body.setAttribute("id", "Site");
    // create the outer container element
    const outerContainer = document.createElement("div");
    outerContainer.setAttribute("class", "Site-content");
    document.body.appendChild(outerContainer);
    // create wrapper container that contains all the other elements
    // all tags bound in the same way to wrapperContainer either directly
    // or through parent.
    const wrapperContainer = document.createElement("div");
    // append wrapperContainer to body
    document.body.appendChild(wrapperContainer);
    // create wrapperContainer class
    wrapperContainer.classList.add("wrapper-container");
    // create app title
    const manipulateDOM = document.createElement("h1");
    // set id attribute
    manipulateDOM.setAttribute("id", "manipulate-dom");
    // set manipulateDOM innerHTML
    manipulateDOM.innerHTML = `Welcome to my DOM manipulation site!`;
    // append manipulateDOM to wrapperContainer
    wrapperContainer.appendChild(manipulateDOM);
    // create new section element
    const newSection = document.createElement("section");
    // append newSection to wrapperContainer
    wrapperContainer.appendChild(newSection);
    // create p tag
    const newPara = document.createElement("p");
    newSection.appendChild(newPara);
    const newContent = document.createTextNode(
    "I just created my first HTML elements!"
    );
    newPara.appendChild(newContent);
    // add new elements and their content to the DOM
    const bodySection = document.getElementById("Site");
}
document.body.onload = addElement;
                

Manipulating the DOM: recap

In the previous slide, we created some new elements and manipulated the DOM with the following methods:

document.createElement('tagName')

document.setAttribute('attrName', 'value')

document.appendChild(childIdentifierName)

document.createTextNode('Some text')

document.body.onload

Other methods (and properties) used were:

document.getElementById('idName') - method

.innerHTML - property

classList.add('className') - classList is a property, .add() is a built-in method.

Removing/Adding Attributes revisited

Removing/Adding Attributes: code

JavaScript:


function toggleSrc() {
    let image = document.getElementById('image');
    const hiddenCan = 'Hidden_Can.png';
    const visibleCan = 'Visible_Can.png';
    if (image.getAttribute('src') === hiddenCan) {
        image.setAttribute("src", visibleCan);
    } else {
        image.setAttribute("src", hiddenCan);
    }
}
const imgBtn = document.getElementById('imageBtn');
imgBtn.addEventListener('click', toggleSrc);
                

HTML:


Creating elements and the load event

Did you ever notice how sometimes a web page takes such a long time to load, or the page seems to have to be refreshed a number of times before any of the content comes into view?

This is especially the case when there is a lot of styling involved, images to load, and even APIs to fetch, which might be populating your images as well as data content within your document.

The reason why I bring this up in this particular slide deck, is because in Project 8, the Monsters API App, is all about creating elements.

The only thing we already have in index.html inside of the body element is a ul element. Everything else is created with the built-in method createElement(), set on the document object.

So what does creating elements have to do with the load event? What if a page is loaded and the elements which are being created with the createElement() method haven't been created yet? Nothing would render to the page!

Creating elements and the load event: scripts & external stylesheets

If a script element comes after an external stylesheet, then that script must wait until the stylesheet loads.

The reason why this happens is because the script may want to get style dependent properties of elements.

Example:


 const monstersBtn = document.querySelector('.monsters-btn');
 monstersBtn.addEventListener('click', fetchMonsters);
                

The most common workaround for this is to place the script at the bottom of the index.html, right before the closing body tag. However, that does not completely take care of the issue.

What if the page is very long and/or has many elements to load? In cases like that, the browser may pick up on the script element, but start downloading it only after it has downloaded the whole HTML document. For long HTML documents, this may result in a long delay.

Creating elements and the load event: page load performance

When loading a script on an HTML page, you need to be careful not to damage the page's load performance.



                

Whenever the HTML parser comes across the script element, a request is made to fetch the script, and the script is executed.

Once the process is complete, the parsing can continue, and the rest of the HTML analyzed.

If the script takes longer to load than expected, the visitor will probably see a blank page until the script is completely executed.

Creating elements and the load event: the importance of the script position

When some of us first started learning HTML, we may have been told to place the script element in the head.

Giving the parser immediate access to the script element could result in even longer page load delays. It's only after the element has been executed will the parser continue parsing the rest of the HTML.

The common solution to this problem is to place the script element at the bottom of the page, right above the closing body tag.

This is definitely a great improvement, as the script is then loaded and executed after the page is already parsed and loaded.

This is the best way to go if you need to support older browsers that do not support the relatively recent async and defer attributes.

Creating elements and the load event: async and defer script attributes

Both async and defer are boolean attributes. They are added to the script element in the same way:




                

Please note that in actuality, async, and defer, as shown in the slide deck markup itself, does not have an explicitly visible value. It either is true or false. Please ignore the ="".

If one includes both in the opening script tag, async takes precedence in modern browsers, and defer takes precedence in older browsers.

Using either of these attributes only makes sense if you are placing the script element in the head. They don't do anything if you place your script element above the closing body tag.

Creating elements and the load event: the async vs defer script tag attribute

  • No defer or async attribute in the opening script tag right above the closing body tag:
  • The parsing takes place without interruption, and only when it is finished is the script loaded and executed. This means that the page is viewable by the user way before it would have been if the script element had been placed in the head.

  • async script attribute in the head:
  • The script is fetched asynchronously, and when the script is ready for execution, the HTML parsing is paused, allowing for execution of the script, and afterwards, the HTML parsing is continued.

  • defer script attribute in the head:
  • The script is fetched asynchronously, and it's executed only after the HTML parsing is done. Parsing is completed the same way as with the script element right above the closing body tag, but the process is much faster, because the script has been downloaded in parallel with the HTML parsing. This is the best way to go, and what I ended up doing in the Monsters API App.

  • async blocks parsing:
  • async blocks parsing of the page. defer does not.

  • blocking rendering:
  • Neither async nor defer guarantee that rendering will not be blocked. It is up to you to make sure that it is not. i.e., making sure that your scripts run after the load event.

  • keeping scripts in order:
  • async ignores the order of scripts. scripts with the async attribute are executed as they become available. This can be a problem when order matters. And oftentimes, it does! scripts with the defer attribute are executed after parsing has been completed, and in the order in which they are defined in the markup.

    To learn more about async vs defer, please visit the post entitled Efficiently load JavaScript with defer and async by Flavio Copes.

Related Resources