HTML Jquery

HTML Jquery

HTML Jquery Core Element Selectors

In this first section on the topic of finding HTML elements in a document, I discuss selecting elements by using some of the more traditional element properties, such as ID, class, and tag name. Here, I compare element selection in jQuery with “vanilla” JavaScript through examples that interface directly with the DOM by making use of the functionality codified in various web API specifications.

 

After completing this section, you will have the necessary confidence and understanding to select elements in the DOM using the most common methods—without relying on jQuery at all.

 

IDs

The W3C HTML4 specification1 defines the id attribute as one that must be unique among all IDs defined inside of a document. This part of the specification goes on to describe its primary uses, such as element selection and navigation to other sections of a page using anchor links.  The DOM Level 1 specification defines the HTML Element interface, from which all other elements inherit from. The id property is defined in this interface, which is directly connected to the id attribute defined on the corresponding element in the markup.

For example, consider the following markup: <div  id="my-element-id"></div>

 

The <div> element’s id attribute is also accessible via the JavaScript representation of the element. This is exposed by the element object’s id property:

//  `theDiv`  is  the  <div>  from  our  sample  HMTL  above

   theDiv.id  ===  'my-element-id';  //  returns  true

 

jQuery

In jQuery-land, obtaining a handle on the <div> element object looks something like Listing 1.

Select by ID: jQuery
// returns a jQuery object with 1 element -
// the <div> from our sample HMTL above 3 var result = $('#my-element-id');
// assuming our element has been found in the document 6 result.is('#my-element-id'); // returns true

 

In the jQuery example, we are using the ID selector string, which was first defined in the W3C CSS1 specification. The jQuery object returned by this selection attempt is a pseudo-array. This pseudo-array contains the HTMLElement object representation of this element in the document.

 

Web API

Web API

Selecting the same exact element without the help of jQuery is surprisingly easy, and in fact the code to achieve this looks surprisingly similar. There are two different ways to select an element by ID using the web API. The first such method involves using the getElementById method defined on the Document interface, first formalized in the DOM Level 2 Core specification. This method is supported in all browsers in existence:

// returns the matching HTMLElement - the <div> from our sample
var result = document.getElementById('my-element-id');
// assuming our element has been found in the document
result.id === 'my-element-id'; // returns true

 

A second approach makes use of the querySelector method, which was first defined on both the Document and Element interfaces in the W3C Selectors API Level 1 specification.6 Remember that the HTMLElement interface, on which the id attribute is defined, inherits from the Element interface, so Elements have an id property as well. The querySelector method is available in all modern browsers, as well as Internet Explorer 8.

 

In Listing 2, you will start to notice some stark similarities between the native approach and the jQuery shortcut.

Select by ID: Web API, Modern Browsers and Internet Explorer 8
// returns the matching HTMLElement - the <div> from our sample
var result = document.querySelector('#my-element-id');
// assuming our element has been found in the document 5 result.id === 'my-element-id'; // returns true

 

Performance Note querySelector is a bit slower than getElementById, but this performance gap is closing as browser JavaScript engines evolve.

 

Classes

Contrary to the focus of IDs, class attributes do not uniquely identify an element in a document. Instead, classes are traditionally used to semantically group elements in the context of an application as a whole. While IDs can certainly be used to style elements via CSS, this role is most often tied to class attributes.

Elements may also be assigned multiple class names, while they are limited to one ID (for obvious reasons). The HTML 4.01 specification goes into more detail regarding the role of class attributes.

 

Generally speaking, a valid CSS class is case-insensitive, can only contain alphanumeric characters or hyphen or underscore, and may not start with a digit or two hyphens or a hyphen and a digit. These rules also apply to IDs, along with other element properties used to target elements via CSS. You can read about all of the allowed values in CSS selectors in the CSS 2.1 specification.

For example, consider the following markup: <span  class="some-class"></span>

 

The span element’s class attribute is also accessible via the JavaScript representation of the element on the object’s className property. Notice the inconsistency here—the attribute name is class, whereas the corresponding Element property is className. This is due to the fact that class is a reserved word in many languages, such as JavaScript (even as late as the ECMAScript 5.1 edition specification), which is why an alternate name exists in the JavaScript representation of an element. For example:

//  `elementObject`  is  the  <span>  in  our  sample  markup  above

   elementObject.className  ===  'some-class';  //  returns  true

 

Selecting an element by class in jQuery 

jQuery

 

Selecting an element by class in jQuery looks very similar to the approach used to select an ID. In fact, all element selection in jQuery follows the same pattern:

/ Returns a jQuery object with 0 elements (element not found)
// or all elements with the 'some-class' class attribute. 3 var result = $('.some-class');
// assuming our element has been found in the document 6 result.is('.some-class'); // returns true

 

If there happen to be three different elements in the document with a class name of some-class, the result jQuery object will have three entries, one for each match.

 

Web API

As with IDs, there are several different way to select elements by class name using the web API. I will demonstrate two of them—both available in modern browsers as well as Internet Explorer 8 (the last example). Listing 3 is the most performant, but Listing is clearly the most elegant.

Select by Class: Web API, Modern Browsers
// Returns an HTMLCollection containing all matching elements,
// which is empty if there are no matches.
var result = anyElement.getElementsByClassName('some-class');
// assuming our element has been found in the document 6 result[0].className === 'some-class'; // returns true

 

The first noticeable difference between getElementById and getElementsByClassName is the fact that the latter returns an array-like object containing all matching elements, instead of a single element. Remember, a document can contain many elements that share the same class name.

 

You may also notice another difference, one which may not be particularly obvious in the simple example provided. The getElementsByClassName method is available on the Document interface, just like getElementById. However, it is also defined to be a method on the Element interface in the W3C DOM4 specification. 

 

This means that you may restrict your query to a specific subset of elements when looking for class name matches by specifying an element in the document. When executed on a specific element, only descendant elements are examined for matches. This allows for more focused and efficient DOM traversal.

 

The getElementsByClassName method’s return value is an HTMLCollection, which is a pseudo-array, and it provides sequentially ordered numeric properties (0, 1, 2, . . .), one for each matching element, along with a length property and some other methods (of limited usefulness). The most notable attribute of an HTMLCollection is the fact that it is a live collection.

 

That is, it is updated automatically to match the underlying elements in the DOM that it represents. For example, if an element contained in the returned HTMLCollection is removed from the DOM, it will also be removed from any HTMLCollection in scope. Note that getElementsByClassName is defined in the W3C DOM4 specification.

 

A second approach, shown in Listing 4, to selecting elements by class name involves a cousin to the previously demonstrated querySelector.

 Select by Class: Web API, Modern Browsers and Internet Explorer 8

//  Returns  a  NodeList  containing  all  matching  elements,
   //  which  is  empty  if  there  are  no  matches.
   var  result  =  anyElement.querySelectorAll('.some-class');

 // assuming our element has been found in the document 6 result[0].className === 'some-class'; // returns true

 

Like getElementsByClassName, querySelectorAll returns all matches in an array-like object. The differences end there, though. For one, querySelectorAll returns a NodeList object, an interface which was first formally defined in the W3C DOM Level 3 Core specification. NodeList differs in one important way from an HTMLCollection: it is not a “live” collection. So, if a matching element contained in a NodeList is removed from the DOM, it will not be removed from any NodeList.

 

Element Tags

Element Tags

Web Components is a collection of specifications, one being the custom elements specification, which details a way to create new HTMLElements with their own API and properties, or even extensions of existing elements—such as the ajax-form custom element,which extends and adds features to a native <form>.

 

To set up our examples, consider the following very simple HTML block:

<code>System.out.println("Hello  world!");</code>

 

If you are given an element reference, you can easily determine the element’s name via the tagName property, which is defined on the Element interface as part of DOM Level 1 Core: 

//  `elementObject`  is  the  <code>  element  from  our  above  HTML

   elementObject.tagName  ===  'CODE';  //  returns  true

 

Selecting elements by jQuery

Selecting elements by jQuery is, predictably, facilitated by passing a CSS element selector into the $ or jQuery function:

// Returns a jQuery object with 0 elements (element not found)
// or all elements with a matching tag name.
var result = $('CODE');
// assuming our element has been found in the document 6 result.is('CODE'); // returns true

Nothing magical here. In fact, the syntax for an element name selector string is defined in the first CSS specification. jQuery has simply provided a simple alias for the native methods available to select elements by tag name, explored next.

 

Web API

Let’s start with a quick look at the traditional method for selecting elements by tag name by interfacing directly with the native web API:

// Returns a HTMLCollection containing all matching elements,
// which is empty if there are no matches.
var result = anyElement.getElementsByTagName('CODE');
// assuming our element has been found in the document 6 result[0].tagName === 'CODE'; // returns true

 

The preceding method has been available as early as DOM Level 1 Core, and, like getElementsByClass-Name, is available on both the Document interface and the Element interface. So, this approach is available on all browsers in existence.

 

A more “modern” approach involves, as you might expect, querySelector or querySelectorAll:

// Returns a NodeList containing all matching elements,
// which is empty if there are no matches.
var result = anyElement.querySelectorAll('CODE');
// assuming our element has been found in the document
result[0].tagName === 'CODE'; // returns true
// ...you can use this if you know there is only one <code>
// element to find, or if you only care about the first.
// Returns true.
anyElement.querySelector('CODE').tagName === 'CODE';

 

There is currently a potentially noticeable performance difference between getElementsByTagName and querySelectorAll(tagName). The performance consequences of using querySelectorAll are apparently attributable to the fact that getElementsByTagName returns a live collection of matching elements in the DOM (an HTMLCollection), while querySelectorAll returns a static collection (a NodeList).

 

The latter requires iterating over all elements in the DOM, while the former returns cached matching elements and then queries the document for updates when the list is accessed.This performance difference is similar to that of getElementsByClassName versus query- SelectorAll(classSelector) for the same reason.

 

Pseudo-classes

Pseudo-classes

