You might also like: JavaScript Enlightenment

An O'Reilly edited version (printed, epub, mobi, pdf) of this book is now avaliable from O'Reilly or Amazon

Warning! This is the free online version of the DOM Enlightenment book in its pre-edited/draft form.

DOM Enlightenment

Exploring the relationship between JavaScript and the modern HTML DOM

By Cody Lindley Version: 0.6.0

Foreword

I make websites. Sometimes I make music. Over the years, I’ve noticed an interesting pattern of behavior from some musicians—often self-taught—who think of themselves as creative types: they display an aversion to learning any music theory. The logic, they say, is that knowing the theory behind music will somehow constrain their creative abilities. I’ve never understood that logic (and I secretly believe that it’s a retroactive excuse for a lack of discipline). To my mind, I just don’t see how any kind of knowledge or enlightenment could be a bad thing.

Alas, I have seen the same kind of logic at work in the world of web design. There are designers who not only don’t know how to write markup and CSS, they actively refuse to learn. Again, they cite the fear of somehow being constrained by this knowledge (and again, I believe that’s a self-justifying excuse).

In the world of front-end development, that attitude is fortunately far less prevalent. Most web devs understand that there’s always more to learn. But even amongst developers who have an encyclopediac knowledge of HTML and CSS, there is often a knowledge gap when it comes to the Document Object Model. That’s understandable. You don’t need to understand the inner workings of the DOM if you’re using a library like jQuery. The whole point of JavaScript libraries is to abstract away the browser’s internal API and provide a different, better API instead.

Nonetheless, I think that many front-end devs have a feeling that they should know what’s going on under the hood. That’s the natural reaction of a good geek when presented with a system they’re expected to work with. Now, thanks to DOM Enlightenment, they can scratch that natural itch.

Douglas Crockford gave us a map to understand the inner workings of the JavaScript language in his book JavaScript: The Good Parts. Now Cody Lindley has given us the corresponding map for the Document Object Model. Armed with this map, you’ll gain the knowledge required to navigate the passageways and tunnels of the DOM. ix

You might not end up using this knowledge in every project. You might decide to use a library like jQuery instead. But now it will be your decision. Instead of having to use a library because that’s all that you know, you can choose if and when to use a library. That’s a very empowering feeling. That’s what knowledge provides. That is true enlightenment.

—Jeremy Keith, founder and technical director of clearleft.com, and author of DOM Scripting: Web Design with JavaScript and the Document Object Model

Introduction

This book is not an exhaustive reference on DOM scripting or JavaScript. It may, however, be the most exhaustive book written about DOM scripting without the use of a library/framework. The lack of authorship around this topic is not without good reason. Most technical authors are not willing to wrangle this topic because of the differences that exist among legacy browsers and their implementations of the DOM specifications (or lack thereof).

For the purpose of this book (i.e. grokking the concepts), I'm going to sidestep the browser API mess and dying browser discrepancies in an effort to expose the modern DOM. That's right, I'm going to sidestep the ugliness in an effort to focus on the here and now. After all, we have solutions like jQuery to deal with all that browser ugliness, and you should definitely be leveraging something like jQuery when dealing with deprecated browsers.

While I am not promoting the idea of only going native when it comes to DOM scripting, I did write this book in part so that developers may realize that DOM libraries are not always required when scripting the DOM. I also wrote for the lucky few who get to write JavaScript code for a single environment (i.e. one browser, mobile browsers, or HTML+CSS+JavaScript-to-native via something like PhoneGap). What you learn in this book may just make a DOM library unnecessary in ideal situations, say for example, some light DOM scripting for deployment on a Webkit mobile browser only.

Who should read this book

As I authored this book, I specifically had two types of developers in mind. I assume both types already have an intermediate to advanced knowledge of JavaScript, HTML, and CSS. The first developer is someone who has a good handle on JavaScript or jQuery, but has really never taken the time to understand the purpose and value of a library like jQuery (the reason for its rhyme, if you will). Equipped with the knowledge from this book, that developer should fully be able to understand the value provided by jQuery for scripting the DOM. And not just the value, but how jQuery abstracts the DOM and where and why jQuery is filling the gaps. The second type of developer is an engineer who is tasked with scripting HTML documents that will only run in modern browsers or that will get ported to native code for multiple OS's and device distributions (e.g. PhoneGap) and needs to avoid the overhead (i.e. size or size v.s. use) of a library.

Technical intentions, allowances, & limitations

License

The DOM Enlightenment HTML version is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license.

Hard Copy & eBook

O'Reilly will release and sell a hard copy & eBook in the near future.

Preface

Before you begin, it is important to understand various styles employed in this book. Please do not skip this section, because it contains important information that will aid you in the unique styles of this book.

This book is not like other programming books

The enlightenment series (jQuery Enlightenment & JavaScript Enlightenment) is written in a style that favors small, isolated, immediately executable code over wordy explanations and monolithic programs. One of my favorite authors, C.S Lewis, asserts that words are the lowest form of communication that humans traffic in. I totally agree with this assertion and use it as the basis for the style of these books. I feel that technical information is best covered with as few words as possible, in conjunction with just the right amount of executable code and commenting required to express an idea. The style of this book attempts to present a clearly defined idea with as few words as possible, backed with real code. Because of this, when you first start grokking these concepts, you should execute and examine the code, thereby forming the foundation of a mental model for the words used to describe the concepts. Additionally, the format of these books attempts to systematically break ideas down into their smallest possible form and examine each one in an isolated context. All this to say that this is not a book with lengthy explanations or in-depth coverage on broad topics. Consider yourself warned. If it helps, think of it as a cookbook, but even more terse and to the point than usual.

Color-coding conventions

In the code examples (example shown below), orange is used to highlight code directly relevant to the concept being discussed. Any additional code used to support the orange colored code will be green. The color gray in the code examples is reserved for comments.

<!DOCTYPE html> <html lang="en"> <body> <script> // this is a comment about a specific part of the code

var foo = 'calling out this part of the code'; </script> </body> </html>

jsFiddle

The majority of code examples in this book are linked to a corresponding jsFiddle page, where the code can be tweaked and executed online. The jsFiddle examples have been configured to use the Firebug lite-dev plugin to ensure the reader views the console.log prevalent in this book. Before reading this book, make sure you are comfortable with the usage and purpose of console.log.

In situations where jsFiddle caused complications with the code example, I simply chose not to link to a live example.

About the Author

Cody Lindley is a client-side engineer (aka front-end developer) and recovering Flash developer. He has an extensive background working professionally (11+ years) with HTML, CSS, JavaScript, Flash, and client-side performance techniques as it pertains to web development. If he is not wielding client-side code he is likely toying with interface/interaction design or authoring material and speaking at various conferences. When not sitting in front of a computer, it is a sure bet he is hanging out with his wife and kids in Boise, Idaho – training for triathlons, skiing, mountain biking, road biking, alpine climbing, reading, watching movies, or debating the rational evidence for a Christian worldview.

Table of Contents

Chapter 1 - Node Overview 1.1 The Document Object Model (aka the DOM) is a hierarchy/tree of JavaScript node objects When you write an HTML document you encapsulate HTML content inside of other HTML content. By doing this you setup a hierarchy that can be expressed as a tree. Often this hierarchy or encapsulation system is indicated visually by indenting markup in an HTML document. The browser when loading the HTML document interrupts and parses this hierarchy to create a tree of node objects that simulates how the markup is encapsulated. <!DOCTYPE html> <html lang="en"> <head> <title>HTML</title> </head> <body> <!-- Add your content here--> </body> </html> The above HTML code when parsed by a browser creates a document that contains nodes structrured in a tree format (i.e. DOM). Below I reveal the tree struture from the above HTML document using Opera's Dragonfly DOM inspector. On the left you see the HTML document in its tree form. And on the right you see the corresponding JavaScript object that represents the selected element on the left. For example, the selected <body> element highlighted in blue, is an element node and an instance of the HTMLBodyElement interface. What you should take away here is that html documents get parsed by a browser and converted into a tree structure of node objects representing a live document. The purpose of the DOM is to provide a programatic interface for scripting (removing, adding, replacing, eventing, modifiying) this live document using JavaScript. Notes The DOM originally was an application programming interface for XML documents that has been extended for use in HTML documents. 1.2 Node object types The most common (I'm not highlighting all of them in the list below) types of nodes (i.e. nodeType/node classifications) one encounters when working with HTML documents are listed below. DOCUMENT_NODE (e.g. window.document)

ELEMENT_NODE (e.g. <body>, <a>, <p>, <script>, <style>, <html>, <h1> etc...)

ATTRIBUTE_NODE (e.g. class="funEdges")

TEXT_NODE (e.g. text characters in an html document including carriage returns and white space)

DOCUMENT_FRAGMENT_NODE (e.g. document.createDocumentFragment())

