Introduction
In the previous chapter, we learnt how to select elements from an HTML document using a handful of properties/methods on the document
object, and by a couple of properties on given Element
instances.
In this chapter, we shall now focus on nodes only. That is, we'll explore the Node
interface in extreme detail. We shall see what's the Node
interface in extreme detail. We shall see what's the Node
interface meant for, what is it an abstract interface, what properties and methods does it come bundled with, and so on and so forth.
What is Node
?
If we recall from the HTML DOM — Basics chapter, the Node
interface is simply used to represent a node in the DOM tree.
It's an abstract interface which is extended by many other interfaces, such as Element
, CharacterData
, Document
, DocumentType
, and so on.
What's an abstract interface?
An abstract interface is one which couldn't be instantiated directly, but only extended by some other interface which can then be instantiated.
In our case, the Node
interface being 'abstract' means that we couldn't have a direct Node
instance — it must be extended by some other interface and then we could instantiate an instance from that particular derived interface.
For e.g. we can have Element
instances in a document, so can we have CharacterData
instances. Both of these interfaces extend Node
.
But we can't have a direct Node
instance.
The Node
interface is extremely useful. It contains a great deal of properties and methods to help us operate on a given node in various ways.
Let's start with the properties:
Property | Purpose |
---|---|
textContent | The textual content of the given node. Depends on the type of the node. |
nodeType | An integer representing the type of the node. |
nodeName | A string representing the name of the node. |
nodeValue | A string representing the value of the node. |
parentNode | A reference to the parent node of the given node. |
childNodes | An NodeList instance containing all the child nodes of the given node. |
firstChild | A reference to the first child node of the given node. |
lastChild | A reference to the last child of the given node. |
nextSibling | A reference to the next sibling of the given node, i.e. the one that comes after it in the HTML source code. |
previousSibling | A reference to the previous sibling of the given node, i.e. the one that comes before it in the HTML source code. |
ownerDocument | A reference to the document object that owns the given node. |
Note that all these properties are accessor properties.
Now, let's talk about the methods of the Node
interface:
Method | Purpose |
---|---|
appendChild() | Adds a node inside the calling node as its last child. |
insertBefore() | Adds a node inside the calling node before a reference child node. |
replaceChild() | Replace a particular child node inside the calling node with another node. |
removeChild() | Remove a particular child node from the calling node. |
cloneNode() | Make a copy of the calling node. |
Apart from these, there are a total of 8 numeric constants defined on Node
(and even Node.prototype
) that represent given node types, each holding an integer from 1
to 8
.
Not of all them are of use to us. The ones relatively more important are shown as follows:
Constant | Value | Purpose |
---|---|---|
ELEMENT_NODE | 1 | Represents the element node type. |
ATTRIBUTE_NODE | 2 | Represents the attribute node type. |
TEXT_NODE | 3 | Represents the text node type. |
COMMENT_NODE | 8 | Represents the comment node type. |
DOCUMENT_NODE | 9 | Represents the (root) document node type. |
DOCUMENT_TYPE_NODE | 10 | Represents the document-type node type. |
DOCUMENT_FRAGMENT_NODE | 11 | Represents the document fragment node type. |
We'll shortly see some examples where these constants are used to determine the types of arbitrary nodes.
Anyways, let's begin discussing about all the Node
properties, starting with childNodes
.
The childNodes
property
The childNodes
property of a given Node
instance returns back a collection of all the children of the node in the DOM tree.
In particular, the collection returned is a NodeList
instance.
First, let's understand what exactly is a NodeList
.
What is a NodeList
?
A NodeList
object is very similar to an Array
object.
It supports the usage of bracket notation to access individual items, and has a length
property containing the total number of items.
For instance, if nodeList
is a NodeList
instance, then we could do something such as nodeList[0]
to access to first item of the list, and similarly nodeList.length
to determine the total number of items in it.
However, it's very important to note that a NodeList
is NOT the same thing as an Array
.
A NodeList
instance doesn't have access to any array methods, such as indexOf()
, push()
, slice()
, etc, simply because it's neither an array itself, nor does it inherit from the Array
interface.
Alright, it's now time to consider an example.
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
Let's try to determine all the children of #main
just by looking at this HTML code.
The first child is a text node, the second child is a <p>
element, the third one is again a text node, the fourth one is a comment, and finally the last one is another text node. So, in total, we have 5 children.
Now, let's inspect the length of the childNodes
property of this #main
element:
var mainElement = document.getElementById('main');
console.log(mainElement.childNodes.length);
As expected, we have five items in the returned NodeList
.
In the following code, we inspect each node in this list one-by-one, by logging its nodeName
property, which we'll explore shortly below:
var mainElement = document.getElementById('main');
for (var i = 0, len = mainElement.childNodes.length; i < len; i++) {
console.log(mainElement.childNodes[i].nodeName);
}
Surely, we'll only be able to understand the output here once we get to know about the nodeName
property, but just from natural intuition, we can still make some sense out of the output.
That is, the first node has the name #text
and, likewise, might refer to a text node; the second one has the name P
and, likewise, might refer to the <p>
element; the third one is again a text node; the fourth one might refer to a comment node, and the last one is yet again another text node.
Simple.
Moving on, one thing very important to note regarding the NodeList
instance returned by childNodes
is that it's a live collection. That is, as we update the children of a given node, its childNodes
property updates automatically.
We'll see what this menans later in this chapter, once we explore the DOM mutating methods appendChild()
, insertBefore()
, and removeChild()
.
The firstChild
and lastChild
properties
The firstChild
and lastChild
properties of a node return its first and last child, respectively.
If the node doesn't have any children at all, both these properties evaluate down to null
.
Keep in mind that for a given node n
, n.firstChild
is functionally equivalent to n.childNodes[0]
while n.lastChild
is functionally equivalent to n.childNodes[n.childNodes.length - 1]
.
Obviously, these two properties, namely firstChild
and lastChild
, are really handy when we are only interested in the first or the last child of a given node, respectively, instead of manually accessing the child via childNodes
.
As before, it's time for an example.
Here's the same HTML code as before:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
And here we log the nodeName
of the firstChild
and the lastChild
of #main
:
var mainElement = document.getElementById('main');
console.log(mainElement.firstChild.nodeName);
console.log(mainElement.lastChild.nodeName);
Quite simple.
The nextSibling
and previousSibling
properties
As we learnt in the chapter HTML DOM — Basics, the siblings of a given node are all the nodes that have the same parent as that node.
The Node
interface provides us with two properties to retrieve the previous and the next sibling of a given node. They are, well, nextSibling
and previousSibling
, respectively.
For a given node, it's nextSibling
returns sibling that appears immediately after it in the HTML markup. Similarly, previousSibling
returns the sibling of the node that comes immediately before it in the HTML markup.
If there is no sibling after or before a given node in the HTML markup, nextSibling
and previousSibling
return null
, respectively.
Let's consider an example.
Once again, below we have the same HTML as before:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
Now, let's select #main
's first child and retrieve its nextSibling
:
var mainElement = document.getElementById('main');
console.log(mainElement.firstChild.nextSibling.nodeName);
As expected, it's the <p>
element.
Now, let's select the last child of #main
and then retrieve its previous sibling.
var mainElement = document.getElementById('main');
console.log(mainElement.lastChild.previousSibling.nodeName);
Once again, as expected, it's the comment node.
Time for a quick test.
What does the following code log?
var mainElement = document.getElementById('main');
console.log(mainElement.lastChild.nextSibling.nodeName);
#text
DIV
null
- It throws an error
#main
that comes after it. Likewise, mainElement.lastChild.nextSibling
resolves down to null
. Thereafter, accessing the property nodeName
on null
leads to an error, as it's illegal to access any property on null
.The parentNode
property
As the name suggests, the parentNode
property of a Node
instance returns back its parent node.
All nodes in the DOM tree have a parent, except for the root node, which is the document
object. The parent node of document
is null
.
Time to consider an example.
Following we have the exact same HTML as before:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
As we already know, this HTML snippet assumes that the markup is encapsulated inside the <body>
tag.
Hence, assuming this notion, let's traverse all the way up from the first child of #main
to document
by following the parent node connections. At each stage, we log the nodeName
of the current node:
var node = document.getElementById('main').firstChild;
console.log(node.nodeName);
console.log(node.parentNode.nodeName);
console.log(node.parentNode.parentNode.nodeName);
console.log(node.parentNode.parentNode.parentNode.nodeName);
console.log(node.parentNode.parentNode.parentNode.parentNode.nodeName);
Hopefully, it's very intuitive to follow along each of the logs in the console snippet above. It's just that we're going up from #main
's first child to document
— that's it.
We can make a fairly good hypothesis at this point that the parent node of the child node of a given element node n
is equal to n
.
Let's see whether or not this is really the case.
In the code below, we compare mainElement
with the parent of main.firstChild
using the ===
operator to see whether or not they both point to the exact same object in memory:
var mainElement = document.getElementById('main');
console.log(mainElement === mainElement.firstChild.parentNode);
And guess what — they really do.
Isn't this remarkable?
The nodeName
property
The nodeName
property of a given node holds a string containing the name of the node.
The name is dependent on the type of the node:
- For text nodes, it's the string
'#text'
. - For comment nodes, it's the string
'#comment'
. - For element nodes, it's the name of the element's tag in uppercase, for e.g.
'SPAN'
for a<span>
element. - For document nodes, it's the string
'#document'
. - For document fragment nodes, it's
'#document-fragment'
.
There is a respective value for every single node type, but since the most used node types are the ones that we just discussed above, we'd let go off nodeName
for other kinds of nodes.
Perhaps, the most important use case of nodeName
is when we want to retrieve the tag name of an arbitrary element node, and determine exactly which element it is.
For example, in the code below, we check if the selected #main
element is a <p>
element. If it is, we log 'Yes'
or else log 'No'
:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
var mainElement = document.getElementById('main');
if (mainElement.nodeName === 'P') {
console.log('Yes');
}
else {
console.log('No');
}
Clearly, we know that #main
is not a <p>
element, but is rather a <div>
element. Thereby, the log made is No
.
Let's inspect the real value of mainElement.nodeName
:
'DIV'
Since the #main
element is a <div>
element, mainElement.nodeName
simply evaluates down to 'DIV'
.
The nodeValue
property
The nodeValue
property helps us retrieve the value of a given node, especially text and comment nodes.
As with nodeName
, nodeValue
has a different return value based on the type of the underlying node:
- For text nodes, it returns the textual content of the node.
- For comment nodes, it operates similar to text nodes, returning back the textual content of the comment (excluding the comment tags).
- For element nodes, it returns
null
. - For document nodes, it returns
null
. - For document fragment nodes, it returns
null
.
nodeValue
is not that useful apart from the fact that we could use it to obtain the textual content of a given text or a comment node.
Consider the code below where we log the nodeValue
of the <p>
element, the nodeValue
of its first (and only) child, which is a text node, and finally the nodeValue
of the comment:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[1];
var commentNode = mainElement.childNodes[3];
// nodeValue of the <p> element.
console.log(paraElement.nodeValue);
// nodeValue of the text inside <p>.
console.log(paraElement.firstChild.nodeValue);
// nodeValue of the comment node.
console.log(commentNode.nodeValue);
The nodeType
property
The nodeType
property is yet another, seemingly, useful property of the Node
interface. It helps us determine the type of an arbitrary node.
It holds an integer that represents a particular node type based on the following table:
Value | Meaning |
---|---|
1 | Represents an Element node. |
2 | Represents an Attr node. |
3 | Represents a Text node. |
4 | Represents a CDATASection node. |
5 | Deprecated. Used to represent an entity reference node. |
6 | Deprecated. Used to represent an entity node. |
7 | Represents a ProcessingInstruction node of an XML document. |
8 | Represents a Comment node. |
9 | Represents a Document node. |
10 | Represents a DocumentType node. |
11 | Represents a DocumentFragment node. |
12 | Deprecated. Used to represent a notation node. |
The Node
interface contains a couple of constants, each holding an integer representing one of these types. Thanks to these constants, we don't have to memorize each of these integers for the type it represents.
Here's the list of a couple of these constants:
Constant | Value |
---|---|
Node.ELEMENT_NODE | 1 |
Node.TEXT_NODE | 3 |
Node.COMMENT_NODE | 8 |
Node.DOCUMENT_NODE | 9 |
Node.DOCUMENT_TYPE_NODE | 10 |
Node.DOCUMENT_FRAGMENT_NODE | 11 |
As always, it's time to consider an example.
In the code below, we determine the node type of a couple of children of the #main
element:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
var mainElement = document.getElementById('main')
mainElement.childNodes[0].nodeType === Node.ELEMENT_NODE
mainElement.childNodes[1].nodeType === Node.ELEMENT_NODE
mainElement.childNodes[2].nodeType === Node.TEXT_NODE
mainElement.childNodes[3].nodeType === Node.COMMENT_NODE
Let's understand each of these logs:
- Evidently, since the first child is a text node, and not an element node, the first log is
false
. - In contrast, since the second child is an element node, the second log is
true
. - The third child node is a text node and likewise the third log is
true
. - The fourth node is a comment, and so the fourth and the last log above is
true
.
Simple?
Now although nodeType
does allow us to determine the precise type of an arbitrary Node
instance, there are other ways to get this done as well.
Can you think of any? We've already covered one before.
Well, we can use the instanceof
operator to test an arbitrary Node
instance against a given constructor function.
If true
is returned, it simply means that the type of the instance was indeed the one represented by the constructor function, or else it wasn't.
instanceof
operator, please refer to the chapter JavaScript Objects — Constructors.For instance, given a node instance n
, we can use the expression n instanceof Element
to check whether n
is an Element
node or not. Similarly, to check if n
is a text node, we'd write n instanceof Text
. And so on and so forth.
As a matter of fact, the nodeType
accessor property itself, internally, uses instanceof
to determine its corresponding integer value.
Node
instance against a specific class, we should stick to using instanceof
with the constructor function representing that specific class.Factory methods to create nodes
One of the most powerful ideas put forward by the DOM API is the ability to programmatically create new nodes. In this section, we shall cover how to create some of the most common nodes in the DOM.
The Document
interface defines a couple of factory methods (i.e. methods that return objects) to create given types of nodes.
Here's a list of the most useful ones:
createElement()
createTextNode()
createDocumentFragment()
Let's first consider createElement()
.
createElement()
The createElement()
method, available on the document
object, creates a given element node.
It takes in a single string argument, specifying the tag name of the element to create, and then returns back an element node for that very element.
Here's its syntax:
document.createElement(tagName)
document
is, obviously, the document
object available in the global context, while tagName
is the name of the element's tag that we ought to create.
createElement()
is case-insensitive when we are dealing with HTML documents.The return value of createElement()
depends on the argument sent in. Talking generally, we could say that it returns back an Element
instance, or more specifically, an HTMLElement
instance (since we are only dealing with HTML documents for now).
For HTML documents, createElement()
returns an instance belonging to some interface that inherits from HTMLElement
, which in turn inherits from Element
.
For example, calling node.createElement('div')
returns back an HTMLDivElement
instance. This interface, as we know from the last HTML DOM — Basics chapter, inherits from HTMLElement
, which inherits from Element
.
Simple?
In the code below, we create a <div>
element node and set its innerHTML
to 'A div node'
:
var divElement = document.createElement('div');
divElement.innerHTML = 'A div node';
Note that when we create a node, nothing happens on the current document. It's only when we add the node to the document that we get to witness any visible changes.
To add the node that we just created now, we'll use the Node
interface's appendChild()
method.
It operates very simply — the node that we provide to it is added as the last child inside the calling Element
instance. We'll learn more about how appendChild()
works in the next section below.
For now, let's consider a quick illustration of adding the node that we just instantiated:
Assuming that our HTML markup is as follows,
<div id="main"></div>
below we add the created divElement
node inside #main
:
var divElement = document.createElement('div');
divElement.innerHTML = 'A div node';
var mainElement = document.getElementById('main');
// Add the <div> element inside #main.
mainElement.appendChild(divElement);
Here's a live example:
And voila! As soon as the HTML page loads, we see the text 'A div node' displayed. This confirms that our DOM <div>
node really got added inside the given #main
element.
createDocumentFragment()
The createDocumentFragment()
factory method of the Document
interface, as the name suggests, creates a document fragment node.
We'll cover document fragments in detail later in this unit.
A document fragment is basically just a very lightweight version of a document. We could use it as a temporary holding ground for a given set of nodes.
In the code below, we create a document fragment, then add two nodes inside it, followed by appending the whole fragment to the #main
element:
<div id="main"></div>
var h1Element = document.createElement('h1');
h1Element.innerHTML = 'A heading';
var paraElement = document.createElement('p');
paraElement.innerHTML = 'A paragraph';
var fragment = document.createDocumentFragment();
fragment.appendChild(h1Element);
fragment.appendChild(paraElement);
var mainElement = document.getElementById('main');
mainElement.appendChild(fragment);
The moment a DocumentFragment
instance is detected by appendChild()
, all the nodes inside the fragment are added to the calling node, leaving the fragment itself empty.
This can be confirmed by the following console snippet:
After the execution of the code above, all the nodes in fragment
are dumped into #main
, leaving fragment
empty.
createTextNode()
Although, it's not used very much in the context of HTML documents, we can also add textual content to a document by creating text nodes manually and then appending them within element nodes.
In this regard, we have at our dispense the method createTextNode()
on the Document
interface.
Here's the syntax of createTextNode()
:
document.createTextNode(text)
text
is a string and holds the textual content of the text node to be created. It's a required parameter; failing to provide a value to it would lead to an error.
Below shown is an example.
Here, we change the content of #main
to 'A div node'
, by means of creating a separate text node with the help of document.createTextNode()
:
<div id="main"></div>
var mainElement = document.getElementById('main');
var textNode = document.createTextNode('A div node');
mainElement.appendChild(textNode);
As is clear, manually creating text nodes and then appending them within given element nodes is quite a tedious activity.
A better approach is to keep using the innerHTML
property whenever we need to add HTML content inside an element node, or maybe even the textContent
property, as we shall explore later on in this chapter.
Adding nodes
appendChild()
The appendChild()
method of the Node
interface adds a given node inside the calling node, as its last child.
The reason it's called 'appendChild' is because it adds a given node at the end of the children of the calling node (i.e. the one that invoked appendChild()
), which means that it becomes a child node of the calling node.
Here's the syntax of appendChild()
:
node.appendChild(childNode)
node
is the node in which we want to add the childNode
, right at its end.
Note that the childNode
argument is required; failing to provide it will lead to an error.
Let's consider an example.
In the following code, we create a <p>
element node, filled with some simple text, and then append it right inside the <body>
element (whose id
has been set to 'body'
):
<body id="body">
<h1>Learning the DOM</h1>
</body>
var paraElement = document.createElement('p');
paraElement.innerHTML = 'Learning <code>appendChild()</code>';
var bodyElement = document.getElementById('body');
bodyElement.appendChild(paraElement);
Thanks to the innerHTML
property, the <code>
element used inside the string 'Learning <code>appendChild()</code>'
gets parsed into a real <code>
element node.
Moving on, if the given argument node to appendChild()
is an existing node in the document, it's removed from its original location and then added to the new location.
This is illustrated in the following example:
<div id="sect1" style="border: 1px solid red">
<h1>Section 1</h1>
<p>Paragraph from section 1</p>
</div>
<div id="sect2" style="border: 1px solid blue">
<h1>Section 2</h1>
<p id="para">Paragraph from section 2</p>
</div>
var section1Element = document.getElementById('sect1');
var paraElement = document.getElementById('para');
section1Element.appendChild(paraElement);
As we open up the link above, we notice that #sect1
has two paragraphs in it — one that was originally in it, while the other that was moved from #sect2
.
The appendChild()
method is a highly useful method in the DOM API. It's used frequently as compared to some other methods to add new elements to a given document simply because of its simplicity and amazing support across older browsers.
insertBefore()
insertBefore()
is yet another method to add a given node to the document. Like its name, it's slightly different than appendChild()
.
Essentially, it's meant to insert a node right before a given node.
Here's its syntax:
node.insertBefore(newNode, childNode)
node
is the node in which we want to add a new child node, newNode
is that new node that we want to add, and childNode
is the node that acts as a reference to specify exactly before which node do we want to add newNode.
Note that as with appendChild()
, the arguments provided to insertBefore()
are both meant to be child nodes of node
. This helps us to remember the signature of insertBefore()
.
If childNode
is null
, it simply means that there is no reference that we could provide to the insertBefore()
method before which to put newNode
. In that case, insertBefore()
simply adds newNode
after the last child of node
— similar to how appendChild()
works.
In the following code, we add an <h3>
element right before the <p>
element in #main
:
<div id="main">
<h1>A heading</h1>
<p>A paragraph</p>
</div>
var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';
var mainElement = document.getElementById('main');
// Add an <h3> before the <p> element (the reference node).
mainElement.insertBefore(h3Element, mainElement.childNodes[3]);
Since the node to be added is to be inside #main
, we invoke insertBefore()
on it. Moreover, since <h3>
has to be added, it's the first argument to the method. Last but not the least, because the <h3>
element ought to be added before <p>
, <p>
becomes the reference node.
Simple?
In the following example, we demonstrate the case when the last childNode argument to insertBefore()
is null
:
var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';
var mainElement = document.getElementById('main');
// Add an <h3> at the end of #main
mainElement.insertBefore(h3Element, null);
Notice how the <h3>
element shows after the <p >
element this time. It's still inside #main
, but added right after its last child.
Removing nodes
Where adding elements to a document is a routine activity, removing elements is also not just an occasional one.
The removeChild()
method of the Node
interface allows us to remove a particular node from the document.
Its syntax is pretty straightforward:
node.removeChild(childNode)
node
is the node whose child we wish to remove, while childNode
is that particular child node. Failing to provide childNode
would lead to an error.
Let's consider an example.
In the code below, we remove the <p>
element from the #main
element:
<div id="main">
<h1>A heading</h1>
<p>A paragraph</p>
</div>
var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[3];
mainElement.removeChild(paraElement);
Once paraElement
is removed in the code above, it's no more a part of the DOM tree, but we can still access it as an individual element node.
Let's try doing this:
var mainElement = document.getElementById('main');
var paraElement = mainElement.childNodes[3];
mainElement.removeChild(paraElement);
console.log(paraElement.innerHTML);
Note that we can't just pick and choose an arbitrary node in the DOM and delete it, right away. We have to access its parent node and only then could we remove it using removeChild()
on the parent node.
However, there is an easy trick that allows us to delete any arbitrary node, still using removeChild()
. That we'll introduce right now.
In the code below, we select the desired <p>
element (which now has an id
for direct access) and then remove it without having to access the #main
element separately:
<div id="main">
<h1>A heading</h1>
<p id="para">A paragraph</p>
</div>
var paraElement = document.getElementById('para');
paraElement.parentNode.removeChild(paraElement);
The trick is to first access the node that we wish to remove, and then traverse up to its parent via its parentNode
property. After this, we ought to call the removeChild()
property on this parent node and then pass in the original node to it as an argument.
In general, to remove any arbitrary node node
from a document, we can just call node.parentNode.removeChild(node)
.
Replacing nodes
In order to replace a node with another node, we can use the replaceChild()
method.
It's important to note that the node that gets removed in the process must be a child of the node calling replaceChild()
.
Here's the method's syntax:
node.replaceChild(newNode, oldNode)
newNode
is the node that we want to add, while oldNode
is the node that we want to get replaced with newNode
. node
is the node inside which we want to make the replacement.
Both these arguments are required; failing to provide either will lead to an error.
insertBefore()
, the first argument to replaceChild()
is the new node to add.Once again, akin to appendChild()
and insertBefore()
, the arguments to replaceChild()
are both meant to be child nodes of node
.
Always keep this thing in mind, i.e. the arguments to all four of these DOM mutating methods, on the Node
interface, are meant to be child nodes of the calling Node
instance.
Anyways, it's time for an example.
In the code below, we replace the <p>
element with an <h3>
element:
<div id="main">
<h1>A heading</h1>
<p>A paragraph</p>
</div>
var h3Element = document.createElement('h3');
h3Element.innerHTML = 'Another heading';
var mainElement = document.getElementById('main');
// Replace the <p> element with an <h3>.
mainElement.replaceChild(h3Element, mainElement.childNodes[3]);
Piece of cake, isn't this?
replaceChild()
method can be implemented solely using removeChild()
and insertBefore()
.Cloning nodes
Recall our statement that if an existing node is passed to appendChild()
(or insertBefore
) as the node to be added, it would be removed from its original position in the document and then added to the new one.
Now what if we want to add a particular existing node inside another node but make sure that no repositioning occurs.
Well, it's quite simple in nature. We make a copy — or better to say, a clone — of the node to be added, and then pass this clone to the DOM mutationg method. This keeps the original node in its original position but adds an identical node to the new location.
So, it's a win-win situation!
But how to make a given node's clone? The answer is pretty much straightforward — using cloneNode()
.
Here's the syntax of cloneNode()
:
node.cloneNode([deepClone])
The method is called upon the node that we wish to clone.
The optional Boolean deepClone
argument specifies whether or not deep cloning is desired, i.e. cloning the entire subtree following from node
(including its children, and the children of these children, and so on).
By default, deepClone
is false
, in which case only the given node
itself is cloned, including all its attributes.
However, it's more often that not changed to true
when cloning element nodes, since these nodes have children nodes, and we typically want to copy the entire content of the element node, including these children.
Anyways, once done, cloneNode()
returns back the clone.
The original node and its clone are identical in all their property values but are, at the end of the day, two distinct objects. That is, the ===
operator won't return true
if we compare a node with its clone.
Time for an example.
In the code below, we clone the <p>
element in #sect2
and then append the resulting clone inside #sect1
:
<div id="sect1" style="border: 1px solid red">
<h1>Section 1</h1>
<p>Paragraph from section 1</p>
</div>
<div id="sect2" style="border: 1px solid blue">
<h1>Section 2</h1>
<p id="para">Paragraph from section 2</p>
</div>
var section1Element = document.getElementById('sect1');
var paraElement = document.getElementById('para');
var paraElementClone = paraElement.cloneNode(true);
section1Element.appendChild(paraElementClone);
Notice the true
argument supplied to cloneNode()
here. This is done because we want all the content inside the <p>
element to be copied, not just the element itself.
Recall that there is a text node inside <p>
holding the text 'Paragraph from section 2'. We want this node to be copied as well.
The textContent
property
The textContent
property's name is quite self-explanatory in its purpose. That is, it's meant to represent all the textual content inside a given node.
As with nodeValue
, textContent
behaves differently based on the type of the underlying node:
- For text nodes, it's identical to
nodeValue
. - For comment nodes, it's identical to
nodeValue
. - For element nodes and document fragment nodes, it holds the concatenation of the
textContent
of each of its child nodes, excluding comment nodes. - For document nodes, it holds the value
null
.
As we shall see in the chapter HTML DOM — Elements, using textContent
is much more better performance-wise than innerHTML
and innerText
if only what we're interested in is to change the textual content inside a given element.
Let's see an example.
In the code below, we retrieve the textContent
of a couple of nodes from the given HTML document:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
var mainElement = document.getElementById('main');
console.log(mainElement.textContent);
console.log(mainElement.childNodes[1].textContent);
console.log(mainElement.childNodes[3].textContent);
As can be seen in the first log, the text content of the #main
element is a little bit weird, but for good reason. All the whitespace text nodes in #main
show up in its textContent
property to ultimately give a string with long sequences of whitespace, just as they appear in the HTML source code.
textContent
can also be assigned a value. In that case, it removes all the children of the given node, if it has any, and creates a single child text node in it, if the underlying node can have children at all.
For example, consider the code below:
<div id="main">
<p>A paragraph</p>
<!--A comment-->
</div>
var mainElement = document.getElementById('main');
console.log('Before:', mainElement.childNodes.length);
// Empty the #main element.
mainElement.textContent = '';
console.log('After:', mainElement.childNodes.length);
We set the textContent
of #main
to ''
which has the effect that the childNodes
list of #main
now holds absolutely nothing.
textContent
property won't have any effect on their childNodes
at all.