facebook google plus twitter
Webucator's Free JavaScript Tutorial

Lesson: CSS Object Model

Welcome to our free JavaScript tutorial. This tutorial is based on Webucator's Introduction to JavaScript Training course.

We can use JavaScript to both retrieve information about an element's CSS styles and to set those styles programmatically.

Lesson Goals

  • Change the values of CSS properties dynamically.
  • Hide and show elements.

Changing CSS with JavaScript

Throughout this course we have used JavaScript to change background colors using the following syntax:

element.style.backgroundColor = value;

But we can do a lot more than change the background color. We can both get and set any styles for most any element.

Each CSS property has a corresponding property of the JavaScript style object:

  • If the CSS property is a simple word (e.g., color) then the JavaScript property is the same (e.g., style.color).
  • If the CSS property has a dash in it (e.g., background-color) then the JavaScript property uses lower camel case (e.g., style.backgroundColor).

The style object is a collection of an element's styles that are either defined within that HTML element's style attribute or directly in JavaScript. Styles defined in the <style> tag or in an external stylesheet are not part of the style object.

The W3C specifies a method for getting at the current (or actual) style of an object: the window object's getComputedStyle() method.

window.getComputedStyle(element)

Note that the reference to window can be excluded as window is the implicit object. For example:

var contactForm = document.getElementById("contact-form");
var computedStyle = getComputedStyle(contactForm);

Using this method - with getComputedStyle(), as opposed to element.style - we can get the styles set with inline CSS (i.e., within style attributes), with embedded CSS (i.e., within <style> tags), or with external (i.e., linked) stylesheets. Furthermore, as the name of the method getComputedStyle() suggests, these are computed (calculated) styles: whereas element.style just gives us style info as set in CSS, getComputedStyle() gives us the real-time calculated CSS.

Let's take a look at a simple example to make this more clear.

Code Sample:

CSSObjectModel/Demos/board.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<style>
div#board {
  margin:auto;
  width:306px;
}

div.row {
  height:100px;
}

div.col {
  border:1px solid black;
  float:left;
  font-size:xx-large;
  height:100px;
  text-align:center;
  width:100px;
}
</style>
<title>Board</title>
</head>
<body>
<main>
  <div id="board" style="font-style:italic;">
    <div class="row">
      <div class="col">A</div>
      <div class="col">B</div>
      <div class="col">C</div>
    </div>
    <div class="row">
      <div class="col">D</div>
      <div class="col">E</div>
      <div class="col">F</div>
    </div>
    <div class="row">
      <div class="col">G</div>
      <div class="col">H</div>
      <div class="col">I</div>
    </div>
  </div>
</main>
</body>
</html>

Code Explanation

Things to notice:

  1. We have explicitly set the font-style of div#board to "italic" using its style attribute.
  2. We have explicitly set the width of div#board to "306px" using an embedded stylesheet.
  3. We have not set the height of div#board.

With that file open, do the following at Chrome DevTools Console:

  1. Type var board = document.getElementById('board'); and press Enter.
  2. Type board.style.fontStyle and press Enter. Notice that it outputs "italic". That's because we set the font-style using the style attribute. An element's style property in JavaScript only has access to properties set using the style attribute.
  3. Type board.style.width and press Enter. Notice that it returns an empty string. That's because we set the width outside of the style attribute.
  4. Type board.style.height and press Enter. Notice that it again returns an empty string. That's because we haven't set the height in the style attribute, or anywhere else for that matter.

Here is a screenshot showing those results: style object

Now let's use getComputedStyle() instead.

  1. Type var board = document.getElementById('board'); and press Enter.
  2. Type var boardStyles = getComputedStyle(board); and press Enter.
  3. Type the following and notice it outputs the computed value each time:
    1. boardStyles.fontStyle and press Enter.
    2. boardStyles.width and press Enter.
    3. boardStyles.height and press Enter.

Here is a screenshot showing those results: Computed Styles

The style Property vs. getComputedStyle()

The takeaway from all this is:

  • Use getComputedStyle(elem) to get the style of an element.
  • Use elem.style to set the style of an element.

