5.1 DOM
# Running JavaScript in the Browser
You can attach one or more JavaScript files to your your webpage by using a <script>
tag.
<script src="myscript.js"></script>
The recommended place to add your scripts is inside the body element, after all your other HTML.
<body>
<script src="myscript.js"></script>
</body>
2
3
Another recommended feature that you can add to your script tag is the defer
attribute. This will tell modern browsers to wait until all your HTML has loaded before trying to run your script.
# Multiple Files and Scope
If you add multiple JavaScript files to the same HTML file they will, by default, all be able to see each other's code. If you declare a variable or function in one of the files then the other file will be aware of them. All the code from each of the files will be loaded into the same global scope.
The exception to this is if you are using ES Modules. This is a fairly new feature added in the last few years. You can add type="module"
to your initial JavaScript file <script>
tag, and then it will be able to import other files as modules. Each JavaScript file module will have it's own scope.
We will be talking about modules later in the semester as our code becomes more complex.
# Document Object Model
When the browser reads an HTML file it creates an Object model representing all the content on the page. It builds a tree structure that helps it to know which elements are nested inside each other, which elements are parents and which are children.
This is a visual representation of how a browser sees the page. You can think of each indent as the start of a new branch in the DOM tree. Inside the html
object, there are two children - head
and body
. Inside the head are two more children - title
and link
. title has a child, which is #text
. The link
element has no children. Inside body
> header
> h1
there are two children - img
and #text
. The img
and the #text
are siblings.
doctype
html
head
title
#text
link
body
header
h1
img
#text
nav
a
#text
a
#text
a
#text
main
h2
#text
p
#text
footer
p
#text
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
You need to be able to visualize every page in this manner. Understand when you are searching for things or altering things that you will be working with parents, children, and siblings.
One of the things that sets NodeJS and JavaScript in the browser apart is the DOM. NodeJS does NOT have access to the DOM.
The DOM is NOT part of core JavaScript.
The DOM is an add-on that browsers get as part of the window
object.
Everything you do in the browser, which is not part of core JavaScript, exists inside the window
object. Because of this, You can write window
as the first step in accessing an Object in the DOM OR you can omit it.
window.alert('This is a pop up message');
alert('This is also a pop up message');
//both these lines access the same alert method
2
3
# Nodes and Elements
Each one of the things listed above, in the DOM diagram, is a Node
. There are several kinds of types of nodes, including ElementNode
, TextNode
, DocumentFragement
, and Comment
. There are others but these are, by far, the most common.
There are node properties and methods which work on all the types, some that work only on Element nodes, and some that work only on Text nodes.
# Attributes
Inside any DOM Element you can add or read attributes. Many of the attributes that you write in your HTML will have a corresponding attribute in JavaScript. Eg: if your element in the HTML has an id
attribute, then when you reference that element in your JS, it will have an id
property.
There are a few exceptions to the naming of the properties. The most notable is the class
attribute in HTML. In JavaScript, you have to use the property name className
because class
is a reserved keyword.
There are also dataset
properties, which are properties that you can invent and insert into the HTML. These attributes all have a name that begins with data-
.
In JavaScript we can add attributes, update the value of attributes, check if attributes exist, read the value of attributes or remove attributes. We will talk more about these methods soon.
# The Document Object
One of the objects inside window
that you will access most is the document
object. It contains all the methods and properties for working with HTML and CSS through JavaScript.
let h = document.querySelector('h1');
//find the first h1 element on the page
console.log(h.textContent);
//output the #text inside the h1 element.
2
3
4
We could, but typically do not, put window
in front of the document.querySelector
command.
It is imporant to remember that there are two main types of Nodes in the DOM - TextNodes and ElementNodes.
Some of the properties and methods that you use will be looking at Nodes
and some will be looking at ElementNodes
. The general object type Node
refers to both kinds, whereas Element
nodes refer just to the tags like <p>
or <div>
or <ul>
.
When you look at the tree structure created as the DOM, this distinct is important. In the following example, the <div>
element has TWO children
, but FIVE childnodes
. The carriage return and spaces that come after <div>
, </h2>
, and </p>
count as text nodes and therefore are childnodes
.
<div>
<h2>A Heading</h2>
<p>a paragraph</p>
</div>
2
3
4
The <h2>
and <p>
element each have 1 children
and 1 childnode
.
# Finding Elements in HTML
A task that you will frequently do in JavaScript is locating parts of your webpage so you can read the content or update the content. Eg: Find the sidebar so you can add new links. Find the main <ul>
so you can add a new list of products.
# querySelector, querySelectorAll, and getElementById
The two methods we use the most to find elements on the page are document.querySelector
and document.querySelectorAll
. The difference between them is that querySelector
finds the first match starting at the top of the page and querySelectorAll
finds ALL the matches.
If you want to change content on the page, find out what the content inside an element is, move an element, or delete an element then you need to find it first.
let p = document.querySelector('p');
//find the first paragraph on the page
let ps = document.querySelectorAll('p');
//find ALL the paragraphs on the page
2
3
4
The querySelector
method will return a single ElementNode
.
The querySelectorAll
method will return a NodeList
. A NodeList
can be thought of as an Array of ElementNodes.
When you want to make changes to a webpage or read the contents of a webpage with JavaScript then you need a way of locating elements. The three methods that you will use most frequently when accessing web page content are:
let element = document.querySelector(cssSelector); //returns a single element node
let nodes = document.querySelectorAll(cssSelector); //returns a NodeList
let element = document.getElementById(id); //returns a single element node
2
3
querySelector( )
and getElementById( )
search the webpage for the first element node that matches.
let anchor = document.querySelector('.main p a');
In the example above, the variable anchor will contain either null, if no match found, or the first anchor tag inside a paragraph inside an element with the className .main
. You can pass ANY valid CSS selector, as a string, to this method.
let foot = document.getElementById('footer');
In this example, the variable foot will contain either null or the element on the page that has the id footer
. You pass any string that should match an id of an element on your page.
querySelectorAll( )
will return a NodeList
, which is similar to an Array in that it is a numbered list, but it can only contain HTML Nodes. When you call querySelectorAll( )
, it will ALWAYS return a NodeList. The NodeList might be empty, but it is still a NodeList with length zero.
let paragraphs = document.querySelectorAll('#main p');
The above example will return a NodeList containing ALL of the paragraphs inside the element with the id main
. Notice that this is a valid CSS selector, just like the querySelector( )
call above.
If you want to loop through all the elements inside of paragraphs
, you could use a for loop
.
Alternatively, a NodeList
has a forEach
method that works just like the Array forEach
method.
let paragraphs = document.querySelectorAll('#main p');
//regular function version
paragraphs.forEach(function (p, index) {
p.textContent = `Paragraph ${index} updated by the first forEach`;
});
//arrow function version
paragraphs.forEach((p, index) => {
p.textContent = `Paragraph ${index} updated by the second forEach`;
});
2
3
4
5
6
7
8
9
10
11
There are a couple other methods that can be used to get a list of elements - getElementsByTagName()
and getElementsByClassName()
. As the names imply you would give a tag name or a class name to find the matches. These methods existed before querySelector
and querySelectorAll
were added to JavaScript and it is rare to find them actually used.
# Parents, children, and Siblings
If you had a variable called someElement
you could use any of the following properties.
someElement.parentElement
Get the parent element that wraps someElementsomeElement.childNodes
Get a list of all the text and element children of someElementsomeElement.children
Get a list of all the element children of someElementsomeElement.nextSibling
Get the text or element sibling that comes after someElementsomeElement.nextElementSibling
Get the element sibling that comes after someElementsomeElement.previousSibling
Get the text or element sibling that comes before someElementsomeElement.previousElementSibling
Get the sibling element that comes before someElementsomeElement.firstElementChild
Get the first element child of someElementsomeElement.lastElementChild
Get the last element child of someElement
# Adding HTML to a Page
There are a number of ways that you can create new content on a webpage. The two main approaches are:
- You can create Elements with the
document.createElement()
method and text nodes with thedocument.createTextNode()
method. After creating the elements you can add attributes likeclassName
. With different nodes created, you can use theappend()
orappendChild()
method to build whatever tree structure you want. - You can create a String that contains whatever HTML you would write if you were putting it in the HTML file. Then, with the
append()
orsetHTML()
methods or theinnerHTML
property you can convert the string into actual nodes in your page. Template strings are commonly used for this approach. - An HTML template can be created and cloned. We will look at this approach more after we cover fetching remote data.
# The createElement() Approach
The createElement
method will create any HTML element you need. Just pass in the name of the tag and it will return a new element of that type.
The createTextNode
method will create a text node. Just pass in the string that you want to use as the text and it will return the new text node.
There is also a createDocumentFragment()
method that lets us create a self-removing wrapper for chunks of HTML that will will inject into the page. More about this later.
let p = document.createElement('p'); //creates an Element Node of the type provided
let txt = document.createTextNode('hello world'); //creates a TextNode
let df = document.createDocumentFragment(); //creates a Document Fragment
2
3
The elements can have attributes too. For most attributes the common approach is to use the property of the same name.
Let's use this HTML as the example of what we want to create. We will assume that the main
element is already in our HTML file and we want to create and add all the content inside it.
<main>
<div class="header">
<h2>Some Heading</h2>
</div>
<div class="content">
<p><img src="./img/logo.png" alt="Company logo" /> Lorem.</p>
<p>Ipsum.</p>
</div>
</main>
2
3
4
5
6
7
8
9
Here is the JavaScript that will create everything inside the <main>
element.
let main = document.querySelector('main');
//create the elements
let header = document.createElement('div');
let content = document.createElement('div');
let h2 = document.createElement('h2');
let p1 = document.createElement('p');
let p2 = document.createElement('p');
//approach one to create the text node
let lorem = document.createTextNode(' Lorem.');
//approach two is to use the .textContent property
p2.textContent = 'Ipsum.';
let img = document.createElement('img');
//add the attributes
header.className = 'header';
content.className = 'content';
img.src = './img/logo.png';
img.alt = 'Company logo';
//then start with the innermost nodes and start appending
//if you didn't use p2.textContent then you need to append a textNode
// p2.appendChild(ipsum); //appendChild can only accept one child at a time
// appendChild can be used to append a child TextNode or a child ElementNode
p1.append(img, lorem); //the order is important. append can accept multiple children
content.append(p1, p2);
header.append(h2);
main.append(header, content);
//appending to main is the last step because this adds it to the page
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# The HTML String Approach
The HTML String approach is the alternative to creating elements. Instead we are writing out the whole string and then asking the browser to do the work of parsing all the elements and appending them at the same time.
Here is the String approach for the same content.
let main = document.querySelector('main');
//use a template string incase you have variables that you want to embedded in the string.
let myhtml = `<div class="header">
<h2>Some Heading</h2>
</div>
<div class="content">
<p><img src="./img/logo.png" alt="Company logo" /> Lorem.</p>
<p>Ipsum.</p>
</div>`;
main.innerHTML = myhtml;
//alternative
main.setHTML(myhtml);
2
3
4
5
6
7
8
9
10
11
12
13
It is important to note that this approach is going to replace any content that is already inside of <main>
and not just append it to the existing content.
# Updating and Removing HTML
Manipulating HTML can be done quite easily once you understand the parent-child-sibling relationship between Nodes and the difference between Element nodes and Text nodes.
Once you have found the ElementNode you need, then you can start to manipulate the content.
let content = document.querySelector('p.first');
//find the first paragraph with the class "first".
content.textContent = 'The new paragraph content';
//change the text inside the paragraph
let main = document.querySelector('main');
//find the main element
let p = document.createElement('p');
//create a new paragraph
p.textContent = 'A new paragraph for the page';
//add some text inside the paragraph
main.appendChild(p);
//add the newly created paragraph as the last child of the main element
2
3
4
5
6
7
8
9
10
11
12
It is important to note that both the append
and the appendChild
methods always inject the new child element or child textnode as the LAST child. It will always be added at the bottom of the parent element.
If you want to inject your new textnode or element in a location other than the last position, then you need to use the insertBefore()
, insertAfter()
, insertAdjacentElement()
, insertAdjacentText()
, or insertAdjacentHTML()
methods. The insert before and after methods want a reference element as well as the child node to insert before or after. The insertAdjacent methods want a reference element plus one of four reference positions. Take this HTML as an example:
<h2>Some heading</h2>
<ul>
<li>first item</li>
<li>second item</li>
<li>third item</li>
</ul>
<p>Some more text</p>
2
3
4
5
6
7
If the <ul>
is my reference element, I can inject my new element in one of the four positions:
- before the
<ul>
starts beforebegin - after the
<ul>
starts but before the first<li>
afterbegin - after the last
<li>
but still inside the<ul>
beforeend - after the
<ul>
ends but before the<p>
afterend
The four strings used as the position values are bolded in the list above.
referenceElement.insertAdjacentElement(position, childElement); //insert Element
referenceElement.insertAdjacentText(position, childTextNode); //insert textNode
referenceElement.insertAdjacentHTML(position, HTMLString); //parse string and insert
2
3
# Side Effects of Appending
It is worth noting, when you create an element with createElement
or reference and element with querySelector
or the other methods, then that element still exists whether it is just in memory or on the page.
let p = document.createElement('p'); //exists in memory
p.textContent = 'I exist!'; //p is still in memory only
let header = document.querySelector('header'); // exists on the page
let main = document.querySelector('main'); // exists on the page
let footer = document.querySelector('footer'); // exists on the page
2
3
4
5
When you call the append
or appendChild
or remove
or removeChild
methods you are actually MOVING the element. You are moving it from memory to the page, from the page to memory, OR from one location in the page to another.
header.append(p); //moved p from memory to inside header on page
main.append(p); //moved p from inside header to inside main
footer.append(p); //moved p from inside main to inside footer
footer.removeChild(p); //moved p from inside footer on page to in memory
2
3
4
The above code sample does NOT create three copies of the paragraph.
If you did want to create copies of the paragraph then you could use the cloneNode
method.
let copy1 = p.cloneNode(true); //creates a copy of p and includes anything it contained
let copy2 = p.cloneNode(true); //creates a copy of p and includes anything it contained
//now we have p, copy1, and copy2 as three separate elements
2
3
# Removing Content
If you want to remove HTML that already exists we have a few options.
- With the
innerHTML
property or thetextContent
property you can set the content of any element to an empty String.
let div = document.querySelector('div'); //find the first div on the page
let p = div.querySelector('p'); //find the first paragraph inside div
p.textContent = ''; //set the text inside the paragraph to empty
div.innerHTML = ''; //remove all html and text inside div. could also use .setHTML()
2
3
4
5
- Using the
remove()
orremoveChild()
methods.
p.remove(); //will remove the paragraph from whatever its parent is.
div.remove(); //will remove div and everything it contains
p.parentElement.removeChild(p); //refer to the parent of the p and use removeChild to target what will be removed
div.removeChild(p); //remove the paragraph from it's parent div
2
3
4
# Node Properties
In the list below there are a series of property pairs. The first properties will return a NodeList or a single Node. Remember that Nodes can be element nodes, text nodes, or comments.
The second property will return a list of Element Nodes or a single Element Node.
node.childNodes v node.children
node.firstChild v. node.firstElementChild
node.lastChild v. node.lastElementChild
node.nextSibling v. node.nextElementSibling
node.previousSibling v. node.previousElementSibling
node.parentNode v. node.parentElement
2
3
4
5
6
These last three node properties return a single piece of information about the node.
node.nodeName; //returns the tagname, if an element node
node.nodeType; //returns the integer referencing the type of node. Eg: 1=element, 3=text
node.nodeValue; //returns the string inside a text node
2
3
# Node Methods
parentNode.appendChild(newNode); //add a new child node inside the parent
parentNode.removeChild(childNode); //remove the child from the parent
node.replaceChild(newNode); //replace one node with a new one
parentNode.contains(node); //checks if the node is a descendant of the parent
node.hasChildNodes(); //returns a boolean indicating if the node has child nodes
node.hasAttributes(); //returns a boolean if the node has attributes
parentNode.insertBefore(newNode, referenceNode); //inserts a new node into the DOM
//immediately before the reference node
2
3
4
5
6
7
8
# What to do this week
TODO
Things to do before next week.
- Read all the content from
Modules 5.1, 5.2, and 6.1
. - Continue working on the Hybrid Exercises
- Submit Hybrid 5