A recent project at work almost required my creating functionality that loaded content stored in HTML5 data attributes onto a web page with mouse clicks. At that point, I had used data attributes in practice code a bit, but readily admitted to not knowing everything about them.

I did some web searches on data attributes and was shocked at the lack of good, descriptive tutorials on the subject. Although I ended up not using them in the work project, I still spent a few days hacking some related code together (all while taking note of the quirks and cross-browser issues), leading to this tutorial where I use info about my favorite footie teams as a data structure.

Table of Contents

  1. How data attributes work
  2. A simple example
  3. Proper naming of data attributes
  4. Store the data attributes in a link
  5. Store the data attributes in multiple links
  6. Use “getAttribute()” as fallback code for “dataset”
  7. Conclusion

How data attributes work

Data attributes are attributes that store data in page elements:


<div id="teamInfo"
     data-team="Chelsea FC"
     data-manager="José Mourinho"
     data-home-pitch="Stanford Bridge">
</div>

The element that contains these data attributes (<div id="teamInfo"> in the above-example) has an internal property called dataset that stores these attributes in an object called DOMStringMap (an API introduced with HTML5):


console.log(teamInfo.dataset);
// logs "DOMStringMap {team: "Chelsea FC", manager: "José Mourinho", homePitch: "Stanford Bridge"}"

The values in an element’s dataset property can be accessed individually:


console.log(teamInfo.dataset.team); // logs "Chelsea FC"
console.log(teamInfo.dataset.manager); // logs "José Mourinho"
console.log(teamInfo.dataset.homePitch); // logs "Stanford Bridge"

All of this means we use JavaScript to find these properties and display them on a web page. But data attribute manipulation doesn’t work in all browsers, specifically, Internet Explorer 10 and lower, so we need to write fallback code for those situations.

Before we create our final-production-ready code with fallbacks, we’ll create some incremental examples to gain a better understanding of how all this works.

A simple example (See the demo)

We’ll start by creating code that changes just one set of data attributes with JavaScript. Let’s start with the CSS, which will be applied to all future code samples and demos:


body {
  background: grey;
  color: #000;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 900;
  font-size: 18px;
}

a:link {
  color: #fff;
  font-weight: 700;
  font-size: 22px;
}

a:visited {
  color: #fff;
}

a:hover {
  color: #fff;
  text-decoration: none;
}

.teamListItem {
  display: inline;
  list-style-type: none;
  padding-right: 20px;
}

.dataTarget {
  padding-left: 40px;
  margin: 10px 0;
}

The HTML for this example looks like this:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>TUTORIAL: Change "data" Attributes with Mouse Clicks</title>
</head>
<body>
  <a href="#" id="chelsea">Load Chelsea FC Info »</a>

  <div id="teamInfo"
       data-team="Chelsea FC"
       data-manager="José Mourinho"
       data-home-pitch="Stanford Bridge">
  </div>

  <div id="team" class="dataTarget"></div>
  <div id="manager" class="dataTarget"></div>
  <div id="homePitch" class="dataTarget"></div>

  <!-- Note that we're using the oldIE-friendly version of jQuery -->
  <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
</body>
</html>

And the JavaScript for this example looks like this:


var team = document.querySelector("#team"),
    manager = document.querySelector("#manager"),
    homePitch = document.querySelector("#homePitch");

$("#chelsea").click(function(event){

  event.preventDefault();

  // Use the dataset property
  team.innerHTML = teamInfo.dataset.team;
  manager.innerHTML = teamInfo.dataset.manager;
  homePitch.innerHTML = teamInfo.dataset.homePitch;

});

Breaking down the HTML first:


<a href="#" id="chelsea">Load Chelsea FC Info »</a>

In the HTML, clicking on the id="chelsea element will load the content stored in the data attributes.


<div id="teamInfo"
     data-team="Chelsea FC"
     data-manager="José Mourinho"
     data-home-pitch="Stanford Bridge">
</div>

Those attributes are listed inside <div id="teamInfo"> and named data-team, data-home-pitch and data-manager.


<div id="team" class="dataTarget"></div>
<div id="manager" class="dataTarget"></div>
<div id="homePitch" class="dataTarget"></div>

On mouse clicks, those attribute values will be placed inside the three div tags at the bottom. Their ids are: team, manager and homePitch.

Breaking down the JavaScript next…


var team = document.querySelector("#team"),
    manager = document.querySelector("#manager"),
    homePitch = document.querySelector("#homePitch");