While the prevalence and number of pseudo-classes has grown substantially in recent versions of the CSS specification, pseudo-classes have existed since the earliest versions of the CSS specification Pseudo-classes are keywords that add state to a selector string or group of elements. For example, the :visited pseudo-class on an anchor selector string will target any links that the user has already visited.

 

Another example, the :focus pseudo-class will target the element that is determined to have focus, such as a text input field that the user is currently interacting with. We will use the latter in our following examples, as browsers prevent programatic selector access in JavaScript to visited links due to privacy concerns.

 

To set up our examples, let’s create a simple form with a couple text inputs, and imagine that the user has clicked on (or tabbed to) the last text input (named “company”). This last input will be the one that is “focused”:

<form>
<label>Full Name
<input name="full-name">
</label>
<label>Company
<input name="company">
</label>
</form>
jQuery
Say we want to select the input that is currently focused, using jQuery:
// Return value will be a jQuery object containing the
// "company" input element
var focusedInputs = $('INPUT:focus');

The preceding is, once again, a standardized CSS selector string. We are making use of a tag name selector with a pseudo-class modifier. jQuery isn’t doing anything special for us at all. In fact, it’s simply delegating directly to the web API.

 

Web API

Consider the following:

//  Return  value  will  be  the  "company"  text  input  field  element

   var  companyInput  =  document.querySelector('INPUT:focus');

 

That code avoids all of the overhead associated with filtering the call through jQuery. If we were to use jQuery instead (as we have done in the previous example) querySelectorAll would have been invoked internally by jQuery’s selector code with the exact same selector string.

Since only one element can have focus at once, querySelector is more appropriate than querySelectorAll. And it’s also a bit faster, for the same reason why any of the getElementsBy methods are faster than querySelectorAll.

 

Selecting Elements Based on Their Relations

Selecting Elements Based on Their Relations

The DOM is organized as a tree-like structure. With this in mind, it is often advantageous to be able to navigate this hierarchy of nodes with relations in mind. Just as we already witnessed in the core selectors section, finding elements based on their relations is fairly straightforward and more performant without jQuery.

 

Parents and Children

Remember from our discussion on the DOM API that an Element is a specific type of Node. A Node or Element may have zero children if it is a “leaf” node. Otherwise, it will have one or more immediate children. But every Node or Element in a document has exactly one immediate parent. Well, almost.

 

There are two exceptions to this rule: one occurs with the <html> tag (HTMLHtmlElement) which is the root Element in a document, and therefore has no parent Element (though it does have a parent Node: document). This brings us to the second exception, the document object ( Document), which has neither a parent Node nor a parent Element. It is the root Node.

 Example Markup for Parent/Children Traversal Examples
<div>
<a href="http://fineuploader.com">
<span>Go to Fine Uploader</span>
</a>
<p>Some text</p>
Some other text
</div>

 

In the following code examples, a distinction will be made between targeting child/parent Nodes and Elements. If this distinction is not already clear, first understand that Listing 5 is made up of Element type object, such as the <div>, the <a>, the <span>, and the <p>. These Elements are also Nodes, since the Element interface is a subtype of the Node interface.

 

But the “Go to Fine Uploader”, “Some text”, and “Some other text” portions of the fragment are not Elements. But they are Nodes. More specifically, they are Text items. The Text interface is a subtype of the CharacterData interface, which itself implements the Node interface.

 

jQuery Parents and Children

jQuery’s API includes a parent method. To keep things simple, we’ll assume that the “current jQuery object” only represents one element. When calling the parent method on this object, the resulting jQuery object will contain either the parent Element, or, in rare instances, a parent Node that is not an Element. See Listing 6.

 

Listing 6. Get Parent Element/Node: jQuery

// Assuming $a is a reference to the anchor in our example HTML,
// $result will contain the <div> above it.
var $result = $a.parent();
// Assuming $span is a reference to the <span> in our example HTML, 6 // the first parent() call references the <a> element, and the
// $result will contain the <div> root element. 8 var $result = $span.parent().parent();
// Assuming someText is a reference to the "Some text" Text node,
var $result = $someText.parent();

 

To locate children, jQuery provides a children() method that will return all immediate child Elements of a given element. You may also select child elements given a reference element using the child selector standardized in the CSS 2.1 W3C specification.

But since children() will only return Elements, we must use jQuery’s contents() API method to obtain any Nodes that are not also Elements, such as Text nodes. Again, to keep this simple, Listing 7 assumes that the reference jQuery object in our example only refers to one specific element in the DOM.

 Get Child Elements and/or Child Nodes: jQuery
// Assuming $div is a jQuery object containing the <div> in our example HTML,
// $result will contain 2 elements: <a> and <p>.
var $result = $div.children();
// $result contains the <p> element in the sample markup 6 var $result = $('DIV > P');
// Again, assuming $div refers to the <div> in our example markup, 9 // $result will contain 3 nodes: <a>, <p>, and "Some other text".
var $result = $div.contents();
// Assuming $a refers to the <a> element in our example markup,
// $result will contains 1 element: <span>.
var $result = $a.children();
// This returns the exact same elements as the previous example.
var $result = $('A > *')

 

Web API

Web API

For the most part, locating the parent of an element/node without jQuery is simple. DOM Level 2 Core was the first specification to define a parentNode property on the Node interface, which, as you might expect, is set to the parent Node of the reference element. Of course, this value may be an Element or any other type of Node.

 

Later on, in the subsequent W3C DOM4 specification, a parentElement property was added to the Node interface. This property will always be an Element.

If the parent of a reference Node is some type of Node other than an Element, the parentElement will be null. But in most cases, parentElement and parentNode will be identical, unless the reference node is <html>, in which case parentNode will be document, and parentElement will be of course null.

 

In a general sense, and especially due to wide browser support, the parentNode property is the best choice, but parentElement is nearly just as safe. See Listing 8.

Get Parent Element/Node: Web API
// Assuming "a" is the <a> element in our HTML example,
// "result" will be the the <div> above it.
var result = a.parentNode;
// Assuming "span" is the <span> element in our HTML example,
// the first parentNode is the <a>, while "result" is the <div> 7 // at the root of our example markup.
var result = span.parentNode.parentNode;
// Assuming "someText" is the "Some text" Text node in our HTML example,
// "result" will be the the <p> that contains it.
var result = someText.parentNode;

 

There are a number of different ways to locate immediate children of an element using the web API.

I will demonstrate two such ways next, and briefly discuss a third approach. The simplest and most common method of locating an element’s children in all modern browsers involves using the children property on the ParentNode interface. ParentNode is defined to be implemented by both the Element and Document interfaces, though it is only commonly implemented on the Element interface.

 

It applies to a Node that may potentially have children. It was first defined in the W3C DOM4 specification and is only available in modern browsers. ParentNode.children returns all children of the reference Node in an HTMLCollection, which you may remember from earlier in this blog represents a “live” collection of Elements:

//  Assuming  "div"  is  an  Element  object  containing  the  <div>  in  our  example  HTML,

 // result will contain an HTMLCollection holding 2 elements: <a> and <p>. 3 var result = div.children;

 

A second method used to locate child Elements involves using querySelectorAll along with the CSS 2 child selector.  This approach allows us to support Internet Explorer 8, in addition to all modern browsers. Remember that querySelectorAll returns a NodeList, which differs from an HTMLCollection in that it is a “static” collection of elements. The collection in this case contains all Element children of the parent Node:

// The result will contain a NodeList holding 2 elements: <a> and <p>
// from our HTML fragment above.
var result = document.querySelectorAll('DIV > *');
// The result will be all <p> children of the <div>, which, in this case 6 // is only one element: <p>Some text</p>.
var result = document.querySelectorAll('DIV > P');

 

A third option used to select children with the web API involves the childNodes property on the Node interface. This property was declared on the original W3C DOM Level 1 Core specification. As a result, it is supported by all browsers, even ancient ones. The childNodes property will reveal all child Nodes, even Text and Comment nodes.

 

You can filter out the non-Element objects in the collection simply by iterating over the results and ignoring any that have a nodeType property that is not equal to 1. This nodeType property was also defined on the original Node interface specification:

//  Assuming  "div"  is  an  Element  object  containing  the  <div>  in

 // our example HTML, result will contain a NodeList 3 // holding 3 Nodes: <a>, <p>, and "Some other text". 4 var result = div.childNodes;

 

Given a parent Node, you may also locate either the first and last child, via the aptly named firstChild and lastChild properties, respectively. Both properties have existed since the original Node interface specification, and they refer to child Nodes, so the first or last child may be a Text Node or an HTMLDivElement, for example.

The firstChild property can be used as part of a fourth method of obtaining the children of a parent Node. This approach is discussed as part of the sibling element selection section below.

 

Siblings

find all sibling Elements jQuery

DOM Nodes are siblings if they share the same immediate parent. They may be adjacent siblings (next to each other) or “general” siblings (not necessarily next to each other). There are a number of ways to find and navigate among sibling Nodes.

 

While I will go over how this is done using jQuery for the purpose of reference, you will see just how easy it is to do this without jQuery as well. Listing 9 will be used as a reference point for all demonstration code.

 Working with Siblings: Markup for Following Demos

<div  id="parent">

<a  href="https://github.com/rnicholus">GitHub</a>
<span>Span  text</span>
<p>Paragraph  text</p>
<div>Div  text</div>
Text  node
   </div>

 

find all sibling Elements jQuery

find all sibling Elements jQuery

 

To find all sibling Elements of a given Element, jQuery provides a siblings method as part of its API. For traversing through the siblings of a given Element, there are next() and prev() methods as well. To keep things simple, I’ll simply review how we have all used jQuery to find and traverse through the siblings of a given element, starting with Listing 10

 Find and Traverse Through Siblings: jQuery
