Categories
JavaScript JavaScript Basics

Useful DOM Traversal Methods

The main use of client-side JavaScript is to manipulate web pages dynamically. We can do this with the DOM traversal methods and properties available to DOM Node objects.

Adding or changing child and sibling elements is easy for any given Node since there are properties built into DOM Node objects to perform these actions. The following are methods of a DOM Node object for getting parent, child, and sibling nodes or elements.

appendChild

The appendChild methods let us attach a child node to a given HTML element as the last child node of the current node. If the argument referenced an existing node on the DOM tree then the node will be detached from its current position and attached to its new position.

It takes one argument, which is a DOM Node object.

For example, given 2 existing nodes in the following HTML:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

We can attach the element with ID bar as a child to the element with ID bar as follows:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
foo.appendChild(bar);

Once we ran that, we should get the following structure:

<div id="foo">  
  foo  
  <div id="bar">  
    bar  
  </div>  
</div>

We can also use it to create an element that’s created on the fly. For example, if we have the following HTML:

<div id='foo'>  
  foo  
</div>

Then we can write the following code to attach a new p element to the div with ID foo:

const foo = document.querySelector('#foo');  
const bar = document.createElement('p');  
bar.textContent = 'bar';
foo.appendChild(bar);

In the code above, we used createElement to create a new p element. Then we set the textContent property to add text inside the p element. At the end, we can appendChild on foo with bar as the argument to attach bar as the child of foo .

cloneNode

The cloneNode method clones a Node object and optionally, all of its content. It doesn’t clone all the content of the Node by default.

It takes one argument, which is an optional argument indicate if it’s a deep clone, which means everything will be cloned. true means do a deep clone and false otherwise.

For example, given the following HTML:

<div>  
  foo  
</div>

We can write the following JavaScript code to clone the div and then attach it to the body element as the last child:

const fooDiv = document.querySelector('div');  
const fooClone = fooDiv.cloneNode(true);  
document.body.appendChild(fooClone);

We pass in true to the cloneNode method to clone everything. Then we call appendChild on document.body with the cloned object passed in as the argument to add it as the child of body.

compareDocumentPosition

The compareDocumentPosition method compares the position of the given node against another node in any document. It takes a DOM Node object as its argument.

It returns a bitmask with the following possible values

  • DOCUMENT_POSITION_DISCONNECTED — 1
  • DOCUMENT_POSITION_PRECEDING — 2
  • DOCUMENT_POSITION_FOLLOWING — 4
  • DOCUMENT_POSITION_CONTAINS — 8
  • DOCUMENT_POSITION_CONTAINED_BY — 16
  • DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC — 32

For example, given the following HTML:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

We can write the following JavaScript to compare the position of the div with ID foo and the one with ID bar:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
const relativePos = foo.compareDocumentPosition(bar);  
console.log(relativePos);

The code above should get us 4 for relativePos, which means the element with ID bar follows the element with ID foo.

contains

The contains method checks if a DOM node is inside the given node. It takes one argument, which is a Node object that we want to check if it’s inside the one that this method is called on.

It returns true if the node in the argument is inside the one that’s called and false otherwise.

For example, given the following HTML:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

Then we can write the following JavaScript to check if the div with ID bar is inside the div with ID foo:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
const fooContainsBar = foo.contains(bar);  
console.log(fooContainsBar);

Of course, fooContainsBar should be false since the div with ID foo isn’t inside the div with ID bar.

On the other hand, if we have the following HTML instead:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>
</div>

Then with the same JavaScript code as the first example, fooContainsBar should true since div with ID foo is inside the div with ID bar.

getRootNode

The getRootNode method returns the Node object’s root, which optionally includes the shadow root if it’s available.

It takes an optional argument with an object with the following properties:

  • composed — a boolean that indicates rather the shadow root should be included. It defaults to false

It returns the Node that either returns an element that’s the root of the given Node or the shadow root will be returned for elements inside the shadow DOM.

For example, if we have the following HTML:

<div id='foo'>  
  foo  
</div>

Then we can call the getRootNode method as follows:

const foo = document.querySelector('#foo');const root = foo.getRootNode();  
console.log(root);

We should get the HTML document as the root node since it’s not inside a shadow DOM.

The root will be the shadow root for Web Components. For example, if we have the following web component:

customElements.define('custom-p',  
  class extends HTMLElement {  
    constructor() {  
      super(); const pElem = document.createElement('p');  
      pElem.id = 'p-element';  
      pElem.textContent = 'shadow p' 
      const shadowRoot = this.attachShadow({  
        mode: 'open'  
      });  
      shadowRoot.appendChild(pElem);  
    }  
  }  
);

And we have the Web Component added in the HTML code:

<custom-p></custom-p>

Then we can get the root node of the element with the ID p-element by writing:

const pElement = document.querySelector('custom-p').shadowRoot.querySelector('#p-element');  
const rootNode = pElement.getRootNode();  
console.log(rootNode);