Dot Notation vs. Square Bracket Notation

It is common to use dot notation whenever possible, and in the examples above, we have done so. However, it is worth noting that you can also use square bracket notation. For example, the two statements below are equivalent:

getComputedStyle(board).fontStyle;
getComputedStyle(board)['fontStyle'];

As we will see later, when the name of the style property is stored in a variable, you can only use square bracket notation. For example:

var styleProp = 'fontStyle';
// The Correct Way
getComputedStyle(board)[styleProp];
				
// Incorrect as styleProp is undefined in getComputedStyle(board)
getComputedStyle(board).styleProp; 

Hiding and Showing Elements

Elements can be hidden and shown by changing their visibility or display values.

The visibility property can be set to "visible" or "hidden" and the display property can be set to "block", "table-row", "list-item", "none", and many other values.

When an element's visibility is set to "hidden", it disappears, but it continues to occupy its space. In the following table, the second row's visibility is set to "hidden":visibility: hidden

When an element's display is set to "none", it disappears, and it no longer occupies any space on the page. In the following table, the second row's display is set to "none":display:none

Take a look at the code in the file below, which shows two different ways of showing and hiding table rows:

Code Sample:

CSSObjectModel/Demos/visibility.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<script>
function toggleVisibility(e) {
  var button = e.currentTarget;
  var elem = document.getElementById(button.title);
  if (getComputedStyle(elem).visibility === "visible") {
    elem.style.visibility = "hidden";
  } else {
    elem.style.visibility = "visible";
  }
  var  computedStyle = getComputedStyle(elem);
  msg(button.title, 'visibility', computedStyle.visibility);
}

function toggleDisplay(e) {
  var button = e.currentTarget;
  var elem = document.getElementById(button.title);
  if (elem.style.display === "none") {
    elem.style.display = "table-row";
  } else {
    elem.style.display = "none";
  }
  var  computedStyle = getComputedStyle(elem);
  msg(button.title, 'display', computedStyle.display);
}

function msg(elemId, styleProp, styleValue) {
  document.getElementById("msg").style.display = "block";
  document.getElementById("elemId").innerHTML = elemId;
  document.getElementById("styleProp").innerHTML = styleProp;
  document.getElementById("styleValue").innerHTML = styleValue;
}

window.addEventListener('load', function() {
  var btnsVisibility = document.getElementsByClassName('visibility');
  for (button of btnsVisibility) {
    button.addEventListener('click', toggleVisibility);
  }

  var btnsDisplay = document.getElementsByClassName('display');
  for (button of btnsDisplay) {
    button.addEventListener('click', toggleDisplay);
  }
})
</script>
<title>Showing and Hiding Elements with JavaScript</title>
</head>
<body>
<main>
  <h1>Hiding and Showing Elements</h1>
  <table>
    <caption id="msg">
      <span id="elemId"></span> <em id="styleProp"></em>
      made <span id="styleValue"></span>
    </caption>
    <tr id="tr1"><td>Row 1</td></tr>
    <tr id="tr2"><td>Row 2</td></tr>
    <tr id="tr3"><td>Row 3</td></tr>
    <tr id="tr4"><td>Row 4</td></tr>
  </table>
  
  <h2>visibility</h2>
  <button title="tr1" class="visibility">Row 1</button>
  <button title="tr2" class="visibility">Row 2</button>
  <button title="tr3" class="visibility">Row 3</button>
  <button title="tr4" class="visibility">Row 4</button>

  <h2>display</h2>
  <button title="tr1" class="display">Row 1</button>
  <button title="tr2" class="display">Row 2</button>
  <button title="tr3" class="display">Row 3</button>
  <button title="tr4" class="display">Row 4</button>
</main>
</body>
</html>

Code Explanation

This page has two functions for changing whether a table row appears: toggleVisibility() and toggleDisplay().

  1. The toggleVisibility() function checks the computed value of the visibility property of a table row. If it is "visible", it sets it to "hidden". Otherwise, it sets it to "visible".
  2. The toggleDisplay() function checks the computed value of the display property of a table row. If it is "none", it sets it to "table-row". Otherwise, it sets it to "none".

