js: BOM, DOM
<html> <body> <div> hi </div> </body> </html> console.log(document.nodeName) console.log(document.tagName) console.log(document.body.nodeName) console.log(document.body.tagName) console.log(document.body.children[1].nodeName) console.log(document.body.children[1].tagName)
"#document" undefined "BODY" "BODY" "DIV" "DIV" - tagName only on Element nodes - nodeName on every node
<body> <a id="a" href="#hello">link</a> </body> for (let e of document.querySelectorAll('a[href]')) { console.log(e.getAttribute('href')) }
"#hello" - how to retrieve value of attr
console.log(location.href)
"https://cdpn.io/boomboom/v2/index.html" - part of BOM - current URL
4 main node types
- 12, but 4 are used most often 1. document - entry point into DOM 2. element nodes - HTML tags, the tree building blocks 3. text nodes 4. comments
BOM
- Browser Object Model - additional objects for things other than doc - navigator object for info on browser - location object for current URL - setTimeout, alert, prompt, etc
DOM
- Document Object Model - main entry point is 'document' object (e.g. document.head, document.body) - represents all page content as objects that can be modified - standard at: https://dom.spec.whatwg.org
travel element nodes
- HTML tag nodes, excluding comments and text - with 'Element' in prop name - props: parentElement, children, previousElementSibling, nextElementSibling, firstElementChild, lastElementChild
<div id="foobar">foobar</div> <ul id="peeps"> <li>John</li> <li>Pete <ul> <li>Ed</li> <li>Cris</li> </ul> </li> </ul> const peeps2 = peeps.cloneNode(true) foobar.prepend(peeps2)
- deep clone a node and insert - cloneNode(false) is shallow clone
4 primary DOM node classes
- define node functionality 1. EventTarget - abstract root class 2. Node - abstract base for parentNode, nextSibling, ChildNode, etc 3. Element - base class for DOM elements for nextElementSibling, children, etc 4. HTMLElement - base class for HTML elements
let date = new Date(Date.now() - 1); date = date.toUTCString(); document.cookie = "user=fred; expires=" + date; console.log(document.cookie);
- deletes cookie user - needs to specify user=fred
methods to work with attributes
- elem.hasAttribute(name) - to check for existence - elem.getAttribute(name) - to get the value - elem.setAttribute(name, value) - to set the value - elem.removeAttribute(name) - to remove the attribute - elem.attributes is a collection of all attributes
Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight )
- for historical reasons, this is the best way to get the height of a document - same for Width - includes any scroll bars
<html> <body> <div id="foobar">hi</div> </body> </html> foobar.hidden = true
- hides the element - same as style="display:none"
getElementsBy*
- legacy query methods - e.g. elem.getElementsByClassName(class) - use querySelector() instead
when script runs, document.body
- might be null - hasn't been loaded yet - happens if script in <head>
methods for node insertion and removal
- node.append(...nodes or strings) - insert into node, at the end, - node.prepend(...nodes or strings) - insert into node, at the beginning, - node.before(...nodes or strings) - insert right before node, - node.after(...nodes or strings) - insert right after node, - node.replaceWith(...nodes or strings) - replace node.
const div = document.createElement("div"); div.className = "alert"; div.innerHTML = "yo mama"; foobar.append(div); setTimeout(() => div.remove(), 2000)
- remove element after 2 sec - could also just set innerHTML
.blue { background-color: blue } foobar.className = "blue"
- set background by using CSS class - must use 'className', not just 'class' - overwrites all classes; instead, use classList.add()
coordinate system: relative to document
- similar to position:absolute - calculated from document top/left edge
coordinate system: relative to window
- similar to position:fixed - calculated from window top/left edge
innerHTML and scripts
- they don't execute
topmost tree nodes
<html> = document.documentElement <body> = document.body <head> = document.head - all available as doc props
<html> <body> <ul> <li id="john">John</li> <li>Pete</li> </ul> </body> </html> console.log(document.querySelector('ul > li:last-child'))
<li>Pete</li> - use innerHTML to get to just Pete - retrieves first item found, not list
Element.prototype.bar = () => console.log('bar') document.body.bar() document.body.children[0].bar()
bar bar - add props to prototype
<body foo="bar"> ... </body> console.log(document.body.getAttribute('foo')) document.body.foo = "hey" console.log(document.body.getAttribute('foo')) console.log(document.body.foo)
bar bar hey - when HTML parsed, non-standard props are stored in special area - use getAtttribute() and setAttribute()
<html> <body> <div>hi</div></body><ul> <li>John</li> <li>Pete</li> </ul> </html> how to get to 'hi' by object reference
document.body.children[0]
<html> <body> <div>hi</div> <ul> <li id="john">John</li> <li>Pete</li> </ul> </body> </html> how to get to 'Pete' by object reference
document.body.children[1].children[1]
how to delete cookies
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - overwrite with expiry date in past
document.foo = () => console.log('foo') document.foo()
foo - add props to document obj
<html> <body> <div id="foobar"> hi </div> </body> </html> console.log(foobar.textContent) foobar.textContent = "bye" console.log(foobar.textContent)
hi bye - gets item by referencing id - and prints only text minus tags - safer way to change text
<body data-foo="yoyo"> ... </body> console.log(document.body.dataset.foo)
yoyo - prefix attr with data- to put into dataset - special attrs
<html> <body> <ul> <li id="john">John</li> <li id="pete">Pete</li> </ul> </body> </html> console.log(document.querySelector('ul > li:last-child').matches('li[id="pete"]') )
true - tests if matches CSS selector
elem.children[0] === elem.firstElementChild
true - firstChild is fast access to first element in childNodes array
elem.children[elem.children.length - 1] === elem.lastElementChild
true - lastChild is fast access to last element in childNodes array
console.log(document instanceof HTMLDocument); console.log(Object.getPrototypeOf(HTMLDocument)); console.log(Object.getPrototypeOf(Document)); console.log(Object.getPrototypeOf(Node));
true function Document() { [native code] } function Node() { [native code] } function EventTarget() { [native code]
<html> <body> <ul> <li id="john">John</li> <li>Pete</li> </ul> </body> </html> const list = document.querySelectorAll('ul > li:last-child') for (let elem of list) { console.log(elem.innerHTML) }
Pete - return all elements matching CSS selector
<body> <div data-widget-name="mama">yo</div> </body> let e = document.querySelector('[data-widget-name]') console.log(e, e.dataset.widgetName)
"<div data-widget-name='mama'>yo</div>" "mama" - note how attr name is in '[]' because not tag name - value is in the element's dataset prop because prefixed with data- - and it is camel cased
console.log(navigator.userAgent)
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" - part of BOM - info on browser
<html> <body> <a id="a" href="#hello">link</a> </body> </html> console.log(a.href) console.log(a.getAttribute('href'))
"https://cdpn.io/boomboom/v2/index.html?editors=1111&key=iFrameKey-3148f8c5-b81b-7391-70a8-db8f2e298acf#hello" "#hello" - standard props are available directly, but may be modified - full URL for anchor tag - use getAttribute to get unmodified value
let date = new Date(Date.now() + 86400e3); date = date.toUTCString(); document.cookie = "user=fred; expires=" + date; console.log(document.cookie);
"name=ed; user=fred" - expire in a day
DOM node class: EventTarget > Node
- abstract, never created - base for DOM nodes for tree access - parentNode, nextSibling, childNodes, etc - derivatives: Text, Element and Comment
DOM node class: EventTarget
- abstract, never created - base for all DOM nodes for event handling - parent is Object
.blue { background-color: blue } .dotted { outline-style: dotted } foobar.classList.add('blue') foobar.classList.add('dotted')
- add classes to list - also, remove(), toggle(), contains()
document.write('<h3>foo</h3>') document.writeln('<h3>foo</h3>')
- add to end of document - also, with newline - old school - don't use! - replaces all HTML and has perf issues - instead, get an element and set innerHTML
travel DOM nodes
- all nodes, including comments and text - props: parentNode, childNodes, previousSibling, nextSibling, firstChild, lastChild
console.log(document.body instanceof HTMLBodyElement); console.log(document.body instanceof HTMLElement); console.log(document.body instanceof Element); console.log(document.body instanceof Node); console.log(document.body instanceof EventTarget); console.log(document.body instanceof Object);
- all true
in DOM, every HTML tag is
- an object - nested tags are children
<div id="blah">foobar</div> blah.after('beforebegin', 'blah')
- anything with 'id' can be referenced by that value - unless there's another, then not defined
HTML comments
- are part of the DOM - everything in HTML are part of DOM - browser will fix up elements (e.g. add closing tags if needed, add TBODY to table)
elem.children
- array (like) structure of children - use for..of to iterate (NOT for..in) - ONLY contains elements, no blank nodes or comments - immutable (use other methods to mutate)
elem.childNodes
- array (like) structure of children - use for..of to iterate (NOT for..in) - includes blank nodes, comments - immutable (use other methods to mutate)
DOM node class: EventTarget > Node > Element
- base for DOM element tree access - nextElementSibling, children, getElementsByTagName, querySelector - derivatives: SVGElement, XMLElement, HTMLElement
DOM node class: EventTarget > Node > Element > HTMLElement
- base for HTML elements - derivatives: HTMLInputElement (for <input>), HTMLBodyElement (for <body>), HTMLAnchorElement (for <a>) ...
<button data-toggle-id="subscribe">Show form</button> <form id="subscribe" hidden> Email: <input type="email"></form> document.addEventListener('click', ev => { let id = event.target.dataset.toggleId if (!id) return let elem = document.getElementById(id) elem.hidden = !elem.hidden })
- behavior pattern - adds show/hide via data-toggle-id attr - via addEventListener on document via dataset prop
const div = document.createElement('div') div.innerHTML = 'yo mama' document.body.append(div)
- create and append new element
methods to create new nodes
- document.createElement(tag) - creates an element with the given tag, - document.createTextNode(value) - creates a text node (rarely used), - elem.cloneNode(deep) - clones the element, if deep==true then with all descendants.
<html> <body> <div>hi</div> <ul> <li id="john">John</li> <li>Pete</li> </ul> </body> </html> how to get to 'John' and set background
- document.querySelector('li[id=john]').style.background = "yellow" (preferred) - document.getElementById('john').style.background = 'red' - john.style.background = "red"
rarely used methods
- elem.insertAdjacentHTML(where, html) - elem.insertAdjacentText - elem.insertAdjacentElement
previousElementSibling, nextElementSibling
- move between siblings/peers - e.g. head, body are peers
if elem is arbitrary DOM node, is it true that elem.children[0].previousSibling is always null?
- no - previousSibling could be a text or comment node - elem.firstChild.previousSibling would always be null - if there are no children at all, this will error
elem.nodeType
- old school way to get node type 1 = element 3 = text 9 = document
"old school" methods
- parent.appendChild(node) - parent.insertBefore(node, nextSibling) - parent.removeChild(node) - parent.replaceChild(newElem, node)
element = document.querySelector(selectors) document.querySelector(".myclass") document.querySelector("div.user-panel.main input[name='login']")
- preferred way to get to HTMLElement - returns first element that matches if found, null otherwise - uses depth-first pre-order traversal starting with first element in document's markup
elementList = parentNode.querySelectorAll(selectors) document.querySelectorAll("p") document.querySelectorAll("div.note, div.alert") document.querySelectorAll('a[href]')
- preferred way to get to NodeList containing one Element object for each that matches
document.querySelectorAll(':hover')
- return list of elements that pointer is over, from outermost HTML to innermost
foobar.style.background = "yellow" foobar.style = "background: red; outline: dotted;" foobar.style.cssText = "background: red; outline: dotted;"
- set background by setting style - object of camelCased styles - prefer using classes
if elem is arbitrary DOM node, is it true that elem.lastChild.nextSibling is always null?
- yes - lastChild is always the last, so no nextSibling - if there are no children at all, this will error
window object roles
1. global object 2. represents browser window with methods to control it
6 main methods to search for nodes
1. querySelector (preferred) 2. querySelectorAll (preferred) 3. getElementById (legacy, prefer querySelector) 4. getElementsByName (legacy, prefer querySelectorAll) 5. getElementsByTagName (legacy, prefer querySelectorAll) 6. getElementsByClassName (legacy, prefer querySelectorAll)
<div id="elem1"></div> <div id="elem2"></div> <div id="elem3"></div> let text = "<b>text</b>"; elem1.append(document.createTextNode(text)); elem2.innerHTML = text; elem3.textContent = text; // which do same?
<b>text</b> text <b>text</b> - first and third do same - second interprets HTML
<html> <body> <div> hi </div> </body> </html> console.log(document.body.children[1].outerHTML) document.body.children[1].outerHTML = "<div>foo" console.log(document.body.children[1].outerHTML)
<div>hi</div> <div>foo</div> - HTML content of node including tag - element node is replaced entirely, and fixed - any further changes to node require a re-query
<html> <body> <ul class="foo"> <li id="john">John</li> <li id="pete"> Pete </li> </ul> </body> </html> const elem = document.getElementById('john') console.log(elem.closest('.foo'))
<ul class="foo">...</ul> - walks parents till it finds match - element itself is included in search
<body> <ul> <li>John</li> <li>Pete <ul> <li>Ed</li> <li>Cris</li></ul> </li> </ul> </body> for (let li of document.querySelectorAll('li')) { console.log(li.firstChild.data.trim()) } console.log(document.querySelectorAll('li').length)
John Pete Ed Cris - gets all <li>, even nested - print out text and length
<html> <body> <div> hi </div> </body> </html> console.log(document.body.children[1].innerHTML) document.body.children[1].innerHTML = "<b>foo" console.log(document.body.children[1].innerHTML)
hi <b>foo</b> - HTML content of node excluding tag - browser fixes errors - only valid for element nodes