Web developer head body joke

CSE 154

Lecture 10: The DOM Tree

CSE 154

Agenda

Reminder: CP3 due today

  • CP2 showcase is up! Check out it out and consider opting into this week's CP3 showcase!)

HW3 notes:

  • Slight change to spec (ordering of JavaScript source)
  • JS Doc requirement (see next slide)

Follow up from Friday ( buttonTest.js )

Reintroducing the keyword this

Modifying the DOM tree

Selecting multiple DOM objects

Reminder: Commenting Expectations with JS using JSDoc

JSDoc section of CSE 154 Code Quality Guide

Got picture
  • A lot of information
  • How to separate content/structure (HTML) from style (CSS)
  • General syntax and Code Quality
  • Understanding of HTML (Semantic tags, classes and ids)
  • Understanding of CSS as style (background, box model, positioning and layout, context selectors)
  • That CSS can be finicky!
  • Flexbox is Magical!
Got picture
  • Examples (Ducky Store)
  • Following instructions for web design
  • Professional searching skills*
  • HW 1 & 2 and CP 1 & 2 done
  • Putting pages together to get something that looks like a website!

Caveat: read Piazza post 358

Need picture

More practice with...

  • semantic tags
  • context selectors
  • inheritance
  • layout and positioning, flexbox
  • why semantic tags like <em>, <strong> and <blockquote>, <section> etc, over <i>, <b>, <div>
  • Javascript
    • The language
    • Anonymous functions, window.onload, events
  • Debugging
Need picture
  • How to print the slides Control or command P!
  • Information on code quality details Code Quality Guide
  • Example code Posted on the calendar after lecture!
  • Comments in lab solutions We’re trying!
  • Midterm practice problems Coming soon! Use Practice it and Code Step By Step!
  • Lectures to go faster vs more live coding in class <headdesk>

this

The keyword this

All JavaScript code actually runs inside of an object

The this keyword refers to the current object

By default, code runs in the global window object so...

  • this === window
  • All global variables and functions you declare are a part of window

          this.fieldName                 // access field
          this.fieldName = value;        // modify field
          this.functionName(parameters); // call method
          

JS

Example with dropdown this

See thisexample.html and thisexample.js

Example drop down list box

Reminder: Dropdown list box using <select>, <option>


            

HTML

output

Option element represents each choice

select optional attributes: disabled, multiple, size

option optional attributes: disabled, selected, label, value

Using this with dropdowns

Best to see example code in thisexample.js


              

HTML


window.onload = function() {
  $("favorite-character").onchange = function() {
    // Accessing the value from the this object
    // which is the drop down favorite-character
    displayFavoriteCharacter(this.value);
  }
};

function displayFavoriteCharacter(value) {
  $("favorite-character-output").innerHTML =
    "Your favorite character is: " + value;
}

JS

The DOM Tree

the DOM hierarchy

The elements of a page are nested into a tree-like structure of objects

Traversing and Manipulating the DOM

We already know how to get an element of of the DOM if the html element is tagged with an id

We also know we can add things to the tree using innerHTML hacking but that we should only add bare text (see next slide)

The DOM also has

  • properties and methods for traversing the tree
  • methods for creating new nodes to add to the tree
  • methods for removing nodes from the tree

Reminder: innerHTML hacking is bad

Why not just code this way?


            document.getElementById("add").innerHTML =  "<p>A result!</p>";
          

JS

Bad code quality (maintainability) on many levels

  • Not modular: HTML code embedded within JS
  • What if you have a complicated new node (with many subchildren) to add?
  • Error-prone: must carefully distinguish " and '
  • Can only add at beginning or end, not in middle of child list

            // Substitutes all children
            $("add").innerHTML =  "<p>A result!</p>";
            // adds a node to the front of the list of children.
            $("add").innerHTML  = "<p>A result!</p>" + $("result").innerHTML;
            // adds a node to the end of the list of children
            $("add").innerHTML += "<p>A result!</p>";
          

JS

Creating New Nodes

Name Description
document.createElement("tag") creates and returns a new empty DOM node representing an element of that type
document.createTextNode("text") creates and returns a text node containing given text

 


          // create a new <h2> node
          let newHeading = document.createElement("h2");
          newHeading.innerHTML = "This is a heading";
          newHeading.style.color = "green";
          