For those div tags that we’re placing the data attribute values inside of , we’re using document.querySelector() to store references to them. This will make them easier to find when I start referring to them in this next function…


$("#chelsea").click(function(event){

  event.preventDefault();

  // Use the dataset property
  team.innerHTML = teamInfo.dataset.team;
  manager.innerHTML = teamInfo.dataset.manager;
  homePitch.innerHTML = teamInfo.dataset.homePitch;

});

The $ tells us that we’re using jQuery to bind the jQuery.click method to the link on the web page which, again, is the one with an id of #chelsea. It has a parameter called event passed to it, which we’ll discuss a little later.

When #chelsea is clicked, it accesses the previously-mentioned dataset property of the #teamInfo element and sees the value of all its data attributes. So for example: after it’s clicked, #chelsea sees that teamInfo.dataset.team is direct reference to the value of the data-team attribute in #teamInfo, and that the value is “Chelsea FC”.

The link will then take that value and place it inside the <div> tags that we referenced with querySelector(), all with help of the innerHTML property. So for example: that value of “Chelsea FC” will be placed inside of ` <div id="team">, which is referenced by the previously-created team` variable.

OK…back to the function’s event parameter…

We have to do this so we can use the event.PreventDefault() method in the link. If we don’t use this and the link’s href attribute is set to # (which is what’s happening with #chelsea), then the # will be passed to the URL.

Depending on the page layout, this will force the page to jump to the top. Which we don’t want.

(Side note: read more about event.PreventDefault() on MDN. There’s also the similar event.stopPropagation() on MDN, but that blocks events a little more obtrusively then event.PreventDefault().)

Proper naming of data attributes (See the demo) Here’s one of the quirks of data attributes…

As just mentioned, teamInfo.dataset.team is a direct reference to the data-team attribute…that makes sense. It’s also the same thing with the manager information: teamInfo.dataset.manager is a direct reference to the data-manager attribute.

But those are one-word attributes and our data-home-pitch attribute is two words. So the syntax is different:


homePitch.innerHTML = teamInfo.dataset.homePitch;

It needs to be this way for two-word data attributes due to how dataset works behind the scenes. Specifically, dataset will look at the data attribute names and automatically drop the data- prefix first, removing hyphens next, and finally convert the attribute to camelCase.

To better understand this, look at the HTML for this section’s demo and note that it has a data-homePitch attribute. Click on the “Load Chelsea FC Info »” link and notice that the pitch content loads in as undefined instead of the expected “Stanford Bridge”.

Adjust the code so it says data-home-pitch and it will work fine. But from there, go to the JavaScript and change teamInfo.dataset.homePitch to teamInfo.dataset.home-pitch…you’ll get a browser console reference error saying “pitch is not defined”.

Because of this, I suggest keeping your data attributes and dataset properties at a two-word minimum. data-home-pitch and teamInfo.dataset.homePitch are fine…data-home-team-pitch and teamInfo.dataset.homeTeamPitch may work, but are too verbose.

Store the data attributes in a link (See the demo) The first example separated the link and the data attribute content for the sake of a clearer explanation, but a real-world use case is to store the attributes in the link being clicked on. Using the same CSS, that code would look like this:

The HTML


...
<a href="#"
   id="chelsea"
   data-team="Chelsea FC"
   data-manager="José Mourinho"
   data-home-pitch="Stanford Bridge">
   Load Chelsea FC Info »
</a>

<div id="team" class="dataTarget"></div>
<div id="manager" class="dataTarget"></div>
<div id="homePitch" class="dataTarget"></div>
...

The JavaScript


var team = document.querySelector("#team"),
    manager = document.querySelector("#manager"),
    homePitch = document.querySelector("#homePitch");

$("#chelsea").click(function(event){

  event.preventDefault();

  team.innerHTML = this.dataset.team;
  manager.innerHTML = this.dataset.manager;
  homePitch.innerHTML = this.dataset.homePitch;

});

In the HTML, we’ve taken the data attributes listed in <div id="teamInfo"> and placed them inside the #chelsea link. This means that <div id="teamInfo"> is no longer needed so we can get rid of it.

In the JavaScript, we’ve replaced all the teamInfo.dataset references to this.dataset. this is a direct reference to the #chelsea link context, meaning it sees everything connected to it…including the data attributes.

In the previous example, we could have used chelsea.dataset instead of this.dataset. But by using this, our code is reusable and lets us create multiple links that use the same functionality.