DOCUMENT_TYPE_NODE (e.g. <!DOCTYPE html>) I've listed the node types above formatted (all uppercase with _ separating words) exactly as the constant property is written in the JavaScript browser environment as a property of the Node object. These Node properties are constant values and are used to store numeric code values which map to a specific type of node object. For example in the following code example, Node.ELEMENT_NODE is equal to 1. And 1 is the code value used to identify element nodes. live code: http://jsfiddle.net/domenlightenment/BAVrs <!DOCTYPE html> <html lang="en"> <body> <script> console.log(Node.ELEMENT_NODE) //logs 1, one is the numeric code value for element nodes </script> </body> </html> In the code below I log all of the node types and there values. live code: http://jsfiddle.net/domenlightenment/YcXGD <!DOCTYPE html> <html lang="en"> <body> <script> for(var key in Node){ console.log(key,' = '+Node[key]); }; /* the above code logs to the console the following ELEMENT_NODE = 1 ATTRIBUTE_NODE = 2 TEXT_NODE = 3 CDATA_SECTION_NODE = 4 ENTITY_REFERENCE_NODE = 5 ENTITY_NODE = 6 PROCESSING_INSTRUCTION_NODE = 7 COMMENT_NODE = 8 DOCUMENT_NODE = 9 DOCUMENT_TYPE_NODE = 10 DOCUMENT_FRAGMENT_NODE = 11 NOTATION_NODE = 12 DOCUMENT_POSITION_DISCONNECTED = 1 DOCUMENT_POSITION_PRECEDING = 2 DOCUMENT_POSITION_FOLLOWING = 4 DOCUMENT_POSITION_CONTAINS = 8 DOCUMENT_POSITION_CONTAINED_BY = 16 DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32 */ </script> </body> </html> The previous code example gives an exhaustive list of all node types. For the purpose of this book I'll be discussing the shorter list of node types listed at the start of this section. These nodes will most likely be the ones you come in contact with when scripting an HTML page. In the table below I list the name given to the interface/constructor that instantiates the most common node types and their corresponding nodeType classification by number and name. What I hope you take away from the table below is the nodeType value (i.e. 1) is just a numeric classificaiton used to describe a certain type of node constructed from a certain JavaScript interface/constructor. For example, the HTMLBodyElement interface reprsents a node object that has a node type of 1, which is a classification for ELEMENT_NODE's. Node Interface/Constructor: nodeType (returned from .nodeType): HTML*Element, (e.g. HTMLBodyElement) 1 (i.e. ELEMENT_NODE) Text 3 (i.e. TEXT_NODE) Attr 2 (i.e. ATTRIBUTE_NODE) HTMLDocument 9 (i.e. DOCUMENT_NODE) DocumentFragment 11 (i.e. DOCUMENT_FRAGMENT_NODE) DocumentType 10 (i.e. DOCUMENT_TYPE_NODE) Notes The DOM specification semantically labels nodes like Node, Element, Text, Attr, and HTMLAnchorElement as an interface, which it is, but keep in mind its also the name given to the JavaScript constructor function that constructs the nodes. As you read this book I will be referring to these interfaces (i.e. Element, Text, Attr, HTMLAnchorElement) as objects or constructor functions while the specification refers to them as interfaces. The ATTRIBUTE_NODE is not actually part of a tree but listed for historical reasons. In this book I do not provide a chapter on attribute nodes and instead discuss them in the Element node chapter given that attributes nodes are sub-like nodes of element nodes with no particiipation in the actual DOM tree structure. Be aware the ATTRIBUTE_NODE is being depreciated in DOM 4. I've not included detail in this book on the COMMENT_NODE but you should be aware that comments in an HTML document are Comment nodes and similar in nature to Text nodes. As I discuss nodes throughout the book I will rarely refer to a specific node using its nodeType name (e.g. ELEMENT_NODE). This is done to be consistent with verbiage used in the specifications provided by the W3C & WHATWG. 1.3 Sub-node objects inherit from the Node object Each node object in a typical DOM tree inherits properties and methods from Node. Depending upon the type of node in the document there are also additional sub node object/interfaces that extend the Node object. Below I detail the inheritance model implemented by browsers for the most common node interfaces (< indicates inherited from). Object < Node < Element < HTMLElement < (e.g. HTML*Element)

Object < Node < Attr (This is deprecated in DOM 4)

Object < Node < CharacterData < Text

Object < Node < Document < HTMLDocument

Object < Node < DocumentFragment Its important not only to remember that all nodes types inherit from Node but that the chain of inheritance can be long. For example, all HTMLAnchorElement nodes inherit properties and methods from HTMLElement, Element, Node, and Object objects. Notes Node is just a JavaScript constructor function. And so logically Node inherits from Object.prototype just like all objects in JavaScript To verify that all node types inherit properties & methods from the Node object lets loop over an Element node object and examine its properties and methods (including inherited). live code: http://jsfiddle.net/domenlightenment/6ukxe/ <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <!-- this is a HTMLAnchorElement which inherits from... --> <script> //get reference to element node object var nodeAnchor = document.querySelector('a'); //create props array to store property keys for element node object var props = []; //loop over element node object getting all properties & methods (inherited too) for(var key in nodeAnchor){ props.push(key); } //log alphabetical list of properties & methods console.log(props.sort()); </script> </body> </html> If you run the above code in a web browser you will see a long list of properties that are available to the element node object. The properties & methods inherited from the Node object are in this list as well as a great deal of other inherited properties and methods from the Element, HTMLElement, HTMLAnchorElement, Node, and Object object. Its not my point to examine all of these properties and methods now but simply to point out that all nodes inherit a set of baseline properties and methods from its constructor as well as properties from the prototype chain. If you are more of visual learner consider the inheritance chain denoted from examining the above HTML document with Opera's DOM inspector. Notice that the anchor node inherits from HTMLAnchorElement, HTMLElement, Element, Node, and Object all shown in the list of properties highlighted with a gray background. This inheritance chain provides a great deal of shared methods and properties to all node types. Notes You can extend the DOM. But its likley not a good idea to extend host objects. 1.4 Properties and methods for working nodes Like we have been discussing all node objects (e.g Element, Attr, Text etc...) inherit properties and methods from a primary Node object. These properties and methods are the baseline values and functions for manipulating, inspecting, and traversing the DOM. In addtion to the properties and methods provided by the node interface there are a great deal of other relevant properties and methods that are provided by sub node interfaces such as the document, HTMLElement, or HTML*Element interface. Below I list out the most common Node properties and methods inherited by all node objects including the relevant inherited properties for working with nodes from sub-node interfaces. Node Properties: childNodes

firstChild

lastChild

nextSibling

nodeName

nodeType

nodeValue

parentNode

previousSibling Node Methods: appendChild()

cloneNode()

compareDocumentPosition()

contains()

hasChildNodes()

insertBefore()

isEqualNode()

removeChild()

replaceChild() Node Properties:Node Methods: Document Methods: document.createElement()

document.createTextNode() HTML * Element Properties: innerHTML

outerHTML

textContent

innerText

outerText

firstElementChild

lastElementChild

nextElementChild

previousElementChild

children HTML element Methods: insertAdjacentHTML() 1.5 Identifying the type and name of a node Every node has a nodeType and nodeName property that is inherited from Node. For example Text nodes have a nodeType code of 3 and nodeName value of '#text'. As previously mentioned the numeric value 3 is a numeric code representing the type of underlying object the node represents (i.e. Node.TEXT_NODE === 3). Below I detail the values returned for nodeType and nodeName for the node objects discussed in this book. It makes sense to simply memorize these numeric code's for the more common nodes given that we are only dealing with 5 numeric codes. live code: http://jsfiddle.net/domenlightenment/8EwNu <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //This is DOCUMENT_TYPE_NODE or nodeType 10 because Node.DOCUMENT_TYPE_NODE === 10 console.log( document.doctype.nodeName, //logs 'html' also try document.doctype to get <!DOCTYPE html> document.doctype.nodeType //logs 10 which maps to DOCUMENT_TYPE_NODE ); //This is DOCUMENT_NODE or nodeType 9 because Node.DOCUMENT_NODE === 9 console.log( document.nodeName, //logs '#document' document.nodeType //logs 9 which maps to DOCUMENT_NODE ); //This is DOCUMENT_FRAGMENT_NODE or nodeType 11 because Node.DOCUMENT_FRAGMENT_NODE === 11 console.log( document.createDocumentFragment().nodeName, //logs '#document-fragment' document.createDocumentFragment().nodeType //logs 11 which maps to DOCUMENT_FRAGMENT_NODE ); //This is ELEMENT_NODE or nodeType 1 because Node. ELEMENT_NODE === 1 console.log( document.querySelector('a').nodeName, //logs 'A' document.querySelector('a').nodeType //logs 1 which maps to ELEMENT_NODE ); //This is TEXT_NODE or nodeType 3 because Node.TEXT_NODE === 3 console.log( document.querySelector('a').firstChild.nodeName, //logs '#text' document.querySelector('a').firstChild.nodeType //logs 3 which maps to TEXT_NODE ); </script> </body> </html> If its not obvious the fastest way to determine if a node is of a certain type is too simply check its nodeType property. Below we check to see if the anchor element has a node number of 1. If it does than we can conclude that its an Element node because Node.ELEMENT_NODE === 1. live code: http://jsfiddle.net/domenlightenment/ydzWL <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //is <a> a ELEMENT_NODE? console.log(document.querySelector('a').nodeType === 1); //logs true, <a> is an Element node //or use Node.ELEMENT_NODE which is a property containg the numerice value of 1 console.log(document.querySelector('a').nodeType === Node.ELEMENT_NODE); //logs true, <a> is an Element node </script> </body> </html> Determining the type of node that you might be scripting becomes very handy so that you might know which properties and methods are available to script the node. Notes The values returned by the nodeName property vary according to the node type. Have a look at the DOM 4 specification provided for the details. 1.6 Getting a nodes value The nodeValue property returns null for most of the node types (except Text, and Comment). It's use is centered around extracting actual text strings from Text and Comment nodes. In the code below I demonstrate its use on all the nodes discussed in this book live code: http://jsfiddle.net/domenlightenment/LNyA4 <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> //logs null for DOCUMENT_TYPE_NODE, DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE below console.log(document.doctype.nodeValue); console.log(document.nodeValue); console.log(document.createDocumentFragment().nodeValue); console.log(document.querySelector('a').nodeVale); //logs string of text console.log(document.querySelector('a').firstChild.nodeValue); //logs 'Hi' </script> </body> </html> Notes Text or Comment node values can be set by providing new strings values for the nodeValue property(i.e. document.body.firstElementChild.nodeValue = 'hi'). 1.7 Creating element and text nodes using JavaScript methods When a browser parses an HTML document it constructs the nodes and tree based on the contents of the HTML file. The browser deals with the creation of nodes for the intial loading of the HTML document. However its possible to create your own nodes using JavaScript. The following two methods allow us to programatically create Element and Text nodes using JavaScript. createElement()

createTextNode() Other methods are avaliable but are not commonly used (e.g. createAttribute() and createComment()) . In the code below I show how simple it is to create element and text nodes. live code: http://jsfiddle.net/domenlightenment/Vj2Tc <!DOCTYPE html> <html lang="en"> <body> <script> var elementNode = document.createElement('div'); console.log(elementNode, elementNode.nodeType); //log <div> 1, and 1 indicates an element node var textNode = document.createTextNode('Hi'); console.log(textNode, textNode.nodeType); //logs Text {} 3, and 3 indicates a text node </script> </body> </html> Notes The createElement() method accepts one parameter which is a string specifying the element to be created. The string is the same string that is returned from the tagName property of an Element object. The createAttribute() method is depricated and should not be used for creating attribute nodes. Instead developers typically use getAttribute(), setAttribute(), and removeAttribute() methods. I will discus this in more detail in the Element node chapter. The createDocumentFragment() will be discussed in the chapter covering this method. You should be aware that a createComment() method is available for creating comment nodes. Its not discussed in this book but is very much available to a developer who finds its usage valuable. 1.8 Creating and adding element and text nodes to the DOM using JavaScript strings The innerHTML, outerHTML, textContent and insertAdjacentHTML() properties and methods provide the functionality to create and add nodes to the DOM using JavaScript strings. In the code below we are using the innerHTML, outerHTML, and textContent properties to create nodes from JavaScript strings that are then immediately added to the DOM. live code: http://jsfiddle.net/domenlightenment/UrNT3 <!DOCTYPE html> <html lang="en"> <body> <div id="A"></div> <span id="B"></span> <div id="C"></div> <div id="D"></div> <div id="E"></div> <script> //create a strong element and text node and add it to the DOM document.getElementById('A').innerHTML = '<strong>Hi</strong>'; //create a div element and text node to replace <span id="B"></div> (notice span#B is replaced) document.getElementById('B').outerHTML = '<div id="B" class="new">Whats Shaking</div>' //create a text node and update the div#C with the text node document.getElementById('C').textContent = 'dude'; //NON standard extensions below i.e. innerText & outerText //create a text node and update the div#D with the text node document.getElementById('D').innerText = 'Keep it'; //create a text node and replace the div#E with the text node (notice div#E is gone)