// $result will be a jQuery object that contains <a>, <span>, <p>,
// and <div> elements inside of the #parent <div>.
var $result = $('SPAN').siblings();
// $result will be a jQuery object that contains the <a> element 6 // that precedes the <span>.
var $result = $('SPAN').prev();
// The first next() refers to the <p>, and the 2nd next()
// refers to the <div>Div text</div> element, which is also
// the element contained in the jQuery $result object.
var $result = $('SPAN').next().next();
// The first next() refers to the <p>, and the 2nd next()
// refers to the <div>Div text</div> element. The final next()
var $result = $('SPAN').next().next().next();

You can also use CSS sibling selectors in jQuery, which we explore a bit in the next section. jQuery actually permits standardized W3C CSS selectors strings for this and other operations.

 

Web API

To mirror the behaviors provided by jQuery’s API, I’ll cover the following topics associated with sibling traversal and discovery:

  • Locating all siblings of a specific Element or Node.
  • Navigating through preceding and subsequent siblings of a specific Element or Node.
  • Locating general and adjacent siblings of an Element using CSS selectors.
  • Locating children using the sibling properties on the Node interface.

The simplest way to locate all sibling Elements of another Element is to make use of the CSS3 general sibling selector. This approach will work as far back as Internet Explorer 8, and provides you with a NodeList of all sibling Elements.

 

The W3C CSS2 specification defined an “adjacent” sibling selector, which only selects the first Element matching the selector that occurs after the reference element. Both of the sibling selectors described here are demonstrated in Listing 11.

 

Listing 11. Find Siblings Using CSS Selectors: Web API, Modern Browsers, and Internet Explorer 8

// "result" contains a NodeList of all siblings that occur after the <span>
// in our example HMTL at the start of this section. These siblings are 3 // the <p> and the <div> elements.
var result = document.querySelectorAll('#parent > SPAN ~ *');
// Another general sibling selector that specifically targets any 7 // subsequent siblings of the <span> that are <div>s. In our case, 8 // there is only one such element - <div>Div text</div>. The
// "result" variable is a NodeList containing this one element.
var result = document.querySelectorAll('#parent > SPAN ~ DIV');
// This is an adjacent sibling selector in action. It will target
// the first sibling after the <span>. So, "result", is the same
// as in the previous general sibling selector example.
var result = document.querySelector('#parent > SPAN + *');

 

You’ll notice that the general sibling selector (∼) does not select any elements that precede the reference element—only the ones that follow it. If you do need to account for any siblings that come before the reference element, you’ll need to make use of either the Node.previousSibling property first defined in W3C DOM Level 1 Core46 or the previousElementSibling property, which is part of the ElementTraversal interface47 first defined in the W3C Element Traversal specification.

 

ElementTraversal is an interface that is implemented by any object that also implements the Element interface. Simply put, all DOM elements have a previousElementSibling property. This is demonstrated in Listing 12.

 

Listing 12. Find Both Preceding and Subsequent Siblings of a Reference Element: Web API, Modern Browsers

// Find all siblings that follow the <span> in our example HTML
var allSiblings = document.querySelectorAll('#parent > SPAN ~ *');
// Converts the allSiblings NodeList into an Array.
allSiblings = [].slice.call(allSiblings);
var currentElement = document.querySelector('#parent > SPAN');
do {
currentElement = currentElement.previousElementSibling;
currentElement && allSiblings.unshift(currentElement);
} while (currentElement);

 

Note Another approach may be to select the parent of the reference element, then collect its children, omitting the reference element. The code in this section was created specifically to demonstrate some standard CSS selectors and element properties.

 

For Internet Explorer 8 support, you will have to use Node.previousSibling instead of Element. previousElementSibling. This is due to lack of support for the Element Traversal spec in any version of Explorer older than . This property returns any Node, so you will want to be sure you add a nodeType property check if you only want to accept Elements. See Listing 13.

 

Listing 13. Find Both Preceding and Subsequent Siblings of a Reference Element: Web API, Modern Browsers, and Internet Explorer 8

var allSiblings = document.querySelectorAll('#parent > SPAN ~ *');
// Converts the allSiblings NodeList into an Array.
var allSiblings = [].slice.call(allSiblings);
var currentElement = document.querySelector('#parent > SPAN');
do {
currentElement = currentElement.previousSibling;
// This differs from the previous example in that we must
// exclude non-Element Nodes by examining the nodeType property.
if (currentElement && currentElement.nodeType === 1) {
allSiblings.unshift(currentElement);
}
} while (currentElement);

 

The web API also exposes a nextSibling property on the Node interface and a nextElementSibling property on the ElementTraversal interface. As Listing 14 shows, browser support of these properties is identical to their “previous” cousins.

 

Listing 14. Traverse Through All Subsequent Siblings: Web API, Modern Browsers, and Internet Explorer 8

// The first nextSibling refers to the <p>, and the 2nd nextSibling
// refers to the <div>Div text</div> element. The final nextSibling 3 // refers to the "Text node" Text Node, since nextSibling targets 4 // any type of Node. So, the result is this Text Node.
var result = document.querySelector('SPAN')
.nextSibling.nextSibling.nextSibling;
// Same as the above example, but the final nextElementSibling returns null,
// since the last Node in the example markup is not an Element. There are only
// 2 Element siblings following the <span>. Note that nextElementSibling
// is not available in ancient browsers.
var result = document.querySelector('SPAN')
.nextElementSibling.nextElementSibling.nextElementSibling;

 

In addition to the methods used to select children outlined in the previous section using the web API, another such option exists to select only Element children of a parent Node in any browser. This involves obtaining the firstChild of the parent Node, locating for the sibling Node of this first child, and then continuing to traverse through all sibling elements using the nextSibling property on each Node until there are no remaining siblings.

And finally, to exclude all non-Element siblings (such as Text Nodes), simply check the nodeType property of each Node, which will have a value of 1 if the Node is more specifically an Element. This is how jQuery implements its children method, at least in the late 1.x versions of the library.

 

This implementation choice is likely due to the fact that all of these properties on the Node interface have wide browser support, even among ancient browsers. However, there are much simpler approaches that are supported in modern browsers, so the path just described is really only relevant from an academic or historic perspective.

 

Ancestors and Descendants

To illustrate the ancestor/descendant Node relationship, let’s start out with a brief HTML fragment:

<body>
<div>
<span>random text</span>
<ul>
<li>
<span>item 1</span>
</li>
<li>
<a href="#some-content">item 2</a>
</li>
</ul>
</div>
</body>

 

An element’s ancestors are any elements that appear before it in the DOM. That is, its parent, its parent’s parent (or grandparent), its parent’s parent’s parent (great-grandparent), and so on. In the preceding HTML fragment, the anchor element’s ancestors include its immediate parent (the <li>), along with the <ul>, <div>, and finally the <body> element.

Conversely, an element’s descendants include its children, its children’s children, and so on. In the preceding markup, the <ul> element has four descendants: the two <li> elements, the <span>, and the <a>.

 

Retrieve elements jQuery

Retrieve elements jQuery

jQuery’s API provides a single method used to retrieve all of an element’s ancestors - parents():

//  Using  our  HTML  example,  $result  is  a  jQuery  object  that
   //  contains  the  following  elements:  <li>,  <ul>,
   //  <div>,  and  <body>
   var  $result  =  $('A').parents();

 

But what if you only want to retrieve the first ancestor that matches a specific condition? In our case, say we are only looking for the first ancestor of the <a> that is also a <div>. For that, we would use jQuery’s closest() method. jQuery implements closest() by brute-force—through examination of each parent of the reference Node:

// Using our HTML example, $result is a jQuery object that
// contains the <div> element.
var $result = $('A').closest('DIV');
For locating descendants, you may use jQuery’s find() method:
// Using our HTML example, $result is a jQuery object that
// contains the following elements: both <li>s, the <span>, 3 // and the <a>.
var $result = $('UL').find('*');
// $result is a jQuery object that contains the <span> 7 // under the first <li>.
var $result = $('UL').find('SPAN');

 

Web API

The native web does not provide a single API method that returns all ancestors of an element. If this is required in your project, you can accumulate these Nodes with a simple loop, making use of the Node. parentNode property or Node.parentElement. Remember that the latter targets only a specific type of Node: an Element. This is usually what we want anyway, so we will make use of parentElement in our examples. See Listing 15.

Listing 15. Retrieve All Element Ancestors: Web API, Any Browser
// When this code is complete, "ancestors" will contain all
// ancestors of the anchor element: <li>, <ul>,
// <div>, and <body>
var currentNode = document.getElementsByTagName('A')[0],
ancestors = [];
while (currentNode.parentElement) {
ancestors.push(currentNode.parentElement);
currentNode = currentNode.parentElement;
}

 

We already know that jQuery provides a method that allows us to easily find the first matching ancestor of an element, closest. The web API has a similar method on the Element interface, also called closest. Element. closest() is part of the WHATWG DOM “living standard”. This method behaves exactly like jQuery’s closest().

 

Browser support for this method is missing from any version of Internet Explorer and Microsoft Edge as of mid-2016, but is supported in Chrome, Firefox, and Safari 9. In the next example, I demonstrate how the web API’s closest() method can be used, and I even include a simple fallback for browsers without native support. Let’s again use our example markup and try to locate the closest ancestor of the <a> that is a <div>. See Listings 16 and 17.

 

Listing 16. Retrieve Closest Element Ancestor: Web API, All Modern Browsers Except IE and Edge

function closest(referenceEl, closestSelector) {
// use Element.closest if it is supported
if (referenceEl.closest) {
return referenceEl.closest(closestSelector);
}
var matches = Element.prototype.matches ||
Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector,
currentEl = referenceEl;
while (currentEl) {
if (matches.call(currentEl, closestSelector)) {
return currentEl;
}
currentEl = currentEl.parentElement;
}
return null;
}
// "result" is the <div> that exists before the <a>
var result = document.querySelector('A').closest('DIV');

 

Listing 17. Retrieve Closest Element Ancestor: Web API, All Modern Browsers