Checking and Changing Other Style Properties

You can check and change other style properties in the same way we have done with display and visibility. For many properties, you can change styles using this function:

function changeStyle(elem, styleProp, styleValue) {
	elem.style[styleProp] = styleValue; // set style
	return getComputedStyle(elem)[styleProp]; // return new style
}

There may be no need to return the new style, but it might be interesting in case you want to log it from the calling code.

Here are some sample calls to changeStyle() to change the h1 element of a page. Open CSSObjectModel/Demos/visibility.html in Google Chrome and try this yourself:Changing Styles in DevTools.

We have created a page to allow you to practice calling the changeStyle() function.

Code Sample:

CSSObjectModel/Demos/changing-styles-simple.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<script>
 function changeStyle(elem, styleProp, styleValue) {
    elem.style[styleProp] = styleValue; // set style
    return getComputedStyle(elem)[styleProp]; // return new style
  }
</script>
<title>Changing Styles</title>
</head>
<body id="changing-styles">
<main>
  <p id="hello-world">Hello, World!</p>
</main>
</body>
</html>

Code Explanation

  1. Open CSSObjectModel/Demos/changing-styles-simple.html in your browser and open the console.
  2. Enter the following to assign the first paragraph to a variable:
    var firstP = document.querySelector('p');
  3. See if you can use the changeStyle() function to change some of the first paragraph's styles.

Here's how we did it: Changing Styles Simple

Increasing and Decreasing Measurements

  1. Open CSSObjectModel/Demos/changing-styles-simple.html in your browser if it's not already open.
  2. Enter the following at the console:
    var firstP = document.querySelector('p');
    getComputedStyle(firstP).width;
  3. Notice that the value returned ends in "px" (e.g., "300px").

Values for margin, padding, height, width, fontSize and other properties are strings and often include the unit (e.g., "px"). If you want to increment or decrement these values, you first have to extract the number from the style value and then, after doing the math, add back the unit. For example, let's say you want to increase the font size of a paragraph by two pixels. The steps to do that would be:

  1. Get the current font size. Let's say it is 20 pixels. That will be returned as "20px".
  2. Use parseInt() to extract the number from that and turn it into an integer.
  3. Add 2 to the result.
  4. Use String() to change the number back to a string and append "px" to the end.

The following example shows how to do this with the fontSize property.

Code Sample:

CSSObjectModel/Demos/changing-font-size.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<script>
    function changeFontSize(elem, change) {
    // Use parseInt to remove the unit and return an integer
    var curFontSize = parseInt(getComputedStyle(elem).fontSize);

    // Add change to curFontSize to get newFontSize
    var newFontSize = curFontSize + change;

    // Set the fontSize by converting newFontSize
    // to a string and appending unit
    elem.style.fontSize = String(newFontSize) + 'px';
    return getComputedStyle(elem).fontSize;
  }

  window.addEventListener('load', function() {
    var p = document.getElementById('hello-world');
    var btnIncrease = document.getElementById('increase');
    var btnDecrease = document.getElementById('decrease');

    btnIncrease.addEventListener('click', function() {
      // Increase font size by 1 unit
      var fontSize = changeFontSize(p, 1);
      console.log(fontSize);
    });

    btnDecrease.addEventListener('click', function() {
      // Decrease font size by 1 unit
      var fontSize = changeFontSize(p, -1);
      console.log(fontSize);
    });
  })
</script>
<title>Changing Font Size</title>
</head>
<body id="changing-styles">
<main>
  <label>Change Font:</label>
  <button id="increase">&uparrow;</button>
  <button id="decrease">&downarrow;</button>
  <p id="hello-world">Hello, World!</p>
</main>
</body>
</html>

Code Explanation

After hitting the up arrow a bunch of times, the page will look like this: Change Font Size

Making changeFontSize() More Flexible

Currently, the changeFontSize() function only works for incrementing and decrementing the font size. The following code makes it more flexible, so that it will work with incrementing or decrementing any property value that is set in pixels:

