Before we do anything with a page, you and I need to have a talk about something very important: the Document Object Model. There are two purposes to the DOM: providing JavaScript with a map of all the elements on our page, and providing us with a set of methods for accessing those elements, their attributes, and their contents.
The “object” part of Document Object Model should make a lot more sense now than it did the first time the DOM came up, though: the DOM is a representation of a web page in the form of an object, made up of properties that represent each of the document’s child elements and subproperties representing each of those elements’ child elements, and so on. It’s objects all the way down.
window
: The Global Context#section2
Everything we do with JavaScript falls within the scope of a single object: window
. The window
object represents, predictably enough, the entire browser window. It contains the entire DOM, as well as—and this is the tricky part—the whole of JavaScript.
When we first talked about variable scope, we touched on the concept of there being “global” and “local” scopes, meaning that a variable could be made available either to every part of our scripts or to their enclosing function alone.
The window
object is that global scope. All of the functions and methods built into JavaScript are built off of the window
object. We don’t have to reference window
constantly, of course, or you would’ve seen a lot of it before now—since window
is the global scope, JavaScript checks window
for any variables we haven’t defined ourselves. In fact, the console
object that you’ve hopefully come to know and love is a method of the window
object:
window.console.log
function log() { [native code] }
It’s hard to visualize globally vs. locally scoped variables before knowing about window
, but much easier after: when we introduce a variable to the global scope, we’re making it a property of window
—and since we don’t explicitly have to reference window
whenever we’re accessing one of its properties or methods, we can call that variable anywhere in our scripts by just using its identifier. When we access an identifier, what we’re really doing is this:
function ourFunction() { var localVar = "I’m local."; globalVar = "I’m global."; return "I’m global too!"; };
undefinedwindow.ourFunction();
I’m global too!window.localVar;
undefinedwindow.globalVar;
I’m global.
The DOM’s entire representation of the page is a property of window
: specifically, window.document
. Just entering window.document
in your developer console will return all of the markup on the current page in one enormous string, which isn’t particularly useful—but everything on the page can be accessed as subproperties of window.document
the exact same way. Remember that we don’t need to specify window
in order to access its document
property—window
is the only game in town, after all.
document.head
<head>...</head>
document.body
<body>...</body>
Those two properties are themselves objects that contain properties that are objects, and so on down the chain. (“Everything is an object, kinda.”)
Using the DOM#section3
The objects in window.document
make up JavaScript’s map of the document, but it isn’t terribly useful for us—at least, not when we’re trying to access DOM nodes the way we’d access any other object. Winding our way through the document
object manually would be a huge headache for us, and that means our scripts would completely fall apart as soon as any markup changed.
But window.document
isn’t just a representation of the page; it also provides us with a smarter API for accessing that information. For instance, if we want to find every p
element on a page, we don’t have to write out a string of property keys—we use a helper method built into document
that gathers them all into an array-like list for us. Open up any site you want—so long as it likely has a paragraph element or two in it—and try this out in your console:
document.getElementsByTagName( "p" );
[<p>...</p>, <p>...</p>, <p>...</p>, <p>...</p>]
Since we’re dealing with such familiar data types, we already have some idea how to work with them:
var paragraphs = document.getElementsByTagName( "p" );
undefined
paragraphs.length
4
paragraphs[ 0 ];
<p>...</p>
But DOM methods don’t give us arrays, strictly speaking. Methods like getElementsByTagName
return “node lists,” which behave a lot like arrays. Each item in a nodeList
refers to an individual node in the DOM—like a p
or a div
—and will come with a number of DOM-specific methods built in. For example, the innerHTML
method will return any markup a node contains—elements, text, and so on—as a string:
var paragraphs = document.getElementsByTagName( "p" ),
lastIndex = paragraphs.length – 1, /* Use the length of the `paragraphs` node list minus 1 (because of zero-indexing) to get the last paragraph on the page */
lastParagraph = paragraphs[ lastIndex ];
lastParagraph.innerHTML;
And that’s how I spent my summer vacation.

