lesson 5

JS part 2

DOM

One of the primary roles of JavaScript in the browser is as a scripting interface to the Document Object Model (DOM). The DOM is an API for HTML documents, exposing a structural representation of the document, and enabling modification of its content and visual presentation.

data types

The API exposes several different objects and data types, each with their own set of properties and methods.

window

The window object represents the default parent view for a browser page, as well as being the browser's global JavaScript object. Here are some of the key properties and methods available:

location

window.location returns an object containing information about the URL of the document:


    console.log(window.location.href); // "http://skole.apt10.kjapt.no/lesson5/"
    console.log(window.location.pathname); // "/lesson5/"
        
navigator

window.navigator returns an object containing information about the browser:


    console.log(window.navigator.ua);
    //"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) ... Safari/534.55.3"
        
history

window.history returns an object that enables manipulation of the browser's session history, including changing the URL without causing a page refresh:

it's possible to fallback to location.hash changes in order to simulate history session management

oldIEIEWebkitFirefoxiOSAndroid
scroll properties

The following window properties store information regarding the window's scroll state:

timer methods

The following window methods can be used to execute functions after a specified delay:

delays and intervals are approximate: the minimum delay/interval varies by browser (usually 4ms), with lesser values rounded up; execution may be delayed while the browser performs complex computations

window.requestAnimationFrame is a new API for efficiently executing interval code, allowing browsers to better control timing, including throttling requests for content in hidden tabs:


    // will be called approximately every 16ms (60HZ)
    function tick() {
      doSomething();
      doSomethingElse();
      // vendor prefixes required
      window.requestAnimationFrame(tick);
    }
    tick();
        

it's possible to polyfill this behaviour with window.setTimeout() for browsers that don't yet support it

oldIEIEChromeFirefoxSafariiOSAndroid

document

The document object serves as the entry point for a page's DOM tree, and contains a direct reference to some of the main page elements:

element

element objects generally make up the bulk of a page's DOM tree, and include all the element types we are familiar with: div, span, p, ul, etc. An element inherits functionality for navigating/accessing connected nodes:

elements also provide the ability to read and modify their content and visual state:

events

window, document, and all elements emit several events that can be listened for via JavaScript. By employing a registration API, it is easy to add and remove multiple event handlers to objects:


    element.addEventListener('click', clickHandler, false);
    function clickHandler(evt) {
      console.log('clicked');
      element.removeEventListener('click', clickHandler);
    }
        

Naturally, oldIE did things a little differently, so a more cross-browser approach requires a little more work:


    if (element.addEventListener) {
      element.addEventListener('click', clickHandler, false);
    } else {
      element.attachEvent('onclick', clickHandler);
    }
    function clickHandler(evt) {
      console.log('clicked');
      if (element.removeEventListener) {
        element.removeEventListener('click', clickHandler);
      } else {
        element.detachEvent('onclick', clickHandler);
      }
    }
        

using a library is recommended for robust event handling across platforms

bubbling

Because of the DOM's structure, events are able to "bubble" up the tree, ultimately arriving at window. In practice, this means that it is possible for a parent element to listen for events triggered on one of it's children. This event delegation can be more efficient by keeping the number of event registrations to a minimum:


    // parent is a menu of buttons
    parent.addEventListener('click', clickHandler, false);
    function clickHandler(evt) {
      console.log('clicked', evt.target); // clicked, button element
    }
        

DOM ready

The document object emits a load event when a page is rendered after all images and assets have been downloaded. However, instead of waiting for all assets to load, it is often preferable to execute code as soon as the page is parsed and the DOM hierarchy has been fully constructed. Modern browsers emit a DOMContentLoaded event for this reason, and we can see the result in this inspector screen-shot (blue line is DOMContentLoaded, red is load):

dom ready

using a library is recommended in order to enable DOMContentLoaded behaviour in oldIE

working with elements

Most JavaScript in the browser involves the creation and manipulation of DOM elements.

selection

Before an element can be modified, it must first be retrieved from the DOM, and it turns out there are several ways to select an element:

by ID

Because ids are unique, selecting an element by id is the most efficient way of retrieval from the DOM:


    var element = document.getElementById('elementId');
        
by tag name

Selecting all elements of the same tag type returns a collection of elements:


    var elements = document.getElementsByTagName('p');
        

many element collections returned by DOM APIs are "live", and will be updated if matching elements are added or removed from the DOM

by class name

Selecting all elements with the same class name returns a collection of elements:


    var elements = document.getElementsByClassName('my-class');
    var childElements = element.getElementsByClassName('my-class');
        

enabling oldIE compatibility requires first selecting all elements on the page (document.getElementsByTagName('*')), and is therefore quite inefficient

oldIEIEWebkitFirefoxiOSAndroid
by CSS selector

Thanks to the popularity of jQuery (Sizzle selector engine), browsers added similar ability to select elements using CSS selector syntax. Both document and elements inherit querySelector() for selecting the first matching element, and querySelectorAll() for matching all elements:


    var element = document.querySelector('p.red');
    var elements = element.querySelectorAll('li>a');
        

querySelectorAll() returns a non-live collection of elements

IE7IEWebkitFirefoxiOSAndroid

class attribute

Manipulating (adding/removing) class attributes in order to apply styles to an element is a very common task, made more difficult by that fact that element.className only returns a space delimited string of classes. Fortunately, it is made easier in modern browsers by the addition of element.classList, which returns a list of class tokens, and supports add(), remove(), toggle(), and contains() operations:


    element.classList.add('myClass');
    element.classList.toggle('myOtherClass');
    console.log(element.className); // 'myClass myOtherClass'
    element.classList.remove('myClass');
    console.log(element.className); // 'myOtherClass'
        
oldIEIEWebkitFirefoxiOSAndroid

style attribute

Directly manipulating an element's CSS style rules is another very common task, especially when animating an element's properties with JavaScript.

computed style

Although it's simple enough to read values from the element.style object, it will only include values for properties directly set on the element, and not those applied with CSS. In order to read all applied styles, we need to use window.getComputedStyle():


    #myElement {
      background-color: black;
    }

    var element = document.getElementById('myElement');
    element.style.color = '#ff0000';
    var style = window.getComputedStyle(element);
    var color = style.getPropertyValue('color');
    var bgColor = style.getPropertyValue('background-color');
    console.log(color, bgColor); // "rgba(255,0,0,1), rgba(0,0,0,1)"
        

oldIE doesn't support window.getComputedStyle(), but the equivalent can be achieved with element.currentStyle

oldIEIEWebkitFirefoxiOSAndroid

size and position

As outlined above, there are several dimension and placement properties available:


    function getPosition(el) {
      var top = el.offsetTop;
      var left = el.offsetLeft;
      if (el.offsetParent) {
        while (el = el.offsetParent) {
          top += el.offsetTop;
          left += el.offsetLeft;
        }
      }
      return {top: top, left: left};
    }

    // assume element's parent is offset 20px
    var element = document.getElementById('myElement');
    console.log(element.offsetWidth, element.offsetHeight); // 235, 20
    console.log(element.offsetTop, element.offsetLeft); // 0, 0
    console.log(getPosition(element)); //{top:0, left:20}
        
viewport size

With regards to the dimension of the current page, there is only one property that is consistent across devices:


    var viewportWidth = document.documentElement.clientWidth);
    var viewportHeight = document.documentElement.clientHeight);
        

manipulation

Manipulating the order and composition of the DOM tree is as easy as calling appendChild(), insertBefore(), removeChild(), etc on the document or element directly:


    var element = document.getElementById('myElement');
    var otherElement = document.getElementById('myOtherElement');
    otherElement.appendChild(element);
        

appending an existing element elsewhere in the tree will automatically remove it from it's current location

creation

Creating a new element at runtime is fairly straightforward, and is most commonly achieved in one of three ways:

createElement

The document.createElement(tagName) method creates an element of the given type:


    var element = document.createElement('div');
        
createDocumentFragment