Still keeping the CSS the same, we can add a few more links to our HTML while slightly adjusting our JavaScript. Clicking on each link will load different info onto our page.

The HTML


...
<ul>
  <li class="teamListItem">
    <a href="#"
       class="teamLink"
       data-team="Chelsea FC"
       data-manager="José Mourinho"
       data-home-pitch="Stanford Bridge">
       Load Chelsea FC Info »
    </a>
  </li>
  <li class="teamListItem">
    <a href="#"
      class="teamLink"
      data-team="Real Madrid"
      data-manager="Carlo Ancelotti"
      data-home-pitch="Santiago Bernabéu">
      Load Real Madrid Info »
    </a>
  </li>
  <li class="teamListItem">
    <a href="#"
       class="teamLink"
      data-team="AC Milan"
      data-manager="Filippo Inzaghi"
      data-home-pitch="San Siro">
      Load AC Milan Info »
    </a>
  </li>
  <li class="teamListItem">
    <a href="#"
      class="teamLink"
      data-team="Paris Saint-Germain"
      data-manager="Laurent Blanc"
      data-home-pitch="Parc des Princes">
      Load Paris Saint-Germain Info »
    </a>
  </li>
</ul>

<div id="team" class="dataTarget"></div>
<div id="manager" class="dataTarget"></div>
<div id="homePitch" class="dataTarget"></div>
...

The JavaScript


var team = document.querySelector("#team"),
    manager = document.querySelector("#manager"),
    homePitch = document.querySelector("#homePitch");

 $(".teamLink").click(function(event){

  event.preventDefault();

  team.innerHTML = this.dataset.team;
  manager.innerHTML = this.dataset.manager;
  homePitch.innerHTML = this.dataset.homePitch;

});

In the HTML we’ve removed the id property while adding a class called teamLink for each of our links. We’re also no longer targeting the id in the jQuery.click method and, instead, targeting this new teamLink class.

So every time a link with the teamLink class gets clicked, the this keyword forces our JavaScript code to look at the data attributes for that link only, then load them onto the page.

Use getAttribute() as fallback code for dataset (See the demo) dateset is awesome but, as mentioned in the beginning, isn’t cross-browser compliant and doesn’t work in all browsers. We can write fallback code with the getAttribute() method for those browsers, specifically IE 10 and lower.

The CSS and HTML stay the same while the JavaScript goes through some changes…


var team = document.querySelector("#team"),
    manager = document.querySelector("#manager"),
    homePitch = document.querySelector("#homePitch"),
    teamValue,
    managerValue,
    homePitchValue;

$(".teamLink").click(function(event){
  event.preventDefault();

  // if "this.dataset" does NOT exist, do this
  if(!this.dataset) {
    teamValue = this.getAttribute("data-team");
    managerValue = this.getAttribute("data-manager");
    homePitchValue = this.getAttribute("data-home-pitch");

  // if but if it does, do this
  } else {
    teamValue = this.dataset.team;
    managerValue = this.dataset.manager;
    homePitchValue = this.dataset.homePitch;
  };

  team.innerHTML = teamValue;
  manager.innerHTML = managerValue;
  homePitch.innerHTML = homePitchValue;

});

We’ve add three new variables without giving them a value: teamValue, managerValue and homePitchValue. From there, we make sure that every time a link is clicked, it first checks to see if our links have a dataset property.

We do this using a JavaScript if/else statement and first check to see dataset does NOT exist by saying if(!this.dataset). If it doesn’t exist, we’ll use the getAttribute() method to find all the data attribute values and store them in the three new variables we just created.

But if the dataset property DOES exist, we’ll just say that those new variables should use dataset to access and store those values like we’ve been doing up to this point.

We’ve made sure that the teamValue, managerValue and homePitchValue variables are properly storing the data attributes values, regardless of whether or not the browser supports dataset. So in order to get those values on the page, we just need to tell our code to look at those variable values and place them inside the elements we pre-defined awhile ago with document.querySelector().

Conclusion This was a very basic data attribute implementation and, quite frankly, data attribute implementations should be simple. Each link isn’t overloaded with data attributes that have over 100 character lines of copy.

Were that my data structure or something even more complicated, I would use a pure JS implementation and ignore data attributes. That was my opinion at the start of this but reading well-respected developer Divya Manian’s excellent dataset post justifies my feelings.

But for simple data structures, using data attributes will certainly fit your simpler use cases, especially if you need to load them with a mouse event. So there are many reasons to add them to your production code, and I hope this tutorial helps you.