function changeStyleWithPx(elem, styleProp, change) {
  var curStyleValue = parseInt(getComputedStyle(elem)[styleProp]);
  var newStyleValue = curStyleValue + change;
  elem.style[styleProp] = String(newStyleValue) + 'px';
  console.log(styleProp + ': ' + elem.style[styleProp]);
}

Two things to notice:

  1. The function now includes a styleProp parameter to take the style property being changed.
  2. We use square-bracket notation rather than dot notation for the style properties. For example, instead of getComputedStyle(elem).styleProp, we use getComputedStyle(elem)[styleProp]. That is because styleProp is not a property of getComputedStyle(elem), but rather a variable holding a string. If we pass 'fontStyle' as the styleProp, getComputedStyle(elem)[styleProp] gets interpreted as getComputedStyle(elem)['fontStyle']. The code getComputedStyle(elem).styleProp would return undefined because, again, styleProp is not a property of getComputedStyle(elem).

Custom data Attributes

HTML tags can take custom data attributes that take the form of data-attribute-name. These attributes are used to provide additional information about an element and are available through the special dataset property of element nodes. To illustrate, let's look at an example we saw earlier in the course:

Code Sample:

CSSObjectModel/Demos/add-event-listener.html
---- C O D E   O M I T T E D ----
<script>
function changeBg(colorOrEvent) {
  var color = 'white'; // default
  if ( typeof colorOrEvent === 'string' ) {
    color = colorOrEvent;
  } else {
    color = colorOrEvent.currentTarget.id;
  }
  document.body.style.backgroundColor = color;
}

function changeBgWhite(e) {
  changeBg('white');
}
---- C O D E   O M I T T E D ----

Code Explanation

Notice that we key off the id values to set the background color. That's not a great way to handle this as the id value should not be tied to any specific functionality.

Take a look at the following, which solves this problem using custom data attributes:

Code Sample:

CSSObjectModel/Demos/add-event-listener-improved.html
---- C O D E   O M I T T E D ----
function changeBg(e) {
  var eventType = e.type;
  var target = e.currentTarget;
  switch(eventType) {
    case 'click':
    case 'dblclick':
    case 'mousedown':
    case 'mouseover':
      color = target.dataset.activeColor;
      break;
    case 'mouseup':
    case 'mouseout':
      color = target.dataset.inactiveColor;
      break;
    default:
      color = 'white';
  }
  document.body.style.backgroundColor = color;
}

window.addEventListener('load', function() {
  var btnRed = document.getElementById('btn-red');
  var btnGreen = document.getElementById('btn-green');
  var btnOrange = document.getElementById('btn-orange');
  var lnkPink = document.getElementById('link-pink');

  btnRed.addEventListener('click', changeBg);
  btnGreen.addEventListener('dblclick', changeBg);
  btnOrange.addEventListener('mousedown', changeBg);
  btnOrange.addEventListener('mouseup', changeBg);
  lnkPink.addEventListener('mouseover', changeBg);
  lnkPink.addEventListener('mouseout', changeBg);

  document.addEventListener('keypress', changeBg);
});
---- C O D E   O M I T T E D ----

Code Explanation

Things to notice:

  1. We have given the id attributes new values.
  2. We have added new data-active-color and data-inactive-color attributes to the controls.
  3. The changeBg() function now gets the event type from the passed-in event and keys off of it to decide whether to use the activeColor value (click, dblclick, mousedown, and mouseover) or to use the inactiveColor value (mouseout and mouseup). For all other event types (e.g., keypress), it sets color to 'white.'
  4. This switch-case statement might look a little funny to you as it appears that nothing happens for many of the cases (e.g., 'click', and 'dblclick'). Remember that cases continue to be evaluated until a break statement is reached, so color = target.dataset.activeColor; runs for all of the first four cases:
    case 'click':
    case 'dblclick':
    case 'mousedown':
    case 'mouseover':
      color = target.dataset.activeColor;
      break;

Review the following code to see some of these concepts used in practice. Be sure to open CSSObjectModel/Demos/changing-styles.html in the browser and play around.