function closest(referenceEl, closestSelector) {
// use Element.closest if it is supported
if (referenceEl.closest) {
return referenceEl.closest(closestSelector);
}
var matches = Element.prototype.matches ||
Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector,
currentEl = referenceEl;
while (currentEl) {
if (matches.call(currentEl, closestSelector)) {
return currentEl;
}
currentEl = currentEl.parentElement;
}
return null;
}
var result = closest(document.querySelector('A'), 'DIV');

 

Note that the cross-browser solution makes use of Element.matches, which is also defined by WHATWG in their DOM living spec. This method will return true if the element it is called on matches the passed CSS selector. Some browsers, namely IE and Safari, still implement a naming convention consistent with an older version of the specification along with vendor-specific prefixes. I’ve accounted for these in my example.

 

The preceding solution may be less elegant, but it makes better use of the browser’s native power. jQuery’s closest() function always uses the most primitive brute-force approach, even if the browser supports Element.closest natively.

 

Finding descendants using the web API is just as easy as with jQuery (Listing 18).

 

Listing 18. Retrieve Element Descendants: Web API, Modern Browsers, and Internet Explorer 8

// Using our HTML example, result is a NodeList that contains the following elements: the two <li>s, <span>, and <a>.
var result = document.querySelectorAll('UL *');
// "result" is a NodeList that contains the <span> 7 // under the first <li>.
var result = document.querySelectorAll('UL SPAN');

 

Mastering Advanced Element Selection

What follows are some more advanced methods used to select even more specific elements or groups of elements. While jQuery provides API methods to deal with each scenario, you’ll see that modern web specifications also provide the same support, which means that jQuery is not needed in modern browsers for any of these examples. Web API Solutions here will mostly involve the use of various CSS3 selectors,54 which are also usable from jQuery.

 

All the native examples in this section are supported in all modern browsers. In some cases, I also touch on how to achieve the same goals using the web API in ancient browsers as well. Should you find yourself needing some of the following selectors in support of an ancient browser, perhaps you’ll forgo pulling in jQuery after understanding how to approach the problem using the browser’s native tools instead.

Or not, but at least the solution will shed some light on jQuery’s inner-workings, which is still beneficial if you insist on making it part of your core toolset.

 

Excluding Elements

Although the ability to exclude specific matches in a set is part of the jQuery API, we’ll also see how we can achieve the same results natively with another appropriately named pseudo-class. Before we dive into code, let’s consider the following HTML fragment:

<ul  role="menu">

<li>choice  1</li>

<li  class="active">choice  2</li>
<li>choice  3</li>
   </ul>

 

Imagine this is some sort of menu, with three items to choose from. The second item, “choice 2”, is currently selected. What if you want to easily gather all of the menu items that are not selected?

 

Remove any elements jQuery

Remove any elements jQuery

jQuery’s API provides a not() method that will remove any elements that match a selector from the original set of elements:

// $result is a jQuery object that contains all
// `<li>`s that are not "active" (the first and last).
var $result = $('UL LI').not('.active');

Although the preceding example is idiomatic jQuery, you don’t have to use the not() function. Instead, you can make use of a CSS3 selector, discussed next.

 

Web API

The native solution for modern browsers is arguably just as elegant as jQuery’s, and certainly just as easy. Below, we are using the W3C CSS3 negation pseudo-class to locate the non-active list items. There is no library overhead, so this is of course more performant than jQuery’s implementation:

//  "result"  is  a  NodeList  that  contains  all

//  `<li>`s  that  are  not  "active"  (the  first  and  last).

var  result  =  document.querySelectorAll('UL  LI:not(.active)');

 

But what if we still need to support Internet Explorer 8, which unfortunately does not support the negation pseudo-class selector? Well, the solution isn’t as elegant, but still not particularly difficult if we need a quick fix and don’t want to pull in a large library:

 style="margin: 0px; width: 960px; height: 134px;">var allItems = document.querySelectorAll('UL LI'),
result = [];
// "result" will be an Array that contains all
// `<li>`s that are not "active" (the first and last). 6 for (var i = 0; i < allItems.length; i++) {
if (allItems[i].className !== 'active') {
result.push(allItems[i]);
}
}

The preceding solution is still more performant than jQuery’s implementation of the not() method.

 

Multiple Selectors

Suppose you want to select several groups of disparate elements. Consider the following HTML fragment:

<div id="link-container">
<a href="https://github.com/rnicholus">GitHub</a> 3 </div>
<ol>
<li>one</li>
<li>two</li> 7 </ol>
<span class="my-name">Ray Nicholus</span>

What if you want to select the “link-container” and the “my-name” element, along with the ordered list? Let’s also assume you want to accomplish this without loops—in one simple line of code.

 

Multiple Selectors jQuery

jQuery allows you to select multiple unrelated elements simply by providing one long comma-separated string of CSS selectors:

//  $result  is  a  jQuery  object  that  contains  3  elements  -
 // the <div>, <ol> and the <span> from this section's 3 // HTML fragment.
   var  $result  =  $('#link-container,  .my-name,  OL');

 

Web API

The exact same result can be obtained without jQuery, using the web API. And the solution looks eerily similar to the jQuery solution. jQuery is, in this case and many others, simply a very thin wrapper around the web API. jQuery fully supports and uses the CSS specification to its advantage.

 

The ability to select multiple unrelated groups of elements has always been part of the CSS specification. Since jQuery supports standard CSS selector strings, the jQuery approach looks nearly identical to the native path:

//  "result"  is  a  NodeList  that  contains  3  elements  -

 // the <div>, <ol> and the <span> from this section's 3 // HTML fragment.

var  result  =  document.querySelectorAll('#link-container,  .my-name,  OL');

 

Element Categories and Modifiers

Element Categories and Modifiers

 

jQuery’s API provides quite a few of its own proprietary CSS pseudo-class selectors, such as :button, :submit, and :password. In fact, jQuery’s documentation for these non-standard selectors advises against using them, due to the fact that there are much more performant alternatives—standardized CSS selectors. For example, the jQuery API docs for the :button pseudo-class contain the following warning:

 

Because :button is a jQuery extension and not part of the CSS specification, queries using :button cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method.

 

I’ll demonstrate how to mimic the behavior of a few of jQuery’s own pseudo-classes using querySelectorAll. These solutions (shown in Listings 19 and 20) will be far more performant than using the non-standard jQuery selectors. We’ll start with :button, :submit, :password, and :file.

 

Listing 19. Implementing jQuery’s :button Pseudo-class: Web API, Modern Browsers, and Internet Explorer 8

// "result" will contain a NodeList of all <button> and
// <input type="button"> elements in the document, just like
// jQuery's :button pseudo-class.
var result = document.querySelectorAll('BUTTON, INPUT[type="button"]');

 

Listing 20. Implementing jQuery’s :submit Pseudo-class: Web API, Modern Browsers, and Internet Explorer 8

//  "result"  will  contain  a  NodeList  of  all  <button  type="submit">  and
 // <input type="submit"> elements in the document, just like 3 // jQuery's :submit pseudo-class.
   var  result  =  document.querySelectorAll(
'BUTTON[type="submit"],  INPUT[type="submit"]'
);

The native solutions in Listings 21 and 22 are a bit wordier, but not particularly complex, and certainly more performant that jQuery’s :submit. You can see the same performance differences between jQuery’s :button selector and the native solution for more: 

 

Listing 21. Implementing jQuery’s :password Pseudo-class: Web API, Modern Browsers, and Internet Explorer 8

// "result" will contain a NodeList of all <input type="password">
// elements in the document, just like jQuery's :password pseudo-class.
var result = document.querySelectorAll('INPUT[type="password"]');
Listing 22. Implementing jQuery’s :file Pseudo-class: Web API, Modern Browsers, and Internet Explorer 8
// "result" will contain a NodeList of all <input type="file">
// elements in the document, just as jQuery's :file pseudo-class.
var result = document.querySelectorAll('INPUT[type="file"]');

Even this fairly straightforward native CSS selector is much faster than jQuery’s non-standard :file pseudo-class.  Is the performance loss really worth saving a few characters in your code?

 

jQuery also offers a non-standard :first pseudo-class selector. As you might expect, it filters all but the first match in a query’s result set. Consider the following markup:

<div>one</div>
<div>two</div>
<div>three</div>
Suppose we want to select the first <div> in this fragment. With jQuery, our code would look something like this:
// $result is a jQuery object containing
// the first <div> in our example markup.
var $result = $('DIV:first');
// same as above, but perhaps more idiomatic jQuery. 6 var $result = $('DIV').first();

 

The native solution is surprisingly simple and exceptionally performant60 compared to jQuery’s primitive implementation:

//  result  is  the  first  <div>  in  our  example  markup.

   var  result  =  document.querySelector('DIV');

Since querySelector returns the first match for a selector string, this is actually a very elegant alternative to jQuery’s :first pseudo-class or first() API method. You’ll find many other proprietary CSS selectors in jQuery’s arsenal that have straightforward alternatives in the web API.

 

A Simple Replacement for $(selector)

Throughout this blog, you’ve seen a number of element selection approaches that involve passing a CSS selector string into the jQuery function (aliased as $). The native solution often involves the same selector string passed into either querySelector or querySelectorAll. All things being equal, and assuming we are only using valid CSS selector strings, we can replace the jQuery function with a native solution that is both simple to wire up and more performant than jQuery.

If we focus exclusively on selector support, and only need support for modern browsers, we can get pretty far by forgoing jQuery entirely and replacing it with a surprisingly concise native alternative, as shown in Listing 23.

 

Listing 23. Native Replacement for jQuery Function: All Modern Browsers, Internet Explorer 8 for CSS2 Selectors

window.$ = function(selector) {
return document.querySelectorAll(selector);
};
// examples that use our replacement
$('.some-class');
$('#some-id');
$('.some-parent > .some-child');
$('UL LI:not(.active)');

 

The performance benefits seen in some of the more complex selectors exist for the same reason as described earlier when contrasting selecting these same elements using jQuery with the web API. Let’s take a look at the child selector in our above code. Our native solution is definitively faster than jQuery, and the syntax is exactly the same between the two. Here, we give up nothing by abandoning jQuery, and gain performance along with a leaner page—a noteworthy theme of this blog.

 