JS

NOTE: Merely creating an element does not add it to the page

You must add the new element as a child of an existing element on the page...

Modifying the DOM Tree

Every DOM element object has these methods:

Name Description
appendChild(node) places the given node at end of this node's child list
insertBefore(new, old) places the given node in this node's child list just before old child
removeChild(node) removes the given node from this node's child list
replaceChild(new, old) replaces given child with new nodes

 


          let p = document.createElement("p");
          p.innerHTML = "A result!";
          document.getElementById("the-place").appendChild(p);
          

JS

Complex DOM Manipulation Problems

How would we do each of the following in JavaScript code? Each involves modifying each one of a group of elements...

  • When the user hovers over the maze boundary, turn all maze walls red.
  • Change every other item in the ul list with id of 'tas' to have a gray background.
  • When the Go button is clicked, reposition all the divs of class 'puzzle' to random x/y locations.
picture of a maze

from pixabay

Selecting Groups of DOM Objects

Methods in document and other DOM objects:

Name Description
getElementsByTagName(tag) returns array of descendants with the given tag, such as "div"
getElementsByName(name) returns array of descendants with the specified name (mostly useful for accessing form controls)
querySelector(selector) returns the first element that would be matched by the given CSS selector string
querySelectorAll(selector) returns an array of all elements that would be matched by the given CSS selector string

Getting all elements of a certain type

Use querySelectorAll to highlight all paragraphs in the document:


          <body>
            <p>This is the first paragraph</p>
            <p>This is the second paragraph</p>
            <p>You get the idea...</p>
          </body>
          

HTML


          // get all DOM objects that are <p>
          let allParas = document.querySelectorAll("p");
          for (let i = 0; i < allParas.length; i++) {
            allParas[i].style.backgroundColor = "yellow";
          }
          

JS

This is the first paragraph

This is the second paragraph

You get the idea...

output

Complex selectors

Highlight all paragraphs inside of the section with ID 'address':


          
            <p>This won't be highlighted!</p>
            <div id="address">
              <p>1234 Street</p>
              <p>Seattle, WA</p>
            </div>
            
          
          

HTML


          let addrParas = document.querySelectorAll("#address p");
          for (let i = 0; i < addrParas.length; i++) {
            addrParas[i].style.backgroundColor = "yellow";
          }
          

JS

This won't be highlighted!

1234 Street

Seattle, WA

output

Common querySelectorAll issues

Many students forget to write . or # in front of a class or id


          // get all buttons with a class of "control"
          let gameButtons = document.querySelectorAll("control");
          let gameButtons = document.querySelectorAll(".control");
          

JS

querySelectorAll returns an array, not just a single element;
you must loop over the results


          // set all buttons with a class of "control" to have red text
          document.querySelectorAll(".gamebutton").style.color = "red";
          let gameButtons = document.querySelectorAll(".gamebutton");
          for (let i = 0; i < gameButtons.length; i++) {
            gameButtons[i].style.color = "red";
          }
          

JS

Q: Can I still select a group of elements using querySelectorAll even if my CSS file doesn't have any style rule for that same group? (A: Yes!)

Getting the first element of a certain type

Use querySelector to highlight the first paragraph in the document:


          <body>
            <p>This is the first paragraph</p>
            <p>This is the second paragraph</p>
            <p>You get the idea...</p>
          </body>
          

HTML


          // get all DOM objects that are <p>
          let para = document.querySelector("p");
          allParas.style.backgroundColor = "yellow";
          

JS

This is the first paragraph

This is the second paragraph

You get the idea...

output

Reading/Changing Styles

The .style property of a DOM object lets you set any CSS style for an element


           button { font-size: 16pt; }
          

CSS


            
          

HTML


          window.onload = function() {
            document.getElementById("clickme").onclick = biggerFont;
          };

          function biggerFont() {
            let button = document.getElementById("clickme");
            let size = parseInt(button.style.fontSize);
            button.style.fontSize = (size + 4) + "pt"; // notice adding the units!
          }
          

JS

output

Problems with Reading/Changing Styles

