Managing Custom Data with the HTML5 Dataset API

Web & App Developer

For a long time now, web developers have needed to store data on DOM elements. One of the most common methods was to add data as class names. Purists, like me, always felt wrong doing this, because that is not the place for data. An alternative way was to add custom attributes to the elements of interest. This practice lead to invalid markup because custom attributes were not supported by the specification. So, you ended up sacrificing validation in order to achieve your goal. This situation was very frustrating. Fortunately, HTML5 fixed it. In fact, HTML5 not only introduced the possibility of adding custom attributes via data attributes, but also exposed an API, called the dataset API, to work with them. In this article, we’ll discover how this API works and what it can do for us.

What is the Dataset API?

Among all the new elements (such as article, section, header, and footer) and the new APIs (such as High Resolution Time, User Timing, getUserMedia, and Page Visility), HTML5 also introduced data attributes and the dataset API. Before delving into our discussion of the dataset API, I want to give you a quick refresher of what are data attributes.

Data attributes get their name from the data- prefix. This also explains why sometimes they are referred as data-* attributes. An example of an element using data attributes is shown below.

<span id="element" data-level="1" data-points="100" data-opponent="Dragon"></span>

The names you can choose aren’t limited to a single word. Names can also consist of multiple words, separated by hyphens (-). So, let’s say that you want to change the opponent attribute to final opponent. You would write the element as shown in the following example.

<span id="element" data-level="1" data-points="100" data-final-opponent="Dragon"></span>

You should now have a clear idea of what data attributes are, so let’s start discussing the API. The dataset API gives us a simple way of dealing with data attributes. This API allows us to set, get, or even delete data attribute values. The dataset API exposes a DOM element attribute named dataset, which contains a DOMStringMap object. This object’s keys are the names of the data attributes without the data- prefix. The corresponding values are those of data attributes. If the name of an attribute is made of multiple words separated by a hyphen, it’s converted to camelCase. Let’s look at the following example:

var obj = document.getElementById("element").dataset

The previous statement will the following object in the variable obj.

{
  level: "1",
  points: "100",
  finalOpponent: "Dragon"
}

Individual data attributes can be accessed using the setAttribute(), getAttribute(), and removeAttribute() methods. However, the dataset API gives you a convenient and direct way to access custom data. If the API is not supported, you should retrieve all the attributes and then filter those not starting with data-. And, while the dataset API is simpler, it is also slower than the previously mentioned methods, as proven by this JSperf. However, unless you’re accessing thousands of attributes per second, you won’t notice any difference.

Now that we’ve discussed the dataset API, it’s time to see how we can use it.

Setting Values

Imagine we want to add the attribute data-media to our element, and set its value to song. To perform this task, we can write the following code. Note that if the attribute was already defined, its value is overwritten.

document.getElementById("element").dataset.media = "song";

Getting Values

Creating attributes is completely useless if we can’t retrieve them. Let’s say that we want to print the value of the data-final-opponent attribute to the console. The code to do so would like like this:

console.log(document.getElementById("element").dataset.finalOpponent);
// prints "Dragon"

Deleting Attributes

To delete a value, simply overwrite it using the empty string. However, to actually delete an attribute we can use the JavaScript delete operator. An example which deletes the data-final-opponent attribute is shown below.

delete document.getElementById("element").dataset.finalOpponent;

After executing the previous statement, attempting to retrieve the attribute’s value will yield undefined.

Browser Compatibility

The dataset API is widely supported among desktop and mobile browsers, aside from Internet Explorer which only implements the API in IE11. In addition, there are some older mobile browsers that don’t support it, but generally speaking the support is excellent. For those browser that don’t support this API, a polyfill named HTML 5 dataset Support is available. If you don’t want to add a polyfill for such a simple API, you can use setAttribute(), getAttribute(), and removeAttribute() (as previously mentioned).

Demo

Learning something new is great, but it’s even better if we can play with it. So, we’ll build a small demo that will allow us to see how the dataset API works. The general idea is to have an element on which we can get, set and delete data attributes. To see what’s going on and to keep an eye on the current state of the element, we’ll have a small window where we’ll log the changes we made. In addition, we’ll have an area showing the raw HTML code of the element representing its current state.

In order to play with its data attributes, we need two input boxes: key and value. The former allows us to set the name of attribute we want to store, while the latter is where we’ll write the value of the attribute. Because we want to allow for three different actions (get, set, and delete), we’ll also need three buttons to which we’ll add handlers. As always, we’ll also test for browser support, and if the test fails, we’ll show the message “API not supported.”

Before showing you the demo code, there are two considerations I want to share with you. The demo assumes that you’ve read the whole article. Therefore, you’re aware that to perform an action on a data attribute named with more than one word, you have to convert the name to camelCase. If you want to change this behavior and be able to write “final-opponent” instead of “finalOpponent”, I’ve written two utility functions just for you. To use these functions, you need to add them to the demo and change the code so that you call them inside the handlers before performing the action. The source code of these functions is listed below.