Code Sample:

CSSObjectModel/Demos/changing-styles.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<script>
function changeStyle(elem, styleProp, styleValue) {
  elem.style[styleProp] = styleValue;
  console.log(styleProp + ': ' + elem.style[styleProp]);
}

function changeStyleWithPx(elem, styleProp, change) {
  var curStyleValue = parseInt(getComputedStyle(elem)[styleProp]);
  var newStyleValue = curStyleValue + change;
  elem.style[styleProp] = String(newStyleValue) + 'px';
  console.log(styleProp + ': ' + elem.style[styleProp]);
}

window.addEventListener('load', function() {
  var p = document.getElementById('hello-world');
  var selector;

  selector = 'button[data-font-style]';
  var bsFtStyle = document.querySelectorAll(selector);
  for (btn of bsFtStyle) {
    btn.addEventListener('click', function(e) {
      var fontStyle = e.currentTarget.dataset.fontStyle;
      changeStyle(p, 'fontStyle', fontStyle);
    });
  }

  selector = 'button[data-font-size]';
  var bsFtSize = document.querySelectorAll(selector);
  for (btn of bsFtSize) {
    btn.addEventListener('click', function(e) {
      var fontSize = e.currentTarget.dataset.fontSize;
      changeStyle(p, 'fontSize', fontSize);
    });
  }

  selector = 'button[data-border-style]';
  var bsBrdStyle = document.querySelectorAll(selector);
  for (btn of bsBrdStyle) {
    btn.addEventListener('click', function(e) {
      var borderStyle = e.currentTarget.dataset.borderStyle;
      changeStyle(p, 'borderStyle', borderStyle);
    });
  }

  selector = 'button[data-padding]';
  var bsPadding = document.querySelectorAll(selector);
  for (btn of bsPadding) {
    btn.addEventListener('click', function(e) {
      var target = e.currentTarget;
      // Use ternary operator to assign change value
      var change = target.dataset.padding==='increase' ? 10 : -10;
      changeStyleWithPx(p, 'padding', change);
    });
  }

  selector ='button[data-margin]';
  var bsMargin = document.querySelectorAll(selector);
  for (btn of bsMargin) {
    btn.addEventListener('click', function(e) {
      var target = e.currentTarget;
      // Use ternary operator to assign change value
      var change = target.dataset.margin==='increase' ? 10 : -10;
      changeStyleWithPx(p, 'margin', change);
    });
  }
});
</script>
<title>Changing Styles</title>
</head>
<body id="changing-styles">
<main>
  <div>
    <label>fontStyle:</label>
    <button class="font-style italic" data-font-style="italic">Italic</button>
    <button class="font-style normal" data-font-style="normal">Normal</button>
  </div>
  <div>
    <label>fontSize:</label>
    <button class="font-size xx-small" data-font-size="xx-small">A</button>
    <button class="font-size x-small" data-font-size="x-small">A</button>
    <button class="font-size small" data-font-size="small">A</button>
    <button class="font-size medium" data-font-size="medium">A</button>
    <button class="font-size large" data-font-size="large">A</button>
    <button class="font-size x-large" data-font-size="x-large">A</button>
    <button class="font-size xx-large" data-font-size="xx-large">A</button>
  </div>
  <div>
    <label>borderStyle:</label>
    <button class="border-style none" data-border-style="none">none</button>
    <button class="border-style dotted" data-border-style="dotted">dotted</button>
    <button class="border-style dashed" data-border-style="dashed">dashed</button>
    <button class="border-style solid" data-border-style="solid">solid</button>
  </div>
  <div>
    <label>padding:</label>
    <button class="padding increase" data-padding="increase">Increase</button>
    <button class="padding decrease" data-padding="decrease">Decrease</button>
  </div>
  <div>
    <label>margin:</label>
    <button class="margin increase" data-margin="increase">Increase</button>
    <button class="margin decrease" data-margin="decrease">Decrease</button>
  </div>
  <p id="hello-world">Hello, World!</p>