What Is an Attribute

What Is an Attribute

HTML elements, declaratively speaking, are made up of three parts: name, content, and attributes, with the last two being optional. Take a look at the following simple fragment, which I’ll reference as I explain these three parts a bit more.

<form  action="/rest/login.php"  method="POST">
<input  name="username"  required>
<input  type="password"  name="password"  required>
   </form>

 

In that markup, you see three elements tags: one <form> and two <input>s. The <form> element has a tag name of “FORM”. In fact, tagName is a property available on every object in the DOM that implements the Element interface. This property was standardized as part of W3C’s DOM Level 2 Core specification.

 

In the preceding HTML, the <form> element, represented as a HTMLFormElement object, has a tagName property with a value of “FORM”. The two <input> elements are represented as HTMLInputElement objects, and unsurprisingly they each have tagName values of “INPUT”.

 

Content, the second part of an element, describes any other nodes that are descendants of an element. My example <form> has two <input> elements as content, while the two <input> elements have no content. In fact, <input> elements are not allowed to have any content. Th

 

A note about my example form markup Normally you would want to associate each form field with a <label> that contains a text node with the field’s display name. Also, a submit button is usually prudent, but I left all of these out of my preceding markup to keep it simple and focused on the discussion of attributes.

 

Attributes, the third and final part of an element, also optional, provide a way to annotate elements directly in your markup. You may use them to provide data or state. For example, the <form> element above contains two such attributes: action and method, which together tell the form to send a POST request (method) to the “/rest/login.php” server endpoint (action) when the form is submitted. The first input has a name attribute of “username” and the second has a name of “password”.

 

This information is used to construct the request and tie these elements to their values when the server parses the form submit. Although not evident in the preceding HTML, you can even create your own proprietary attributes and reference them in your code for the purposes of associating state or data with elements in your markup. Though not strictly required, the more standard way to do this is with data- attributes, which will be mentioned later on in this blog.

 

In addition to providing data or state, some attributes are used to define specific behaviors for multipurpose elements. Take a look at the type attribute on the second input in the preceding fragment for an example of this. This type attribute defines the second input to be a password input, which signals the browser to mask any characters entered into this field by the user.

 

How Do Attributes Differ from Properties

How Do Attributes Differ from Properties

Now that you have a solid understanding of what element attributes are, you may still be confused regarding their relation to element “properties,” especially if you have been using jQuery for a while. At a very basic level, properties and attributes are entirely different from each other.

 

While attributes are declared at the HTML level in the element’s markup, properties are declared and updated on the element’s object representation. For example, consider the following element:

<div class="bold">I'm a bold element</div>
The <div> has a class attribute with a value of “bold”. But we can set properties on this element too.
Suppose we want to set a property of index with a value of 0:
<div class="bold">I'm a bold element</div>
<script>document.querySelector(".bold").index=0;</script>

 

After executing the preceding fragment, our <div> now has a class attribute with a value of “bold” and a property of index with a value of 0. The property is set on the underlying JavaScript object, which in this case is an implementation of the HTMLDivElement interface.

 

Element object properties such as our index are also known as “expando” properties, which is just a terse way to classify non-standard element object properties. Understand that not all element properties are expando properties. Don’t worry if this is still not entirely clear. I’ll talk more about standardized element properties before this section is complete.

 

Although properties and attributes are conceptually and syntactically different, they are very closely linked together in some cases. In fact, all standardized element attributes have corresponding properties defined in the element’s object representation. For the most part, each standard attribute and property pair share the same value.

 

And in all but one case, the attribute and property share the same name as well. These standardized attributes are special in that you can update them without touching the markup. Updating the corresponding element object’s property value will cause the browser to update the attribute’s value in the document, and updating the attribute value will in turn up the element’s property value.

 

Let’s take a look at a simple example, where we define an anchor link with an initial href, and then update the anchor to point to a different location using JavaScript:

<a href="http://www.widen.com/blog/">Read the Widen blog</a>
<script>document.querySelector("A").href="http://www.widen.com/blog/ray-nicholus";</script>
After executing the script in the above code block, the anchor now appears in the document as follows:
<a href="http://www.widen.com/blog/ray-nicholus">Read the Widen blog</a>

 

In this case, the HTMLAnchorElement, which is the object representation of an <a>, has an href property defined on its prototype that is directly connected to the href attribute on the element tag. This href property is actually inherited from the URLUtils interface,17 which the HTMLAnchorElement object also implements. URLUtils is an interface formally defined in the WHATWG URL Living Standard18 specification.

 

There are many other element attributes with connected properties, such as id (all elements), action (form elements), and class='lazy' data-src (script elements), to name a few. Remember that all attributes that appear in the HTML specifications fall into this category. But there are a few special cases and points to consider.

 

First, class attributes are a bit different in that the corresponding property name is not class, but className. This is due to the fact that “class” is a reserved word in many languages, such as JavaScript. More on the class attribute later on. Also keep in mind that the checked attribute, common to radio and checkbox input elements, is only initially connected to the corresponding element property value. Consider the following code to demonstrate this limitation a bit more clearly:

<input  type="checkbox"  checked>

   <script>
//  this  does  not  remove  the  checked  attribute
document.querySelector('INPUT').checked  =  false;
   </script>

 

After executing the preceding script, you may expect the checked attribute to be removed from the input element, since this would happen for other Boolean attributes, such as required and disabled. However, the checked attribute remains on the element, even though the property value has been changed to false and the checkbox is indeed unchecked.

 

“Custom” attributes, that is, attributes that are not defined in any accepted specification, are not linked in any way to a similarly named property on the element object. Any properties you create to match non-standard attributes are also considered expando properties.

 

Finding Elements Using Attributes

Finding Elements Using Attributes

This section is going to provide a much more comprehensive guide to selecting any and all attributes using the web API. Although ID and class attribute selection is commonly accomplished using a selector syntax specific to these two types of attributes, you can use the more general attribute selection approaches found in this blog as well.

 

In some cases, some of the generic but powerful attribute selectors demonstrated here are most appropriate when looking for multiple elements that follow a known ID or class pattern.

 

For the sake of consistency and reference, jQuery examples will be provided throughout this section. But attributes can be selected in a number of ways without jQuery, simply by using either querySelector or querySelectorAll.

 

Since attribute selectors were first introduced as part of the W3C CSS 2 specification, 20 all of the simple web API examples here (but not all of the more complex ones) are supported all the way back to Internet Explorer 8! You truly don’t need jQuery to write simple but powerful attribute selectors.

 

jQuery

There is one way to select elements given their attributes in jQuery, and that is by passing a valid CSS 2+ attribute selector string into the jQuery function:

var  $result  =  $('[required],  [disabled]');

The preceding code will result in a $result jQuery object that contains the “last-name” and “email” <input> elements, along with the disabled submit <button>. In case the comma in the selector string is causing you some confusion, I covered this in the previous blog’s multiple element selector section. This jQuery code relies entirely on the web API behind the scenes.

 

Web API

Listing 1. Selecting by Attribute Name: Web API, All Modern Browsers, and Internet Explorer 8

var  result  =  document.querySelectorAll('[required],  [disabled]');

 

Similar to the jQuery example, the preceding code will populate the result variable with a NodeList containing the “last-name” and “email” inputs, along with the disabled submit button.

 

While disabled and required are Boolean attributes, the preceding code will yield the same results even if we assigned them values. The attribute selector simply matches on the attribute name—the value (or lack of one) is irrelevant. This means you can easily locate all elements in a document that have any CSS classes assigned to them. For example, Listing 2 shows a simple attribute selector.

 

Listing 2. Selecting All Elements with a Class Attribute: Modern Browsers and Internet Explorer 8

var result = document.querySelectorAll('[class]');
Given the following HTML:
<div class="bold">I'm bold</div>
<span>I'm not</span>
. . the result variable in the previous selector will yield one element: the <div>. But beware, simply adding an empty class attribute to the <span> may result in an unexpected result set:
<div class="bold">I'm bold</div>
<span class>I'm not</span>

 

Even though the <span> does not have any CSS classes assigned to it, the mere presence of the class attribute means that our selector includes it in the result set alongside the <div>.

This is probably not what we wanted. This is not a deficiency of the selector API, but it is important to understand exactly how attribute name selectors function. Note that you will run into the same “problem” using jQuery if you don’t have a firm grasp of this CSS selector.

 

Finding Elements Using Attribute Names and Values

Finding Elements Using Attribute Names and Values

Sometimes locating an element or group of elements by attribute name alone is not sufficient. You may want to, for example, locate all password input fields, in which case you would need to find all <input> elements with a type attribute of “password”. Or perhaps you need to locate all anchor elements that link to a specific endpoint, in which case you’d need to key on the desired value of the href attribute of all <a> elements.

 

To set up our jQuery and web API examples, let’s use the following HTML and state that our goal is to locate all anchors that link to the ajax-form web component documentation page:

<section>
<h2>web components</h2>
<ul>
<li>
<a href="http://file-input.raynicholus.com/">file-input</a>
</li>
<li>
<a href="http://ajax-form.raynicholus.com/">ajax-form</a>
</li>
</ul>
</section>
<section>
<h2>no-dependency libraries</h2>
<ul>
<li>
<a href="http://ajax-form.raynicholus.com/">ajax-form</a>
</li>
<li>
<a href="http://fineuploader.com/">Fine Uploader</a>
</li>
</ul>
</section>

 

jQuery

jQuery

In order to find all anchor elements that point to the ajax-form library page, we’ll use a standardized CSS selector string passed into the jQuery function, as we’ve seen so many times before:

var  $result  =  $('A[href="http://ajax-form.raynicholus.com/"]');

The preceding selector returns a jQuery object containing the two ajax-form HTMLAnchorElement objects from our example markup.

 

Web API