The document.createDocumentFragment() method creates an empty parent DOM node in memory to which child elements may be attached. Because it is not part of the DOM tree, appending children to it won't cause unnecessary page reflows:


    var items = ['item1', 'item2', 'item3'];
    var ul = document.getElementsByTagName('ul')[0];
    var frag = document.createDocumentFragment();
    for (var i = 0, n = items.length; i < n; i++) {
      var li = document.createElement('li');
      li.textContent = items[i];
      frag.appendChild(li);
    }
    ul.appendChild(frag);
        
innerHTML

Modifying element.innerHTML can also be used to generate new elements, though, because it must be formatted as a string, it can be rather cumbersome:


    var ul = document.getElementsByTagName('ul')[0];
    ul.innerHTML = "<li>item1</li><li>item2</li><li>item3</li>"
        

be aware that inserting user generated content directly into innerHTML may enable the insertion of unsafe code via <script>

JSON

JSON (JavaScript Object Notation) is a data-interchange format that closely resembles JavaScript object notation. JSON is capable of representing numbers, booleans, strings, and null, as well as arrays and objects composed of these values.

oldIE doesn't support native JSON parsing, so include a library like json3 to enable support

oldIEIEWebkitFirefoxiOSAndroid

JSON behaviour is encapsulated in the native JSON object, which contains two methods for converting to and from JSON-formatted data:

Parse()

The JSON.parse() method takes a JSON-formatted string and converts it to JavaScript values:


    var jsonString = '{"prop1":true, "prop2":["item1","item2","item3"]}'
    var obj = JSON.parse(jsonString);
    console.log(obj.prop2[1]); //"item2"
        

Stringify()

The JSON.stringify() method takes JavaScript values and converts to a JSON-formatted string.


    var obj = {};
    obj.prop1 = true;
    obj.prop2 = ["item1","item2","item3"];
    var jsonString = JSON.stringify(obj);
    console.log(jsonString);
    //"{'prop1':true,'prop2':'["item1","item2","item3"]'}"
        

toJSON

If an object being stringified has a method called toJSON(), then the return value will be used to represent the object in JSON notation:


    var obj = {};
    obj.prop1 = true;
    obj.toJSON = function() {
      return false;
    }
    var jsonString = JSON.stringify(obj);
    console.log(jsonString); //"false"
        

AJAX

AJAX (Asynchronous JavaScript + XML) is a term that describes a technique for asynchronously (without forcing a page refresh) communicating with a remote server. Although the 'X' stands for XML, AJAX calls can send and receive data in a variety of other formats, including JSON, HTML, and plain text. Regardless of the format, the primary benefit of AJAX over other approaches is the ability to efficiently update parts of a page when needed based on user interaction.

XMLHttpRequest

All AJAX calls start with an instance of the XMLHttpRequest object and a url/path from which to retrieve data:


    var request = new XMLHttpRequest();
    // assign callback handler
    request.onreadystatechange = function() {
      if (request.readyState === 4) {
        if (request.status === 200) {
          console.log(request.responseText);
        }
      }
    }
    request.open('GET', 'path/to/some/data', true);
    request.send();
        

JSON-P

Because of the potential for malicious code execution, certain types of data transfer via JavaScript is restricted by a same-origin policy. In practice, this means that retrieving data via AJAX from a different domain will be prevented.

One method around this restriction is through the use of JSON-P. More of a technique than a specification, JSON-P uses a dynamically injected <script> tag to call out to a remote data service location. The response handler is passed to this remote script, and subsequently called with the requested JSON data when the loaded JavaScript is executed.

because of a lack of specification and enforcement by the browser, JSON-P calls are inherently unsafe and hacky

CORS

An emerging solution to cross-domain data loading is CORS (Cross-Origin Resource Sharing). An extension to the XMLHttpRequest object, CORS allows browsers to make calls across domains by first asking the server for permission.

CORS requires a server configured to intercept and respond to the authorization request-headers

oldIEIEWebkitFirefoxiOSAndroid

next week...js patterns

60