document.getElementById('E').outerText = 'real!'; console.log(document.body.innerHTML); /* logs <div id="A"><strong>Hi</strong></div> <div id="B" class="new">Whats Shaking</div> <span id="C">dude</span> <div id="D">Keep it</div> real! */ </script> </body> </html> The insertAdjacentHTML() method which only works on Element nodes is a good deal more precise than the previously mentioned methods. Using this method its possible to insert nodes before the beginning tag, after the beginning tag, before the end tag, and after the end tag. Below I construct a sentence using the insertAdjacentHTML() method. live code: http://jsfiddle.net/domenlightenment/tvpA6 <!DOCTYPE html> <html lang="en"> <body><i id="elm">how</i> <script> var elm = document.getElementById('elm'); elm.insertAdjacentHTML('beforebegin', '<span>Hey-</span>'); elm.insertAdjacentHTML('afterbegin', '<span>dude-</span>'); elm.insertAdjacentHTML('beforeend', '<span>-are</span>'); elm.insertAdjacentHTML('afterend', '<span>-you?</span>'); console.log(document.body.innerHTML); /* logs <span>Hey-</span><i id="A"><span>dude-</span>how<span>-are</span></i><span>-you?</span> */ </script> </body> </html> Notes The innerHTML property will convert html elements found in the string to actual DOM nodes while the textContent can only be used to construct text nodes. If you pass textContent a string containing html elements it will simply spit it out as text. document.write() can also be used to simultaneously create and add nodes to the DOM. However its typically not used anymore unless its usage is required to accomplish 3rd party scripting tasks. Basically the write() method will output the values passed to it into the page during page loading/parsing. You should be aware that using the write() method will stall/block the parsing of the html document being loaded. innerHTML invokes a heavy & expensive HTML parser where as text node generation is trivial thus use the innerHTML & friends sparingly The insertAdjacentHTML options "beforebegin" and "afterend" will only work if the node is in the DOM tree and has a parent element. Support for outerHTML was not available natively in Firefox until version 11. A polyfill is avaliable. textContent gets the content of all elements, including <script> and <style> elements, innerText does not innerText is aware of style and will not return the text of hidden elements, whereas textContent will Avaliable to all modern browser except Firefox is insertAdjacentElement() and insertAdjacentText() 1.9 Extracting parts of the DOM tree as JavaScript strings The same exact properties (innerHTML, outerHTML, textContent) that we use to create and add nodes to the DOM can also be used to extract parts of the DOM (or really the entire DOM) as a JavaScript string. In the code example below I use these properties to return a string value containing text and html values from the HTML document. live code: http://jsfiddle.net/domenlightenment/mMYWc <!DOCTYPE html> <html lang="en"> <body> <div id="A"><i>Hi</i></div> <div id="B">Dude<strong> !</strong></div> <script> console.log(document.getElementById('A').innerHTML); //logs '<i>Hi</i>'



console.log(document.getElementById('A').outerHTML); //logs <div id="A">Hi</div> //notice that all text is returned even if its in child element nodes (i.e. <strong> !</strong>) console.log(document.getElementById('B').textContent); //logs 'Dude !' //NON standard extensions below i.e. innerText & outerText console.log(document.getElementById('B').innerText); //logs 'Dude !'



console.log(document.getElementById('B').outerText); //logs 'Dude !'​​ </script> </body> </html> Notes The textContent, innerText, outerText property when being read will return all of the text nodes contained within the selected node. So for example (not a good idea in practice), document.body.textContent will get all the text nodes contained in the body element not just the first text node. 1.10 Adding node objects to the DOM using appendChild()& insertBefore() The appendChild() and insertBefore() Node methods allow us to insert JavaScript node objects into the DOM tree. The appendChild() method will append a node(s) to the end of the child node(s) of the node the method is called on. If there are no child node(s) then the node being appended is appended as the first child. For example in the code below we are creating a element node (<strong>) and text node (Dude). Then the <p> is selected from the DOM and our <strong> element is appended using appendChild(). Notice that the <strong> element is encapsulated inside of the <p> element and added as the last child node. Next the <strong> element is selected and the text 'Dude' is appended to the <strong> element. live code: http://jsfiddle.net/domenlightenment/HxjFt <!DOCTYPE html> <html lang="en"> <body> <p>Hi</p> <script> //create a blink element node and text node var elementNode = document.createElement('strong'); var textNode = document.createTextNode(' Dude'); //append these nodes to the DOM document.querySelector('p').appendChild(elementNode); document.querySelector('strong').appendChild(textNode); //log's <p>Hi<strong> Dude</strong></p> console.log(document.body.innerHTML); </script> </body> </html> When it becomes necessary to control the location of insertion beyond appending nodes to the end of a child list of nodes we can use insertBefore(). In the code below I am inserting the <li> element before the first child node of the <ul> element. live code: http://jsfiddle.net/domenlightenment/UmkME <!DOCTYPE html> <html lang="en"> <body> <ul> <li>2</li> <li>3</li> </ul> <script> //create a text node and li element node and append the text to the li var text1 = document.createTextNode('1'); var li = document.createElement('li'); li.appendChild(text1); //select the ul in the document var ul = document.querySelector('ul'); /* add the li element we created above to the DOM, notice I call on <ul> and pass reference to <li>2</li> using ul.firstChild */ ul.insertBefore(li,ul.firstChild); console.log(document.body.innerHTML); /*logs <ul> <li>1</li> <li>2</li> <li>3</li> </ul> */ </script> </body> </html> The insertBefore() requires two parameters, the node to be inserted and the reference node in the document you would like the node inserted before. Notes If you do not pass the insertBefore() method a second parameter then it functions just like appendChild(). We have more methods (e.g. prepend(), append(), before(), after()) to look forward too in DOM 4. 1.11 Removing and replacing nodes using removeChild() and replaceChild() Removing a node from the DOM is a bit of a multi-step process. First you have to select the node you want to remove. Then you need to gain access to its parent element typically using the parentNode property. Its on the parent node that you invoke the removeChild() method passing it the reference to the node to be removed. Below I demonstrate its use on an element node and text node. live code: http://jsfiddle.net/domenlightenment/VDZgP <!DOCTYPE html> <html lang="en"> <body> <div id="A">Hi</div> <div id="B">Dude</div> <script> //remove element node var divA = document.getElementById('A'); divA.parentNode.removeChild(divA); //remove text node var divB = document.getElementById('B').firstChild; divB.parentNode.removeChild(divB); //log the new DOM updates, which should only show the remaining empty div#B console.log(document.body.innerHTML); </script> </body> </html> Replacing an element or text node is not unlike removing one. In the code below I use the same html structure used in the previous code example except this time I use replaceChild() to update the nodes instead of removing them. live code: http://jsfiddle.net/domenlightenment/zgE8M <!DOCTYPE html> <html lang="en"> <body> <div id="A">Hi</div> <div id="B">Dude</div> <script> //replace element node var divA = document.getElementById('A'); var newSpan = document.createElement('span'); newSpan.textContent = 'Howdy'; divA.parentNode.replaceChild(newSpan,divA); //replace text node var divB = document.getElementById('B').firstChild; var newText = document.createTextNode('buddy'); divB.parentNode.replaceChild(newText, divB); //log the new DOM updates, console.log(document.body.innerHTML); </script> </body> </html> Notes Depending upon what you are removing or replacing simply providing the innerHTML, outerHTML, and textContent properties with an empty string might be easier and faster. Careful memory leaks in brwoser might get you however. Both replaceChild() and removeChild() return the replaced or remove node. Basically its not gone just because you replace it or remove. All this does is takes it out of the current live document. You still have a reference to it in memory. We have more methods (e.g.replace(), remove()) to look forward too in DOM 4. 1.12 Cloning nodes using cloneNode() Using the cloneNode() method its possible to duplicate a single node or a node and all its children nodes. In the code below I clone only the <ul> (i.e. HTMLUListElement) which once cloned can be treated like any node reference. live code: http://jsfiddle.net/domenlightenment/6DHgC <!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hi</li> <li>there</li> </ul> <script> var cloneUL = document.querySelector('ul').cloneNode(); console.log(cloneUL.constructor); //logs HTMLUListElement() console.log(cloneUL.innerHTML); //logs (an empty string) as only the ul was cloned </script> </body> </html> To clone a node and all of its children nodes you pass the cloneNode() method a parameter of of true. Below I use the cloneNode() method again but this time we clone all of the child nodes as well. live code: http://jsfiddle.net/domenlightenment/EyFEC <!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hi</li> <li>there</li> </ul> <script> var cloneUL = document.querySelector('ul').cloneNode(true); console.log(cloneUL.constructor); //logs HTMLUListElement() console.log(cloneUL.innerHTML); //logs <li>Hi</li><li>there</li> </script> </body> </html> Notes When cloning an Element node all attributes and values are also cloned. In fact, only attributes are copied! Everything else you can add (e.g. event handlers) to a DOM node is lost when cloning. You might think that cloning a node and its children using cloneNode(true) would return a NodeList but it in fact does not. cloneNode() may lead to duplicate element IDs in a document 1.13 Grokking node collections (i.e. Nodelist & HTMLcollection) When selecting groups of nodes from a tree (cover in chaper 3) or accessing pre-defined sets of nodes, the nodes are either placed in a NodeList (e.g. document.querySelectorAll('*')) or HTMLCollection (e.g. document.scripts). These array like (i.e. not a real Array) object collections that have the following characteristics. A collection can either be live or static. Meaning that the nodes contained in the collection are either literally part of the live document or a snapshot of the live document.

By default nodes are sorted inside of the collection by tree order. Meaning the order matches the liner path from tree trunk to branches.

