Simple HTML5 data-* jQuery tabs, markup free and relative to their container for ultimate re-use

JavaScript Tagged in JavaScript May 20, 2013 5 mins read by Todd Motto

One of my favourite additions to the HTML5 spec is data-* attributes, they’re useful for such an array of things. I love integrating them into jQuery/JavaScript and seeing what difference to HTML they make.

Two things have inspired this post to create some mega simple tabbed content UI components, the first - Twitter Bootstrap. Twitter Bootstrap is used by so many people, but I really have no use for it other than pinching one of two of the jQuery plugins whilst working in development. The second reason for creating this is AngularJS from Google - a super intelligent web application framework that I am learning more and more of.

The Bootstrap tabs are okay, they’re a little heavy for what they really achieve and the code isn’t very self explanatory for developers wanting to learn from.

And back to AngularJS, I just love the way it works. It focuses on the view (being the DOM), in which you bind a model to (JavaScript). This makes the framework so reusable and flexible, and the future of web development definitely lies within where AngularJS is going. So here we are - the next idea. AngularJS makes use of their own attributes, literally extending the DOM and HTML’s capabilities, a simple Angular demo:

<div ng-app>
  <input type=text ng-model="inputted">
</div>

The above may not look like much, but you can see I’ve binded ‘ng-model’ onto the input element, and can essentially mirror/call the model using double curly brackets {{inputted}} - which means anything I type into the input will be reflected into the DOM too. Built into AngularJS are directives that get this to work obviously, but you can see the simplicity behind it, as well as the fact it’s totally reusable on as many elements throughout the DOM as you need. So let’s head that way. Enough with the UI components that need actual hard coding - let’s create objects that are reusable.

HTML5 data-* attributes

AngularJS doesn’t just use ‘ng-*’ prefixes to their binding, for validation purposes you can use data-ng-* to stay safe - and data attributes are the way to go here too. Let’s create two types of data, a tab and the content:

<a href="#" data-tab="">Tab</a>
<div data-content="">Content</div>

This sets up the DOM for us to build upon. What I want to do next is essentially match the tab clicked with the content box, so we need to pair the data-* attributes, however the developer decides to match them with naming conventions is up to them; this script should be really flexible as long as the data-* values pair:

<a href="#" data-tab="1">Tab</a>
<div data-content="1">Content</div>

This now pairs them! So what next? We need to get started with the jQuery. We need to grab the data-tab value once clicked, and match it against an element that contains the match pair inside data-content. Let’s setup the click handler to target our data-tab elements first:

$('[data-tab]').on('click', function (e) {
  
})

Then log a result using jQuery’s built-in .data() API:

$('[data-tab]').on('click', function (e) {
  console.log($(this).data('tab'))
})

You’ll then see this logs the value inside the data-tab attribute inside the console, step one is complete. Now step two, making it dynamically match by looking for the element’s matching data-* pair:

$(this).siblings('[data-content=' + $(this).data('tab') + ']')

The above scans the sibling elements from the $(this) element (current element clicked on) - and then scans the sibling elements to find an element that contains a data-content selector with the identical data value.

We need to create some fuller markup now to get a better picture of what’s happening:

<div class="tabs">
  <a href="#" data-tab="1" class="tab active">Tab 1</a>
  <a href="#" data-tab="2" class="tab">Tab 2</a>
  <a href="#" data-tab="3" class="tab">Tab 3</a>
  
  <div data-content="1" class="content active">Tab 1 Content</div>
  <div data-content="2" class="content">Tab 2 Content</div>
  <div data-content="3" class="content">Tab 3 Content</div>
</div>

An active classes need pushing around now it’s in the markup, let’s put the above together and swap out some classes:

$('[data-tab]').on('click', function () {
  $(this).addClass('active').siblings('[data-tab]').removeClass('active')
  $(this).siblings('[data-content=' + $(this).data('tab') + ']').addClass('active').siblings('[data-content]').removeClass('active')
})

The active tab is set to display:block; and all data-content blocks are set to display:none; which means only the content with the ‘active’ class will be shown. After chaining some jQuery methods, in 4 lines of code the tabs are fully functional, and completely independent of any markup, no specifying parent selectors, giving it a class or ID - it just works. It’s very similar to Angular in a few ways, but obviously Angular is a massive web framework that allows for an MVC approach.

The finishing touch on the script is to prevent the <a href=”#”> links from bouncing when you click the anchors, we simply capture the click event passed through the function, and prevent default on it:

$('[data-tab]').on('click', function (e) {
  $(this).addClass('active').siblings('[data-tab]').removeClass('active')
  $(this).siblings('[data-content=' + $(this).data('tab') + ']').addClass('active').siblings('[data-content]').removeClass('active')
  e.preventDefault()
})

One line of code

What’s interesting about jQuery is the fact you can chain functions/methods together as it keeps returning the jQuery object after each one. I’ve actually split this code onto two lines (you see both calls for $(this) but actually, these tabs are totally functional with chaining everything on just one line of code (ignoring the click handler):

$(this).addClass('active').siblings('[data-tab]').removeClass('active').siblings('[data-content=' + $(this).data('tab') + ']').addClass('active').siblings('[data-content]').removeClass('active')

Ultimate re-use

Because the script is setup to search for sibling selectors, feeding from a $(this) element - it means we can have multiple tabs per page with the same data-* values!

Extending the tabs

The tabs are setup to be totally markup free, and in true AngularJS fashion, you literally can just add more data-* attributes and let it do it all for you:

<div class="tabs">
  <a href="#" data-tab="1" class="tab active">Tab 1</a>
  <a href="#" data-tab="2" class="tab">Tab 2</a>
  <a href="#" data-tab="3" class="tab">Tab 3</a>
  <a href="#" data-tab="4" class="tab">Tab 4</a>
  <a href="#" data-tab="5" class="tab">Tab 5</a>
  <a href="#" data-tab="6" class="tab">Tab 6</a>
  <a href="#" data-tab="7" class="tab">Tab 7</a>
  
  <div data-content="1" class="content active">Tab 1 Content</div>
  <div data-content="2" class="content">Tab 2 Content</div>
  <div data-content="3" class="content">Tab 3 Content</div>
  <div data-content="4" class="content">Tab 4 Content</div>
  <div data-content="5" class="content">Tab 5 Content</div>
  <div data-content="6" class="content">Tab 6 Content</div>
  <div data-content="7" class="content">Tab 7 Content</div>
</div>

7, 8, 9, 10… and so on!

Just add more data-* attributes and you’re golden! :)

Angular Online Courses Angular shields
Ultimate Angular

Become an Angular expert

Online courses that give you the knowledge to master Angular and build real world applications.

Explore Courses Navigation arrow