You’ve already seen how a standard CSS selector is required to select by attribute name and value when using jQuery, so of course the same selector is most appropriate when attempting to find specific anchor elements without jQuery. The solution here is, as you have seen in most other element selection examples, almost identical to the jQuery approach, but more efficient:

var  result  = document.querySelectorAll('A[href="http://ajax-form.raynicholus.com/"]');

 

The result variable is a NodeList containing both ajax-form anchors from our example HTML at the start of this section. Notice that I’m combining the attribute name/value selector with a tag name selector. This ensures that any other elements that may include a non-standard href attribute are ignored (along with any <link> elements), since we are only concerned with anchor links.

 

Remember the empty class attribute example from the selecting by attribute name section? During our search for all elements with CSS classes, we were unable to ignore empty class attributes with a simple attribute name selector. 

Listing . Find Anchors with Specific href Attributes: Web API, Modern Browsers

var  result  =  document.querySelectorAll('[class]:not([class=""]');

 

Using the sample HTML from the initial empty class attribute example section, the preceding code block, result contains a NodeList containing only the <div> with a class attribute of “bold”. The <span> with an empty class attribute has been successfully skipped over.

 

The Power of Wildcard and Fuzzy Attribute Selectors

This last part of the attribute selectors section focuses on more advanced use cases. In this section, I demonstrate four very powerful attribute selector tricks that are also easy to understand and supported in all modern browsers as well as Internet Explorer 8.

 

The pattern you have already seen (many times) between the jQuery and the web API selector code continues in this last set of examples. So, let’s just forgo the jQuery versus web API code snippets, as they are mostly redundant when discussing element selectors.

If you really want to run the following examples “the jQuery way,” just replace document.querySelectorAll() with $() and be prepared for your code to run a bit slower.

 

Looking for Specific Characters

Looking for Specific Characters

Remember the example from the section on attribute names and values? We wanted to locate any anchor links in a document that pointed to a very specific endpoint. But what if we don’t care about the entire URL? What if we are only concerned with the domain? Consider the following HTML fragment:

<a  href="http://fineuploader.com/">home  page</a>
   <a  href="http://fineuploader.com/demos">demos</a>
   <a  href="http://docs.fineuploader.com/">docs</a>
   <a  href="http://fineuploader.com/purchase">purchase</a>

 

If we want to locate all anchor links at http://fineuploader.com, the instance substring attribute selector, first standardized in the W3C CSS 3 specification, allows us to do this: var  result  = document.querySelectorAll('A[href*="http://fineuploader.com"]');

 

The result variable above is a NodeList containing all anchor links except for the third one. Why is this? Well, we are looking for an href attribute that contains the string "http://fineuploader.com". The third anchor link does not contain this string. Perhaps this is not our intent, and we simply want to find all anchor links that point in some way to fineuploader.com. Simple!

var  result  =

document.querySelectorAll('A[href*="fineuploader.com"]');

 

Looking for Specific Words

Instead of looking for groups of characters, perhaps we need to locate a specific “word” inside of an attribute value. For example, we can write an alternate CSS class selector using this attribute word selector. Consider the following HTML fragment:

<div  class="one  two  three">1  2  3</div>
   <div  class="onetwothree">123</div>

Let’s say we want to find only the element with a CSS class of “two”. In addition to the CSS class selector

var  result  =  document.querySelectorAll('[class~=two]');

 

The result variable is a NodeList containing one entry—the first <div> in our sample element collection—exactly what we were looking for. But why do we need to create another class selector? We don’t, and the preceding example is not exactly practical, though it does illustrate the behavior of this selector very well. A more realistic example may be to locate a specific word in an element title attribute. Consider this set of elements:

<a href="https://github.com/rnicholus/frame-grab.js" title="frame-grab repo">frame-grab GitHub repo</a> 3
<a href="https://github.com/rnicholus/frame-grab.js/blob/master/docs/api.md" title="frame-grab docs">frame-grab documentation</a> 6
<a href="https://www.youtube.com/watch?v=hHBhP03JHIQ" title="frame-grab + fine-uploader">Video frame uploader</a> 9
<img class='lazy' data-src="https://travis-ci.org/rnicholus/frame-grab.js.svg?branch=master" title="frame-grab  build  status">
<a href="https://foo.bar/subframe-grabber" title="window-subframe-grabber">
Locates all iframes inside of a given iframe</a>

 

Imagine the two links and one image, all obviously related to the frame-grab library, exist among a number of other unrelated links and images in a large document. But we want to find only those resources that directly relate to the frame-grab library. We can’t key on “frame-grab.js” using the substring attribute selector since not all of the elements contain an href or class='lazy' data-src attribute with “frame-grab.js”.

 

We also don’t want to key on the phrase “frame-grab”, as this would include the last link, which does not relate to the frame-grab library. Instead we need to select all elements with a title attribute that contains the specific phrase “frame-grab”.

var  result  =  document.querySelectorAll('[title~=frame-grab]');

result is a NodeList that contains all elements in our HTML sample except for the last anchor link, which is the exact result we are looking for.

 

Attribute Values That Start or End With . . .

 

The final set of useful advanced attribute selectors to be aware of allow you to locate elements in a document with attribute values that either start with or end with one or more specific characters. Perhaps you are wondering at this point why such a selector would be useful, pragmatically speaking. As we have done so many times before, let’s start with a bit of HTML, and then discuss how these two attribute selectors may be important to us:

<img  id="dancing-cat"  class='lazy' data-src="/images/dancing-cat.gif">
   <img  id="still-cat"  class='lazy' data-src="/images/still-cat.png">
   <img  id="dancing-zebra"  class='lazy' data-src="dancing-zebra.gif">
 <a href="#dancing-zebra">watch the zebra dance</a> 5 <a href="/logout">logout</a>

That fragment is likely to appear inside a large document, among many other anchor links and images, but can be considered representative of a number of such elements. Say we want to locate two things in this document:

 

Reading Attributes

Let’s start with a simple input element that includes both a Boolean attribute and a standard string value attribute:

<input  type="password"  name="user-password"  required>

Suppose we are given this element and we want answers to two questions:

 

What type of <input> is this element

Is this <input> a required field?

 

This is one area (of many) where jQuery fails miserably to ease the burden on the developer. While reading an attribute value is simple, there is no API method dedicated to detecting the presence of an attribute on a specific element. Though it is still possible to do this with jQuery, the solution is not very intuitive and will likely require those new to the library to do a bit of web searching:

//  returns  "password"
   $inputEl.attr('type');

   //  returns  "true"
   $inputEl.is('[required]');

 

jQuery does not define a hasAttr method. Instead, you must check the element using a CSS attribute name selector. The web API does provide these conveniences, and has done so since Internet Explorer 8:

//  returns  "password"

inputEl.getAttribute('type');

   //  returns  "true"
   inputEl.hasAttribute('required');

 

The getAttribute method was first defined on the Element interface all the way back in 1997 as part of the W3C DOM Level 1 Core specification. And hasAttribute was added to the same interface 3 years later, in 2000, in the DOM Level 2 Core spec.30

We can make the second half of the jQuery example a bit more intuitive simply by breaking out of the jQuery object and operating directly on the underlying Element:

//  returns  "true"

$inputEl[0].hasAttribute('required');

 

So if you’re stuck with jQuery for whatever reason, consider the preceding example as a more straightforward way to determine whether an element contains a particular attribute. As an added bonus, you’ll find that bypassing jQuery as much as possible here is, as always, more performant than relying on the library wholesale.

 

Modifying Attributes

We have a handle on a specific <input> element in our document, and the element looks like this:

  <input  name="temp"  required>

 

We want to modify this HTMLInputElement in three ways:

  • Make it an “email” input field.
  • Ensure it is not required.
  • Rename it to “userEmail”.

 

jQuery requires we solve this problem using attr() to add and change attributes and removeAttr() to remove them:

$inputEl

.attr('type',  'email')  //  #1

removeAttr('required')  //  #2

attr('name',  'userEmail');  //  #3

 

Without jQuery, our solution looks almost identical, and has the same wide browser support. The Element interface was defined to have a setAttribute method since W3C’s DOM Level 1 Core specification.32 With this method, we can change and add element attribute, just like jQuery’s attr() method.

 

To remove attributes, we have removeAttribute(), another method also defined on the Element interface in DOM Level 1 Core.33 With these two methods, we can modify our input element as described earlier quite easily:

inputEl.setAttribute('type',  'email');  //  #1

   inputEl.removeAttribute('required');  //  #2

   inputEl.setAttribute('name',  'userEmail');  //  #3

 

Apart from the lack of chaining support, the native approach is just as intuitive as the route that relies on jQuery. This is one area in which web standards have always been adequate, and jQuery has never provided more than a minor convenience advantage. As you have seen throughout this section, working with attributes in general is surprisingly easy without any assistance from a library.

 

HTML Element Data Storage and Retrieval

 I’ll show you how data is attached to elements and then read back using jQuery initially. But most importantly, you’ll see how to do all this without jQuery. I’ll also explain exactly how jQuery makes use of the web API and JavaScript to provide its own support for element data.

 

The future of managing element data is exciting. I’ll show you how the web API and JavaScript are prepared to eclipse jQuery’s support for element data across all browsers in the very near future. And this “futuristic” native support is already available for many browsers. I’ll be sure to include copious code examples detailing how you can make use of this built-in support right now in your project.

 

Why Would You Want to Attach Data to Elements

Attach Data to Elements

Especially in modern web applications, there is a real need to tie data to the elements on a page. In this section, we will explore common reasons for attaching custom data to your markup, and how this is commonly accomplished, at a high level. You will see that there are many reasons why you may find it useful to track data alongside your elements.

 

Tracking State

Perhaps you are maintaining a page for a realtor that contains a table of properties that are currently on the market. Presumably, you’d want to order them from most popular to least, and maybe you’d like to be able to adjust this order via drag-and-drop directly on the page:

<table>
<thead>
<tr>
<th>Address</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>6911 Mangrove Ln Madison, WI</td>
<td>$10,000,000</td>
</tr>
<tr>
<td>1313 Mockingbird Ln Mockingbird Heights, CA</td>
<td>$100,000</td>
</tr>
</tbody>
</table>

 