</main>
</body>
</html>

Gotcha with fontWeight

These days, the most popular browsers tend to be very much in sync with their support and implementation of CSS and JavaScript; however, Google Chrome and Safari return different values when reading the computed fontWeight style property.

  • Chrome returns the weight as a number: "400" for "normal" and "700" for "bold".
  • Safari returns the weight as a keyword: "normal" and "bold".

The following two screenshots illustrate this: Font Weight - Google Chrome Font Weight - Safari

As a result, when checking to see if an element is bold or not, you need to write code that checks both possibilities. The following demo shows a function for handling this:

Code Sample:

CSSObjectModel/Demos/changing-font-weight.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<script>
  function toggleBold(elem) {
    var weight = getComputedStyle(elem).fontWeight;
    console.log("Old weight: " + weight);
    if (weight === 'bold' || weight > 400) {
      elem.style.fontWeight = "normal";
    } else {
      elem.style.fontWeight = "bold";
    }
    console.log("New weight: " + getComputedStyle(elem).fontWeight);
  }

  window.addEventListener('load', function() {
    var p = document.getElementById('hello-world');
    var btn = document.getElementById('font-weight');
    btn.addEventListener('click', function() {
      toggleBold(p);
    });
  });
</script>
<title>Changing Font Weight</title>
</head>
<body id="changing-styles">
<main>
  <label>Change Font Weight:</label>
  <button id="font-weight">Toggle Font Weight</button>
  <p id="hello-world">Hello, World!</p>
</main>
</body>
</html>

Font Awesome