function hyphenToCamelCase(string) {
  return string.replace(/-([a-z])/g, function(string) {
    return string[1].toUpperCase();
  });
}

function camelCaseToHyphen(string) {
  return string.replace(/([A-Z])/g, function(string) {
    return '-' + string.toLowerCase();
  });
}

The second thing to keep in mind is that so far we have accessed the data attributes using the dot operator because we knew its name ahead of time. In the demo we don’t have this information, so to access the properties of the dataset, we’ll use the equivalent square bracket notation.

Now, it’s time to show you the source code. As usual, a live demo of the code is available here.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Dataset API Demo</title>
    <style>
      body
      {
        max-width: 500px;
        margin: 2em auto;
        font-size: 20px;
      }

      h1
      {
        text-align: center;
      }

      .hidden
      {
        display: none;
      }

      #log
      {
        height: 200px;
        width: 100%;
        overflow-y: scroll;
        border: 1px solid #333333;
        line-height: 1.3em;
      }

      .buttons-demo-wrapper
      {
        text-align: center;
      }

      .button-demo
      {
        padding: 0.5em;
        margin: 1em;
      }

      .author
      {
        display: block;
        margin-top: 1em;
      }
    </style>
  </head>
  <body>
    <h1>Dataset API</h1>

    <h3>Live sample element</h3>
    <div id="showcase">
      &lt;span id="play-element" class="hidden" data-level="1" data-points="100" data-final-opponent="Dragon"&gt;&lt;/span&gt;
    </div>

    <h3>Play area</h3>
    <div>
      <label for="key">Key:</label>
      <input type="text" id="key"></input>
      <label for="value">Value:</label>
      <input type="text" id="value"></input>

      <div class="buttons-demo-wrapper">
        <button id="set-data" class="button-demo">Set data</button>
        <button id="get-data" class="button-demo">Get data</button>
        <button id="delete-data" class="button-demo">Delete data</button>
      </div>
    </div>

    <span id="d-unsupported" class="hidden">API not supported</span>

    <h3>Log</h3>
    <div id="log"></div>
    <button id="clear-log" class="button-demo">Clear log</button>

    <span id="play-element" class="hidden" data-level="1" data-points="100" data-final-opponent="Dragon"></span>

    <script>
      if (!"dataset" in document.createElement("span")) {
        document.getElementById("d-unsupported").classList.remove("hidden");
        ["set-data", "get-data", "delete-data"].forEach(function(elementId, index) {
          document.getElementById(elementId).setAttribute("disabled", "disabled");
        });
      } else {
        var playElement = document.getElementById("play-element");
        var key = document.getElementById("key");
        var value = document.getElementById("value");
        var log = document.getElementById("log");
        var showcase = document.getElementById("showcase");

        document.getElementById("clear-log").addEventListener("click", function() {
          log.innerHTML = "";
        });
        document.getElementById("set-data").addEventListener("click", function() {
          if (key.value.indexOf("-") !== -1) {
            log.innerHTML = "Warning! Hyphen not allowed. Use camelCase instead.\n" + log.innerHTML;
          } else {
            playElement.dataset[key.value] = value.value;
            showcase.textContent = playElement.outerHTML;
            log.innerHTML = "Set data-" + key.value + " attribute to '" + value.value + "'<br />" + log.innerHTML;
          }
        });
        document.getElementById("get-data").addEventListener("click", function() {
          if (key.value.indexOf("-") !== -1) {
            log.innerHTML = "Warning! Hyphen not allowed. Use camelCase instead.<br />" + log.innerHTML;
          } else {
            log.innerHTML = "Get data-" + key.value + " attribute. Value: '" + playElement.dataset[key.value] + "'<br />" + log.innerHTML;
          }
        });
        document.getElementById("delete-data").addEventListener("click", function() {
          if (key.value.indexOf("-") !== -1) {
            log.innerHTML = "Warning! Hyphen not allowed. Use camelCase instead.<br />" + log.innerHTML;
          } else {
            delete playElement.dataset[key.value];
            showcase.textContent = playElement.outerHTML;
            log.innerHTML = "Deleted data-" + key.value + " attribute<br />" + log.innerHTML;
          }
        });
      }
    </script>
  </body>
</html>

Conclusions

In this article, we’ve discussed the dataset API and how it can help us to store custom attributes on DOM elements. As we’ve seen, the API is very easy to use. In addition, it’s also widely supported among desktop and mobile browsers. You can start using it right away in your next project. In case you have any doubts, I encourage you to play with the provided demo or post a question.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Jingqi Xie

    On my IE 11, datasets are about twice as fast as attributes.

    • Aurelio De Rosa

      True, but for other major browsers this isn’t the case. In addition, IE11 is very slower than other browsers.

      • Jingqi Xie

        Yes, compared to other browsers, it is sort of slower in DOM, but faster in JavaScript.
        Of course DOM manipulation is the slowest step that affects a web page’s performance a lot more than mere JavaScript execution.

  • Rolograaf

    Seems to work in Node-Webkit