The collections have a length property that reflects the number of elements in the list 1.14 Gettting a list/collection of all immediate child nodes Using the childNodes property produces an array like list (i.e. NodeList) of the immediate child nodes. Below I select the <ul> element which I then use to create a list of all of the immediate child nodes contained inside of the <ul>. live code: http://jsfiddle.net/domenlightenment/amDev <!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hi</li> <li>there</li> </ul> <script> var ulElementChildNodes = document.querySelector('ul').childNodes; console.log(ulElementChildNodes); //logs an array like list of all nodes inside of the ul /*Call forEach as if its a method of NodeLists so we can loop over the NodeList. Done because NodeLists are array like, but do not directly inherit from Array*/ Array.prototype.forEach.call(ulElementChildNodes,function(item){ console.log(item); //logs each item in the array }); </script> </body> </html> Notes The NodeList returned by childNodes only contains immediate child nodes Be aware childNodes contains not only Element nodes but also all other node types (e.g. Text and Comment nodes) [].forEach was implemented in ECMAScript 5th edtion 1.15 Convert a NodeList or HTMLCollection to JavaScript Array Node lists and html collections are array like but not a true JavaScript array which inherits array methods. In the code below we programtically confirm this using isArray(). live code: http://jsfiddle.net/domenlightenment/n53Xk <!DOCTYPE html> <html lang="en"> <body> <a href="#"></a> <script> console.log(Array.isArray(document.links)); //returns false, its an HTMLCollection not an Array console.log(Array.isArray(document.querySelectorAll('a'))); //returns false, its an NodeList not an Array </script> </body> </html> Notes Array.isArray was implemented in ECMAScript 5th edtion or ES5 Converting a node list and html collection list to a true JavaScript array can provide a good deal of advantages. For one it gives us the ability to create a snapshot of the list that is not tied to the live DOM considering that NodeList and HTMLCollection are live lists. Secondly, converting a list to a JavaScript array gives access to the methods provided by the Array object (e.g. forEach, pop, map, reduce etc...). To convert an array like list to a true JavaScript array pass the array-like list to call() or apply(), in which the call() or apply() is calling a method that returns an un-altered true JavaScript array. In the code below I use the .slice() method, which doesn't really slice anything I am just using it to convert the list to a JavaScript Array due to the fact the slice() returns an array. live code: http://jsfiddle.net/domenlightenment/jHgTY <!DOCTYPE html> <html lang="en"> <body> <a href="#"></a> <script> console.log(Array.isArray(Array.prototype.slice.call(document.links))); //returns true console.log(Array.isArray(Array.prototype.slice.call(document.querySelectorAll('a')))); //returns true </script> </body> </html> Notes In ECMAScript 6th edtion we have Array.from to look forward to which converts a single argument that is an array-like object or list (eg. arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap (used by attributes property)) into a new Array() and returns it 1.16 Traversing nodes in the DOM From a node reference (i.e. document.querySelector('ul')) its possible to get a different node reference by traversing the DOM using the following properties: parentNode

firstChild

lastChild

nextSibling

previousSibling In the code example below we examine the Node properties providing DOM traversal functionality. live code: http://jsfiddle.net/domenlightenment/Hvfhv <!DOCTYPE html> <html lang="en"> <body><ul><!-- comment --> <li id="A"></li> <li id="B"></li> <!-- comment --> </ul> <script> //cache selection of the ul var ul = document.querySelector('ul'); //What is the parentNode of the ul? console.log(ul.parentNode.nodeName); //logs body //What is the first child of the ul? console.log(ul.firstChild.nodeName); //logs comment //What is the last child of the ul? console.log(ul.lastChild.nodeName); //logs text not comment, because there is a line break //What is the nextSibling of the first li? console.log(ul.querySelector('#A').nextSibling.nodeName); //logs text //What is the previousSibling of the last li? console.log(ul.querySelector('#B').previousSibling.nodeName); //logs text </script> </body> </html> If you have been around the DOM much then it should be no surprise that traversing the DOM includes not just traversing element nodes but also text and comment nodes. I believe the last code example makes this clear, and this is not exactly ideal. Using the following properties we can traverse the DOM ignoring text and comment nodes. firstElementChild

lastElementChild

nextElementChild

previousElementChild

children Notes The childElementCount is not mentioned but you should be aware of its avaliablity for calculating the number of child elements a node contains. Examine our code example again using only element traversing methods. live code: http://jsfiddle.net/domenlightenment/Wh7nf <!DOCTYPE html> <html lang="en"> <body><ul><!-- comment --> <li id="A"></li> <li id="B"></li> <!-- comment --> </ul> <script> //cache selection of the ul var ul = document.querySelector('ul'); //What is the first child of the ul? console.log(ul.firstElementChild.nodeName); //logs li //What is the last child of the ul? console.log(ul.lastElementChild.nodeName); //logs li //What is the nextSibling of the first li? console.log(ul.querySelector('#A').nextElementSibling.nodeName); //logs li //What is the previousSibling of the last li? console.log(ul.querySelector('#B').previousElementSibling.nodeName); //logs li //What are the element only child nodes of the ul? console.log(ul.children); //HTMLCollection, all child nodes including text nodes </script> </body> </html> 1.17 Verify a node position in the DOM tree with contains() & compareDocumentPosition() Its possible to know if a node is contained inside of another node by using the contains() Node method. In the code below I ask if <body> is contained inside of <html lang="en">. live code: http://jsfiddle.net/domenlightenment/ENU4w <!DOCTYPE html> <html lang="en"> <body> <script> // is <body> inside <html lang="en"> ? var inside = document.querySelector('html').contains(document.querySelector('body')); console.log(inside); //logs true </script> </body> </html> If you need more robust information about the position of a node in the DOM tree in regards to the nodes around it you can use the compareDocumentPosition() Node method. Basically this method gives you the ability to request information about a selected node relative to the node passed in. The information that you get back is a number that corresponds to the following information. number code returned from compareDocumentPosition(): number code info: 0 Elements are identical. 1 DOCUMENT_POSITION_DISCONNECTED

Set when selected node and passed in node are not in the same document. 2 DOCUMENT_POSITION_PRECEDING

Set when passed in node is preceding selected node. 3 DOCUMENT_POSITION_FOLLOWING

Set when passed in node is following selected node. 8 DOCUMENT_POSITION_CONTAINS

Set when passed in node is an ancestor of selected node. 16, 10 DOCUMENT_POSITION_CONTAINED_BY (16, 10 in hexadecimal)

Set when passed in node is a descendant of selected node. Notes contains() will return true if the node selected and node passed in are identical. compareDocumentPosition() can be rather confusing because its possible for a node to have more than one type of relationship with another node. For example when a node both contains (16) and precedes (4) the returned value from compareDocumentPosition() will be 20. 1.18 How to determine if two nodes are identical According to the DOM 3 specification two nodes are equal if and only if the following conditions are satisfied: The two nodes are of the same type.

The following string attributes are equal: nodeName, localName, namespaceURI, prefix, nodeValue. That is: they are both null, or they have the same length and are character for character identical.

The attributes NamedNodeMaps are equal. That is: they are both null, or they have the same length and for each node that exists in one map there is a node that exists in the other map and is equal, although not necessarily at the same index.

The childNodes NodeLists are equal. That is: they are both null, or they have the same length and contain equal nodes at the same index. Note that normalization can affect equality; to avoid this, nodes should be normalized before being compared. Calling the .isEqualNode() method on a node in the DOM will ask if that node is equal to the node that you pass it as a parameter. Below I exhibt a case of an two equal nodes and two non-identical nodes. live code: http://jsfiddle.net/domenlightenment/xw68Q <!DOCTYPE html> <html lang="en"> <body> <input type="text"> <input type="text"> <textarea>foo</textarea> <textarea>bar</textarea> <script> //logs true, because they are exactly idential var input = document.querySelectorAll('input'); console.log(input[0].isEqualNode(input[1])); //logs false, because the child text node is not the same var textarea = document.querySelectorAll('textarea'); console.log(textarea[0].isEqualNode(textarea[1])); </script> </body> </html> Notes If you don't care about two nodes being exactly equal but instead want to know if two node references refer to the same node you can simply check it using the === opertor (i.e. document.body === document.body). This will tell us if they are identical but no equal.

Chapter 2 - Document Nodes 2.1 document node overview The HTMLDocument constructor (which inherits from document) when instantiated represents specifically a DOCUMENT_NODE (i.e. window.document) in the DOM. To verify this we can simply ask which constructor was used in the creation of the document node object. live code: http://jsfiddle.net/domenlightenment/qRAzL <!DOCTYPE html> <html lang="en"> <body> <script> console.log(window.document.constructor); //logs function HTMLDocument() { [native code] } console.log(window.document.nodeType); //logs 9, which is a numeric key mapping to DOCUMENT_NODE </script> </body> </html> The code above concludes that the HTMLDocument constructor function constructs the window.document node object and that this node is a DOCUMENT_NODE object. Notes Both Document and HTMLDocument constructors are typically instantiated by the browser when you load an HTML document. However, using document.implementation.createHTMLDocument() its possible to create your own HTML document outside of the one currently loaded into the browser. In addtion to createHTMLDocument() its also possible to create a document object which has yet to be setup as an HTML document using createDocument(). Typically the use of theses methods are associated with programatically providing an HTML document to an iframe. 2.2 HTMLDocument properties and methods (including inherited) To get accurate information pertaining to the available properties and methods on an HTMLDocument node its best to ignore the specification and to ask the browser what is available. Examine the arrays created in the code below detailing the properties and methods available from an HTMLDocument node (a.k.a. window.document) object. live code: http://jsfiddle.net/domenlightenment/jprPe <!DOCTYPE html> <html lang="en"> <body> <script> //document own properties console.log(Object.keys(document).sort()); //document own properties & inherited properties var documentPropertiesIncludeInherited = []; for(var p in document){ documentPropertiesIncludeInherited.push(p); } console.log(documentPropertiesIncludeInherited.sort()); //documment inherited properties only var documentPropertiesOnlyInherited = []; for(var p in document){ if( !document.hasOwnProperty(p)){documentPropertiesOnlyInherited.push(p); } } console.log(documentPropertiesOnlyInherited.sort()); </script> </body> </html> The available properties are many even if the inherited properties were not considered. Below I've hand pick a list of noteworthy properties and methods for the context of this chapter. doctype

documentElement

implementation.*

activeElement

body

head

title

lastModified

referrer

URL

defaultview

compatMode

ownerDocument