The same way these methods give us access to information on the rendered page, they allow us to alter that information, as well. For example, the innerHTML
method does this the same way we’d change the value of any other object: a single equals sign, followed by the new value.
var paragraphs = document.getElementsByTagName( "p" ),
firstParagraph = paragraphs[ 0 ];
firstParagraph.innerHTML = "Listen up, chumps:";
"Listen up, chumps:"
JavaScript’s map of the DOM works both ways: document
is updated whenever any markup changes, and our markup is updated whenever anything within document
changes (Fig 5.1).
Likewise, the DOM API gives us a number of methods for creating, adding, and removing elements. They’re all more or less spelled out in plain English, so even though things can seem a little verbose, it isn’t too hard to break down.
DOM Scripting#section4
Before we get started, let’s abandon our developer console for a bit. Ages ago now, we walked through setting up a bare-bones HTML template that pulls in a remote script, and we’re going to revisit that setup now. Between the knowledge you’ve gained about JavaScript so far and an introduction to the DOM, we’re done with just telling our console to parrot things back to us—it’s time to build something.
We’re going to add a “cut” to an index page full of text—a teaser paragraph followed by a link to reveal the full text. We’re not going to make the user navigate to another page, though. Instead, we’ll use JavaScript to show the full text on the same page.
Let’s start by setting up an HTML document that links out to an external stylesheet and external script file—nothing fancy. Both our stylesheet and script files are empty with .css and .js extensions, for now—I like to keep my CSS in a /css subdirectory and my JavaScript in a /js subdirectory, but do whatever makes you most comfortable.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<script src="js/script.js"></script>
</body>
</html>
We’re going to populate that page with several paragraphs of text. Any ol’ text you can find laying around will do, including—with apologies to the content strategists in the audience—a little old-fashioned lorem ipsum. We’re just mocking up a quick article page, like a blog post.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<h1>JavaScript for Web Designers</h1>
<p>In all fairness, I should start this book with an apology—not to you, reader, though I don’t doubt that I’ll owe you at least one by the time we get to the end. I owe JavaScript a number of apologies for the things I’ve said to it during the early years of my career, some of which were strong enough to etch glass.</p>
<p>This is my not-so-subtle way of saying that JavaScript can be a tricky thing to learn.</p>
[ … ]
<script src="js/script.js"></script>
</body>
</html>
Feel free to open up the stylesheet and play with the typography, but don’t get too distracted. We’ll need to write a little CSS later, but for now: we’ve got scripting to do.
We can break this script down into a few discrete tasks: we need to add a Read More link to the first paragraph, we need to hide all the p
elements apart from the first one, and we need to reveal those hidden elements when the user interacts with the Read More link.
We’ll start by adding that Read More link to the end of the first paragraph. Open up your still-empty script.js file and enter the following:
var newLink = document.createElement( "a" );
First, we’re intializing the variable newLink
, which uses document.createElement( "a" )
to—just like it says on the tin—create a new a
element. This element doesn’t really exist anywhere yet—to get it to appear on the page we’ll need to add it manually. First, though, <a></a>
without any attributes or contents isn’t very useful. Before adding it to the page, let’s populate it with whatever information it needs.
We could do this after adding the link to the DOM, of course, but there’s no sense in making multiple updates to the element on the page instead of one update that adds the final result—doing all the work on that element before dropping it into the page helps keep our code predictable.
Making a single trip to the DOM whenever possible is also better for performance—but performance micro-optimization is easy to obsess over. As you’ve seen, JavaScript frequently offers us multiple ways to do the same thing, and one of those methods may technically outperform the other. This invariably leads to “excessively clever” code—convoluted loops that require in-person explanations to make any sense at all, just for the sake of shaving off precious picoseconds of load time. I’ve done it; I still catch myself doing it; but you should try not to. So while making as few round-trips to the DOM as possible is a good habit to be in for the sake of performance, the main reason is that it keeps our code readable and predictable. By only making trips to the DOM when we really need to, we avoid repeating ourselves and we make our interaction points with the DOM more obvious for future maintainers of our scripts.
So. Back to our empty, attribute-less <a></a>
floating in the JavaScript ether, totally independent of our document.
Now we can use two other DOM interfaces to make that link more useful: setAttribute
to give it attributes, and innerHTML
to populate it with text. These have a slightly different syntax. We can just assign a string using innerHTML
, the way we’d assign a value to any other object. setAttribute
, on the other hand, expects two arguments: the attribute and the value we want for that attribute, in that order. Since we don’t actually plan to have this link go anywhere, we’ll just set a hash as the href
—a link to the page you’re already on.
var newLink = document.createElement( "a" );
newLink.setAttribute( "href", "#" );
newLink.innerHTML = "Read more";
You’ll notice we’re using these interfaces on our stored reference to the element instead of on document
itself. All the DOM’s nodes have access to methods like the ones we’re using here—we only use document.getElementsByTagName( "p" )
because we want to get all the paragraph elements in the document. If we only wanted to get all the paragraph elements inside a certain div
, we could do the same thing with a reference to that div
—something like ourSpecificDiv.getElementsByTagName( "p" );
. And since we’ll want to set the href
attribute and the inner HTML of the link we’ve created, we reference these properties using newLink.setAttribute
and newLink.innerHTML
.
Next: we want this link to come at the end of our first paragraph, so our script will need a way to reference that first paragraph. We already know that document.getElementsByTagName( "p" )
gives us a node list of all the paragraphs in the page. Since node lists behave like arrays, we can reference the first item in the node list one by using the index 0
.
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.innerHTML = "Read more";
For the sake of keeping our code readable, it’s a good idea to initialize our variables up at the top of a script—even if only by initializing them as undefined
(by giving them an identifier but no value)—if we plan to assign them a value later on. This way we know all the identifiers in play.
So now we have everything we need in order to append a link to the end of the first paragraph: the element that we want to append (newLink
) and the element we want to append it to (firstParagraph
).
One of the built-in methods on all DOM nodes is appendChild
, which—as the name implies—allows us to append a child element to that DOM node. We’ll call that appendChild
method on our saved reference to the first paragraph in the document, passing it newLink
as an argument.
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.innerHTML = "Read more";
firstParagraph.appendChild( newLink );
Now—finally—we have something we can point at when we reload the page. If everything has gone according to plan, you’ll now have a Read More link at the end of the first paragraph on the page. If everything hasn’t gone according to plan—because of a misplaced semicolon or mismatched parentheses, for example—your developer console will give you a heads-up that something has gone wrong, so be sure to keep it open.
Pretty close, but a little janky-looking—our link is crashing into the paragraph above it, since that link is display: inline
by default (Fig 5.2).

