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:
window.location.href
: entire URLwindow.location.pathname
: path relative to the hostwindow.location.hash
: part of the URL after the # symbolwindow.location.search
: part of the URL after a ? (query) symbol
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:
window.navigator.userAgent
: user agent stringwindow.navigator.plugins
: array of installed browser plugins
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:
window.history.back()
: go to previous page in session historywindow.history.forward()
: go to next page in session historywindow.history.pushState(data, title, url)
: add location to session history stackwindow.history.replaceState(data, title, url)
: update current location entry
it's possible to fallback to location.hash
changes in order to simulate history session management
scroll properties
The following window
properties store information regarding the window's scroll state:
window.scrollY
: number of pixels the document has scrolled verticallywindow.scrollX
: number of pixels the document has scrolled horizontallywindow.scrollMaxY
: maximum vertical scroll offset in pixelswindow.scrollMaxX
: maximum horizontal scroll offset in pixelswindow.scrollTo(x, y)
: scroll to a particular coordinate in the document
timer methods
The following window
methods can be used to execute functions after a specified delay:
window.setTimeout(callback, delay)
: executes the callback after delay in milliseconds (returns unique id)window.setInterval(callback, interval)
: executes the callback each interval in milliseconds (returns unique id)window.clearTimeout(id)
: clears delay set bywindow.setTimeout()
window.clearInterval(id)
: clears interval set bywindow.setInterval()
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
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:
document.documentElement
: returns the<html>
elementdocument.head
: returns the<head>
elementdocument.body
: returns the<body>
element
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:
childNodes
: live collection of child nodesfirstChild
: first direct child nodelastChild
: last direct child nodenextSibling
: immediately following nodepreviousSibling
: immediately preceding nodeparentNode
: parent nodenodeType
: number representing node type (1 forelement
)nodeName
: name of node (LI, P, etc)appendChild(node)
: insert a node as last childinsertBefore(newNode, referenceNode)
: inserts the specified node (as a child) before a reference childremoveChild(node)
: remove a child nodereplaceChild(node)
: replace a child node with anothercloneNode(deep)
: clone a node, and (optionally) all of its contentshaschildNodes()
: check if any child nodes are present
element
s also provide the ability to read and modify their content and visual state:
attributes
: returns all attributesid
: get/set value ofid
attributeclassName
: get/set value ofclass
attributestyle
: get/set value ofstyle
attributeclientHeight/clientWidth
: inner dimensionsoffsetHeight/offsetWidth
: dimensions including padding and bordersoffsetTop/offsetLeft
: offset from border to parent's borderoffsetParent
: parent element from which all offset calculations are madescrollHeight/scrollWidth
: total dimensions including overflowscrollTop/scrollLeft
: scroll offsetinnerHTML
: get/set HTML contenttextContent
: get/set text contentgetAttribute(name)
: retrieve the value of named attributesetAttribute(name, value)
: set the value of named attributehasAttribute(name)
: check if has named attributeremoveAttribute(name)
: remove the named attribute
events
window
, document
, and all element
s 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
):

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 element
s.
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 element
s:
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 element
s:
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
by CSS selector
Thanks to the popularity of jQuery (Sizzle selector engine), browsers added similar ability to select element
s using CSS selector syntax. Both document
and element
s inherit querySelector()
for selecting the first matching element
, and querySelectorAll()
for matching all element
s:
var element = document.querySelector('p.red');
var elements = element.querySelectorAll('li>a');
querySelectorAll()
returns a non-live collection of element
s
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'
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
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 element
s 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
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