034.4 Lesson 1
Certificate: |
Web Development Essentials |
---|---|
Version: |
1.0 |
Topic: |
034 JavaScript Programming |
Objective: |
034.4 JavaScript Manipulation of Website Content and Styling |
Lesson: |
1 of 1 |
Introduction
HTML, CSS, and JavaScript are three distinct technologies that come together on the Web. In order to make truly dynamic and interactive pages, the JavaScript programmer must combine components from HTML and CSS at run time, a task that is greatly facilitated by using the Document Object Model (DOM).
Interacting with the DOM
The DOM is a data structure that works as a programming interface to the document, where every aspect of the document is represented as a node in the DOM and every change made to the DOM will immediately reverberate in the document. To show how to use the DOM in JavaScript, save the following HTML code in a file called example.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML Manipulation with JavaScript</title>
</head>
<body>
<div class="content" id="content_first">
<p>The dynamic content goes here</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Second section</p>
</div><!-- #content_second -->
</body>
</html>
The DOM will be available only after the HTML is loaded, so write the following JavaScript at the end of the page body (before the ending </body>
tag):
<script>
let body = document.getElementsByTagName("body")[0];
console.log(body.innerHTML);
</script>
The document
object is the top DOM element, all other elements branch off from it. The getElementsByTagName()
method lists all the elements descending from document
that have the given tag name. Even though the body
tag is used only once in the document, the getElementsByTagName()
method always return an array-like collection of found elements, hence the use of the [0]
index to return the first (and only) element found.
HTML Content
As shown in the previous example, the DOM element returned by the document.getElementsByTagName("body")[0]
was assigned to the body
variable. The body
variable can then be used to manipulate the page’s body element, because it inherits all the DOM methods and attributes from that element. For instance, the innerHTML
property contains the entire HTML markup code written inside the corresponding element, so it can be used to read the inner markup. Our console.log(body.innerHTML)
call prints the content inside <body></body>
to the web console. The variable can also be used to replace that content, as in body.innerHTML = "<p>Content erased</p>"
.
Rather than changing entire portions of HTML markup, it is more practical to keep the document structure unaltered and just interact with its elements. Once the document is rendered by the browser, all the elements are accessible by DOM methods. It is possible, for example, to list and access all the HTML elements using the special string *
in the getElementsByTagName()
method of the document
object:
let elements = document.getElementsByTagName("*");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>New content</p>";
}
}
This code will place all the elements found in document
in the elements
variable. The elements
variable is an array-like object, so we can iterate through each of its items with a for
loop. If the HTML page where this code runs has an element with an id
attribute set to content_first
(see the sample HTML page shown at the start of the lesson), the if
statement matches that element and its markup contents will be changed to <p>New content</p>
. Note that the attributes of an HTML element in the DOM are accessible using the dot notation of JavaScript object properties: therefore, element.id
refers to the id
attribute of the current element of the for
loop. The getAttribute()
method could also be used, as in element.getAttribute("id")
.
It is not necessary to iterate through all the elements if you want to inspect only a subset of them. For example, the document.getElementsByClassName()
method limits the matched elements to those having a specific class:
let elements = document.getElementsByClassName("content");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>New content</p>";
}
}
However, iterating through many document elements using a loop is not the best strategy when you have to change a specific element in the page.
Selecting Specific Elements
JavaScript provides optimized methods to select the exact element you want to work on. The previous loop could be entirely replaced by the document.getElementById()
method:
let element = document.getElementById("content_first");
element.innerHTML = "<p>New content</p>";
Every id
attribute in the document must be unique, so the document.getElementById()
method returns only a single DOM object. Even the declaration of the element
variable can be omitted, because JavaScript lets us chain methods directly:
document.getElementById("content_first").innerHTML = "<p>New content</p>";
The getElementById()
method is the preferable method to locate elements in the DOM, because its performance is much better than iterative methods when working with complex documents. However, not all elements have an explicit ID, and the method returns a null value if no element matches with the provided ID (this also prevents the use of chained attributes or functions, like the innerHTML
used in the example above). Moreover, it is more practical to assign ID attributes only to the main components of the page and then use CSS selectors to locate their child elements.
Selectors, introduced in an earlier lesson on CSS, are patterns that match elements in the DOM. The querySelector()
method returns the first matching element in the DOM’s tree, while querySelectorAll()
returns all elements that match the specified selector.
In the previous example, the getElementById()
method retrieves the element bearing the content_first
ID. The querySelector()
method can perform the same task:
document.querySelector("#content_first").innerHTML = "<p>New content</p>";
Because the querySelector()
method uses selector syntax, the provided ID must begin with a hash character. If no matching element is found, the querySelector()
method returns null.
In the previous example, the entire content of the content_first
div is replaced by the text string provided. The string has HTML code in it, which is not considered a best practice. You must be careful when adding hard-coded HTML markup to JavaScript code, because tracking elements can become difficult when changes to the overall document structure are required.
Selectors are not restricted to the ID of the element. The internal p
element can be addressed directly:
document.querySelector("#content_first p").innerHTML = "New content";
The #content_first p
selector will match only the first p
element inside the #content_first
div. It works fine if we want to manipulate the first element. However, we may want to change the second paragraph:
<div class="content" id="content_first">
<p>Don't change this paragraph.</p>
<p>The dynamic content goes here.</p>
</div><!-- #content_first -->
In this case, we can use the :nth-child(2)
pseudo-class to match the second p
element:
document.querySelector("#content_first p:nth-child(2)").innerHTML = "New content";
The number 2
in p:nth-child(2)
indicates the second paragraph that matches the selector. See the CSS selectors lesson to know more about selectors and how to use them.
Working with Attributes
JavaScript’s ability to interact with the DOM is not restricted to content manipulation. Indeed, the most pervasive use of JavaScript in the browser is to modify the attributes of the existing HTML elements.
Let’s say our original HTML example page has now three sections of content:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HTML Manipulation with JavaScript</title>
</head>
<body>
<div class="content" id="content_first" hidden>
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third" hidden>
<p>Third section.</p>
</div><!-- #content_third -->
</body>
</html>
You may want to make only one of them visible at a time, hence the hidden
attribute in all div
tags. This is useful, for example, to show only one image from a gallery of images. To make one of them visible when the page loads, add the following JavaScript code to the page:
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).removeAttribute("hidden");
The expression evaluated by the switch
statement randomly returns the number 0, 1, or 2. The corresponding ID selector is then be assigned to the content_visible
variable, which is used by the querySelector(content_visible)
method. The chained removeAttribute("hidden")
call removes the hidden
attribute from the element.
The opposite approach is also possible: All sections could be initially visible (without the hidden
attribute) and the JavaScript program can then assign the hidden
attribute to every section except the one in content_visible
. To do so, you must iterate through all the content div elements that are different from the chosen one, which can be done by using the querySelectorAll()
method:
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
// Hide all content divs, except content_visible
for ( element of document.querySelectorAll(".content:not("+content_visible+")") )
{
// Hidden is a boolean attribute, so any value will enable it
element.setAttribute("hidden", "");
}
If the content_visible
variable was set to #content_first
, the selector will be .content:not(#content_first)
, which reads as all elements having the content
class except those having the content_first
ID. The setAttribute()
method adds or changes attributes of HTML elements. Its first parameter is the name of the attribute and the second is the value of the attribute.
However, the proper way to change the appearance of elements is with CSS. In this case, we can set the display
CSS property to hidden
and then change it to block
using JavaScript:
<style>
div.content { display: none }
</style>
<div class="content" id="content_first">
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Third section.</p>
</div><!-- #content_third -->
<script>
// Which content to show
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).style.display = "block";
</script>
The same good practices that apply to mixing HTML tags with JavaScript apply also to CSS. Thus, writing CSS properties directly in JavaScript code is not recommended. Instead, CSS rules should be written apart from JavaScript code. The proper way to alternate visual styling is to select a pre-defined CSS class for the element.
Working with Classes
Elements may have more than one associated class, making it easier to write styles that can be added or removed when necessary. It would be exhausting to change many CSS attributes directly in JavaScript, so you can create a new CSS class with those attributes and then add the class to the element. DOM elements have the classList
property, which can be used to view and manipulate the classes assigned to the corresponding element.
For example, instead of changing the visibility of the element, we can create an additional CSS class to highlight our content
div:
div.content {
border: 1px solid black;
opacity: 0.25;
}
div.content.highlight {
border: 1px solid red;
opacity: 1;
}
This stylesheet will add a thin black border and semi-transparency to all elements having the content
class. Only the elements that also have the highlight
class will be fully opaque and have the thin red border. Then, instead of changing the CSS properties directly as we did before, we can use the classList.add("highlight")
method in the selected element:
// Which content to highlight
let content_highlight;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_highlight = "#content_first";
break;
case 1:
content_highlight = "#content_second";
break;
case 2:
content_highlight = "#content_third";
break;
}
// Highlight the selected div
document.querySelector(content_highlight).classList.add("highlight");
All the techniques and examples we have seen so far were performed at the end of the page loading process, but they are not restricted to this stage. In fact, what makes JavaScript so useful to Web developers is its ability to react to events on the page, which we will see next.
Event Handlers
All visible page elements are susceptible to interactive events, such as the click or the movement of the mouse itself. We can associate custom actions to these events, which greatly expands what an HTML document can do.
Probably the most obvious HTML element that benefits from an associated action is the button
element. To show how it works, add three buttons above the first div
element of the example page:
<p>
<button>First</button>
<button>Second</button>
<button>Third</button>
</p>
<div class="content" id="content_first">
<p>First section.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Second section.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Third section.</p>
</div><!-- #content_third -->
The buttons do nothing on their own, but suppose you want to highlight the div
corresponding to the pressed button. We can use the onClick
attribute to associate an action to each button:
<p>
<button onClick="document.getElementById('content_first').classList.toggle('highlight')">First</button>
<button onClick="document.getElementById('content_second').classList.toggle('highlight')">Second</button>
<button onClick="document.getElementById('content_third').classList.toggle('highlight')">Third</button>
</p>
The classList.toggle()
method adds the specified class to the element if it is not present, and removes that class if it is already present. If you run the example, you will note that more than one div
can be highlighted at the same time. To highlight only the div
corresponding to the pressed button, it is necessary to remove the highlight
class from the other div
elements. Nonetheless, if the custom action is too long or involves more than one line of code, it’s more practical to write a function apart from the element tag:
function highlight(id)
{
// Remove the "highlight" class from all content elements
for ( element of document.querySelectorAll(".content") )
{
element.classList.remove('highlight');
}
// Add the "highlight" class to the corresponding element
document.getElementById(id).classList.add('highlight');
}
Like the previous examples, this function can be placed inside a <script>
tag or in an external JavaScript file associated with the document. The highlight
function first removes the highlight
class from all the div
elements associated with the content
class, then adds the highlight
class to the chosen element. Each button should then call this function from its onClick
attribute, using the corresponding ID as the function’s argument:
<p>
<button onClick="highlight('content_first')">First</button>
<button onClick="highlight('content_second')">Second</button>
<button onClick="highlight('content_third')">Third</button>
</p>
In addition to the onClick
attribute, we could use the onMouseOver
attribute (triggered when the pointing device is used to move the cursor onto the element), the onMouseOut
attribute (triggered when the pointing device is no longer contained within the element), etc. Moreover, event handlers are not restricted to buttons, so you can assign custom actions to these event handlers for all visible HTML elements.
Guided Exercises
-
Using the
document.getElementById()
method, how could you insert the phrase “Dynamic content” to the inner content of the element whose ID ismessage
? -
What is the difference between referencing an element by its ID using the
document.querySelector()
method and doing so via thedocument.getElementById()
method? -
What is the purpose of the
classList.remove()
method? -
What is the result of using the method
myelement.classList.toggle("active")
ifmyelement
does not have theactive
class assigned to it?
Explorational Exercises
-
What argument to the
document.querySelectorAll()
method will make it mimic thedocument.getElementsByTagName("input")
method? -
How can you use the
classList
property to list all the classes associated with a given element?
Summary
This lesson covers how to use JavaScript to change HTML contents and their CSS properties using the DOM (Document Object Model). These changes can be triggered by user events, which is useful to create dynamic interfaces. The lesson goes through the following concepts and procedures:
-
How to inspect the structure of the document using methods like
document.getElementById()
,document.getElementsByClassName()
,document.getElementsByTagName()
,document.querySelector()
anddocument.querySelectorAll()
. -
How to change the document’s content with the
innerHTML
property. -
How to add and modify the attributes of page elements with methods
setAttribute()
andremoveAttribute()
. -
The proper way to manipulate elements classes using the
classList
property and its relation to CSS styles. -
How to bind functions to mouse events in specific elements.
Answers to Guided Exercises
-
Using the
document.getElementById()
method, how could you insert the phrase “Dynamic content” to the inner content of the element whose ID ismessage
?It can be done with the
innerHTML
property:document.getElementById("message").innerHTML = "Dynamic content"
-
What is the difference between referencing an element by its ID using the
document.querySelector()
method and doing so via thedocument.getElementById()
method?The ID must be accompanied by the hash character in functions that use selectors, such as
document.querySelector()
. -
What is the purpose of the
classList.remove()
method?It removes the class (whose name is given as the argument of the function) from the
class
attribute of the corresponding element. -
What is the result of using the method
myelement.classList.toggle("active")
ifmyelement
does not have theactive
class assigned to it?The method will assign the
active
class tomyelement
.
Answers to Explorational Exercises
-
What argument to the
document.querySelectorAll()
method will make it mimic thedocument.getElementsByTagName("input")
method?Using
document.querySelectorAll("input")
will match all theinput
elements in the page, just likedocument.getElementsByTagName("input")
. -
How can you use the
classList
property to list all the classes associated with a given element?The
classList
property is an array-like object, so afor
loop can be used to iterate through all the classes it contains.