(D)ocument (O)bject (M)odel:
the data structure of web pages.
<html>
<head><title>wow</title></head>
<body>
<h1 id="top">very tree</h1>
<div class="msg">
so <span>tall</span>
</div>
</body>
</html>
- document.head ->
<head>...</head>
- document.body ->
<body>...</body>
Each html tag is called an element.
When you have an element reference, you get:
elem.childNodes
- an array of childrenelem.parentNode
- the parent of the current element
document.head.childNodes[0]
=== <title>wow</title>
document.body.childNodes[0]
=== <h1 id="top">very tree</h1>
document.body.childNodes[1]
=== <div class="msg">so <span>tall</span></div>
document.body.childNodes[1].childNodes[0]
=== <span>tall</span>
Using document.body.[...]
to fetch every element is not
very flexible!
Instead, we can fetch elements with a css selector:
document.querySelector('h1')
=== <h1>very tree</h1>
Select an element by a tag name:
span
-><span>tall</span>
Select an element by its class name:
.msg
-><div class="msg">...</div>
Select an element by its id
:
#top
-><h1 id="top">very tree</h1>
Select an element by attribute:
[id="top"]
-><h1 id="top">very tree</h1>
You can combine id, class, attribute, and element selectors.
All of the constraints must match:
document.querySelector('h1#cool.row[x="5"]')
will match:
<h1 id="cool" class="row" x="5">whatever</h1>
but not:
<h1 id="sweet" class="row" x="5">hey</h1>
-
.msg span
-><span>tall</span>
-
#top span
-> null (matches nothing)
document.querySelector()
-> first matching elementdocument.querySelectorAll()
-> all matching elements
If you have an element, you can also call .querySelector()
on that element to query its descendents:
elem.querySelector()
-> first matching elementelem.querySelectorAll()
-> all matching elements
document.getElementById()
document.getElementsByClassName()
once you have an element reference, you can modify the contents in plenty of ways:
- set the inner text or html
- add and remove children
- set and remove attributes
var elem = document.querySelector('.msg');
elem.textContent = 'purr cats <3 <3 <3'
With textContent
you don't need to worry about elements
being interpreted as html.
var elem = document.querySelector('.msg');
elem.innerHTML = '<h1>wow</h1>'
var div = document.createElement('div');
var div = document.createElement('div');
div.innerHTML = '<b>wow such inner</b>';
var div = document.createElement('div');
var b = document.createElement('b');
b.textContent = 'wowsers';
div.appendChild(b);
document.body.appendChild(b);
suppose we have some html:
<html>
<body>
<div>
cool cool
<span>get rid of me</span>
</div>
</body>
</html>
we can get rid of the inner span by fetching references to
the span and its parent, then on the parent we can call
.removeChild()
:
var div = document.querySelector('div');
var span = div.querySelector('span');
div.removeChild(span);
and now the html will be:
<html>
<body>
<div>
cool cool
</div>
</body>
</html>
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('value', 'wow');
document.body.appendChild(input);
and now the body will have an input
element with
type="text"
and value="wow"
:
<html>
<body>
<input type="text" value="wow">
</body>
</html>
We can also insert elements before another element. Given this html:
<html>
<body>
<ul>
<li class="zero">zero</li>
<li class="one">one</li>
<li class="three">three</li>
</ul>
</body>
</html>
We can insert an element before <li>three</li>
:
var ul = document.querySelector('ul');
var three = ul.querySelector('.three');
var two = document.createElement('li');
two.textContent = 'two';
two.setAttribute('class', 'two');
ul.insertBefore(two, three);
and now the html will be:
<html>
<body>
<ul>
<li class="zero">zero</li>
<li class="one">one</li>
<li class="two">two</li>
<li class="three">three</li>
</ul>
</body>
</html>
We can adjust css on the fly with .style
.
Using the html from the previous example, we can do:
var zero = document.querySelector('.zero');
zero.style.color = 'purple';
and now the first element in the list will have purple text.
We can respond to page actions by using
.addEventListener()
. With this html:
<body>
<button>wow</button>
</body>
we can insert an element when somebody clicks the button:
var button = document.querySelector('button');
button.addEventListener('click', function (ev) {
var msg = document.createElement('div');
msg.textContent = new Date().toISOString();
document.body.appendChild(msg);
});
Sometimes events have default actions, like forms will send
a GET or POST request when the submit button is clicked. You
can override these actions by calling preventDefault()
on
the event object.
Given this html:
<form>
<input type="text" name="cool">
<input type="submit">
</form>
We can capture the submit event and prevent the default action:
var form = document.querySelector('form');
form.addEventListener('submit', function (ev) {
ev.preventDefault();
form.querySelector('[name=cool]')
});
Suppose we have some html:
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
There are two ways we can register a "click" event for each
li
element.
We can register a "click" listener on each element:
var items = document.querySelectorAll('ul li');
for (var i = 0; i < items.length; i++) (function (elem) {
elem.addEventListener('click', function (ev) {
ev.target.style.fontWeight = 'bold';
});
})(items[i]);
This is kind of messy. We need to use a closure because the
for loop modifies the i
index before the events fire so it
is always the last index.
A simpler way to register a listener for many elements is to
register a listener on the parent ul
and check the
ev.target
:
var ul = document.querySelector('ul');
ul.addEventListener('click', function (ev) {
ev.target.style.fontWeight = 'bold';
});
We can make http requests with the DOM too!
It's really awkward to do this with raw javascript:
var xhr = new XMLHttpRequest;
xhr.addEventListener('readystatechange', function (ev) {
if (xhr.readyState === 4) {
console.log('body=', xhr.responseText);
}
});
xhr.open('POST', '/', true);
xhr.send('foo=bar&x=5');
Luckily, there is a package on npm we can use. First do:
npm install xhr
then in your browser code you can do:
var xhr = require('xhr');
var opts = {
method: 'POST',
uri: '/',
body: 'foo=bar&x=5'
};
xhr(opts, function (err, res, body) {
console.log('body=', body);
});
or with the help of the built-in querystring module:
var xhr = require('xhr');
var qs = require('querystring');
var opts = {
method: 'POST',
uri: '/',
body: qs.stringify({ foo: 'bar', x: 5 })
};
xhr(opts, function (err, res, body) {
console.log('body=', body);
});
If you want to use require()
to load modules from npm,
you'll need to use a tool like
browserify:
$ browserify browser.js > bundle.js
then in your html:
<script src="bundle.js"></script>