Note: The contents of this post are intended to be introductory and does not include use of any libraries like jQuery.
JavaScript is the language we use to interact with the contents of a page, served on a browser or some other platform. What this means is you have the power to manipulate everything you see on the page. As we all know, with great power comes great understanding of DOM trees and knowing how to use selectors to get the DOM elements and handling events and, oh, responsibility.
We can access the page contents through the Document Object Model (DOM). The DOM represents the page as a document
in the form of a tree. This tree is made up of nodes which are just objects representing HTML tags such as div
(referred to as elements
) or just text. These objects have many properties and methods we can use for manipulation.
In our code, we can use console.dir()
in order to interact with the object and instead of its literal HTML you get from console.log()
.
Then we can fiddle around with the browser's console and view the many properties and methods of a node inside the browser:
right click on the page → inspect → console tab
the tip of the element iceberg
Let's take a look at some of the useful properties and methods.
element.children
returns an array-like object of the element nodes nested under this element.element.childNodes
returns an array-like object and includes any nodes including text that is not wrapped in HTML tags.
<div id="example">
Just text
<p>Text in a p tag</p>
</div>
const el = document.getElementById('example')
el.children
// - [0] p
el.childNodes
// - [0] text
// - [1] p
So which do you use? Generally you would probably use element.childrenmore as you only want to manipulate elements. However, whenever you need to access content that is not a node element (not wrapped in an HTML tag) then you would use element.childNodes.
Keep in mind that any nested node will also have a parent
node!
element.textContent
returns all the text content in the element.element.innerText
which returns only visible text and would exclude text hidden by css.While this works well for changing text directly inside an element, if you were to try to change the textContent of an element with nested elements, that would affect all of the nested content. Using the same HTML example as above:
const el = document.getElementById('example')
el.textContent = "New text"
<!-- Result: -->
<div id="example">
New Text
</div>
This is where using element.childNodes would come in handy, since we can access just the text without changing the other child nodes.
const el = document.getElementById('example')
el.childNodes
// -[0] text
// - textContent: "Just text"
// -[1] p
// - textContent: "Text in a p tag"
el.childNodes[0].textContent = "New text"
<!-- Result: -->
<div id="example">
New Text
<p>Text in a p tag</p>
</div>
element.classList.add("hidden")
or removed from with element.classList.remove("hidden")
.For other elements with properties specific to that element, you can access the properties directly:
img
elementsa
elementsYou can also generally modify any attributes (including the above).
Examples of other attributes:
name
title
disabled
data-*
Events can be attached to elements (the EventTarget) via addEventListener()
by listening for certain events and calling a callback function when the event is fired.
The callback functions will pass in an event
parameter for that event with a lot of specific properties for the events. All of the event objects have a target
property, which will give you the element
on which the event was fired. It will be very useful to have this object reference since you can then access its methods and properties discussed above.
There are LOTS of DOM events but I'll just go over the basic ones (excluding deprecated events) used for creating interactive UI.
You can check to see if the user pressed or released a specific key by comparing the keycode value. It is recommended to use e.keyCode
since e.code
can vary across browers. Keycode.info is a great resource for quickly getting keycodes.
element.addEventListener('keydown', e => {
if (e.keyCode === 17) // (e.code === "control") works as well
// Do something
})
element.addEventListener('click', () => {
clickNum++
})
input
, select
, textarea
) is changed.It is important to note that when a form is submitted, the corresponding action will attempt a page redirect, which will trigger a page refresh. To prevent this, you must add e.preventDefault()
to suppress the default behavior.
element.addEventListener('change', e => {
newValue = e.target.value
})
element.addEventListener('submit', e => {
e.preventDefault()
submitForm()
})
Let's talk a bit more about e.preventDefault()
. We know it can be used on the 'submit'
event to prevent the default page redirect behavior. What about other behaviors?
If we wanted to listen for Arrow Key inputs, the 'up' and 'down' arrow keys will cause the page to scroll (if any). This is another example of where we can use e.preventDefault()
to suppress the default behavior. It is applicable to many different elements, and can be used whenever we want more control over events. Other examples can be found here.
We can use the various attributes and types of HTML elements to grab the ones we need. Here are some of the widely used as well as recommended methods I gathered from my research.
document.getElementById
is the way to grab a very specific element. It will of course require an id on the element which must be unique. Assigning ids to elements should be done judiciously.document.getElementsByClassName()
returns an array-like object of all elements with the given class name.getElementsByTagName()
returns an array-like object of all elements of the given HTML tag.document.querySelector()
is another way to get a single element by query. It will grab the first element that matches the query.document.querySelectorAll()
functions the same way as querySelector()
but returns all elements that matches the query.#example
or the first element of a class "card" by .card
.document.querySelector('.card, div')
will grab the first element that has a class "card" or the div
tag.We will use document.querySelector()
again to provide an example of how we can grab a nested element. If we wanted to grab a div element inside the first div with the class name "card" we would use a CSS selector document.querySelector('.card div')
As discussed previously, element.children
returns an array-like object. What I mean by an array-like object is that it is a list of objects that can be accessed by indices, but not quite iterable like an Array. So if we wanted to use a forEach
loop instead of a for
loop to access its elements, we can do the following to convert it into an Array first:
Array.from(element.children).forEach(el => {
// You now have access each element el
})
We could get the inputs inside a form by iterating its children, but what if we wanted to know exactly which input to get instead of keeping track of the indices?
A suggestion: Use id
to grab the form, name
to grab the inputs
A form element has a property elements
which is an array-like but also an associative array-like object that allows us to get the form inputs by name.
<form id="form">
<input name="name" type="text">
<input name="number" type="number">
<input type="submit">
</form>
const form = document.getElementById('form')
const nameVal = form.elements['name'].value
const numberVal = form.elements['number'].value
New Elements
We can create new elements with document.createElement()
and specifying the tag name, for example, a div
. But once we create a new element, where does it exist?
In order to actually display our newly created element, we must append it to the DOM somewhere. Typically we will grab a parent node and append it to that parent. Say for example, we have a div
container and we want to add a list to it.
<div id="list-container">
</div>
const listContainer = document.getElementById('list-container')
const list = document.createElement('ul')
const listItem1 = document.createElement('li')
const listItem2 = document.createElement('li')
const listItem3 = document.createElement('li')
listItem1.textContent = "foo"
listItem2.textContent = "bar"
listItem3.textContent = "baz"
// This would not display the list to the DOM since our <ul> is still not included in the DOM!
list.appendChild(listItem1)
list.appendChild(listItem2)
list.appendChild(listItem3)
// Now the list will be displayed since we have appended it to an element in the DOM.
listContainer.appendChild(list)
<!-- Result: -->
<div id="list-container">
<ul>
<li>foo</li>
<li>bar</li>
<li>baz</li>
</ul>
</div>
If we want to render something different that involves more changes than a simple text change (for example, a UI with tabs and displaying different content on tab change), then we need to do some kind of a "re-render". Since we are doing appendChild
operations, we would end up appending the different content on top of the previous content.
So how do we make sure we render changes the right way? We can remove the "old" elements and then append the new ones.
<div id="list-container">
<ul>
<li>old foo</li>
<li>old bar</li>
<li>old baz</li>
</ul>
</div>
const listContainer = document.getElementById('list-container')
const list = document.createElement('ul')
// Iterate through list's children and remove them all
while (list.firstChild)
list.firstChild.remove()
// New content
const listItem1 = document.createElement('li')
const listItem2 = document.createElement('li')
const listItem3 = document.createElement('li')
listItem1.textContent = "foo"
listItem2.textContent = "bar"
listItem3.textContent = "baz"
// Then append the new children
list.appendChild(listItem1)
list.appendChild(listItem2)
list.appendChild(listItem3)
listContainer.appendChild(list)
<!-- Result: -->
<div id="list-container">
<ul>
<li>foo</li>
<li>bar</li>
<li>baz</li>
</ul>
</div>
After you are comfortable with vanilla JS, if you are looking for ways to refactor or write more powerful code, you may be interested in looking at a popular library called jQuery or modern front-end frameworks like React.
References:
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
https://www.digitalocean.com/community/tutorial_series/understanding-the-dom-document-object-model
https://kellegous.com/j/2013/02/27/innertext-vs-textcontent/
We are a leading niche digital & tech recruitment specialist for the North East of England. We Specialise in the acquisition of high-performing technology talent across a variety of IT sectors including Digital & Technology Software Development.
Our ultimate goal is to make a positive impact on every client and candidate we serve - from the initial call and introduction, right up to the final delivery, we want our clients and candidates to feel they have had a beneficial and productive experience.
If you’re looking to start your journey in sourcing talent or find your dream job, you’ll need a passionate, motivated team of experts to guide you. Check out our Jobs page for open vacancies. If interested, contact us or call 0191 620 0123 for a quick chat with our team.
Follow us on our blog, Facebook, LinkedIn, Twitter or Instagram to follow industry news, events, success stories and new blogs releases.
Back to Blog