First, we get the custom element, then we get the shadowRoot property which lets us access the shadow DOM of our custom-p web component. Then we can get the p element with the ID p-element with the shadow root.

After that, we get the root node of it by callinggetRootNode on pElement which represents p element with the ID p-element .

The console.log should get us the shadow root of the Web Component.

Conclusion

With these DOM traversal methods, we can set the nodes however we like for simple situations. Also, there’re methods to check whether an element is the child of another one, and also to get the root node of the given element.

Categories
JavaScript JavaScript Basics

Useful Lodash Array Functions — Extraction and Intersection

Lodash is a utility library that has lots of methods for manipulating objects. It has stuff that we use all the time and also things that we don’t use frequently or don’t think of using.

In this article, we’ll look at more useful Lodash array methods, including the head , indexOf , initial , intersection , intersectionBy , and intersectionWith methods.

head

The head method gets the first element of an array and returns it. first is an alias of head .

It takes an array as its only argument.

We can use it as follows:

import * as _ from "lodash";  
const array = [1, 2, 3];  
const result = _.head(array);  
console.log(result);

Then we get 1 for result . head returns undefined is the array passed in is empty.

indexOf

indexOf gets the index of the first occurrence of the item found in the array that’s passed into the argument and returns it.

It takes up to 3 arguments. The first is the array to search. The second argument is the value to look for. The third is an optional argument for the index to start the search from.

For example, we can use it as follows:

import * as _ from "lodash";  
const array = [1, 2, 3];  
const result = _.indexOf(array, 2, 1);  
console.log(result);

Then we get 1 since 2 is in the 2nd position and we search from the start.

initial

The initial method gets all but the last element of an array and returns it.

It takes an array as its only argument.

For example, we can use it as follows:

import * as _ from "lodash";  
const array = [1, 2, 3];  
const result = _.initial(array);  
console.log(result);

Then we get [1, 2] for result .

intersection

The intersection method returns an array of values that are included in all given arrays. Equality comparison is done with the SameValueZero comparison which is the same as === except that NaN is considered equal to itself.

It takes a comma-separated list of arrays to return the intersection from.

For example, we can use it as follows:

import * as _ from "lodash";  
const arr1 = [1, 2, 3];  
const arr2 = [3, 4, 5];  
const result = _.intersection(arr1, arr2);  
console.log(result);

Then we get [3] since only 3 are present in both arr1 and arr2 .

intersectionBy

intersectionBy is like intersection except that it takes a function which is invoked for each element of each array to generate the criterion for which items are compared. The first array determines the order and reference of results.

It takes a comma-separated list of arrays as arguments and a function for the criterion to compare to find the intersection with or a string with the property name to compare.

For example, we can use it as follows:

import * as _ from "lodash";  
const arr1 = [  
  { name: "Joe", age: 10 },  
  { name: "Mary", age: 12 },  
  { name: "Jane", age: 13 }  
];  
const arr2 = [  
  { name: "Joe", age: 10 },  
  { name: "Jerry", age: 12 },  
  { name: "Amy", age: 13 }  
];  
const result = _.intersectionBy(arr1, arr2, a => a.name);  
console.log(result);

Then we get:

[  
  {  
    "name": "Joe",  
    "age": 10  
  }  
]

for result .

We can replace a => a.name with 'name' as follows:

import * as _ from "lodash";  
const arr1 = [  
  { name: "Joe", age: 10 },  
  { name: "Mary", age: 12 },  
  { name: "Jane", age: 13 }  
];  
const arr2 = [  
  { name: "Joe", age: 10 },  
  { name: "Jerry", age: 12 },  
  { name: "Amy", age: 13 }  
];  
const result = _.intersectionBy(arr1, arr2, "name");  
console.log(result);

Then we get the same thing.

intersectionWith

intersectionWith is like intersection except that it takes a comparator function as the last argument. The parameters for the function are 2 entries of from the arrays to compare against.

It takes a comma-separated list of arrays as arguments and the comparator function as the last argument.

For example, we can use it as follows:

import * as _ from "lodash";  
const arr1 = [  
  { name: "Joe", age: 10 },  
  { name: "Mary", age: 12 },  
  { name: "Jane", age: 13 }  
];  
const arr2 = [  
  { name: "Joe", age: 10 },  
  { name: "Jerry", age: 12 },  
  { name: "Amy", age: 13 }  
];  
const result = _.intersectionWith(arr1, arr2, (a, b) => a.name === b.name);  
console.log(result);

Then we get:

[  
  {  
    "name": "Joe",  
    "age": 10  
  }  
]

for result .

The head method gets the first element of an array and returns it. first is an alias of head .

indexOf gets the index of the first occurrence of the item found in the array that’s passed into the argument and returns it. It can take a starting index to search from which defaults to 0.

initial gets all but the last element of an array and returns it.

To find array entries that are in all arrays, we can use the intersection , intersectionBy , and intersectionWith methods. They differ in that they may take functions for the criterion to compare or a comparator method to compare for equality respectively.