We have a couple of options for dealing with this: I won’t get into all the various syntaxes here, but the DOM also gives us access to styling information about elements—though, in its most basic form, it will only allow us to read and change styling information associated with a style
attribute. Just to get a feel for how that works, let’s change the link to display: inline-block
and add a few pixels of margin to the left side, so it isn’t colliding with our text. Just like setting attributes, we’ll do this before we add the link to the page:
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.innerHTML = "Read more";
newLink.style.display = "inline-block";
newLink.style.marginLeft = "10px";
firstParagraph.appendChild( newLink );
Well, adding those lines worked, but not without a couple of catches. First, let’s talk about that syntax (Fig 5.3).

Remember that identifiers can’t contain hyphens, and since everything is an object (sort of), the DOM references styles in object format as well. Any CSS property that contains a hyphen instead gets camel-cased: margin-left
becomes marginLeft
, border-radius-top-left
becomes borderRadiusTopLeft
, and so on. Since the value we set for those properties is a string, however, hyphens are just fine. A little awkward and one more thing to remember, but this is manageable enough—certainly no reason to avoid styling in JavaScript, if the situation makes it absolutely necessary.
A better reason to avoid styling in JavaScript is to maintain a separation of behavior and presentation. JavaScript is our “behavioral” layer the way CSS is our “presentational” layer, and seldom the twain should meet. Changing styles on a page shouldn’t mean rooting through line after line of functions and variables, the same way we wouldn’t want to bury styles in our markup. The people who might end up maintaining the styles for the site may not be completely comfortable editing JavaScript—and since changing styles in JavaScript means we’re indirectly adding styles via style
attributes, whatever we write in a script is going to override the contents of a stylesheet by default.
We can maintain that separation of concerns by instead using setAttribute
again to give our link a class. So, let’s scratch out those two styling lines and add one setting a class in their place.
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.setAttribute( "class", "more-link" );
newLink.innerHTML = "Read more";
firstParagraph.appendChild( newLink );
Now we can style .more-link
in our stylesheets as usual:
.more-link {
display: inline-block;
margin-left: 10px;
}
Much better (Fig 5.4). It’s worth keeping in mind for the future that using setAttribute
this way on a node in the DOM would mean overwriting any classes already on the element, but that’s not a concern where we’re putting this element together from scratch.