hasFocus() Notes The HTMLDocument node object is used to access (typically inherit) a great deal of the methods and properties available for working with the DOM (i.e. document.querySelectorAll()). You will be seeing many of these properties not discussed in this chapter discussed in the appropriate chapter's following this chapter. 2.3 Getting general HTML document information (title, url, referrer, lastModified, compatMode) The document object provides access to some general information about the HTML document/DOM being loaded. In the code below I use the document.title, document.URL, document.referrer, document.lastModified, and document.compatMode properties to gain some general information about the document. Based on the property name the returned values should be obvious. live code: http://jsfiddle.net/domenlightenment/pX8Le <!DOCTYPE html> <html lang="en"> <body> <script> var d = document; console.log('title = ' +d.title); console.log('url = ' +d.URL); console.log('referrer = ' +d.referrer); console.log('lastModified = ' +d.lastModified); //logs either BackCompat (Quirks Mode) or CSS1Compat (Strict Mode) console.log('compatibility mode = ' +d.compatMode); </script> </body> </html> 2.4 document child nodes Document nodes can contain one DocumentType node object and one Element node object. This should not be a surprise since HTML documents typically contain only one doctype (e.g. <!DOCTYPE html>) and one element (e.g. <html lang="en">). Thus if you ask for the children (e.g. document.childNodes) of the Document object you will get an array containing at the very least the documents doctype/DTD and <html lang="en"> element. The code below showcases that window.document is a type of node object (i.e Document) with child nodes. live code: http://jsfiddle.net/domenlightenment/UasKc <!DOCTYPE html> <html lang="en"> <body> <script> //This is the doctype/DTD console.log(document.childNodes[0].nodeType); //logs 10, which is a numeric key mapping to DOCUMENT_TYPE_NODE //This is the <html> element console.log(document.childNodes[1].nodeType); //logs 1, which is a numeric key mapping to ELEMENT_TYPE_NODE </script> </body> </html> Notes Don't confuse the window.document object created from HTMLDocument constructor with the Document object. Just remember window.document is the starting point for the DOM interface. That is why document.childNodes contains child nodes. If a comment node (not discussed in this book) is made outside of the <html lang="en"> element then it will become a child node of the window.document. However having comment nodes outside of the <html> element can cause some buggy results in IE and also is a violation of the DOM specification. 2.5 document provides shortcuts to <!DOCTYPE>, <html lang="en">, <head>, and <body> Using the properties listed below we can get a shortcut reference to the following nodes: document.doctype refers to <!DOCTYPE>

document.documentElement refers to <html lang="en">

document.head refers to <head>

document.body refers to <body> This is demonstrated in the code below. live code: http://jsfiddle.net/domenlightenment/XsSTM <!DOCTYPE html> <html lang="en"> <body> <script> console.log(document.doctype); // logs DocumentType {nodeType=10, ownerDocument=document, ...} console.log(document.documentElement); // logs <html lang="en"> console.log(document.head); // logs <head> console.log(document.body); // logs <body> </script> </body> </html> Notes The doctype or DTD is a nodeType of 10 or DOCUMENT_TYPE_NODE and should not be confused with the DOCUMENT_NODE (aka window.document constructed from HTMLDocument()). The doctype is constructed from the DocumentType() constructor.

In Safari, Chrome, and Opera the document.doctype does not appear in the document.childNodes list. 2.6 Detecting DOM specifications/features using document.implementation.hasFeature() Its possible using document.implementation.hasFeature() to ask (boolean) the current document what feature and level the browser has implemented/supports. For example we can ask if the browser has implemented the core DOM level 3 specification by passing the name of the feature and the version to the hasFeature() method. In the code below I ask if the browser has implemented the Core 2.0 & 3.0 specification. live code: http://jsfiddle.net/domenlightenment/TYYZ6 <!DOCTYPE html> <html lang="en"> <body> <script> console.log(document.implementation.hasFeature('Core','2.0')); console.log(document.implementation.hasFeature('Core','3.0')); </script> </body> </html> The following table defines the features (spec calls these modules) and versions that you can pass the hasFeature() method. Feature Supported Versions Core 1.0, 2.0, 3.0 XML 1.0, 2.0, 3.0 HTML 1.0, 2.0 Views 2.0 StyleSheets 2.0 CSS 2.0 CSS2 2.0 Events 2.0, 3.0 UIEvents 2.0, 3.0 MouseEvents 2.0, 3.0 MutationEvents 2.0, 3.0 HTMLEvents 2.0 Range 2.0 Traversal 2.0 LS (Loading and saving between files and DOM trees synchronously) 3.0 LS-Asnc (Loading and saving between files and DOM trees asynchronously) 3.0 Validation 3.0 Notes Don't trust hasFeature() alone you should also use capability detection in addition to hasFeature(). Using the isSupported method implementation information can be gathered for a specific/selected node only (i.e. element.isSupported(feature,version). You can determince online what a user agent supports by visiting http://www.w3.org/2003/02/06-dom-support.html. Here you will find a table indicating what the browser loading the url claims to implement. 2.7 Get a reference to the focus/active node in the document Using the document.activeElement we can quickly get a reference to the node in the document that is focused/active. In the code below, on page load, I am setting the focus of the document to the <textarea> node and then gaining a reference to that node using the activeElement property. live code: http://jsfiddle.net/domenlightenment/N9npb <!DOCTYPE html> <html lang="en"> <body> <textarea></textarea> <script> //set focus to <textarea> document.querySelector('textarea').focus(); ​//get reference to element that is focused/active in the document console.log(document.activeElement); //logs <textarea> </script> </body> </html> Notes The focused/active element returns elements that have the ability to be focused. If you visit a web page in a browser and start hitting the tab key you will see focus shifting from element to element in that page that can get focused. Don't confuse the selection of nodes (highlight sections of the HTML page with mouse) with elements that get focus for the purpose of inputting something with keystrokes, spacebar, or mouse. 2.8 Determing if the document or any node inside of the document has focus Using the document.hasFocus() method its possible to know if the user currently is focused on the window that has the HTML document loaded. In the code below you see that if we execute the code and then focus another window, tabe, or application all together the getFocus() will return false. live code: http://jsfiddle.net/domenlightenment/JkE3d <!DOCTYPE html> <html lang="en"> <body> <script> //If you keep focus on the window/tab that has the document loaded its true. If not it's false. setTimeout(function(){console.log(document.hasFocus())},5000); </script> </body> </html> 2.9 document.defaultview is a shortcut to the head/global object You should be aware that the defaultView property is a shortcut to the JavaScript head object or what some refer to as the global object. The head object in a web browser is the window object and defaultView will point to this object in a JavaScript browser enviroment. The code below demonstrates the value of defaultView in a browser. live code: http://jsfiddle.net/domenlightenment/QqK6Q <!DOCTYPE html> <html lang="en"> <body> <script> console.log(document.defaultView) //reference, head JS object. Would be window object in a browser. </script> </body> </html> If you are dealing with a DOM that is headless or an JavaScript enviroment that is not running in a web browser (i.e. node.js) this property can get you access to the head object scope. 2.9 Getting a reference to the Document from an element using ownerDocument The ownerDocument property when called on a node returns a reference to the Document the node is contained within. In the code below I get a reference to the Document of the <body> in the HTML document and the Document node for the <body> element contained inside of the iframe. live code: N/A <!DOCTYPE html> <html lang="en"> <body> <iframe src="http://someFileServedFromServerOnSameDomain.html"></iframe> <script> //get the window.document that the <body> is contained within console.log(document.body.ownerElement); //get the window.document the <body> inside of the iframe is contained within console.log(window.frames[0].document.body.ownerElement); </script> </body> </html> If ownerDocument is called on the Document node the value returned is null.

Chapter 3 - Element Nodes 3.1 HTML*Element object overview Elements in an html document all have a unique nature and as such they all have a unique JavaScript constructor that instantiates the element as a node object in a DOM tree. For example an <a> element is created as a DOM node from the HTMLAnchorElement() constructor. Below we verify that an anchor element is created from HTMLAnchorElement(). live code: http://jsfiddle.net/domenlightenment/TgcNu <!DOCTYPE html> <html lang="en"> <body> <a></a> <script> // grab <a> element node from DOM and ask for the name of the constructor that constructed it console.log(document.querySelector('a').constructor); //logs function HTMLAnchorElement() { [native code] } </script> </body> </html> The point I am trying to express in the previous code example is that each element in the DOM is constructed from a unique JavaScript intefaces/constructor. The list below (not a complete list) should give you a good sense of the interfaces/constructors used to create HTML elements. HTMLHtmlElement

HTMLHeadElement

HTMLLinkElement

HTMLTitleElement

HTMLMetaElement

HTMLBaseElement

HTMLIsIndexElement

HTMLStyleElement

HTMLBodyElement

HTMLFormElement

HTMLSelectElement

HTMLOptGroupElement

HTMLOptionElement

HTMLInputElement

HTMLTextAreaElement

HTMLButtonElement

HTMLLabelElement

HTMLFieldSetElement

HTMLLegendElement

HTMLUListElement

HTMLOListElement

HTMLDListElement

HTMLDirectoryElement

HTMLMenuElement

HTMLLIElement

HTMLDivElement

HTMLParagraphElement

HTMLHeadingElement

HTMLQuoteElement

HTMLPreElement

HTMLBRElement

HTMLBaseFontElement

HTMLFontElement

HTMLHRElement

HTMLModElement

HTMLAnchorElement

HTMLImageElement

HTMLObjectElement

HTMLParamElement

HTMLAppletElement

HTMLMapElement

HTMLAreaElement

HTMLScriptElement

HTMLTableElement

HTMLTableCaptionElement

HTMLTableColElement

HTMLTableSectionElement

HTMLTableRowElement

HTMLTableCellElement

HTMLFrameSetElement

HTMLFrameElement

HTMLIFrameElement Keep in mind each HTML*Element above inherits properties and methods from HTMLElement, Element, Node, and Object. 3.2 HTML*Element object properties and methods (including inherited) To get accurate information pertaining to the available properties and methods on an HTML*Element node its best to ignore the specification and to ask the browser what is available. Examine the arrays created in the code below detailing the properties and methods available from HTML element nodes. live code: http://jsfiddle.net/domenlightenment/vZUHw <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> var anchor = document.querySelector('a'); //element own properties console.log(Object.keys(anchor).sort()); //element own properties & inherited properties var documentPropertiesIncludeInherited = []; for(var p in document){ documentPropertiesIncludeInherited.push(p); } console.log(documentPropertiesIncludeInherited.sort()); //element inherited properties only var documentPropertiesOnlyInherited = []; for(var p in document){ if(!document.hasOwnProperty(p)){ documentPropertiesOnlyInherited.push(p); } } console.log(documentPropertiesOnlyInherited.sort()); </script> </body> </html> The available properties are many even if the inherited properties were not considered. Below I've hand pick a list of note worthy properties and methods (inherited as well) for the context of this chapter. createElement()

tagName

children

getAttribute()

setAttribute()