After a row is moved, or perhaps even as part of the initial markup, you may want to annotate each row with its original index in the table. This could potentially be used to revert any changes without calling the server. A data- or custom attribute (data-original-idx or original-idx respectively) is most appropriate here.

 

You may also want to track initial style information for an element, such as dimensions. If you allow the user to dynamically adjust the width and height of an element, you will likely want a simple way to reset these dimensions should your user have a change of heart. You can store the initial dimension alongside the element, possibly using data- attributes.

 

Common Pitfalls of Pairing Data with Elements

It’s becoming easier to pair data with your elements, thanks to rapidly evolving web and JavaScript specifications. Don’t worry, I’ll cover specifics very soon. But life is not simple even with these advancements. There is still potential for trouble, provided this new power is not used responsibly. Of course, life before the modern web and JavaScript was much more difficult. Attaching trivial data to elements was done using primitive means. Storing complex data, such as other Nodes, could result in memory leaks. This section covers all of that.

 

Memory Leaks

When connecting two (or more) elements together, the natural instinct is to simply store a reference to the other elements in some common JavaScript object. For example, consider the following markup:

<ul>

<li>Ford</li>

<li>Chevy</li>

<li>Mercedes</li> 5 </ul>

 

Each of these car types responds to click events, and when one of the cars is clicked, the clicked car must be prominently styled, while all other cars must become less prominent. One way to accomplish that is to store references to all elements in a JavaScript array and iterate over all elements in the array when one of the list items is clicked. The clicked item must be colored red, while the others should be set to their default color. Our JavaScript might look something like this:

var standOutOnClick = function(el) {
el.onclick = function() {
for (var i = 0; i < el.typeEls.length; i++) {
var currentEl = el.typeEls[i];
if (el === currentEl) {
currentEl.style.color = 'red';
}
else {
currentEl.style.color = '';
}
}
};
},
setupCarTypeEls = function() {
var carTypes = [],
carTypeEls = document.getElementsByTagName('LI');
for (var i = 0; i < carTypeEls.length; i++) {
var thisCarType = carTypeEls[i];
thisCarType.typeEls = carTypes;
carTypes.push(thisCarType);
standOutOnClick(thisCarType);
}
};
setupCarTypeEls();

 

Don’t use inline event handlers In the preceding example, I am assigning a click handler to an element via the element’s onclick property. This is known as an inline event handler, and you should avoid doing it. Because I haven’t covered events yet, I took this shortcut to keep the code example as simple as possible, but you should never use inline event handler assignments in your code. 

 

Avoid inline style assignment In the preceding example, I change the color of a <li> by altering the color value of the element’s style property. I took this shortcut to keep the example as simple as possible, but you should almost always avoid this type of style assignment in your code. The proper approach involves removing or adding CSS classes for each element, with the proper styles/colors defined in a style sheet for these specific classes.

 

The preceding code works in every browser available, including Internet Explorer 6, though a large hidden issue exists. It demonstrates a circular reference involving a DOM object (the JavaScript representation of the <li> element) and a “plain” JavaScript object (the carTypeEls array).

 

Each <li> references the carTypeEls array, which in turn references the <li> element. This is a good example of a well-documented memory leak present in Internet Explorer 6 and 7. The leak is so severe that the memory may be unclaimed even after a page refresh. Luckily, Microsoft fixed this issue in Internet Explorer 8,1 but this demonstrates some early challenges with storing data alongside HTML elements.

 

Managing Data

For trivial amounts of data, you can make use of data- attributes or other custom attributes. But what if you need to store a lot of data? You could perhaps attach the data to a custom property on the element. This is known as an expando property. This is was illustrated in the previous example.

 

To avoid potential memory leaks, you may instead elect to store the data in a JavaScript object along with a selector string for the associated element(s). This ensures the reference to the element is a “weak” one. Unfortunately, neither of these approaches is particularly intuitive, and you get the feeling that you are either reinventing the wheel or writing kludgey, brittle code along the way. Surely there must be an easier route.

 

Then again, what is a “trivial” amount of data? When do attributes become a less-feasible storage and retrieval mechanism? To developers who have not come across this problem before, the large array of approaches may be a bit overwhelming. Can you simply make use of expando properties for all instances?

 

What are the drawbacks and advantages to one approach over the others? Don’t worry, you’ll not only understand how and when to use a specific approach when storing element data, but you’ll also learn how to do it easily and effectively in the final two sections of this blog.

 

Using a Solution for All Browsers

Although there exist some pretty nifty ways to read and track element data in new specifications, such as ECMAScript 2015 and HTML5, I realize that the browser support for some of these APIs is not yet comprehensive. In a relatively short amount of time, these new tools will be implemented in the vast majority of all browsers in use.

Until then, you should understand how to accomplish these same tasks with the most commonly available APIs. In some cases, the approaches described in this section are likely to withstand the test of time and remain most appropriate and simplest even as web standards continue to evolve.

 

Storing Small Bits of Data Using data- Attributes

Small Bits of Data Using data

Data attributes, which first appeared in the W3C HTML5 specification, is one example of an existing standard that is simple enough to be usable in all current browsers. Its simplicity and flexibility is such that it may be leveraged in future incarnations of web specifications. In fact, data attributes already are a lot more powerful due to a relatively new Element interface property defined in the HTML5 spec (more on that soon).

The HTML5 specification declares data- attributes to be custom attributes. The two are one and the same.

The only valid custom attribute is a data- attribute. The specification describes data- attributes as follows:

A custom data attribute is an attribute in no namespace whose name starts with the string “data-” has at least one character after the hyphen. . . .

 

The spec also gives data- attributes a specific purpose. They are “intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements.”

So, if you need to describe a title for an anchor link, use the title attribute.3 If you must define a language for a paragraph that differs from the language defined on the <html> element for the rest of the document, you should use the lang attribute.

 

But what if you need to store an alternate URL for an <img>, one that is used when the image either receives focus via the keyboard or when the user hovers over it with a pointing device, such as a mouse. In this instance, there is no standard attribute to store this information. So, we must make use of a custom data-attribute:

<img  class='lazy' data-src="default.png"

data-zoom-url="default-zoomed.png"

alt="default  image">

 

The image to display on focus/hover is stored in the data-zoom-url attribute. We may follow the same approach if we want to annotate a <video> with the offsets at which the scenes change:

<video  class='lazy' data-src="my-video.mp4"  data-scene-offsets="9,22,38">

 

The preceding video changes scenes at the 9-, 22-, and 38-second marks, according to the data-scene-offsets custom attribute we’ve tied to the element.

There are no drastic consequences for defining a custom element that does not confirm to the data-convention defined in the HTML5 spec. The browser will not complain or fail to render your document. But you will lose the ability to utilize any future portions of the API that build on this convention, including the dataset property. More on that specific property shortly.

 

Reading and Updating data- Attributes with jQuery

Now that we have a way to annotate our elements with a bit of data via our markup, how can we actually read this data in our code? If you are familiar with jQuery, you probably already know about the data() API method. Just in case the details are a little fuzzy, take a look at the following example:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>var offsets=$("VIDEO").data("sceneOffsets");</script>

 

Notice that we must access the value of the data- attribute by referencing the unique portion of the attribute name as a camel-case string. Changing the value of the data attribute is very similar:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>$("VIDEO").data("sceneOffsets","1,2,3");</script>

 

Notice that there is something peculiar and unexpected about jQuery’s data() method. When attempting to update the data- attribute via this method, nothing appears to happen. That is, the data-scene-offsets attribute value remains unchanged in the document. Instead, jQuery stores this value, and all subsequent values, in a JavaScript data store. There are a couple downsides to this implementation:

 

Our markup is now out-of-sync with the element’s data.

  • Any changes we make to the element’s data are only accessible to jQuery.
  • Though there are some good reasons for this implementation, it seems unfortunate in this situation.
  • Using the Web API to Read and Update data- Attributes

 

Later, I describe a more modern way to read and update data- attributes using JavaScript with the same elegance as jQuery’s data() method but without the drawbacks. In the meantime, let’s explore a solution that will work with any browser:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>var offsets=document.getElementsByTagName("VIDEO")[0].getAttribute("data-scene-offsets");7;</script>

 

We’ve already seen this before back in the “reading attributes” section of the previous blog. The data- attribute is, of course, just an element attribute, so we can easily read it in any browser using getAttribute(). As you might expect, updating data- attributes without jQuery makes use of the setAttribute() method, courtesy of the web API’s Element interface:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>setAttribute("data-scene-offsets","1,2,3");</script>

 

This primitive yet effective approach yields two benefits over jQuery’s data() method in this situation:

Our markup is always in-sync with the element’s data. Any changes we make to the element’s data are accessible to any JavaScript. So, in this instance, the native solution may be a better route route.

 

Complex Element Data Storage and Retrieval

 Data Storage and Retrieval

Simple element data consists of a short string, such as a phrase, word, or short sequence of characters or numbers. Perhaps even a small JSON object or array can be considered simple. But what about complex data?

 

Remember the list of cars from the memory leaks section earlier in this blog? I demonstrated a way to link the individual list item elements such that we could easily highlight the clicked item while making the other items in the list less prominent. We were associating JavaScript representations of HTML elements with other elements. This can certainly be considered “complex” data.

 

If we expand upon the previous example with the <video> tag, another example of complex element data can be demonstrated. In addition to scene offsets, we also need to record a short description of each scene, along with a title and a location. What we are describing here is something that demands a proper JavaScript object instead of a single string of text stored as an attribute value.

 

The solution I proposed in the memory leaks section involved use of expando properties, which was, in part, responsible for a memory leak in older browsers. Even though this leak has been patched in all modern browsers, expando properties are discouraged, as is modifying JavaScript representations of elements in any non-standard way.

 

