Introduction
In the previous HTML DOM — Elements chapter, we got to know about the Element
interface of the DOM API, various of its properties and methods, and how to work with them.
In the Text Content Exercise, the Redefining replaceChild()
Exercise and the Node Count Exercise, we even practiced some of those concepts and also our understanding of JavaScript, in general.
Now in this chapter, we aim to understand another extremely important concept of the DOM, i.e. working with HTML attributes. Recall that we stated in the previous chapter that there are many methods, and even some properties available an Element
and HTMLElement
together, that we'll explore in a later chapter specifically meant to deal with HTML attributes. This is that chapter.
In particular, we'll see how to add new attributes to a given element, deleting existing ones, check if an attribute exists already, retrieve the value of a given attribute, work with the extremely common HTML class
attribute, the HTML data-
category of attributes, and even the HTML id
attribute.
We'll also take a look at the now-old-fashioned Attr
interface along with the attributes
property of Element
which were used back then to directly work with attribute nodes in the DOM.
In short, there is just a lot to cover, so let's get going...
Attributes in the DOM
As we all know, an HTML (and XML) document is mainly based on elements. These elements can contain many attributes to further define their characteristics, such as their title, their class, their styles, their interaction, and so on.
Talking about HTML documents specifically, it's practically impossible to imagine even a simple HTML page without using at least one attribute. Attributes are one of the most important aspects of HTML (and even XML).
There are a plethora of attributes that can be defined on HTML elements. Each element has its own set of valid attributes. Many attributes are common amongst many categories of elements but some are only specific to a few of them.
For instance, the placeholder
attribute is meant for <input>
elements only, usually text input elements. Similarly, the title
attribute can be set on almost every element.
The DOM API allows us to obtain access to precisely every single attribute used in a given HTML document.
Depending on the attribute we wish to work with, given an element node, we can either:
- Use a bunch of methods of the element node to work with the attribute.
- Use a property of the element node, meant to reflect the attribute.
- Use a specialized interface, exposed via a property of the element node, to work with the attribute.
In the following sections, we'll explore all three of these.
Let's start with the simplest and most cross-browser compatible way of working with attributes, the first in the list above — methods of the Element
interface.
Getting an attribute's value
The getAttribute()
method of the Element
interface allows us to obtain the value of an arbitrary attribute defined on the calling element.
Here's its syntax:
element.getAttribute(name)
name
is the name of the attribute whose value we want to retrieve, treated case-insensitively for HTML documents. Upon success, the method returns that value.
If there is no such attribute on the calling element node whose name is name
, getAttribute()
returns null
.
getAttribute()
returns back the value of the attribute once it completes. Typically, a method whose name begins with 'get' tends to return back a value.Anyways, let's consider an example.
In the markup below, we've defined exactly three attributes on the <h1>
element, namely id
, style
and contenteditable
:
<h1 id="h1" style="color: blue" contenteditable="true">A heading</h1>
In the following code, we access the values of all three of these attributes, with the help of getAttribute()
:
var h1Element = document.getElementById('h1');
console.log(h1Element.getAttribute('id'));
console.log(h1Element.getAttribute('style'));
console.log(h1Element.getAttribute('contenteditable'));
Note that if the attribute is defined on the element without any value, i.e. it's a Boolean attribute, getAttribute()
will return back an empty string (''
) as its value.
An example is shown below:
<h1 id="h1" contenteditable>A heading</h1>
var h1Element = document.getElementById('h1')
h1Element.getAttribute('contenteditable')
Simple?
The best thing about getAttribute()
is that it has an amazing support across many old browsers. Hence, if we plan to support these old browsers, then we must go for getAttribute()
.
Setting an attribute
Apart from getting the value of a given attribute, another common concern in programs is to add a new attribute or modify the value of an existing attribute. Both of these tasks are essentially accomplished using the same method — setAttribute()
.
The setAttribute()
method is used to set an attribute to a given value. If the attribute doesn't exist on the calling element, it's added.
The syntax of setAttribute()
is pretty predictable:
element.setAttribute(name, value)
name
is the name of the attribute while value
is its value. Both are treated case-insensitively.
The method returns back nothing (i.e returns the value undefined
).
Below shown is an example:
<h1 id="h1">A heading</h1>
.text-blue {
color: blue
}
We add the class
attribute to the <h1>
element and set its value to 'text-blue'
. This gets its text to be colored blue:
var h1Element = document.getElementById('h1');
// Add a new class attribute.
h1Element.setAttribute('class', 'text-blue');
A heading
It works as expected.
As stated before, setAttribute()
doesn't just allow us to add a new attribute to a given element node, but also to update the value of an existing attribute. This is demonstrated below.
<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
color: blue
}
.text-red {
color: red
}
We change the class
attribute of the <h1>
element from 'text-blue'
to 'text-red'
. This obviously gets its text to be colored red:
var h1Element = document.getElementById('h1');
// Modify the existing class attribute.
h1Element.setAttribute('class', 'text-red');
A heading
Once again, simple and concise.
Moving on, there is an important thing to keep in mind when setting attributes via setAttribute()
and designating them to the value true
. It's detailed as follows:
Watch out for the attribute value true
!
One important case to keep in mind when working with setAttribute()
is when working with Boolean attributes, such as contenteditable
.
We can go on and set such an attribute to true
using setAttribute()
, however this value would be coerced into the string 'true
' and then set as the value of the attribute in the DOM. Now here comes the problem — if the HTML engine doesn't recognize this string value of the attribute, it would have no effect.
Rather, what we should do in this case is to either set the attribute to the name of the attribute (i.e. disabled="disabled"
) or set it to an empty string.
The HTML engine recognizes empty attribute values and treats them as if the underlying attribute is present. This doesn't hold for the Boolean true
as we just learnt a while ago.
Checking for attributes
Often times rather than retrieving the value of a given attribute, we're more interested in figuring out whether or not it really even exists on a particular element.
This can be done very easily using the hasAttribute()
method.
As its name suggests, hasAttribute()
returns back a Boolean value indicating whether the calling element node has a given attribute present or not.
Here's its syntax:
element.hasAttribute(name)
As before, element
is the element node whose attributes we want to check for existence, while name
is the name of the attribute that we want to test.
Note that the name
argument is required. If we omit it, a TypeError
exception is raised.
Let's quickly consider a basic example.
Given the markup below,
<div id="main">
<p>Paragraph 1</p>
<p title="Paragraph 2">Paragraph 2</p>
</div>
we'll select a couple of elements from it, and then check whether they have given attributes, using hasAttribute()
:
var mainElement = document.getElementById('main')
mainElement.hasAttribute('id')
mainElement.hasAttribute('ID')
mainElement.hasAttribute('class')
mainElement.children[0].hasAttribute('id')
mainElement.children[1].hasAttribute('contenteditable')
mainElement.children[1].hasAttribute('title')
As you can confirm from the snippet above, the attribute name sent to hasAttribute()
, just like with all the methods shown above, is treated case-insensitively.
Let's also try calling hasAttribute()
without any argument. Ideally, we should get an error:
mainElement.hasAttribute()
And we indeed get one.
Moving on, JavaScript also provides us with a more generic method to check if an element node has any attribute at all, not just a specific one. That method is hasAttributes()
.
Once again, the name is pretty self-explanatory in the purpose of the method and even its signature.
That is, hasAttributes()
tells us whether or not the calling element has any attributes at all. Moreover, as for its signature (i.e syntax), it doesn't require any arguments, quite obviously.
Here's its syntax:
element.hasAttributes()
Let's consider an example using hasAttributes()
.
Here's the same HTML markup that we saw above:
<div id="main">
<p>Paragraph 1</p>
<p title="Paragraph 2">Paragraph 2</p>
</div>
As before, we'll call hasAttributes()
on all of the elements shown and see its result:
var mainElement = document.getElementById('main')
mainElement.hasAttributes()
mainElement.children[0].hasAttributes()
mainElement.children[1].hasAttributes()
Obviously, if hasAttributes()
returns false
, then clearly hasAttribute()
would return false
as well for literally any given argument.
Removing an attribute
The removeAttribute()
method allows us to remove an attribute from an element node.
If the attribute doesn't exist on the element, removeAttribute()
ignore silently and doesn't throw any error.
Its syntax is also pretty much predictable:
element.removeAttribute(name)
We just ought to remove a given attribute, likewise only its name is required. As with setAttribute()
, removeAttribute()
also returns back nothing.
Time to consider an example.
In the code below, we remove the class attribute from #main
and thus get the corresponding CSS styles applied with that class to be removed:
<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
color: blue
}
var h1Element = document.getElementById('h1');
// Remove the class attribute.
h1Element.removeAttribute('class');
A heading
As can be seen, the <h1>
element has the default black color applied, which means that the class
attribute has been removed from it.
To further testify this fact, let's use the hasAttribute()
method on h1Element
to check for the existence of the class
attribute:
h1Element.hasAttribute('class')
Not surprisingly, we get false
returned, testifying the fact that there is really no class
attribute present on the <h1>
element.
The classList
property
The classList
property is a specialized feature to work with the HTML class
attribute in the DOM.
The reason for providing a specialized feature to work with class
is quite apparent — the attribute is used more than just frequently in HTML documents.
It's very common to designate particular styles to given HTML classes in the CSS and then use these classes on given HTML elements to trigger the rendering of those styles.
Once we cover the Events API, in the next JavaScript Events unit, we'll see that this is one of the mainstream tasks in JavaScript programs, i.e. to add/remove given classes from an element to trigger the rendering of particular styles. It's sometimes also required to toggle a class on an element, i.e to remove it if it exists, or to add it otherwise.
In short, the class
attribute alone requires a bunch of utilities to ease the process of adding/removing/toggling given classes. This inspires the DOM API to provide us with a separate feature to work with class
, and that feature is the classList
property of the Element
interface.
The classList
property returns back an instance of the DOMTokenList
interface. DOMTokenList
is a means of working with a given attribute in terms of a set of tokens which are simply strings.
Now what are tokens?
Well, recall the HTML class
attribute. It can have multiple class names inside it, separated via the space character. These individual class names are what the DOM calls tokens.
For instance in the attribute definition class="text-blue text-red"
, there are two token present, i.e. text-blue
and text-red
.
The weird name: DOMTokenList
The name DOMTokenList
, according to the WHATWG DOM specification itself, is a 'legacy mishap'. It actually represents a set, containing a bunch of unique tokens, yet its name gets us to rather think of it as a list of elements.
A much better name would have been DOMTokenSet
, but we couldn't make this change now, given that DOMTokenList
has been adopted extensively across various DOM implementations.
Legacy often hurts in programming.
Anyways, coming back to DOMTokenList
, it provides us with the following properties and methods to work with an attribute's value:
length
— the total number of tokens in the underlying token set.value
— the stringified value of the underlying token set.add()
— adds a new token to the underlying token set.remove()
— removes a given token from the underlying token set.toggle()
— adds or removes a token based on its existence in the underlying token set.contains()
— checks whether the underlying token set contains a particular token.forEach()
— iterates over all the tokens in the underlying token set, invoking the callback function provided as an argument.
Let's consider a bunch of examples to better understand DOMTokenList
as exposed by the classList
property.
In the code below, we add a new class to the <h1>
element by calling the add()
method on its classList
property:
<h1 id="h1">A heading</h1>
.text-blue {
color: blue
}
var h1Element = document.getElementById('h1');
// Add the class 'text-blue'.
h1Element.classList.add('text-blue');
A heading
Even though there is no class
attribute present on <h1>
before calling classList.add()
, the method takes care of that itself.
In the second example below, we remove the class 'text-blue'
from <h1>
by calling classList.remove()
:
<h1 id="h1" class="text-blue">A heading</h1>
.text-blue {
color: blue
}
var h1Element = document.getElementById('h1');
// Remove the class 'text-blue'.
h1Element.classList.remove('text-blue');
A heading
The color of <h1>
here clearly confirms the fact that the class text-blue
has been removed from it. However, keep in mind that the class
attribute is still there on the element — it's just that the text-blue
token is removed from its value.
In the snippet below, we confirm this fact:
h1Element.hasAttribute('class')
As the return value true
indicates, <h1>
still has the class
attribute on it.
Moving on, now let's see classList.contains()
in action.
In the code below, we perform a couple of checks on the class
attribute of <h1>
:
<h1 id="h1" class="text-blue padded LARGE">A heading</h1>
var h1Element = document.getElementById('h1');
h1Element.classList.contains('text-blue')
h1Element.classList.contains('TEXT-BLUE')
h1Element.classList.contains('LARGE')
h1Element.classList.contains('large')
h1Element.classList.contains('padded')
h1Element.classList.contains(' padded ')
h1Element.classList.contains('text-red')
h1Element.classList.contains('class')
This snippet contains a lot more information than might be observed in the first glance:
- Tokens are matched case-sensitively. That is, if a token in the HTML element's
class
attribute isPADDED
, then only the string'PADDED'
would match it when passed tocontains()
. - The string passed to
contains()
isn't trimmed for whitespace before begin checked for existence in the underlying token set. That is, given that the tokenPADDED
exists in the underlying token set of aDOMTokenList
instance, only the string'PADDED'
will match it. contains()
only checks if a given token exists in the value of a given element'sclass
attribute. It doesn't check if theclass
attribute itself exists or not.
Simple?
The dataset
property
If you've worked with HTML for quite a while, then you would surely know about the category of HTML data-
attributes. They are a standard way or defining custom attributes on HTML elements.
For instance, we could represent all the data of an <ol>
element as a comma-delimited string of items in the data-list
attribute, as shown below:
<h1>Some programming languages</h1>
<ol data-list="Python,JavaScript,PHP"></ol>
Using various methods of the DOM API, we can read and thereby process this data-list
attribute to actually fill the <ol>
element with corresponding <li>
elements. We'll demonstrate this shortly below.
data-list
— we could also call it data-items
, or data-collection
, or just about anything meaningful.The DOM API obviously allows us to retrieve the values of these attributes, and even set them, by means of the getAttribute()
and setAttribute()
methods, as we've discussed them before.
However, what we haven't discussed thus far is that the DOM API even provides a specialized property on element nodes to operate on its data-
attributes. That is dataset
.
The dataset
property of an element node (or precisely speaking, of the HTMLElement
interface) allows us to work with data-
attributes on the element. It returns back a DOMStringMap
instance.
The DOMStringMap
interface is solely meant to ease the process of working with data-
attributes. It doesn't have other applications apart from operating on data-
attributes.
DOMStringMap
is an exotic interface, if we are to refer to it in the argot of the ECMAScript specification. Let's see what this means, in detail:
DOMStringMap
is exotic!
Do you recall what's an exotic object from the previous chapters?
Well, an exotic object (or interface) is one which doesn't follow the normal internal utilities of objects. The arguments
object is an example of an exotic object.
Talking about DOMStringMap
, it's exotic in the sense that it doesn't treat property get, set and delete expressions normally. Let's take property-get as an example to help illustrate what we mean by this.
In a normal object obj
, when we get a property using an expression such as obj.prop
, the value of the underlying property is returned, just as it's stored in memory (assuming that we're talking about a data property).
However, in the case of a DOMStringMap
instance obj
, when we get a property using an expression obj.prop
, an internal function is called that forms the return value right at the moment, by getting the value of the data-prop
attribute from the calling element node.
This is exotic behavior when getting a property on a DOMStringMap
object.
For this same reason, there are no predefined properties or methods available on DOMStringMap
. If there were, the interface would've had to separately deal with those predefined properties and methods when we retrieved them, so as to not trigger its internal property-retrieval utility on them.
Using dataset
, we can get the values of, set (i.e. update or add) and even remove data-
attributes from a given element node.
- Getting an attribute's value is as simple as getting a property on
dataset
. - Setting an attribute is as simple as setting a property on
dataset
using the assignment operator (=
). - As for removing an attribute, it's as simple as deleting a property from
dataset
(via thedelete
keyword).
It's worth mentioning here that there is a name conversion that happens each time when we get/set/delete a data-
attribute using the dataset
property.
This conversion happens by virtue of the fact that the word data
is stripped from the corresponding property name (on dataset
) representing a data-
attribute.
That is, to access the data-list
attribute on a given element node element
, we'd write the following:
element.dataset.list
Notice that the property name list
, which represents the data-list
attribute, doesn't have the word data
in it. The word is automatically added when by DOMStringMap
when we set the property, or is automatically truncated when we retrieve the property.
In addition to this, the name conversion also happens because DOMStringMap
doesn't allow accessing properties whose name includes a hyphen (-
) character in it, and so we have to use some other methodology of working with data-
attributes that are comprised of multiple words separated by hyphens (-
).
That is, we can't do the following to set a data-some-thing
attribute on a given element node element
:
element.dataset['some-thing'] = value
The string 'some-thing'
inside the pair of brackets ([]
) represents a property name which, although isn't invalid for JavaScript objects otherwise, is considered an illegal property name by the DOMStringMap
interface just because it contains a hyphen (-
) in it.
So the methodology that DOMStringMap
uses is as follows:
data-
attribute that consists of multiple words, e.g. data-list-id
, the corresponding property name is expressed in camel casing, whereby the words in the casing are the ones separated by a hyphen (-
) in the attribute's name, except for the word data
.For instance, to access data-list-id
, we'd use dataset.listId
. The words taken from the attribute are list
and id
(excluding the very first word data
). When we apply the camel casing on these words, we get listId
.
As another example, to access data-list-update-command
, we'd write dataset.listUpdateCommand
.
Alright, it's time to consider some examples.
In the code below, we retrieve the value of the data-list
attribute on the <ol>
element and then convert it into an array using the split()
string method:
<h1>Some programming languages</h1>
<ol id="list" data-list="Python,JavaScript,PHP"></ol>
var listElement = document.getElementById('list');
var list = listElement.dataset.list.split(',');
console.log(list);
['Python', 'JavaScript', 'PHP']
Now, let's consider setting a data-
attribute using dataset
.
In the code below, we add a data-list-id
attribute to the <ol>
element and set its value to '1580'
. This is just some arbitrary value applied to an arbitrary attribute for the sake of an example — there isn't any practical importance of the attribute or the particular value '1580'
:
<h1>Some programming languages</h1>
<ol id="list" data-list="Python,JavaScript,PHP"></ol>
var listElement = document.getElementById('list');
// Add a data-list-id attribute to <ol>.
listElement.dataset.listId = '1580';
console.log(listElement.getAttribute('data-list-id'));
The expression listElement.dataset.listId = '1580'
sets a data-list-id
attribute on the <ol>
element and makes its value '1580'
.
In the next statement, we manually retrieve this attribute's value via getAttribute()
to see if it really exists on <ol>
or not. And it turns out, based on the log, that it does exist.
Polyfilling DOMStringMap
Polyfilling DOMStringMap
wasn't possible back then when there was absolutely no way to tap into the internals of JavaScript.
However, these days thanks to the Proxy
and Reflect
interfaces, it's entirely possible to polyfill DOMStringMap
. But the thing is that almost all browsers that support Proxy
and Reflect
support DOMStringMap
as well.
In other words, there is absolutely no need of polyfilling DOMStringMap
on newer browsers. For browsers that do require a polyfill, there is no way to create one.
Properties mirroring HTML attributes
So far, we've seen two properties available on element nodes in an HTML DOM tree that allow us to work with given attributes, i.e. classList
(a property of the Element
interface) that operates on the class
attribute, and dataset
(a property of the HTMLElement
interface) that operates on data-
attributes.
Apart from these, almost all HTML attributes of an element are exposed to the end user by means of similarly-named properties. In this section, we'll cover two such properties:
id
— mirrors theid
attribute.className
— mirrors theclass
attribute.
In the latter part of this course, we'll cover almost all of the frequently-used properties of HTMLElement
nodes in an HTML DOM tree, with each property, or set of properties, dedicated a different unit or chapter.
Anyways, let's come back to the discussion and begin with id
.
id
All Element
nodes have an id
property available on them.
This id
property is meant to act as a mirror for the id
attribute on the element node.
To be a 'mirror' means that when the property is retrieved in a get context, the corresponding attribute's value is returned back; and similarly when the property is assigned a value, the corresponding attribute's value is changed to that value.
As with almost all properties in the DOM API, id
is an accessor property. It has associated getter and setter functions that perform the tasks mentioned above, respectively.
In the code below, we set the id
attribute on the <h1>
element to the value 'title'
by setting the id
property on its element node to 'title'
:
<div id="main">
<h1>A heading</h1>
<p>A paragraph</p>
</div>
#title {
font-size: 50px;
color: blue;
}
var mainElement = document.getElementById('main');
// Set the id attribute of <h1> to 'title'.
mainElement.firstElementChild.id = 'title';
The addition of id="title"
to <h1>
would get the CSS shown above to be put into action. Thus the output of this code would be as follows:
A heading
A paragraph
Moving on, we can even use the id
property of an element node to empty its id
attribute. This is demonstrated below.
In the following example, we set the id
attribute of <h1>
to the value ''
by setting the id
property on its corresponding element node to ''
:
<div id="main">
<h1 id="title">A heading</h1>
<p>A paragraph</p>
</div>
#title {
font-size: 50px;
color: blue;
}
var h1Element = document.getElementById('title');
// Empty the id attribute of <h1>.
h1Element.id = '';
Quite expectedly, as the code above runs, the 'title'
id
is removed from <h1>
, and thus the styles associated with it get removed as well, which ultimately gives us the following output:
A heading
A paragraph
One important thing to keep in mind regarding the code above is that we retrieved the <h1>
element using its id="title"
attribute, but removed it later on (or precisely speaking, just emptied the id
attribute).
Now we might think that this would lead to the element node h1Element
becoming null
but that's NOT the case.
Once we have obtained a node, as we did above when we called document.getElementById('title')
, then even if we change a characteristic of the node that was used to obtain it in the first place (like we emptied the id
of <h1>
, which was actually used to obtain it), we still have access to the node.
The node doesn't just become null
surprisingly.
Surely, if we inspect the characteristic later on after changing it on the element node, it would be changed. For instance, in the following console snippet, we inspect the value of the id
attribute on h1Element
via getAttribute()
:
h1Element.getAttribute('id')
h1Element.hasAttribute('id')
As we can see, the attribute's value is clearly empty (but the attribute itself is still there, as confirmed by the hasAttribute()
call).
Stating it once again, a changed characteristic doesn't mean that the underlying node would become null
. It will be the same node (in memory) before and after the change of a characteristic.
Simple?
className
Second in the list we have className
.
Despite its strange name, className
is the corresponding property for the HTML class
attribute.
Let's first settle down the most common question asked regarding className
: why is it called className
, and not just class
?
Well, the answer is quite intuitive.
Why is className
not called class
?
At least these days, JavaScript engines don't throw an error if we set the name of a property to the name of a reserved keyword of JavaScript.
However, back then, this was actually the case. Technically speaking, class
wasn't actually a keyword in old JavaScript implementations, they did include it in their list of reserved keywords, terming it (and a couple more words such as private
, public
, static
, etc.) as 'might be used in the future'.
Thus, it was outright invalid to name a property as class
, and thus the property meant to mirror the HTML class
attribute was named as className
.
If this isn't enough to convince us, we should also recall that the DOM API was, and still is, meant to be implemented in any language apart from JavaScript. In these languages as well, class
is a reserved keyword, plus there is also no guarantee that setting a property's name to class
is a valid action.
Likewise, the standardization committee at W3C working on the DOM specification (long ago) chose className
as the name of the property mirroring the HTML class
attribute.
Coming back to the className
property, it allows us to get the value of the class
attribute, as well as set its value.
The Attr
interface
In the chapter HTML DOM — Basics, we learnt about the Attr
interface but only on the outskirts. In this section, we'll explore a little bit more about this old-fashioned interface.
As we know by now, the DOM tree is built upon nodes — we have a document node containing element nodes, text nodes, comment nodes, and let's not forget about attribute nodes. All the attributes present in a given HTML/XML document are also represented in the DOM tree as nodes.
In particular, attribute nodes are represented by the Attr
interface which extends Node
.
Attr
defines a bunch of properties specifically meant for attributes. The table below details some of these properties. (Obviously we don't need to learn all of the properties.)
Property | Purpose |
---|---|
localName | The name of the attribute. |
value | The value of the attribute. |
ownerElement | The element node that owns the attribute, i.e. the one on which the attribute is defined. |
ownerDocument | The document node that contains the ownerElement of the attribute. |
Attr
nodes are part of the DOM tree, but they don't (and can't) have any parent (and thus no siblings) or any children. They are owned by given element nodes and are accessible via the attributes
property on those element nodes. We'll come to this in a while.
But first let's see how to create an attribute node.
The createAttribute()
factory method of the Document
interface creates and returns back an Attr
instance. The name of the attribute is specified as an argument to the method, which is then converted to lowercase when creating the attribute.
The value of the attribute is provided via the value
property of the returned Attr
instance.
Let's consider an example.
In the code below, we create a class
attribute node and then set its value to 'highlighted'
:
var classAttribute = document.createAttribute('class');
classAttribute.value = 'highlighted';
This code only creates a new attribute node but doesn't add it to an element.
To add an attribute node to an element, we use the setAttributeNode()
method of the Element
interface.
setAttributeNode()
accepts a given Attr
node and then adds the corresponding attribute to the calling element or updates it if it already exists (just like how setAttribute()
operates).
setAttribute()
, setAttributeNode()
returns back a value which is either the replaced attribute node, if there was any attribute present on the calling element node with the same name, or else null
.In the example below, we add the attribute node that we just created above to our <h1>
element:
<h1 id="h1">A heading</h1>
.highlighted {
background-color: yellow
}
var classAttribute = document.createAttribute('class');
classAttribute.value = 'highlighted';
// Add class="highlighted" to <h1>.
var h1Element = document.getElementById('h1');
h1Element.setAttributeNode(classAttribute);
A heading
And voila! The code works.
Similarly, to get an attribute node or remove an attribute node from an element, we are provided with the methods getAttributeNode()
and removeAttributeNode()
, respectively.
Now as you might have noticed, directly working with attribute nodes to get/set/delete attributes from given elements in the DOM is really not a simple task. It unnecessarily complicates the code, leaving us tangled in multiple Attr
instances where we could've just relied on working with mere strings.
For this reason, you'll sparingly notice any programs these days operating directly on Attr
nodes. It won't be wrong to say that the Attr
interface has gone out of fashion. We're much better off if we stick to using getAttribute()
, setAttribute()
and removeAttribute()
.
It's a good thing now that we at least know about the Attr
interface and how to directly work with attribute nodes, but remember that there is no need to use this interface — it just adds clutter to the code.
The attributes
property
The attributes
property of the Element
interface returns back a list of all the attribute nodes on a given element.
In particular, it returns back a NamedNodeMap
instance.
The NamedNodeMap
interface defines named keys pointing to given Attr
nodes on an element (with the keys being the names of the respective attributes) as well as integer keys.
If we wish to access a specific attribute node based on the attribute's name, then the named keys come in handy. Otherwise, if we wish to iterate over all the attribute nodes as if they are stored in the form of a list, then the ineger keys come in handy.
NamedNodeMap
instance. We might be tempted to think that the order in which attributes are written in the HTML source code is the same order in which they show up in NamedNodeMap
, but this is not guaranteed to be the case.The NamedNodeMap
interface provides a bunch of useful properties and methods to work with attributes.
They are listed as follows:
length
— returns the total number of attributes on the element.getNamedItem()
— retrieves an attribute node based on its name.setNamedItem()
— sets an attribute node.removeNamedItem()
— removes an attribute node based on its name.
Let's consider a couple of examples.
In the following code, we log the total number of attributes on the <h1>
element:
<h1 id="h1" class="text-blue" contenteditable title="A div">A heading</h1>
document.getElementById('h1').attributes.length
As we can see, there are a total of 4 attributes on <h1>
and likewise, attributes.length
returns 4
.
In the following code, we remove the contenteditable
attribute from the <h1>
element and then check whether it exists using the hasAttribute()
method:
<h1 id="h1" class="text-blue" contenteditable title="A div">A heading</h1>
var h1Element = document.getElementById('h1');
h1Element.attributes.removeNamedItem('contenteditable');
console.log(h1Element.hasAttribute('contenteditable'));
As expected, attributes.removeNamedItem()
removes the contenteditable
attribute from the <h1>
element, as is confirmed by the output of hasAttribute()
.
Moving on, with the help of the length
property of attributes
and the fact that attributes
could be accessed by indices via bracket notation, just like an array, we could process all the attributes of a given element.
In the following example, we log the names of all the attributes of the <h1>
element:
<h1 id="h1" class="text-blue" contenteditable title="A div">A heading</h1>
var h1Element = document.getElementById('h1');
for (var i = 0, len = h1Element.attributes.length; i < len; i++) {
console.log(h1Element.attributes[i].name);
}
Like some NodeList
instances, and all HTMLCollection
instances, a NamedNodeMap
instance is a live collection of nodes.
That is, if we retrieve a NamedNodeMap
associated with a particular element and then later on modify the attributes of that element, the instance would update automatically; we won't need to create a new NamedNodeMap
instance.