hasAttribute()

removeAttribute()

classList()

dataset

attributes For a complete list check out the MDN documentation which covers the general properties and methods available to most HTML elements. 3.3 Creating Elements Element nodes are instantiated for us when a browser interputs an HTML document and a corresponding DOM is built based on the contents of the document. After this fact, its also possible to programaticlly create Element nodes using createElement(). In the code below I create a <textarea> element node and then inject that node into the live DOM tree. live code: http://jsfiddle.net/domenlightenment/d3Yvv <!DOCTYPE html> <html lang="en"> <body> <script> var elementNode = document.createElement('textarea'); //HTMLTextAreaElement() constructs <textarea> document.body.appendChild(elementNode); console.log(document.querySelector('textarea')); //verify it's now in the DOM </script> </body> </html> The value passed to the createElement() method is a string that specifices the type of element (aka tagName) to be created. Notes This value passed to createElement is changed to a lower-case string before the element is created. 3.4 Get the tag name of an element Using the tagName property we can access the name of an element. The tagName property returns the same value that using nodeName would return. Both return the value in uppercase regardless of the case in the source HTML document. Below we get the name of an <a> element in the DOM. live code: http://jsfiddle.net/domenlightenment/YJb3W <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> console.log(document.querySelector('a').tagName); //logs A //the nodeName property returns the same value console.log(document.querySelector('a').nodeName); //logs A </script> </body> </html> 3.5 Getting a list/collection of element attributes and values Using the attributes property (inherited by element nodes from Node) we can get a collection of the Attr nodes that an element currently has defined. The list returned is a NameNodeMap. Below I loop over the attributes collection exposing each Attr node object contained in the collection. live code: http://jsfiddle.net/domenlightenment/9gVQf <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" class="yes" style="margin:0;" foo="boo"></a> <script> var atts = document.querySelector('a').attributes; for(var i=0; i< atts.length; i++){ console.log(atts[i].nodeName +'='+ atts[i].nodeValue); } </script> </body> </html> Notes The array returned from accessing the attributes property should be consider live. Meaning that its contents can be changed at anytime. The array that is returned inherits from the NameNodeMap which provides methods to operate on the array such as getNamtedItem(), setNamedItem(), and removeNamedItem(). Operating on attributes with these methods should be secondary to using getAttribute(), setAttribute(), hasAttribute(), removeAttribute(). Its this authors opinion that dealing with Attr nodes is messy. The only merit in using the attributes is found only in its funcitonaly for returning a list of live attributes. The attributes property is an array like collection and has a read only length property. Boolean attributres (e.g. <option selected>foo</option>) show up in the attributes list but of course have no value unless you provide one (e.g. <option selected="selected">foo</option>). 3.6 Getting, Setting, & Removing an element's attribute value The most consistent way to get, set, or remove an elements attribute value is to use the getAttribute(), setAttribute(), and removeAttribute() method. In the code below I demonstrate each of these methods for managing element attributes. live code: http://jsfiddle.net/domenlightenment/wp7rq <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" style="margin:0;" class="yes" foo="boo" hidden="hidden">#link</a> <script> var atts = document.querySelector('a'); //remove attributes atts.removeAttribute('href'); atts.removeAttribute('title'); atts.removeAttribute('style'); atts.removeAttribute('data-foo'); atts.removeAttribute('class'); atts.removeAttribute('foo'); //custom attribute

atts.removeAttribute('hidden'); //boolean attribute //set (really re-set) attributes atts.setAttribute('href','#'); atts.setAttribute('title','title'); atts.setAttribute('style','margin:0;'); atts.setAttribute('data-foo','dataFoo'); atts.setAttribute('class','yes'); atts.setAttribute('foo','boo'); atts.setAttribute('hidden','hidden'); //boolean attribute requires sending the attribute as the value too //get attributes console.log(atts.getAttribute('href')); console.log(atts.getAttribute('title')); console.log(atts.getAttribute('style')); console.log(atts.getAttribute('data-foo')); console.log(atts.getAttribute('class')); console.log(atts.getAttribute('foo')); console.log(atts.getAttribute('hidden')); </script> </body> </html> Notes Use removeAttribute() instead of setting the attribute value to null or '' using setAttribute() Some element attributes are available from element nodes as object properties (i.e. document.body.id or document.body.className). This author recommends avoiding these properties and using the remove, set, and get attribute methods. 3.7 Verifying an element has a specific attribute The best way to determine (i.e. boolean) if an element has an attribute is to use the hasAttribute() method. Below I verify if the <a> has a href, title, style, data-foo, class, and foo attribute. live code: http://jsfiddle.net/domenlightenment/hbCCE <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" style="margin:0;" class="yes" goo></a> <script> var atts = document.querySelector('a'); console.log( atts.hasAttribute('href'), atts.hasAttribute('title'), atts.hasAttribute('style'), atts.hasAttribute('data-foo'), atts.hasAttribute('class'), atts.hasAttribute('goo') //Notice this is true regardless if a value is defined ) </script> </body> </html> This method will return true if the element contains the attribute even if the attribute has no value. For example using hasAttribute() we can get a boolean response for boolean attributes. In the code example below we check to see if a checkbox is checked. live code: http://jsfiddle.net/domenlightenment/tb6Ja <!DOCTYPE html> <html lang="en"> <body> <input type="checkbox" checked></input> <script> var atts = document.querySelector('input'); console.log(atts.hasAttribute('checked')); //logs true </script> </body> </html> 3.8 Getting a list of class attribute values Using the classList property available on element nodes we can access a list (i.e. DOMTokenList) of class attribute values that is much easier to work with than a space-delimited string value returned from the className property. In the code below I contrast the use of classList with className. live code: http://jsfiddle.net/domenlightenment/DLJEA <!DOCTYPE html> <html lang="en"> <body> <div class="big brown bear"></div> <script> var elm = document.querySelector('div'); console.log(elm.classList); //big brown bear {0="big", 1="brown", 2="bear", length=3, ...} console.log(elm.className); //logs 'big brown bear' </script> </body> </html> Notes Given the classList is an array like collection it has a read only length property. classList is read-only but can be modifyied using the add(), remove(), contains(), and toggle() methods IE9 does not support classList. Support will land in IE10. Several polyfills are avaliable. 3.9 Adding & removing sub-values to a class attribute Using the classList.add() and classList.remove() methods its extremely simple to edit the value of a class attribute. In the code below I demonstrated adding and removing class values. live code: http://jsfiddle.net/domenlightenment/YVaUU <!DOCTYPE html> <html lang="en"> <body> <div class="dog"></div>​ <script> var elm = document.querySelector('div'); elm.classList.add('cat'); elm.classList.remove('dog'); console.log(elm.className); //'cat' </script> </body> </html> 3.10 Toggling a class attribute value Using the classList.toggle() method we can toggle a sub-value of the class attribute. This allows us to add a value if its missing or remove a value if its already added. In the code below I toggle the 'visible' value and the 'grow' value. Which essentially means I remove 'visible' and add 'grow' to the class attribute value. live code: http://jsfiddle.net/domenlightenment/uFp6J <!DOCTYPE html> <html lang="en"> <body> <div class="visible"></div>​ <script> var elm = document.querySelector('div'); elm.classList.toggle('visible'); elm.classList.toggle('grow'); console.log(elm.className); //'grow' </script> </body> </html> 3.11 Determining if a class attribute value contains a specific value

Using the classList.contains() method its possible to determine (boolean) if a class attribute value contains a specific sub-value. In the code below we test weather the <div> class attribute contains a sub-value of brown. live code: http://jsfiddle.net/domenlightenment/njyaP <!DOCTYPE html> <html lang="en"> <body> <div class="big brown bear"></div>​ <script> var elm = document.querySelector('div'); console.log(elm.classList.contains('brown')); // logs true </script> </body> </html> 3.12 Getting & Setting data-* attributes The dataset property of a element node provides an object containing all of the attributes of an element that starts with data-*. Because its a simply a JavaScript object we can manipulate dataset and have the element in the DOM reflect those changes live code: http://jsfiddle.net/domenlightenment/ystgj <!DOCTYPE html> <html lang="en"> <body> <div data-foo-foo="foo" data-bar-bar="bar"></div>​ <script> var elm = document.querySelector('div'); //get console.log(elm.dataset.fooFoo); //logs 'foo' console.log(elm.dataset.barBar); //logs 'bar' //set elm.dataset.gooGoo = 'goo'; console.log(elm.dataset); //logs DOMStringMap {fooFoo="foo", barBar="bar", gooGoo="goo"} //what the element looks like in the DOM console.log(elm); //logs <div data-foo-foo="foo" data-bar-bar="bar" data-goo-goo="goo"> </script> </body> </html> Notes dataset contains camel case versions of data attributes. Meaning data-foo-foo will be listed as the property fooFoo in the dataset DOMStringMap object. The- is replaced by camel casing. Removing a data-* attribute from the DOM is as simple using the delete operator on a property of the datset (e.g. delete dataset.fooFoo) dataset is not supported in IE9. A polyfill is avaliable. However, you can always just use getAttribute('data-foo'), removeAttribute('data-foo'), setAttribute('data-foo'), hasAttribute('data-foo').

Chapter 4 - Element Node Selecting 4.1 Selecting a specific element node The most common methods for getting a reference to a single element node are: querySelector()

getElementById() In the code below I leverage both of these methods to select an element node from the HTML document. live code: http://jsfiddle.net/domenlightenment/b4Rch <!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hello</li> <li>big</li> <li>bad</li> <li id="last">world</li> </ul> <script> console.log(document.querySelector('li').textContent); //logs Hello console.log(document.getElementById('last').textContent); //logs world </script> </body> </html> The getElementById() method is pretty simple compared to the more robust querySelector() method. The querySelector() method permits a parameter in the form of a CSS selector syntax. Basically you can pass this method a CSS 3 selector (e.g. '#score>tbody>tr>td:nth-of-type(2)') which it will use to select a single element in the DOM. Notes querySelector() will return the first node element found in the document based on the selector. For example, in the code example above we pass a selector that would select all the li's in CSS, but only the first one is returned. querySelector() is also defined on element nodes. This allows for the method to limit (allows for context querying) its results to a specific vein of the DOM tree 4.2 Selecting/creating a list (aka NodeList) of element nodes The most common methods for selecting/creating a list of nodes in an HTML document are: querySelectorAll()

getElementsByTagName()