Now we’re ready to move on to the second item on our to-do list: hiding all the other paragraphs.
Since we’ve made changes to code we know already worked, be sure to reload the page to make sure everything is still working as expected. We don’t want to introduce a bug here and continue on writing code, or we’ll eventually get stuck digging back through all the changes we made. If everything has gone according to plan, the page should look the same when we reload it now.
Now we have a list of all the paragraphs on the page, and we need to act on each of them. We need a loop—and since we’re iterating over an array-like node list, we need a for
loop. Just to make sure we have our loop in order, we’ll log each paragraph to the console before we go any further:
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.setAttribute( "class", "more-link" );
newLink.innerHTML = "Read more";
for( var i = 0; i < allParagraphs.length; i++ ) {
console.log( allParagraphs[ i ] );
}
firstParagraph.appendChild( newLink );
Your Read More link should still be kicking around in the first paragraph as usual, and your console should be rich with filler text (Fig 5.5).

Now we have to hide those paragraphs with display: none
, and we have a couple of options: we could use a class the way we did before, but it wouldn’t be a terrible idea to use styles in JavaScript for this. We’re controlling all the hiding and showing from our script, and there’s no chance we’ll want that behavior to be overridden by something in a stylesheet. In this case, it makes sense to use the DOM’s built-in methods for applying styles:
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.setAttribute( "class", "more-link" );
newLink.innerHTML = "Read more";
for( var i = 0; i < allParagraphs.length; i++ ) {
allParagraphs[ i ].style.display = "none";
}
firstParagraph.appendChild( newLink );
If we reload the page now, everything is gone: our JavaScript loops through the entire list of paragraphs and hides them all. We need to make an exception for the first paragraph, and that means conditional logic—an if
statement, and the i
variable gives us an easy value to check against:
var newLink = document.createElement( "a" );
var allParagraphs = document.getElementsByTagName( "p" );
var firstParagraph = allParagraphs[ 0 ];
newLink.setAttribute( "href", "#" );
newLink.setAttribute( "class", "more-link" );
newLink.innerHTML = "Read more";
for( var i = 0; i < allParagraphs.length; i++ ) {
if( i === 0 ) {
continue;
}
allParagraphs[ i ].style.display = "none";
}
firstParagraph.appendChild( newLink );
If this is the first time through of the loop, the continue
keyword skips the rest of the current iteration and then—unlike if we’d used break
—the loop continues on to the next iteration.
If you reload the page now, we’ll have a single paragraph with a Read More link at the end, but all the others will be hidden. Things are looking good so far—and if things aren’t looking quite so good for you, double-check your console to make sure nothing is amiss.
Now that you’ve got a solid grounding in the DOM, let’s really dig in and see where to take it from here.
Want to read more?#section5
The rest of this chapter (even more than you just read!) goes even deeper—and that’s only one chapter out of Mat’s hands-on, help-you-with-your-current-project guide. Check out the rest of JavaScript for Web Designers at A Book Apart.
How/why is this pertinent to a designer? Seems firmly in the developer’s court.
Thank you very much for this read – I think as a designer nowadays i should also know how to work with JS to achieve a better understanding in how i should create my layout. I’ve met a lot of ‘classic’ designers who failed in creating a solid layout because they didn’t know whats possible and whats not… (hope you understood my english)
some developer *are* designer, Emile
*developers/designers
A designer should know enough theory to understand roughly what is possible…but knowing how to actually achieve it is developing surely?
Also, this reads as an extra long advert if I’m being brutally honest.
not bad!
Hey Mat,
Thanks for sharing Knowledge!
This is awesome write for beginners to get a clear idea of how JavaScript makes impact on DOM. I hope we will get more great writes from you.
Have a great day ahead 🙂
This piece ended rather abruptly. Getting to a point where you can reveal the hidden paragraphs wouldn’t have taken much more time. Leaving that to the book does not sit well.
I realize this is a teaser piece but your content should at least reflect your title. So far, it just seems like a Javascript 101 tutorial and I don’t see any non-dev designers wanting to learn more in a hurry, especially when the content is so technical. However, still a pretty good read though… 🙂
Great article – I just spotted a minor issue, when console.log in the for loop it won’t show the “Read more” link in the first paragraph as it hasn’t been appended yet – you append the “Read more” link underneath the for loop.
Moving the for loop under the append would resolve it.