DOM

use the arrow keys to navigate; press space for slide overview

DOM

Intro

Scripts recap (1)

  • Link your scripts at the bottom, vendor scripts first, your scripts last:
    <!DOCTYPE html>
    <html>
    <head>
      <title>How to include scripts</title>
      <meta charset="utf-8" />
    </head>
    <body>
      ...
      <!-- load external libraries -->
      <script src="vendor/external-script-1.js"></script>
      <script src="vendor/external-script-2.js"></script>
      <!-- load your scripts -->
      <script src="js/my-script.js"></script>
    </body>
    </html>

Scripts recap (2)

  • To avoid name conflicts with third party scripts, wrap your code in a function:
    (function() {
      'use strict';
    
      let function1 = function() {
        // ...
      }
    
      let function2 = function() {
        // ...
      }
    
      window.addEventListener('load', function() {
        // start your script here
        // ...
      });
    })();

The DOM

  • In previous presentation, we already saw a few examples of how to interact with the HTML page and CSS here and here.
  • A programmable model of the current HTML page accessible for Javascript is called the Document Object Model, in short the DOM.
  • In early days browser DOM support used to be disastrous, but today you don't have to worry about browser differences (at least not in this course)

Trees (1)

  • Before we talk about DOM, some terms used in tree structures:

Trees (2)

  • Some more terms:

DOM — full version

  • The actual DOM is, well, quite complex:

This tree looks weird, and it actually is. It contains some elements like forms, images, applets, the body etc... but many other elements are missing like tables, divs, the head etc...

The reason is that the blueprint of this tree representation isn't the result of a logical process, but has rather grown organically in a time no real standard existed on the background of a full-scale browser war between Netscape and Internet Explorer. By the time the W3C jumped in to develop a standard, they had no choice but use a compromise between the IE and NN DOM models instead of starting from scratch.

DOM — final version

  • Luckily, we just need a small part of it:

DOM objects

  • The DOM consists of the window, location, history and document objects, and all HTML element objects that can be found with one of the three methods.
  • Each DOM object has
    • properties like width, value, src etc... (some are read/write, others are read only)
    • methods like submit(), focus() etc.. (which are actually properties too)
    • events like onclick, onchange, onload etc...
  • An excellent DOM reference is W3Schools

window object

window object

→ full listing here

window object

  • Some properties:
    console.log('kader: ' + window.innerWidth + 'x' + window.innerHeight);
  • Some Methods:
    window.alert('hi');
    if (window.confirm('print this page?')) window.print();
  • As window is the root of any path, you may omit this:
    console.log('kader: ' + innerWidth + 'x' + innerHeight);
    alert('hi');
    if (confirm('print this page?')) print();

history object

history object

→ full listing here

history object

  • You are not supposed to mess with browser history:
    history.back(); // don't use this!
    
    
  • Since HTML5 however this node contains some interesting new methods like pushState() and popState(), which can be used to maintain browsing history logic in rich Javascript applications. This falls outside the scope of this course.

location object

location object

→ full listing here

location object

  • Refers to the address bar. Example forcing a redirect:
    location.href = 'http://www.google.be';
    
  • Return page hash:
    console.log(location.hash);
    
  • Reload the page:
    location.reload();
    

document object

document object

→ full listing here