getElementsByClassName() Below we use all three of these methods to create a list of the <li> elements in the document. live code: http://jsfiddle.net/domenlightenment/nT7Lr <!DOCTYPE html> <html lang="en"> <body> <ul> <li class="liClass">Hello</li> <li class="liClass">big</li> <li class="liClass">bad</li> <li class="liClass">world</li> </ul> <script> //all of the methods below create/select the same list of <li> elements from the DOM console.log(document.querySelectorAll('li')); console.log(document.getElementsByTagName('li')); console.log(document.getElementsByClassName('liClass')); </script> </body> </html> If its not clear the methods used in the code example above do not select a specific element, but instead creates a list (aka NodeLists) of elements that you can select from. Notes NodeLists created from getElementsByTagName() and getElementsByClassName() are considered live are will always reflect the state of the document even if the document is updated after the list is created/selected. The querySelectorAll() method does not return a live list of elements. Meaning that the list created from querySelectorAll() is a snap shot of the document at the time it was created and is not reflective of the document as it changes. The list is static not live. querySelectorAll(), getElementsByTagName(), and getElementsByClassName are also defined on element nodes. This allows for the method to limit its results to specific vein(s) of the DOM tree (e.g. document.getElementById('header').getElementsByClassName('a')). I did not mention the getElementsByName() method as it not commonly leverage over other solutions but you should be aware of its existence for selecting form, img, frame, embed, and object elements from a document that all have the same name attribute value. Passing either querySelectorAll() or getElementsByTagName() the string '*', which generally means all, will return a list of all elements in the document. Keep in mind that childNodes will also return a NodeList just like querySelectorAll(), getElementsByTagName(), and getElementsByClassName The NodeLists are array like (but does not inherit array methods) lists/collections and have a read only length property 4.3 Selecting all immediate child element nodes Using the children property from an element node we can get a list (aka HTMLCollection) of all the immediate children nodes that are element nodes. In the code below I use children to create a selection/list of all of the <li>'s contained wiithin the <ul>. live code: http://jsfiddle.net/domenlightenment/svfRC <!DOCTYPE html> <html lang="en"> <body> <ul> <li><strong>Hi</strong></li> <li>there</li> </ul> <script> var ulElement = document.querySelector('ul').children; //logs a list/array of all immediate child element nodes console.log(ulElement); //logs [<li>, <li>] </script> </body> </html> Notice that using children only gives us the immediate element nodes excluding any nodes (e.g. text nodes) that are not elements. If the element has no children then children will return an empty array-like-list. Notes HTMLCollection's contain elements in document order, that is they are placed in the array in the order the elements appear in the DOM HTMLCollection's are live, which means any change to the document will be reflected dynamically in the collection 4.4 Contextual element selecting The methods querySelector(), querySelectorAll(), getElementsByTagName(), and getElementsByClassName typically accessed from the document object are also defined on element nodes. This allows for these methods to limit its results to specific vein(s) of the DOM tree. Or said another, you can select a specific context in which you would like the methods to search for element nodes by invoking these methods on element node objects. live code: http://jsfiddle.net/domenlightenment/fL6tV <!DOCTYPE html> <html lang="en"> <body> <div>

<ul>

<li class="liClass">Hello</li>

<li class="liClass">big</li>

<li class="liClass">bad</li>

<li class="liClass">world</li>

</ul>

</div> <ul>

<li class="liClass">Hello</li>

</ul> <script> //select a div as the context to run the selecting methods only on the contents of the div var div = document.querySelector('div'); console.log(div.querySelector('ul')); console.log(div.querySelectorAll('li')); console.log(div.getElementsByTagName('li')); console.log(div.getElementsByClassName('liClass')); </script> </body> </html> These methods not only operate on the live dom but programatic DOM structures that are created in code as well. live code: http://jsfiddle.net/domenlightenment/CCnva <!DOCTYPE html> <html lang="en"> <body> <script> //create DOM structure var divElm = document.createElement('div'); var ulElm = document.createElement('ul'); var liElm = document.createElement('li'); liElm.setAttribute('class','liClass'); ulElm.appendChild(liElm); divElm.appendChild(ulElm); //use selecting methods on DOM structure console.log(divElm.querySelector('ul')); console.log(divElm.querySelectorAll('li')); console.log(divElm.getElementsByTagName('li')); console.log(divElm.getElementsByClassName('liClass')); </body> </html> 4.5 Pre-configured selections/lists of element nodes You should be aware that there are some legacy, pre-configured arrays-like-lists, containing element nodes from an HTML document. Below I list a couple of these (not the complete list) that might be handy to be aware of. document.all - all elements in HTML document

document.forms - all <form> elements in HTML document

document.images - all <img> elements in HTML document

document.links - all <a> elements in HTML document

document.scripts - all <script> elements in HTML document

document.styleSheets - all <link> or <style> objects in HTML document Notes These pre-configured arrays are constucted from the HTMLCollection interface/object, except document.styleSheets it uses StyleSheetList HTMLCollection's are live just like NodeList's. Oddly document.all is constucted from a HTMLAllCollection not an HTMLCollection and is not supported in Firefox 4.6 Verify an element will be selected using matchesSelector() Using the matchesSelector() method we can determine if an element will match a selector string. For example say we want to determine if an <li> is the first child element of a <ul>. In the code example below I select the first <li> inside of the <ul> and then ask if that element matches the selector, li:first-child. Because it in fact does, the matchesSelector() method returns true. live code: http://jsfiddle.net/domenlightenment/9RayM <!DOCTYPE html> <html lang="en"> <body> <ul> <li>Hello</li> <li>world</li> </ul> <script> //fails in modern browser must use browser prefix moz, webkit, o, and ms console.log(document.querySelector('li').matchesSelector('li:first-child')); //logs false //prefix moz //console.log(document.querySelector('li').mozMatchesSelector('li:first-child')); //prefix webkit //console.log(document.querySelector('li').webkitMatchesSelector('li:first-child')); //prefix o //console.log(document.querySelector('li').oMatchesSelector('li:first-child')); //prefix ms //console.log(document.querySelector('li').msMatchesSelector('li:first-child')); </script> </body> </html> Notes matchesSelector has not seen much love from the browsers as its usage is behind browser prefixes mozMatchesSelector(), webkitMatchesSelector(), oMatchesSelector(), msMatchesSelector() In the future matchesSelector() will be renamed to matches()

Chapter 5 - Element Node Geometry & Scrolling Geometry 5.1 Element node size, offsets, and scrolling overview DOM nodes are parsed and painted into visual shapes when viewing html documents in a web browser. Nodes, mostly element nodes, have a corresponding visual representation made viewable/visual by browsers. To inspect and in some cases manipulate the visual representation and gemometry of nodes programatically a set of API's exists and are specified in the CSSOM View Module. A subset of methods and properties found in this specification provide an API to determine the geometry (i.e. size & position using offset) of element nodes as well as hooks for manipulating scrollable nodes and getting values of scrolled nodes. This chapter breaks down these methods and properties. Notes Most of the properties (excluding scrollLeft & scrollTop) from the CSSOM View Module specification are read only and calculated each time they are accessed. In other words, the values are live 5.2 Getting an elements offsetTop and offsetLeft values relative to the offsetParent Using the properties offsetTop and offsetLeft we can get the offset pixel value of an element node from the offsetParent. These element node properties give us the distance in pixels from an elements outside top and left border to the inside top and left border of the offsetParent. The value of the offsetParent is determined by searching the nearest ancestor elements for an element that has a CSS position value not equal to static. If none are found then the <body> element or what some refer to as the "document" (as opposed to the browser viewport) is the offsetParent value. If during the ancestral search a <td>, <th>, or <table> element with a CSS position value of static is found then this becomes the value of offsetParent. Lets verify that offsetTop and offsetLeft provide the values one might expect. The properties offsetLeft and offsetTop in the code below tell us that the <div> with an id of red is 60px's from the top and left of the offsetParent (i.e. the <body> element in this example). live code: http://jsfiddle.net/domenlightenment/dj5h9 <!DOCTYPE html> <html lang="en"> <head> <style> body{margin:0;} #blue{height:100px;width:100px;background-color:blue;border:10px solid gray; padding:25px;margin:25px;}

#red{height:50px;width:50px;background-color:red;border:10px solid gray;} </style> </head> <body> <div id="blue"><div id="red"></div></div> <script> var div = document.querySelector('#red');



console.log(div.offsetLeft); //logs 60

console.log(div.offsetTop); //logs 60 console.log(div.offsetParent); //logs <body> </script> </body> </html> Examine the following image showing what the code visually show in browser to help aid your understanding of how the offsetLeft and offsetTop values are deteremined. The red <div> shown in the image is exactly 60 pixels from the offsetParent. Notice I am measuring from the outside border of the red <div> element to the inside border of the offsetParent (i.e. <body>). As previously mentioned If I was to change the blue <div> in the above code to have a position of absolute this would alter the value of the offsetParent. In the code below, absolutely positioning the blue <div> will cause the values returned from offsetLeft and offsetTop to report an offset (i.e. 25px's). This is because the offset parent is now the blue <div> and not the <body> . live code: http://jsfiddle.net/domenlightenment/ft2ZQ <!DOCTYPE html> <html lang="en"> <head> <style> #blue{height:100px;width:100px;background-color:blue;border:10px solid gray; padding:25px;margin:25px;position:absolute;} #red{height:50px;width:50px;background-color:red;border:10px solid gray;} </style> </head> <body> <div id="blue"><div id="red"></div></div> <script> var div = document.querySelector('#red');



console.log(div.offsetLeft); //logs 25

console.log(div.offsetTop); //logs 25 console.log(div.offsetParent); //logs <div id="blue"> </script> </body> </html> The image of the browser view shown below clarifies the new measurements returned from offsetLeft and offsetTop when the offsetParent is the blue <div>. Notes Many of the browsers break the outside border to inside border measurement when the offsetParent is the <body> and the <body> or <html> element has a visible margin, padding, or border value. The offsetParent, offsetTop, and offsetLeft are extensions to the HTMLelement object. 5.3 Getting an elements top, right, bottom and left border edge offset relative to the viewport using getBoundingClientRect() Using the getBoundingClientRect() method we can get the position of an elements outside border edges as its painted in the browser viewport relative to the top and left edge of the viewport. This means the left and right edge are measured from the outside border edge of an element to the left edge of the viewport. And the top and bottom edges are measured from the outside border edge of an element to the top edge of the viewport. In the code below I create a 50px X 50px <div> with a 10px border and 100px margin. To get the distance in pixels from each border edge of the <div> I call the getBoundingClientRect() method on the <div> which returns an object containing a top, right, bottom, and left property. live code: http://jsfiddle.net/domenlightenment/A3RN9 <!DOCTYPE html> <html lang="en"> <head> <style> body{margin:0;}

div{height:50px;width:50px;background-color:red;border:10px solid gray;margin:100px;}