A catch: you can only use this to read styles set in the html/css files or with the DOM .style. You cannot read dynamic css properties from this.

Be careful to add the units to numerical values like pt, px, vw, etc

Accessing Elements' Computed Styles

getComputedStyle method of global window object accesses existing styles


          window.getComputedStyle(element).propertyName;
          

JS (template)


          div {
            height: 100%;
            width: 100%;
          }
          

CSS


          > document.querySelector("div").style.height
          > "100%"
          > window.getComputedStyle(document.querySelector("div")).height
          > "950px"
          

JS Console Output

Thanks to Daniel H for the example

Common Bug: Incorrect Usage of Existing Styles

The following example attempts to add 100px to the top of main, but fails.

Consider the case when main has top set to "200px". Then this code would update style.top to be the invalid value of "200px100px"


          let main = document.getElementById("main");
          main.style.top = window.getComputedStyle(main).top + 100 + "px";
          

JS

A corrected version:


          main.style.top = parseInt(window.getComputedStyle(main).top) + 100 + "px";
          

JS

Meh: Getting/Setting CSS Classes with .className

JS DOM's className property corresponds to HTML class attribute


          function highlightField() {
            // turn text yellow
            let text = document.getElementById("text");
            if (!text.className) {
              text.className = "highlight";
            } else if (text.className.indexOf("invalid") < 0) {
              text.className += "highlight"; // awkward
            }
          }
          

JS

Works well for adding one class to the object

Somewhat clunky when dealing with multiple space-separated classes as one big string

Better: Getting/Setting CSS Classes with classList


          function highlightField() {
            // turn text yellow
            let text = document.getElementById("text");
            if (!text.classList.contains("invalid")) {
              text.classList.add("highlight");
            }
          }
          

JS

classList collection has methods add, remove, contains, and toggle to manipulate CSS classes

Similar to existing className DOM property, but don't have to manually split by spaces

Removing a Node From the Page


          function slideClick() {
            let bullet = document.getElementById("removeme");
            bullet.parentNode.removeChild(bullet);
          }
          

JS

To remove a node from the tree, you have to do this odd thing:
obj.parentNode.remove(obj)

Types of DOM Nodes


            

This is a paragraph of text with a link in it.

HTML

Node Types

Element Nodes (HTML tag)

  • Can have children and/or attributes

Text Nodes (text in a block element)

Attribute Nodes (attribute/value pair)

  • text/attributes are children in an element node
  • Cannot have children and/or attributes
  • Not usually shown when drawing the DOM tree

Traversing the DOM Tree Manually

Every node's DOM object has the following properties:

Name(s) Description
firstChild, lastChild start/end of this node's list of children
childNodes array of all of this node's children
nextSibling, previousSibling neighboring nodes with the same parent
parentNode, the element that contains this node

Complete list of DOM node properties

Browser incompatibility information (IE6 sucks)

DOM Tree Traversal Example


                

This is a paragraph of text with a link.

HTML

DOM traversal example

Element vs. Text Nodes


          

This is a paragraph of text with a link.

HTML

Q: How many children does the div have? A: 3

  • an element node representing the <p>
  • two text nodes representing "\n\t" (before/after the paragraph)

Q: How many children does the paragraph have? A: 3 (text, a, text)

Q: The a tag? A: 1 (text)

Back to this

Redundant way of handling radio buttons


 Visa
 Mastercard
 American Express
          

HTML


 function processCard() {
    let output = "";
    if (document.getElementById("visa").checked) {
      output = "Visa";
    } else if (document.getElementById("mastercard").checked) {
      output = "Mastercard";
    } else {
      output = "American Expresss";
    }
    document.getElementById("cc-output").innerHTML = "You chose " + output;
  }
          

JS

Output

Fixing Redundant Code with this

Mind blowing: If the same function is assigned to multiple elements, each gets its own bound copy (difference between what we read and what is executed)


          window.onload = function() {
            let cards = document.getElementsByName("cc");
            for (let ii = 0; ii < cards.length; ii++){
              cards[ii].onchange = function() {
                processCard(this);
              }
            }
          };

          function processCard(obj) {
            let output = this.value;
            document.getElementById("cc-output").innerHTML = "You chose " + obj.value;
          }
          

JS