document object

  • This node carries the entire HTML document. The node itself is rather uninteresting, with only a few properties and methods (apart from the selector functions we'll talk about in a minute):
    document.title = "hello there";
    
    document.write('kot kot kedei'); // beware: this page will be overwritten
    

Finding HTML elements

Finding HTML elements

  • Single HTML elements can be found with the methods getElementById() and querySelector():
    let button1 = document.getElementbyId('btn1'); // element with id 'btn1'
    let button1 = document.querySelector('#btn1'); // this is identical
    let msg = document.querySelector('#login input'); // get first #login input element
    
  • getElementById(): get by id; returns a single result
  • querySelector(): get by CSS selector; returns a single result

getElementById

  • getElementById() gets an element by its id, e.g.:
    <p id="message">message here...</p>
    <button id="btn1">say hello</button>
    document.getElementById('btn1').addEventListener('click', function() {
      document.getElementById('message').innerHTML = 'Hello World';
    });
    ...or a little bit cleaner:
    let button1 = document.getElementById('btn1'); // get button by id
    let msg = document.getElementById('message'); // get paragraph by id
    button1.addEventListener('click', function() {
      msg.innerHTML = 'Hello World';
    });

querySelector

  • querySelector() gets a single element by CSS selector, e.g.:
    <form id="frmLogin">
      <span class="error"></span> 
      <label for="uname">Login: </label><input type="text" id="uname" value="Bob">
      <label for="pw">Password: </label><input type="password" id="pw" value="XYZ">
      <input type="submit" value="login">
    </form>
    let inpName = document.querySelector('#frmLogin input[type=text]');
    let inpPw = document.querySelector('#frmLogin input[type=password]');
    console.log(inpName.value);
    console.log(inpPw.value);
    
  • you can use any selector you know from CSS, so dust off your CSS knowledge!

Finding HTML elements

querySelectorAll

  • Find all HTML that match a CSS query with querySelectorAll():
    <nav>
      <ul>
        <li><a href="">item1</a></li>
        <li><a href="">item2</a></li>
        <li><a href="">item3</a></li>
        <li><a href="">item4</a></li>
      </ul>
    </nav>
    let navlinks = document.querySelectorAll('nav a');
    console.log(navlinks.length); // will display 4

querySelectorAll

  • Note: it returns an array, so you will need to iterate the items:
    <nav>
      <ul>
        <li><a href="">item1</a></li>
        <li><a href="">item2</a></li>
        <li><a href="">item3</a></li>
        <li><a href="">item4</a></li>
      </ul>
    </nav>
    // get even items (this will be item 2 and 4)
    let itemsToHide = document.querySelectorAll('nav li:nth-child(2n)');
    
    // show content of first found
    console.log(itemsToHide[0].innerHTML); // <a href="">item2</a>
    
    // iterate results and do something, e.g. hide them
    for (let item of itemsToHide) {
      item.style.display = 'none'; 
    }

Chaining queries

  • You can chain queries, e.g.:
    // find form, then find password field in that form
    let loginForm = document.getElementbyId('frm1'); 
    let inpPw = loginForm.querySelector('input[type=password]'); // look in loginForm
    
    // find table, then find all cells in that table
    let table1 = document.getElementbyId('my-table'); 
    let cells = table1.querySelectorAll('td'); // look in table1
    
    // this is of course equivalent to:
    let inpPw = document.querySelector('#frm1 input[type=password]'); 
    let cells = document.querySelectorAll('#my-table td'); 
    

DOM

Manipulating the DOM

DOM — documentation

  • Most interesting properties and methods for DOM Elements: → full listing here

DOM — documentation

  • For individual elements, additional properties and methods exist, e.g. for DOM Input Checkbox: → full listing here

Manipulating CSS and content

  • Some things you can do with almost all elements (see documentation):
    // cache the variable
    let el = document.getElementById('manipulatingDemo');
    
    // change CSS properties (note: camelCased!)
    el.style.backgroundColor = '#cc6';
    el.style.padding = '20px';
    
    // add/remove CSS classes
    el.classList.add('test'); 
    el.classList.remove('test2'); 
    
    // change the content
    el.innerHTML = 'Hammertime';
    
    // add events
    el.addEventListener('mouseover', function() {
      this.innerHTML = 'Can\'t touch this';
    });

    I am the demo

To run the demo, click 'run' first, then hover over the 'I am the demo' text below

Manipulating properties

  • There's a wide variety of elements: images, links, buttons, divs... Each may have its own additional properties, events and methods. Some examples:
    <form id="demoForm1">
      <label>Yes/no: <input type="checkbox" name="chb1" id="chb1" /></label>;
    </form>
    <img src="img/02_dom/me.jpg" id="img1" width="40" height="40" />
    
    document.getElementById('chb1').checked = true;
    document.getElementById('img1').width = 20;
    this is me

Classlist (1)

  • You could set CSS styles in Javascript directly with the style property:
    <button id="btn1">change view</button>
    <img src="img/02_dom/photo.jpg" id="img-car">
    
    #img-car { opacity: 0.7; }
    let img = document.getElementById('img-car');
    img.style.opacity = '1.0';
    img.style.transition = 'all 0.3s';
    img.style.transform = 'scale(1.3)';
    img.style.boxShadow = '#888888 5px 5px 10px';
    
    this is me
  • we always try to keep HTML, CSS and Javascript separated
  • here we set CSS directly in Javascript, which we want to avoid

Classlist (2)

  • To set or remove CSS with Javascript, it is better to simply add or remove CSS classes with the classList property:
    <img src="img/02_dom/photo.jpg" id="img-car-2">
    
    #img-car-2 { opacity: 0.7; transition: all 0.3s; }
    #img-car-2.zoom { opacity: 1.0; transform: scale(1.3); box-shadow: #888888 5px 5px 10px;' }
    let img = document.getElementById('img-car-2');
    img.classList.add('zoom');
    
    let img = document.getElementById('img-car-2');
    img.classList.remove('zoom');
    
    this is me
  • note: if a CSS property is calculated (e.g. if you wish to set an opacity depending on a HTML slider value), you still will have to set CSS properties directly with elem.style

Classlist (3)

  • Just as an illustration, the same example with a button:
    <button id="btn-view-3">change view</button>
    <img src="img/02_dom/photo.jpg" id="img-car-3">
    
    #img-car-3 { opacity: 0.7; transition: all 0.3s; }
    #img-car-3.zoom { opacity: 1.0; transform: scale(1.3); box-shadow: #888888 5px 5px 10px;' }
    let img = document.querySelector('#img-car-3');
    let button = document.querySelector('#btn-view-3');
    button.addEventListener('click', function() {
      if (img.classList.contains('zoom')) img.classList.remove('zoom'); 
      else img.classList.add('zoom');
    });                    
    
    this is me

DOM

Event handling

Events — documentation

→ full listing here

Attaching events

  • Some examples:
    let par = document.getElementById('eventsDemo');
    
    par.addEventListener('click', function() {
      console.log('You clicked me!');
    });
    par.addEventListener('mouseover', function() {
      this.style.backgroundColor = '#333';
      this.style.color = 'white';
    });
    par.addEventListener('mouseout', function() {
      this.style.backgroundColor = '';
      this.style.color = 'black';
    });

    I am the demo

  • event names are used without 'on', e.g. 'onclick' becomes 'click'

To run the demo, click 'run' first, then click on the 'I am the demo' text below

Attaching events

  • You can hook more than one function to the same event:
    let par = document.getElementById('eventsDemo2');
    
    par.addEventListener('click', function() {
      console.log('You clicked me (1)!');
    });
    par.addEventListener('click', function() {
      console.log('You clicked me (2)!');
    });
    

    I am the demo

To run the demo, click 'run' first, then click on the 'I am the demo' text below

Attaching events

  • You may also run across the older syntax for event hooking (notice onclick instead of click):
    document.getElementById('btn1').onclick = function() {
      ...
    };
    
  • This syntax does not allow hooking more than once or removing listeners, so don't use it.

Removing events

  • You can also remove an event, but only if the attached function has a name:
    let par = document.getElementById('eventsDemo3');
    let clickHandler1 = function() {
      console.log('This is the first handler');
    };
    let clickHandler2 = function() {
      console.log('This is the second handler');
      this.removeEventListener('click', clickHandler1);
    };
    par.addEventListener('click', clickHandler1);
    par.addEventListener('click', clickHandler2);
    

    I am the demo

To run the demo, click 'run' first, then click on the 'I am the demo' text below multiple times

Event bubbling

  • What happens if two or more nested elements handle the same event:
    <div id="nDemo">
      <a href="http://www.google.be"><img src="img/02_dom/me.jpg" alt=""></a>
    </div>
    
    document.querySelector('#nDemo').addEventListener('click', function() {
      console.log('div clicked');
    });
    document.querySelector('#nDemo img').addEventListener('click', function() {
      console.log('img clicked');
    });
    document.querySelector('body').addEventListener('click', function() {
      console.log('body clicked');
    });
    document.querySelector('#nDemo a').addEventListener('click', function() {
      console.log('a clicked');
    });

Prevent the default

  • All events are executed from inside to outside, and finally the default browser action is executed: event bubbling.
  • If you just want to prevent the browser action, call preventDefault() anywhere along the route:
    ...
    document.querySelector('#nDemo a').addEventListener('click', function(e) {
      console.log('a clicked');
      e.preventDefault();
    });
    

Stop bubble propagation

  • If you want to stop bubbling, call stopPropagation():
    ...
    document.querySelector('#nDemo a').addEventListener('click', function(e) {
      console.log('a clicked');
      e.stopPropagation();
    });
    
  • If you want to stop both, call both:
    ...
    document.querySelector('#nDemo a').addEventListener('click', function(e) {
      console.log('a clicked');
      e.stopPropagation();
      e.preventDefault();
    });
    

Event object properties

→ full listing here

Note: events should be used without 'on', e.g. window.addEventListener('load', ...)

Example: target

  • At any time during bubbling you can know which top element was clicked (even if it doesn't handle the event):
    ...
    document.querySelector('body').addEventListener('click', function(e) {
      console.log(e.target.tagName + ' clicked');
    });
    
  • Say you have a table with 1000 cells; instead of binding 1000 events, thanks to bubbling it's possible to bind a single handler on the <table> itself, which saves memory.
    let table = document.querySelector('#bubbling');
    table.addEventListener('click', function(e) {
      console.log('click was on ' + e.target.innerHTML);
    });
    1 2 3
    4 5 6

Example: mouse

  • The e parameter of the event listener contains information of the event. An example for mouse events:
    <input type="button" id="btn1" value="Left or right click me">
    <script>
    document.getElementById('btn1').addEventListener('mousedown', function(e) {
      let right = e.button && e.button == 2;
      console.log('You clicked ' + (right ? 'right' : 'left')
        + ' on position (' + e.pageX + ',' + e.pageY + ')');
    });
    </script>
  • Browser incompatibility may occur. Some cross-browser scripts can be found online on quirksmode.org or use a jQuery.

Example: keyboard

  • An example for keyboard events:
    <p>type something...</p>
    <input type="text" id="inp1" />
    <script>
    document.getElementById('inp1').addEventListener('keypress', function(e) {
      console.log('You pressed ' + (e.shiftKey ? 'Shift-' : '')  + e.key + ', code ' + e.keyCode);
    });
    </script>
  • Browser incompatibility may occur. Some cross-browser scripts can be found online on quirksmode.org or use a jQuery.

DOM

Example 1 — Slideshow

HTML only version

  • We'll start from this HTML/CSS page:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <title>Show photo | Slideshow</title>
      <meta charset="utf-8">
      <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
      <div id="slideshow">
        <ul id="thumbsmenu">
          <li class="active"><a href="img/photo1.jpg"><img src="img/photo1S.jpg" alt="house outside"></a></li>
          <li><a href="img/photo2.jpg"><img src="img/photo2S.jpg" alt="saloon"></a></li>
          <li><a href="img/photo3.jpg"><img src="img/photo3S.jpg" alt="garden"></a></li>
          <li><a href="img/photo4.jpg"><img src="img/photo4S.jpg" alt="living room"></a></li>
        </ul>
        <p id="big"><img src="img/photo1.jpg" alt="foto 1"></p>
      </div>
    </body>
    </html>

Linking the script

  • Next we link our script:
    <!DOCTYPE html>
    <html>
    <head>
      <title>Slideshow</title>
      ...
    </head>
    <body>
      ...
      <script src="js/scripts.js"></script>
    </body>
    </html>

Click events

  • The Javascript code isn't that complicated; I'm sure you can figure this out:
    // aliases
    let thumbs = document.querySelectorAll('#thumbsmenu li');
    let big = document.querySelector('#big');
    let photo = big.querySelector('img');
    
    // attach click events to thumbnails
    for (let thumb of thumbs) {
        let link = thumb.querySelector('a');
        let img = thumb.querySelector('img');
        link.addEventListener('click', function(e) {
            // prevent default link action
            e.preventDefault();
            // show image
            photo.src = link.href;
            photo.alt = img.alt;
            // change active state
            document.querySelector('#thumbsmenu .active').classList.remove('active');
            thumb.classList.add('active');
        });
    }
    

Custom attributes

  • HTML actually allows you to add any attribute you like, as long as you prefix it with data-. These are called custom attributes.
  • Let's add captions with custom data-captions attributes, and use a <figure> with <figcaption> for the big photo:
    ...
    <div id="slideshow">
      <ul id="thumbsmenu">
        <li class="active" data-caption="Picture from our lovely house seen from the street"><a href="img/photo1.jpg"><img src="img/photo1S.jpg" alt="house outside"></a></li>
        <li data-caption="This is our saloon in full belle epoque style"><a href="img/photo2.jpg"><img src="img/photo2S.jpg" alt="saloon"></a></li>
        <li data-caption="The sunny garden, overlooking the south"><a href="img/photo3.jpg"><img src="img/photo3S.jpg" alt="garden"></a></li>
        <li data-caption="This is where we live"><a href="img/photo4.jpg"><img src="img/photo4S.jpg" alt="living room"></a></li>
      </ul>
      <figure id="big">
        <img src="img/photo1.jpg" alt="foto 1">
        <figcaption>house outside</figcaption>
      </figure>
    </div>
    ...

Custom attributes

  • The code changes are small:
    // aliases
    ...
    let caption = big.querySelector('figcaption');
    
    // attach events
    for (let thumb of thumbs) {
        ...
        link.addEventListener('click', function(e) {
            ...
            caption.innerHTML = thumb.getAttribute('data-caption');
            ...
        });
    }
    

DOM

Example 2 — Formchecking

The HTML

  • Let's start with a classic HTML form:
    <!DOCTYPE html>
    <html>
    <head>
      <title>Formchecking</title>
      <meta charset="utf-8" />
      <link rel="stylesheet" href="demos/02_dom/fs/fonts/lato/webfont.css" />
      <link rel="stylesheet" href="demos/02_dom/fs/css/styles.css" />
    </head>
    <body>
      <form id="form1">
        <h1>Registratie</h1>
        <fieldset>
          <legend>Adresgegevens</legend>
          <div class="qstn">
            <label for="qstStreet">Straat en nummer *</label>
            <input type="text" id="qstStreet" value="" required />
            <span class="message message--error" id="errStreet">&nbsp;</span>
          </div>
          <div class="qstn">
            <label for="qstZip">Postcode *</label>
            <input type="number" id="qstZip" value="" class="input--half" required />
            <span class="message message--error" id="errZip">&nbsp;</span>
          </div>
          <div class="qstn">
            <label for="qstCity">Gemeente *</label>
            <input type="text" id="qstCity" value="" required />
            <span class="message message--error" id="errCity">&nbsp;</span>
          </div>
        </fieldset>
        <div class="buttons clearfix">
          <input type="submit" id="btnSubmit" value="Registreren" />
        </div>
      </form>
    </body>
    </html>

What to check

  • Note that basic HTML5 validation is already in place
  • Watertight form validation should happen serverside; clientside formchecking just adds a nicer experience
  • Don't make clientside tests rediculously paranoia and detailed
  • We'll basically test:
    • are all fieds filled?

Linking the script

  • We link our scripts at the end of the body:
    <!DOCTYPE html>
    <html>
    <head>
      <title>Formchecking demo</title>
      ...
    </head>
    <body>
      ...
      <script src="js/scripts.js"></script>
    </body>
    </html>

Disabling HTML5 validation

  • We'll start form our basic script, and disable HTML5 form validation:
    ;(function() {
      'use strict';
    
      // wait till DOM is loaded
      window.addEventListener('load', function() {
        // disable HTML5 form validation
        document.getElementById('form1').setAttribute('novalidate', 'novalidate');
    
        // formchecking starts here
        // ...
      });
    })();
    

Intercepting the form

  • We'll intercept the form using the submit event; basic validation scheme:
        //...
    
        // listen to form submit
        document.getElementById('form1').addEventListener('submit', function(e) {
          // halt event
          e.preventDefault();
          e.stopPropagation();
    
          // all ok for now
          let isValid = true;
    
          // perform checks here
          // ...
    
          // draw conclusion
          if (isValid) {
            console.log('all ok');
          } else {
            console.log('form contains errors');
          }
        });
    
        //...
    

Checking the form

  • Validating the first input:
          // ...
    
          // error message shorthand
          let errStreet = document.getElementById('errStreet');
    
          // input shorthand
          let qstStreet = document.getElementById('qstStreet');
    
          // clear error message
          errStreet.innerHTML = '&nbsp;';
    
          // check street and number
          if (qstStreet.value == '') {
            isValid = false;
            errStreet.innerHTML = 'gelieve een straat en nummer in te vullen';
          }
    
          // ...

Full listing

  • The full script:
    ;(function() {
      'use strict';
    
      // wait till DOM is loaded
      window.addEventListener('load', function() {
        // disable HTML5 form validation
        document.getElementById('form1').setAttribute('novalidate', 'novalidate');
    
        // intercept document submit
        document.getElementById('form1').addEventListener('submit', function(e) {
          // halt event
          e.preventDefault();
          e.stopPropagation();
    
          // form checking
          let allOk = true;
    
          // error messages shortcuts
          let errStreet = document.getElementById('errStreet');
          let errZip = document.getElementById('errZip');
          let errCity = document.getElementById('errCity');
    
          // input shortcuts
          let qstStreet = document.getElementById('qstStreet');
          let qstZip = document.getElementById('qstZip');
          let qstCity = document.getElementById('qstCity');
    
          // clear all error messages
          let errMessages = document.querySelectorAll('.message--error');
          for (let i = 0; i < errMessages.length; i++) {
            errMessages[i].innerHTML = '&nbsp;';
          }
    
          // check street and number
          if (qstStreet.value == '') {
            allOk = false;
            errStreet.innerHTML = 'gelieve een straat en nummer in te vullen';
          }
    
          // check zip
          if (qstZip.value == '') {
            allOk = false;
            errZip.innerHTML = 'gelieve een postcode in te vullen';
          }
    
          // check city
          if (qstCity.value == '') {
            allOk = false;
            errCity.innerHTML = 'gelieve een gemeente in te vullen';
          }
    
          // draw conclusion
          if (allOk) {
            console.log('all ok');
          } else {
            console.log('form contains errors');
          }
    
        });
      });
    
    })();
Odisee logo