</style> </head> <body> <div></div> <script> var divEdges = document.querySelector('div').getBoundingClientRect();



console.log(divEdges.top, divEdges.right, divEdges.bottom, divEdges.left); //logs '100 170 170 100' </script> </body> </html> The image below shows the browser rendered view of the above code with some added measurement indicators to show exactly how getBoudingClientRect() is calculated. The top outside border edge of the <div> element is 100px from the top edge of the viewport. The right outside border edge of the element <div> is 170px from the left edge of the viewport. The bottom outside border edge of the element <div> is 170px from the top edge of the viewport. And the left outside border edge of the element <div> is 100px from the left edge of the viewport. 5.4 Getting an elements size (border + padding + content) in the viewport The getBoundingClientRect() returns an object with a top, right, bottom, and left property/value but also with a height and width property/value. The height and width properties indicate the size of the element where the total size is derived by adding the content of the div, its padding, and borders together. In the code below I get the size of the <div> element in the DOM using getBoundingClientRect(). live code: http://jsfiddle.net/domenlightenment/PuXmL <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}

</style>

</head>

<body>



<div></div>



<script>



var div = document.querySelector('div').getBoundingClientRect();

console.log(div.height, div.width); //logs '125 125' //because 25px border + 25px padding + 25 content + 25 padding + 25 border = 125



</script>

</body> </html> The exact same size values can also be found using from the offsetHeight and offsetWidth properties. In the code below I leverage these properties to get the same exact height and width values provided by getBoundingClientRect(). live code: http://jsfiddle.net/domenlightenment/MSzL3 <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}

</style>

</head>

<body>



<div></div>



<script>



var div = document.querySelector('div');

console.log(div.offsetHeight, div.offsetWidth); //logs '125 125' //because 25px border + 25px padding + 25 content + 25 padding + 25 border = 125



</script>

</body> </html> 5.5 Getting an elements size (padding + content) in the viewport excluding borders The clientWidth and clientHeight properties return a total size of an element by adding together the content of the element and its padding excluding the border sizes. In the code below I use these two properties to get the height and width of an element including padding but excluding borders. live code: http://jsfiddle.net/domenlightenment/bSrSb <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}

</style>

</head>

<body>



<div></div>



<script>



var div = document.querySelector('div');

console.log(div.clientHeight, div.clientWidth); //logs '75 75' because 25px padding + 25 content + 25 padding = 75



</script>

</body> </html> 5.6 Getting topmost element in viewport at a specific point using elementFromPoint() Using elementFromPoint() it's possible to get a reference to the topmost element in an html document at a specific point in the document. In the code example below I simply ask what is the topmost element 50 pixels from the top and left of the viewport. Since we have two <div>'s at that location the topmost (or if there is no z-index set the last one in document order) div is selected and returned. live code: http://jsfiddle.net/domenlightenment/8ksS5 <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:50px;width:50px;background-color:red;position:absolute;top:50px;left:50px;}

</style>

</head>

<body>



<div id="bottom"></div><div id="top"></div>



<script>



console.log(document.elementFromPoint(50,50)); //logs <div id="top">



</script>

</body> </html> 5.7 Getting the size of the element being scrolled using scrollHeight and scrollWidth The scrollHeight and scrollWidth properties simply give you the height and width of the node being scrolled. For example, open any HTML document that scrolls in a web browser and access these properties on the <html> (e.g. document.documentElement.scrollWidth) or <body> (e.g. document.body.scrollWidth) and you will get the total size of the HTML document being scrolled. Since we can apply, using CSS (i.e overflow:scroll), to elements lets look at a simpler code example. In the code below I make a <div> scroll a <p> element that is 1000px's x 1000px's. Accessing the scrollHeight and scrollWidth properties on the <div> will tell us that the element being scroll is 1000px's x 1000px's. live code: http://jsfiddle.net/domenlightenment/9sZtZ <!DOCTYPE html> <html lang="en"> <head>

<style> *{margin:0;padding:0;}

div{height:100px;width:100px; overflow:auto;}

p{height:1000px;width:1000px;background-color:red;}

</style>

</head>

<body>



<div><p></p></div>



<script>



var div = document.querySelector('div');



console.log(div.scrollHeight, div.scrollWidth); //logs '1000 1000'



</script>

</body> </html> Notes If you need to know the height and width of the node inside a scrollable area when the node is smaller than the viewport of the scrollable area don't use scrollHeight and scrollWidth as this will give you the size of the viewport. If the node being scrolled is smaller than the scroll area then use clientHeight and clientWidth to determine the size of the node contained in the scrollable area. 5.8 Getting & Setting pixels scrolled from the top and left using scrollTop and scrollLeft The scrollTop and scrollLeft properties are read-write properties that return the pixels to the left or top that are not currently viewable in the scrollable viewport due to scrolling. In the code below I setup a <div> that scrolls a <p> element. live code: http://jsfiddle.net/domenlightenment/DqZYH <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:100px;width:100px;overflow:auto;}

p{height:1000px;width:1000px;background-color:red;}

</style>

</head>

<body>



<div><p></p></div>



<script>



var div = document.querySelector('div');



div.scrollTop = 750;

div.scrollLeft = 750;



console.log(div.scrollTop,div.scrollLeft); //logs '750 750'



</script>

</body> </html> I programatically scroll the <div> by setting the scrollTop and scrollLeft to 750. Then I get the current value of scrollTop and scrollLeft, which of course since we just set the value to 750 will return a value of 750. The 750 reports the number of pixels scroll and indicates 750 px's to the left and top are not viewable in the viewport. If it helps just think of these properties as the pixel measurements of the content that is not shown in the viewport to the left or top. 5.9 Scrolling an element into view using scrollIntoView() By selecting a node contained inside a node that is scrollable we can tell the selected node to scroll into view using the scrollIntoView() method. In the code below I select the fifth <p> element contained in the scrolling <div> and call scrollIntoView() on it. live code: http://jsfiddle.net/domenlightenment/SyeFZ <!DOCTYPE html> <html lang="en"> <head>

<style>

div{height:30px;width:30px; overflow:auto;}

p{background-color:red;}

</style>

</head>

<body>



<div>

<content>

<p>1</p>

<p>2</p>

<p>3</p>

<p>4</p>

<p>5</p>

<p>6</p>

<p>7</p>

<p>8</p>

<p>9</p>

<p>10</p>

</content>

</div>



<script>

//select <p>5</p> and scroll that element into view, I pass children '4' because its a zero index array-like structure

document.querySelector('content').children[4].scrollIntoView(true);



</script>

</body> </html> By passing the scrollIntoView() method a parameter of true I am telling the method to scroll to the top of the element being scrolled too. The true parameter is however not needed as this is the default action performed by the method. If you want to scroll align to the bottom of the element pass a parameter of false to the scrollIntoView() method.

Chapter 6 - Element Node Inline Styles 6.1 Style Attribute (aka element inline CSS properties) Overview Every HTML element has a style attribute that can be used to inline CSS properties specific to the element. In the code below I am accessing the style attribute of a <div> that contains several inline CSS properties. live code: http://jsfiddle.net/domenlightenment/A4Aph <!DOCTYPE html> <html lang="en"> <body> <div style="background-color:red;border:1px solid black;height:100px;width:100px;"></div> <script> var divStyle = document.querySelector('div').style; //logs CSSStyleDeclaration {0="background-color", ...} console.log(divStyle); </script> </body> </html> Notice in the code above that what is returned from the style property is a CSSStyleDeclaration object and not a string. Additionally note that only the elements inline styles (i.e. excluding the computed styles, computed styles being any styles that have cascaded from style sheets) are included in the CSSStyleDeclartion object. 6.2 Getting, setting, & removing individual inline CSS properties Inline CSS styles are individually represented as a property (i.e. object property) of the style object avaliabe on element node objects. This provides the interface for us to get, set, or remove individual CSS properties on an element by simply setting an objects property value. In the code below we set, get, and remove styles on a <div> by manipulating the properties of the style object. live code: http://jsfiddle.net/domenlightenment/xNT85 <!DOCTYPE html> <html lang="en"> <body> <div></div> <script> var divStyle = document.querySelector('div').style; //set divStyle.backgroundColor = 'red'; divStyle.border = '1px solid black'; divStyle.width = '100px'; divStyle.height = '100px'; //get console.log(divStyle.backgroundColor); console.log(divStyle.border); console.log(divStyle.width); console.log(divStyle.height); /*remove divStyle.backgroundColor = ''; divStyle.border = ''; divStyle.width = ''; divStyle.height = ''; */ </script> </body> </html> Notes The property names contained in the style object do not contain the normal hyphen that is used in CSS property names. The translation is pretty simple. Remove the hyphen and use camel case. (e.g. font-size = fontSize or background-image = backgroundImage). In the case where a css property name is a JavaScript keyword the javascript css property name is prefixed with "css" (e.g. float = cssFloat). Short hand properties are available as properties as well. So you can not only set margin, but also marginTop. Remember to include for any css property value that requires a unit of measure the appropriate unit (e.g. style.width = '300px'; not style.width = '300';). When a document is rendered in standards mode the unit of measure is require or it will be ignored. In quirksmode assumptions are made if not unit of measure is included. CSS Property JavaScript Property background background background-attachment backgroundAttachment background-color backgroundColor background-image backgroundImage background-position backgroundPosition background-repeat backgroundRepeat border border border-bottom borderBottom border-bottom-color borderBottomColor border-bottom-style borderBottomStyle border-bottom-width borderBottomWidth border-color borderColor border-left borderLeft border-left-color borderLeftColor border-left-style borderLeftStyle border-left-width borderLeftWidth border-right borderRight border-right-color borderRightColor border-right-style borderRightStyle border-right-width borderRightWidth border-style borderStyle border-top borderTop border-top-color borderTopColor border-top-style borderTopStyle border-top-width borderTopWidth border-width borderWidth clear clear clip clip color color cursor cursor display display filter filter font font font-family fontFamily font-size fontSize font-variant fontVariant font-weight fontWeight height height left left letter-spacing letterSpacing line-height lineHeight list-style listStyle list-style-image listStyleImage list-style-position listStylePosition list-style-type listStyleType margin margin margin-bottom marginBottom margin-left marginLeft margin-right marginRight margin-top marginTop overflow overflow padding padding padding-bottom paddingBottom padding-left paddingLeft padding-right paddingRight padding-top paddingTop page-break-after pageBreakAfter page-break-before pageBreakBefore position position float styleFloat text-align textAlign text-decoration textDecoration text-decoration: blink textDecorationBlink text-decoration: line-through textDecorationLineThrough text-decoration: none textDecorationNone tex