Font Awesome (at https://fontawesome.com) provides a collection of free vector icons that you can use on your websites. You can get access to these icons through a free content delivery network (CDN). To do so, you simply add a <link> tag to your web page:

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">

To get the <link> tag with the latest version of Font Awesome:

  1. Go to https://fontawesome.com.
  2. Click the How to Use menu item at the top of the page.
  3. Copy the link on the page: Font Awesome Link
  4. Paste the link into the head of your HTML:
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="../normalize.css">
    <link rel="stylesheet" href="../styles.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
      integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" 
      crossorigin="anonymous">
    <title>Font Awesome</title>
    </head>

Finding and Using Icons

To find Font Awesome icons:

  1. Use the search feature at https://fontawesome.com/icons: Font Awesome Search
  2. Click on the icon you want to use.
  3. Find the HTML code snippet on the icon page and copy it by clicking the clipboard icon:Font Awesome Copy
  4. Then paste that code snippet wherever you want the icon to show up.

Below is a page containing several Font Awesome icons:

Code Sample:

CSSObjectModel/Demos/font-awesome.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../normalize.css">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
  integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" 
  crossorigin="anonymous">
<title>Font Awesome</title>
</head>
<body id="font-awesome">
<main>
  <i class="fas fa-phone"></i>
  <i class="fas fa-envelope-square"></i>
  <i class="fas fa-search"></i>
  <i class="fab fa-grunt"></i>
  <i class="fab fa-facebook"></i>
  <i class="fas fa-chevron-up"></i>
  <i class="fas fa-chevron-down"></i>
</main>
</body>
</html>

Code Explanation

Open this page in your browser to see the icons.

classList Property

The classList property of an element returns a list of the classes the element contains. This list can be modified using the following methods:

classList Methods
Method Description
add(className) Adds className class.
remove(className) Removes className class. Note that this doesn't error if the element doesn't contain the className class.
toggle(className) If element contains the className class, it removes it. If it doesn't contain the className class, it adds it.
contains(className) Returns true if the element contains the className class. Otherwise, returns false.
replace(oldClass, newClass) Replaces oldClass with newClass.

Showing and Hiding Elements

Duration: 20 to 30 minutes.

Google search has a "People also ask" feature that shows a list of questions similar to your search. When you click on one of the questions the beginning of the answer shows up below it:People also ask - Google

In this exercise, you will start with a list of questions with the answers hidden as shown below:People also ask - 1

When the user clicks on one of the questions, the answer will appear:People also ask - 2

The starting code is shown below.

Code Sample:

CSSObjectModel/Exercises/people-also-ask.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="../styles.css" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" 
  integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU"
  crossorigin="anonymous">
<script>
  function toggleDisplay(elem) {
    // Toggle the display of elem between "block" and "none".
  }

  function toggleAnswer(e) {
    /*
      Get the question div that was clicked
      and use it to then get the answer div.
      Send the answer div to toggleDisplay().
      Toggle the class of the chevron i element
      between 'fa-chevron-down' and 'fa-chevron-up'.      
    */
  }

  window.addEventListener('load', function(e) {
    var questions = document.querySelectorAll('.question');
    for (question of questions) {
      question.addEventListener('click', toggleAnswer);
    }
  });
</script>
<title>People Also Ask</title>
</head>
<body id="people-also-ask">
<main>
  <h1>People also ask</h1>
  <ul id="questions">
    <li>
      <div class="question">
        How do I enable JavaScript Chrome?
        <i class="fas fa-chevron-down chevron"></i>
      </div>
      <div class="answer">
        <p><strong>If you'd like to turn JavaScript 
    off or on for all sites:</strong></p>
        <ol>
          <li>Click the Chrome menu in the top right-hand
      corner of your browser.</li>
          <li>Select Settings.</li>
          <li>Click Show advanced settings.</li>
          <li>Under the "Privacy" section, 
      click the Content settings button.</li>
        </ol>
      </div>
    </li>
---- C O D E   O M I T T E D ----

  </ul>
</main>
</body>
</html>

Code Explanation

  1. Open CSSObjectModel/Exercises/people-also-ask.html for editing.
  2. Write the code for the toggleDisplay() function so that it toggles the value of the display property of the passed-in element between "block" and "none".
  3. Write the code for the toggleAnswer() function so that it:
    1. Gets the question div that was clicked and uses it to then get the answer div. Hint: You may want to review Accessing Elements Hierarchically.
    2. Sends the answer div to toggleDisplay().
    3. Toggles the class of the chevron i element between "fa-chevron-down" and "fa-chevron-up". You should use one or more of the methods for classList.
  4. Test your solution in a browser.

Challenge

  1. When a question is opened, make the question bold:People also ask - 3
  2. When a question is closed, meaning that's it already been opened and presumably the answer has been seen, change the opacity of the question to .5:People also ask - 4

Solution:

CSSObjectModel/Solutions/people-also-ask.html
---- C O D E   O M I T T E D ----

<script>
  function toggleDisplay(elem) {
    if (getComputedStyle(elem).display === "none") {
      elem.style.display = "block";
    } else {
      elem.style.display = "none";
    }
  }

  function toggleAnswer(e) {
    var question = e.currentTarget;
    var answer = question.nextElementSibling;
    toggleDisplay(answer);
    var chevron = question.querySelector('.chevron');
    chevron.classList.toggle('fa-chevron-down');
    chevron.classList.toggle('fa-chevron-up');
  }

  window.addEventListener('load', function(e) {
    var questions = document.querySelectorAll('.question');
    for (question of questions) {
      question.addEventListener('click', toggleAnswer);
    }
  });
</script>
---- C O D E   O M I T T E D ----

Code Explanation

Challenge Solution:

CSSObjectModel/Solutions/people-also-ask-challenge.html
---- C O D E   O M I T T E D ----
function toggleBold(elem) {
    var weight = getComputedStyle(elem).fontWeight;
    if (weight === 'bold' || weight > 400) {
      elem.style.fontWeight = "normal";
      markRead(elem);
    } else {
      elem.style.fontWeight = "bold";
    }
  }

  function markRead(elem) {
    elem.style.opacity = .5;
  }

  function toggleAnswer(e) {
    var question = e.currentTarget;
    var answer = question.nextElementSibling;
    toggleDisplay(answer);
    toggleBold(question);
    var chevron = question.querySelector('.chevron');
    chevron.classList.toggle('fa-chevron-down');
    chevron.classList.toggle('fa-chevron-up');
  }
---- C O D E   O M I T T E D ----