The video data scenario I detailed earlier is far too much data to store in a data- attribute. And of course, we shouldn’t resort to expando properties here either. So the proper way to associate these types of complex data with elements is to maintain a JavaScript object that is linked to one or more elements via a data- attribute. This is the approach jQuery takes, and we can do the same without jQuery fairly easily.

 

The Familiar jQuery Approach

The jQuery solution to our problem involves, as you might have already surmised, the data() method:

$('VIDEO').data('scenes', [
{
offset: 9,
title: 'intro',
description: 'introducing the characters',
location: 'living room'
},
{
offset: 22,
title: 'the problem',
description: 'characters have some issues',
location: 'the park'
},
{
offset: 38,
title: 'the resolution',
description: 'characters resolve their issues',
location: 'the cemetery'
}
]);
Now, if we want to look up the title of the second scene:
// variable will have a value of 'the problem'
var sceneTwoTitle = $('VIDEO').data('scenes')[1].title;

jQuery maintains the array we supplied inside an internal cache object. Each cache object is given an “index,” and this index is stored as the value of an expando property that jQuery adds to the HTMLVideoElement object, which is the JavaScript representation of a <video> tag.

 

Using a More Natural Approach

No jQuery

When deciding how to tie complex data to an element in this section, we must be aware of our three goals:

  • No jQuery.
  • Must work in all browsers.
  • No expando properties.

 

And we can respect the first two goals by mimicking jQuery’s approach to storing element data. To respect the third, we must make some adjustments to jQuery’s approach. In other words, we will have to tie our elements to the underlying JavaScript object via a simple data- attribute instead of an expando property:

var cache = [],
setData = function(el, key, data) {
var cacheIdx = el.getAttribute('data-cache-idx'),
cacheEntry = cache[cacheIdx] || {};
cacheEntry[key] = data;
if (cacheIdx == null) {
cacheIdx = cache.push(cacheEntry) - 1;
el.setAttribute('data-cache-idx', cacheIdx);
}
};
setData(document.getElementsByTagName('VIDEO')[0],
'scenes', [
{
offset: 9,
title: 'intro',
description: 'introducing the characters',
location: 'living room'
},
{
offset: 22,
title: 'the problem',
description: 'characters have some issues',
location: 'the park'
},
{
offset: 38,
title: 'the resolution',
description: 'characters resolve their issues',
location: 'the cemetery'
}
]);

 

What’s going on here? First, I’ve created a convenience method (the setData function) to handle association of data with a specific element, along with an array ( cache) used to hold data for all my elements. The setData function has been set up to accept an element, data key, and data object, while the cache array holds one JavaScript object per element with data attached to (potentially) multiple key properties.

 

When handling a call, we first check to see whether the element is already tied to data in our cache. If it is, we look up the existing data object in cache using the array index stored in the data-cache-idx attribute on the element and then add a new property to this object that contains the passed data.

Otherwise, we create a new object initialized to contain the passed data with the passed key. If this element does not yet have an entry in cache, a data-cache-idx attribute with the index of the new object in cache must be created as well.

 

As with the jQuery solution, we want to look up the title of second scene, and we can do that with just a bit more code:

var cacheIdx = document.getElementsByTagName('VIDEO')[0]
.getAttribute('data-cache-idx');
// variable will have a value of 'the problem'
var sceneTwoTitle = cache[cacheIdx].scenes[1].title;

 

We could easily create a getData() function to accompany our setData() that makes storing and looking up our element data a bit more intuitive. But this all-browser non-jQuery solution is surprisingly simple. For an even more elegant non-jQuery approach that targets more modern browsers, check out the next section, where I demonstrate the dataset element property and the WeakMap API.

 

Removing Data from Our Cache When Elements Are Removed from the DOM

One potential issue with the approach I just demonstrated is the fact that the cache will grow unbounded. It would be useful to remove items from the cache when corresponding elements are removed from the DOM. Ideally, we could simply “listen” to DOM element removal “events” and revoke elements from our cache accordingly.

 

Luckily, this is possible in most modern browsers natively, thanks to MutationObserver, a web standard maintained by WHATWG as part of its DOM specification. 5 Internet Explorer 9 and 10 are holdouts, but a polyfill fills in6 those two gaps. Before MutationObserver, there was still native ability to observe changes to the DOM via “Mutation Events,” but these proved to be highly inefficient and are no longer part of any active specification. The polyfill I just referred to falls back to Mutation Events in IE10 and 9.

 

Mutation Observers allow for a callback function to be executed whenever any change to any DOM element (or its child or descendants) is detected. This is exactly what we are looking for. More specifically, when a DOM element attached to a cache item is removed, we’d like to be notified so the cache can be cleaned up. Take the <video> element from the cache example.

 

Remember that we stored some data about various scenes present in the video in a cache object. When the <video> is removed, the cache entry should be removed as well to prevent our cache from growing needlessly. Using Mutation Observers, our code to accomplish that may look something like this:

var videoEl = document.querySelector('video'),
observer = new MutationObserver(function(mutations) {
var wasVideoRemoved = mutations.some(function(mutation) {
return mutation.removedNodes.some(function(removedNode) {
return removedNode === videoEl;
});
});
if (wasVideoRemoved) {
var cacheIdx = videoEl.getAttribute('data-cache-idx');
cache.splice(cacheIdx, 1);
observer.disconnect();
}
});
observer.observe(videoEl.parentNode, {childList: true});

 

There, all changes to children of our video’s parent element are being observed. If we observe the video element directly, we won’t be notified when it is removed. The childList configuration option passed to our observer ensures that we are notified whenever our video or any of its siblings are changed.

When our callback function is hit, if our video element was removed, we remove the corresponding entry in the cache and then disconnect our Mutation Observer, since we no longer need it. For more on MutationObserver, have a look at Mozilla Developer Network.

 

The HTML5 Dataset Property

HTML5 Dataset Property

The HTML5 specification, which became a recommendation in October of 2014, defined a new property on the HTMLElement interface: dataset. Think of this new property as a JavaScript object available on any element object. In fact, it is an object, more specifically a DOMStringMap object, which is also defined in the HTML5 spec.

 

Any property you add to the dataset object is reflected as data- attribute on the element’s tag in your document. You can also read any data- attribute defined on the element’s tag by checking the corresponding property on the element’s dataset object. In this respect, HTMLElement.dataset provides all the behaviors you have come to love about jQuery’s data() method.

 

It’s an intuitive way to read and write data to an element, without the drawbacks. Because changes to the properties on the dataset object are always synced to the element’s markup, and vice-versa, this new standard property is a perfect way to deal with trivial element data.

Element.dataset is currently available on a subset of “modern” browsers—Internet Explorer 9 and 10 are not supported, though polyfills are available, such as https://www.npmjs.com/package/dataset.

 

Keep this in mind when viewing the following code examples. And for our first demonstration, let’s re-write the first code block displayed in the earlier section on reading and updating data- attributes using the web API

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>var offsets=document.querySelector("VIDEO").dataset.sceneOffsets;</script>

 

Here we’ve simplified the earlier example quite a bit. Notice how we must use the camel-case form of the data- attribute. Arguably, the dataset model is more intuitive to use than jQuery’s data() method. We treat all of our data as properties on an object, which is exactly how jQuery represents this data internally.

 

But when using jQuery’s API, we are expected to call the function passing the key as a string argument. Take a look at a more modern version of the second code example, which illustrates how to change or add data to an element:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>document.querySelector("VIDEO").dataset.sceneOffsets="1,2,3";</script>

 

The element data has been updated along with the associated data- attribute, all with one simple and elegant line of code. But we can do more! Because dataset is a JavaScript object, we can easily remove data from our element, just as we would remove a property from any other JavaScript object:

<video class='lazy' data-src="my-video.mp4" data-scene-offsets="9,22,38">
<script>delete document.querySelector("VIDEO").dataset.sceneOffsets;</script>

 

You can now see how dataset actually exceeds the convenience of jQuery’s data() method.

Leveraging ECMAScript 2015 WeakMap Collections

You already know how to leverage the latest web technology to connect trivial data to elements. But what about complex data? We could make use of our previous example, but maybe the latest and greatest web specifications bring us a more elegant solution, maybe something more intuitive that is a perfect fit for this type of problem.

 

ECMAScript 2015 brings a new collection called a WeakMap. A WeakMap can contain keys that are objects and values that are anything—elements, objects, primitives, and so on. In this new collection, keys are “weakly” held. This means that they are eligible for garbage collection by the browser if nothing else references them. This allows us to safely use the reference elements as keys!

 

Although WeakMap is only supported in the latest and greatest browsers (Internet Explorer 11+, Chrome 36+, Safari 7.1+) along with Firefox 6+, it provides an exceptionally simple way to associate HTML elements with data. Remember the all-browser code examples demonstrated earlier? Let’s start rewriting them using WeakMap:

var cache = new WeakMap();
cache.set(document.querySelector('VIDEO'), {scenes: [
{
offset: 9,
title: 'intro',
description: 'introducing the characters',
location: 'living room'
},
{
offset: 22,
title: 'the problem',
description: 'characters have some issues',
location: 'the park'
},
{
offset: 38,
title: 'the resolution',
description: 'characters resolve their issues',
location: 'the cemetery'
}
]});

 

Thanks to WeakMap, we’ve managed to eliminate all the boilerplate from our earlier non-jQuery example. The elegancy of this approach equals that of jQuery’s data() method, which I also demonstrated earlier. Looking up data is just as easy:

// variable will have a value of 'the problem'
var sceneTwoTitle = cache.get(document.querySelector('VIDEO')).scenes[1].title;
And finally, we can clean up after ourselves by removing elements we no longer want to track with a simple API call:
cache.delete(document.querySelector('VIDEO'));

 

Once the element is removed from the DOM, the video element should be eligible for garbage collection by the browser, assuming there are no other references to this element. Since the video element reference is weakly held by WeakMap, this by itself does not prevent garbage collection. Since the video element should then be removed from the WeakMap automatically once the element is no longer in the DOM, we probably don’t even need to explicitly delete this entry.