Create A Tabbed Interface Part 1

Aaron Gustafson
Aaron GustafsonWeb Design Author, Evangelist, Practitioner, and Teacher at Microsoft

In the last issue, Aaron Gustafson explained how to create a tabbed interface using CSS and JavaScript. Now he focuses on making the JavaScript more flexible and accessible. An excerpt from issue 194 of .net magazine.

.net technique JavaScript



 JavaScript create                                                                                                                                                 l CD
                                                                                                                                                   Your essentia uire
                                                                                                                                                             ’ll req




a tabbed interface
                                                                                                                                                   All the files you
                                                                                                                                                                   al can be
                                                                                                                                                   for this tutori
                                                                                                                                                                    issue’s CD.
                                                                                                                                                   found on this




 Part one In the first of a two-part tutorial, Aaron Gustafson demonstrates how to create a
script for a lightweight yet powerful tabbed interface using CSS and basic JavaScript

Knowledge needed Basic JavaScript, (x)HTML, CSS                                           Feeling inspired, I set out to write the script we’ll walk through here. As with
Requires A text editor                                                                any great piece of writing, I needed a goal and a plan of action for reaching
                                                                                      it. What was the script going to do exactly? What were the constraints? What
Project time 30-45 minutes
                                                                                      sort of flexibility needed to be built in? I started with a list of requirements.
                                                                                      It needed to: work in any container-type element (most likely a div, but not
          I’ve always been intrigued by the concept of a tabbed interface. My         necessarily); separate the content into tabs based on the first encountered
          main issue with previous attempts at building one was that they             heading level; and offer a good deal of flexibility for styling.
          almost always required you to add extra markup to the document –                With those guidelines, I decided on the following steps for the code to use
such as section wrappers and a list of links that would become the tabs – so          as a blueprint: find any element classified as tabbed (that single class would be
they could be leveraged by JavaScript when constructing the interface.                the hook that triggers the script); parse the contents of that element, breaking
   I wanted to find a way around manually adding that cruft. After all, that          the content into chunks based on the first heading level encountered within it;
extra markup was useless without JavaScript on, so it made no sense for it to         generate the wrapper markup for each section and insert the content; generate
be hard-coded into the document to begin with. JavaScript is perfectly adept          the tab allowing access to that content; assign the appropriate event handlers to
at manipulating the DOM, so why not use it to inject all of the code necessary        the tabs; and append it all back into the document.
to drive the tabbed interface when we know it can actually be used? In other              The steps seemed pretty straightforward except the chunking bit; that would
words, I was yearning for an unobtrusive way to construct a tabbed interface.         require a little regular expression magic, but we’ll get to that shortly.
   Generating the markup needed for a widget via script is nothing new, but               With a plan in place, I threw together a simple page with a recipe for
the challenge lies in determining where to break the content up. I’d always felt      making a pumpkin pie. Here’s a rough overview of its markup:
the need for some sort of markup-based hook to indicate where the content
chunks should start and end. Then I realised that headings (h1-h6) – because
they create a document outline – were exactly the kind of natural language
section divider that I was looking for.
                                                                                       JavaScript is perfectly adept at
                                                                                       manipulating the DOM, so
                                                                                       we’ll use it to inject the code
                                                                                      <h1>Pumpkin Pie</h1>
                                                                                      <div class="tabbed">
                                                                                       <h2>Overview</h2>
                                                                                       <img src="pie.jpg" alt="" />
                                                                                       <p>Whether you're hosting a festive party or a casual get-together
                                                                                       with friends, our Pumpkin Pie will make entertaining easy!</p>
                                                                                       ...
                                                                                       <h2>Ingredients</h2>
                                                                                       ...
                                                                                      </div>

                                                                                      Next, I manually tweaked the page to determine what markup I wanted to
                                                                                      use in the final markup and what hooks I needed for styling. I opted for a
                                                                                      fairly traditional structure of wrapper divs, with the whole thing contained
                                                                                      within that initial container classified as tabbed. I decided the script would
                                                                                      use variable names based upon the concept of a filing cabinet (folders, tabs,
                                                                                      etc) and I carried it through to the generated markup as well, classifying each
                                                                                      division as a ‘folder’, with the first one receiving a class of visible since it would
                                                                                      be the one shown by default. I also classified each h2 as ‘hidden’ so they could
                                                                                      be removed from view, and then added the list of tabs to the end as ul.tab-list,
                                                                                      giving the currently active tab a class of, well, active.
Off the hook This script relies only on a single hook: a class of tabbed. All other       Finally, I changed the overall container’s class from tabbed to tabbed-
necessary markup is generated by the script when it runs                              on. The script itself will look for tabbed and then swap it for this new class



70     .net october 2009
.net technique JavaScript



                                                                                         Reign in your styles
                                                                                         Don’t let them run amok!
                                                                                         If you’ve worked a lot with CSS, you’ll know there’s a balance to be struck
                                                                                         with selectors. Make a selector too generic and you could unintentionally
                                                                                         set a property on an element you didn’t mean to target; make it too
                                                                                         specific and your whole style sheet can erupt into an arms race of
                                                                                         increasingly specific selectors when you want to override a property.
                                                                                         When JavaScript enters the picture, things get a bit more complex.
                                                                                             For one, you have to make sure the styles needed for the JavaScript
                                                                                         widget don’t bleed over and begin to affect other elements on the page
                                                                                         (ones that, for instance, share the same class). This can be avoided by
                                                                                         hanging your styles off of a class or id that’s specific to the JavaScript
                                                                                         object. For example, if your script is WickedCool.js, make the wrapper
                                                                                         element .WickedCool or #WickedCool and preface all related selectors
                                                                                         with that unique hook.
Bare bones With no styles applied, the page looks pretty ugly, but is still usable           Be wary of timing when you apply your widget-related styles. For
                                                                                         example, if a user has JavaScript turned off, you don’t want to have
when it’s ready to run in order to ensure styles are not applied prematurely.            styles applied to the page that hide content the widget was supposed
This technique, known as class-swapping, is a great strategy for maintaining             to make visible; the user will never be able to see it. This issue is easily
separation between presentation and behaviour. With the markup in place, I               resolved by hanging all of your styles off of a class that’s not used
wrote some basic styles to lay out the tabbed interface. Of particular note is           directly in the markup, instead using one that’s set by JavaScript.
how I chose to hide the inactive content sections:                                           This can be done in a few different ways: change the form of the
                                                                                         employed class (eg from change-me to changed); append -on to the
.tabbed-on .folder {                                                                     employed class (eg from tabbed to tabbed-on); add a second class
 position: absolute;                                                                     (eg on). All of these options act as a switch that your script can trigger
 top: 0;                                                                                 when it knows it’s safe for the styles to be applied. It should be noted,
 left: -999em;                                                                           however, that compound class selectors are not supported by IE6, so if
}                                                                                        you need to support that browser, don’t consider the third option.

and then make them visible again:
                                                                                      $(document).ready(function(){
.tabbed-on .folder.visible {                                                           $(".tabbed").each(function(){
 position: static;                                                                       new TabInterface();
}                                                                                      });
                                                                                      });
    Armed with a plan and the knowledge of the markup I’d need to make it all
work, I set about writing the script that we’ll now build together. To start things       This little bit of code (which could be replicated easily in the library of your
off, create an object constructor called TabInterface:                                choice or via native JS methods) runs when the DOM is loaded and scans
                                                                                      through the document, creating a new TabInterface instance for every element
function TabInterface(){}                                                             it encounters with a class of tabbed. Of course, as we only have the skeleton
                                                                                      of a constructor, the TabInterface object isn’t set up to capture and manipulate
   It’s really nothing more than a function but, as everything in JavaScript is an    the content that needs tabbing, but we’ll get there.
object, it’s also an object and it can be instantiated as many times as necessary
on a page.                                                                            Construction time
   So, using a library like jQuery, we could say:                                     There’s no reason to expose TabInterface object’s inner workings, so we’ll
                                                                                      build them all as private members of this object. As we’ll be operating inside
                                                                                      of a function, we’ll need to create the object’s properties as variables and its
                                                                                      methods as functions. (And yes, functions can contain other functions.) Each
                                                                                      property and method will remain private to TabInterface unless we expose it by
                                                                                      directly assigning it as a property of this. The only member we’ll expose is the
                                                                                      version of the script (available as TabInterface.Version and set as this.Version),
                                                                                      in case anyone wants to implement an extension to the script and wants to see
                                                                                      which version it is. Here’s a basic outline of the properties we need to create:
                                                                                            Version – the current script version
                                                                                            _active – the ID of the active folder (false by default)
                                                                                            _index – a DOM-generated unordered list that will contain the tabs
                                                                                            _els – an object literal containing two properties of its own
                                                                                            li – a DOM-generated list item
                                                                                            div – a DOM-generated division.
                                                                                          Of note is the final property, _els. JavaScript can create elements on the
                                                                                      fly, but cloning an element (using element.cloneNode()) is much faster than
                                                                                      generating a new element (using document.createElement()) each time you
                                                                                      need it. As we’ll be generating multiple divisions around the content chunks
Styled up With basic typographic and colour styles applied, the page looks a little   and multiple list items for our tab list, it makes sense to create those
better and is completely functional, even in the absence of JavaScript                elements before we need them so we can clone them easily later on.



                                                                                                                                   .net october 2009         71    next>
.net technique JavaScript


      Once you’ve added those properties, create two new functions to serve
      as the core methods for TabInterface:
    initialize – the function that will build the tabbed interface
    swap – the method that will handle changing currently displayed content
Because we’ll be doing some class manipulation, include two helper methods,
addClassName and removeClassName, using the following code:

function addClassName( e, c ){
 var classes = ( !e.className ) ? [] : e.className.split( ' ' );
 classes.push( c );
 e.className = classes.join( ' ' );
}
function removeClassName( e, c ){
 var classes = e.className.split( ' ' );
 for( var i=classes.length-1; i>=0; i-- ){
   if( classes[i] == c ) classes.splice( i, 1 );
 }
 e.className = classes.join( ' ' );
}
                                                                                          Tab happy With the widget’s styles applied, the pumpkin pie recipe is transformed into
   With the preliminaries out of the way, let’s start building out TabInterface in        an elegant, compact, tabbed interface
earnest. The first thing we need to do is provide a means for the element that
needs tabbing to be passed into the object so we can manipulate it. To do that,               The next step is to divvy up the content by determining the heading level
add an argument to the TabInterface function itself. While you’re at it, add a            (h1-h6) upon which to base the chunking. But first, let’s make the node
second argument to accept an iteration number (which we’ll put to good use                iteration a little easier (and more cross-browser compatible) by stripping out
in a moment). Name these two arguments _cabinet and _i, respectively, as you              any nodes of whitespace that are children of _cabinet:
will need to reference them later. Your code should look (roughly) like this:

function TabInterface( _cabinet, _i ){
 this.Version = '0.3';
                                                                                           We must provide a means for
Now let’s tackle initialize(). You’ll want to update the initialisation of TabInterface
                                                                                           the element that needs tabbing
(above) to pass in the arguments we just added, if you want to see your work in
action as you build. To allow the script to work efficiently, we’ll generate unique        to be passed into the object
ids for each of the sections as well as the tabs. To keep things organised and
keep the ids sensible, each should relate back to the id of the containing element
(passed as _cabinet). Of course, _cabinet may not have an id, so we may need to            var node = _cabinet.firstChild;
generate one. We can do that using the passed iterator ('cabinet-' + _i). Once you         while( node ){
have the id, store it to a local variable called _id so you can reference it later:          var nextNode = node.nextSibling;
                                                                                             if( node.nodeType == 3 &&
function initialize(){                                                                           !/S/.test( node.nodeValue ) ){ _cabinet.removeChild( node ); }
 // set the id                                                                               node = nextNode;
 var _id = el.getAttribute( 'id' ) || 'cabinet-' + _i;                                     }
 if( !el.getAttribute( 'id' ) ) el.setAttribute( 'id', _id );
                                                                                              Then, you can determine the heading level of _cabinet’s firstChild by testing
                                                                                          it against a list of known heading levels:
 Resources Where to find out more
                                                                                           var headers = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
                                                                                           for( var i=0, len=headers.length; i<len; i++ ){
                                                                                             if( _cabinet.firstChild.nodeName.toLowerCase() == headers[i] ){
                                                                                               var _tag = headers[i];
                                                                                               break;
                                                                                             }
                                                                                           }

                                                                                              This code block loops through the header types and tests each against the
   Understanding Progressive                    Unobtrusive JavaScript for                lower-cased tag name (nodeName) of the first child element (firstChild) of
   Enhancement                                  Progressive Enhancement                   the container (_cabinet). When a match is encountered, the script assigns the
   For a bit more background                    Phil Hawksworth put together this         correct heading level to _tag and the loop is broken.
   on the concept of progressive                wonderful demonstration of how                Now that we have the heading level, we can chunk the content of _cabinet
   enhancement, check out this                  JavaScript can be used in smart           using regular expressions and innerHTML, storing the chunks as an array in the
   primer in A List Apart, as well as           ways, and helpfully it comes              local variable arr. The split I employ uses a regular expression replacement that
   its two companion articles.                  with an in-depth explanation of           inserts an arbitrary, yet uncommon series of characters (||||) in front of the
   alistapart.com/articles/                     how to apply the same techniques          opening header tag before the content is split (creating an array) using those
   understandingprogressive                     to your projects.                         very same characters.
   enhancement                                  unobtrusify.com                               The first member of the array will be empty (because it comes before the
                                                                                          first instance of the heading element), but shift() removes it:




<prev     72      .net october 2009
.net technique JavaScript


 var rexp = new RegExp( '<(' + _tag + ')', 'ig' );
 var arr = _cabinet.innerHTML.replace( rexp, "||||<$1" ).split( '||||' );
 arr.shift();

   With the content of _cabinet tucked safely away in arr, you can empty the
container’s contents and do a little class-swapping to turn on your styles:

 _cabinet.innerHTML = '';
 removeClassName( _cabinet, 'tabbed' );
 addClassName( _cabinet, 'tabbed-on' );

    Next, loop through arr’s members and create both a folder and a tab for
each content chunk. The folders will be clones of _els.div and should be
appended directly to _cabinet. The tabs will be cloned from _els.li and should
be appended to the ul we created and stored as _index. The text for the tab
will come from the heading element, and classifying the heading as hidden will
allow you to hide it with CSS, keeping users from seeing the same text twice.
Along the way, assign unique ids to the tabs and folders using _id.                         Button it An early version of this script was used on Nestlé’s Idol Elimination site. The
    While you’re at it, add an onclick event handler to the tab (a reference to             id-based hooks generated by this script enabled the addition of Next and Previous buttons
the swap method we’ll get to in a moment), classify the first folder as visible
and store its id in the _active variable, and then activate the current tab by               _index.className = 'index';
giving it a class of active. Your code should look similar to this:                          _cabinet.appendChild( _index );

 for( i=0, len=arr.length; i<len; i++ ){                                                    With the markup adjustments complete, all that’s left to do is fill in the logic
   var folder = _els.div.cloneNode( true );                                                 for the swap event handler. The logic doesn’t need to be very complex; it
   folder.setAttribute( 'id', _id + '-' + i );                                              simply needs to swap visible folders and activated tabs.
   folder.innerHTML = arr[i];
   addClassName( folder, 'folder' );                                                        Almost there
   _cabinet.appendChild( folder );                                                          To get the currently active folder, tap into the _active property of TabInterface,
   var heading = folder.getElementsByTagName( _tag )[0];                                    as it contains the active folder’s id. Then use the helper methods to move the
   addClassName( heading, 'hidden' );                                                       appropriate classes from the currently active folder and tab to the newly activated
   var tab = _els.li.cloneNode( true );                                                     ones, and update the _active property to refer to the newly opened folder:
   tab.setAttribute( 'id', 'id', _id + '-' + i + '-tab' );
   tab.innerHTML = heading.innerHTML;                                                       function swap( e ){
   tab.onclick = swap;                                                                       e = ( e ) ? e : event;
   _index.appendChild( tab );                                                                var tab = e.target || e.srcElement;
   if( i === 0 ){                                                                            var folder_id = tab.getAttribute( 'id' ).replace( '-tab', '' );
     addClassName( folder, 'visible' );                                                      removeClassName( document.getElementById( _active + '-tab' ), 'active' );
     _active = _id + '-' + i;                                                                removeClassName( document.getElementById( _active ), 'visible' );
     addClassName( tab, 'active' );                                                          addClassName( tab, 'active' );
   }                                                                                         addClassName( document.getElementById( folder_id ), 'visible' );
 }                                                                                           _active = folder_id;
                                                                                            }
Finally, with the loop complete, classify _index as an index and add it to _cabinet:
                                                                                               The final step is to trigger initialize() to run when a new TabInterface is
                                                                                            created. To do that, add a call to it at the end of the TabInterface function:

                                                                                            initialize();

                                                                                            In less than 100 lines of code, you’ve build a powerful, yet simple tabbed
                                                                                            interface script, and you did it using a combination of regular expressions,
                                                                                            object-orientation and unobtrusive scripting.
                                                                                                Don’t miss part two of this article in next month’s issue, in which we’ll
                                                                                            improve the flexibility of this script and make it more accessible using WAI-ARIA
                                                                                            roles and states. l
                                                                                            Note: TabInterface is available under the liberal MIT licence. The complete latest
                                                                                            version of the script can be downloaded from GitHub at: easy-designs.github.com/
                                                                                            tabinterface.js.


                                                                                                                  About the author
                                                                                                                  Name Aaron Gustafson
                                                                                                                  Site easy-designs.net
                                                                                                                  Areas of expertise Front- and back-end
                                                                                                                  development, strategy
                                                                                                                  Clients Brighter Planet, Yahoo, Artbox.com
Out of the box In The Amanda Project (bit.ly/JKze6), Jason Santa Maria took the tab
metaphor out of its traditional box and integrated it perfectly with the site’s aesthetic                         Favourite ice cream Mint chocolate chip




                                                                                                                                                      .net october 2009          73

Recommended

Bonjour America by
Bonjour AmericaBonjour America
Bonjour America2lingua
571 views8 slides
Voyage à paris (script) by
Voyage à paris (script)Voyage à paris (script)
Voyage à paris (script)2lingua
737 views8 slides
Fascismo y nazismo by
Fascismo y nazismoFascismo y nazismo
Fascismo y nazismo4ABRodrigocaro
3.3K views15 slides
Turn your students into first time authors by
Turn your students into first time authorsTurn your students into first time authors
Turn your students into first time authors2lingua
433 views23 slides
The Features of Highly Effective Forms [SmashingConf NYC 2016] by
The Features of Highly Effective Forms [SmashingConf NYC 2016]The Features of Highly Effective Forms [SmashingConf NYC 2016]
The Features of Highly Effective Forms [SmashingConf NYC 2016]Aaron Gustafson
1.4K views118 slides
Les Descendants by
Les DescendantsLes Descendants
Les Descendants2lingua
665 views11 slides

More Related Content

Similar to Create A Tabbed Interface Part 1

Desingning reusable web components by
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsJoonas Lehtinen
2.5K views89 slides
DevOps introduction with ansible, vagrant, and docker by
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerMark Stillwell
510 views13 slides
DevOps introduction with ansible, vagrant, and docker by
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerMark Stillwell
1.9K views13 slides
Desingning reusable web components by
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsJoonas Lehtinen
763 views92 slides
Desingning reusable web components by
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsjojule
1K views92 slides
Behavior Driven Development Testing (BDD) by
Behavior Driven Development Testing (BDD)Behavior Driven Development Testing (BDD)
Behavior Driven Development Testing (BDD)Dignitas Digital Pvt. Ltd.
1.5K views14 slides

Similar to Create A Tabbed Interface Part 1(20)

Desingning reusable web components by Joonas Lehtinen
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
Joonas Lehtinen2.5K views
DevOps introduction with ansible, vagrant, and docker by Mark Stillwell
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and docker
Mark Stillwell510 views
DevOps introduction with ansible, vagrant, and docker by Mark Stillwell
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and docker
Mark Stillwell1.9K views
Desingning reusable web components by Joonas Lehtinen
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
Joonas Lehtinen763 views
Desingning reusable web components by jojule
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
jojule1K views
Better. Faster. UXier. — AToMIC Design by jennifer gergen
Better. Faster. UXier. — AToMIC DesignBetter. Faster. UXier. — AToMIC Design
Better. Faster. UXier. — AToMIC Design
jennifer gergen6K views
Maven: Managing Software Projects for Repeatable Results by Steve Keener
Maven: Managing Software Projects for Repeatable ResultsMaven: Managing Software Projects for Repeatable Results
Maven: Managing Software Projects for Repeatable Results
Steve Keener707 views
Google compute presentation puppet conf by bodepd
Google compute presentation puppet confGoogle compute presentation puppet conf
Google compute presentation puppet conf
bodepd791 views
Front End Development Automation with Grunt by Ladies Who Code
Front End Development Automation with GruntFront End Development Automation with Grunt
Front End Development Automation with Grunt
Ladies Who Code3.3K views
Fullstack Academy - Awesome Web Dev Tips & Tricks by Frances Coronel
Fullstack Academy - Awesome Web Dev Tips & TricksFullstack Academy - Awesome Web Dev Tips & Tricks
Fullstack Academy - Awesome Web Dev Tips & Tricks
Frances Coronel520 views
New ways to present your images by tutorialsruby
New ways to present your imagesNew ways to present your images
New ways to present your images
tutorialsruby168 views
&lt;img src="../i/r_14.png" /> by tutorialsruby
&lt;img src="../i/r_14.png" />&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />
tutorialsruby51 views
Prerendering Chapter 0 by Samael Wang
Prerendering Chapter 0Prerendering Chapter 0
Prerendering Chapter 0
Samael Wang431 views

More from Aaron Gustafson

Delivering Critical Information and Services [JavaScript & Friends 2021] by
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Aaron Gustafson
259 views138 slides
Adapting to Reality [Guest Lecture, March 2021] by
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Aaron Gustafson
131 views93 slides
Designing the Conversation [Beyond Tellerrand 2019] by
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Aaron Gustafson
633 views123 slides
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019] by
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]Aaron Gustafson
2.3K views179 slides
Progressive Web Apps: Where Do I Begin? by
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Aaron Gustafson
519 views155 slides
Media in the Age of PWAs [ImageCon 2019] by
Media in the Age of PWAs [ImageCon 2019]Media in the Age of PWAs [ImageCon 2019]
Media in the Age of PWAs [ImageCon 2019]Aaron Gustafson
522 views77 slides

More from Aaron Gustafson(20)

Delivering Critical Information and Services [JavaScript & Friends 2021] by Aaron Gustafson
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]
Aaron Gustafson259 views
Adapting to Reality [Guest Lecture, March 2021] by Aaron Gustafson
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]
Aaron Gustafson131 views
Designing the Conversation [Beyond Tellerrand 2019] by Aaron Gustafson
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]
Aaron Gustafson633 views
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019] by Aaron Gustafson
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]
Aaron Gustafson2.3K views
Progressive Web Apps: Where Do I Begin? by Aaron Gustafson
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?
Aaron Gustafson519 views
Media in the Age of PWAs [ImageCon 2019] by Aaron Gustafson
Media in the Age of PWAs [ImageCon 2019]Media in the Age of PWAs [ImageCon 2019]
Media in the Age of PWAs [ImageCon 2019]
Aaron Gustafson522 views
Adapting to Reality [Starbucks Lunch & Learn] by Aaron Gustafson
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]
Aaron Gustafson660 views
Conversational Semantics for the Web [CascadiaJS 2018] by Aaron Gustafson
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]
Aaron Gustafson649 views
Better Performance === Greater Accessibility [Inclusive Design 24 2018] by Aaron Gustafson
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Aaron Gustafson587 views
PWA: Where Do I Begin? [Microsoft Ignite 2018] by Aaron Gustafson
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]
Aaron Gustafson500 views
Designing the Conversation [Concatenate 2018] by Aaron Gustafson
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]
Aaron Gustafson481 views
Designing the Conversation [Accessibility DC 2018] by Aaron Gustafson
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]
Aaron Gustafson660 views
Performance as User Experience [AEADC 2018] by Aaron Gustafson
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]
Aaron Gustafson440 views
The Web Should Just Work for Everyone by Aaron Gustafson
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for Everyone
Aaron Gustafson414 views
Performance as User Experience [AEA SEA 2018] by Aaron Gustafson
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]
Aaron Gustafson950 views
Performance as User Experience [An Event Apart Denver 2017] by Aaron Gustafson
Performance as User Experience [An Event Apart Denver 2017]Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]
Aaron Gustafson647 views
Designing the Conversation [Paris Web 2017] by Aaron Gustafson
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]
Aaron Gustafson1.2K views
Exploring Adaptive Interfaces [Generate 2017] by Aaron Gustafson
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]
Aaron Gustafson1.2K views

Recently uploaded

How to reduce cold starts for Java Serverless applications in AWS at JCON Wor... by
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...Vadym Kazulkin
75 views64 slides
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu... by
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...NUS-ISS
37 views54 slides
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV by
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTVSplunk
88 views20 slides
20231123_Camunda Meetup Vienna.pdf by
20231123_Camunda Meetup Vienna.pdf20231123_Camunda Meetup Vienna.pdf
20231123_Camunda Meetup Vienna.pdfPhactum Softwareentwicklung GmbH
28 views73 slides
STPI OctaNE CoE Brochure.pdf by
STPI OctaNE CoE Brochure.pdfSTPI OctaNE CoE Brochure.pdf
STPI OctaNE CoE Brochure.pdfmadhurjyapb
12 views1 slide
The details of description: Techniques, tips, and tangents on alternative tex... by
The details of description: Techniques, tips, and tangents on alternative tex...The details of description: Techniques, tips, and tangents on alternative tex...
The details of description: Techniques, tips, and tangents on alternative tex...BookNet Canada
121 views24 slides

Recently uploaded(20)

How to reduce cold starts for Java Serverless applications in AWS at JCON Wor... by Vadym Kazulkin
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
How to reduce cold starts for Java Serverless applications in AWS at JCON Wor...
Vadym Kazulkin75 views
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu... by NUS-ISS
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...
Architecting CX Measurement Frameworks and Ensuring CX Metrics are fit for Pu...
NUS-ISS37 views
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV by Splunk
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV
.conf Go 2023 - How KPN drives Customer Satisfaction on IPTV
Splunk88 views
STPI OctaNE CoE Brochure.pdf by madhurjyapb
STPI OctaNE CoE Brochure.pdfSTPI OctaNE CoE Brochure.pdf
STPI OctaNE CoE Brochure.pdf
madhurjyapb12 views
The details of description: Techniques, tips, and tangents on alternative tex... by BookNet Canada
The details of description: Techniques, tips, and tangents on alternative tex...The details of description: Techniques, tips, and tangents on alternative tex...
The details of description: Techniques, tips, and tangents on alternative tex...
BookNet Canada121 views
How the World's Leading Independent Automotive Distributor is Reinventing Its... by NUS-ISS
How the World's Leading Independent Automotive Distributor is Reinventing Its...How the World's Leading Independent Automotive Distributor is Reinventing Its...
How the World's Leading Independent Automotive Distributor is Reinventing Its...
NUS-ISS15 views
DALI Basics Course 2023 by Ivory Egg
DALI Basics Course  2023DALI Basics Course  2023
DALI Basics Course 2023
Ivory Egg14 views
Special_edition_innovator_2023.pdf by WillDavies22
Special_edition_innovator_2023.pdfSpecial_edition_innovator_2023.pdf
Special_edition_innovator_2023.pdf
WillDavies2216 views
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze by NUS-ISS
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng TszeDigital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze
Digital Product-Centric Enterprise and Enterprise Architecture - Tan Eng Tsze
NUS-ISS19 views
AMAZON PRODUCT RESEARCH.pdf by JerikkLaureta
AMAZON PRODUCT RESEARCH.pdfAMAZON PRODUCT RESEARCH.pdf
AMAZON PRODUCT RESEARCH.pdf
JerikkLaureta15 views
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen... by NUS-ISS
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...
NUS-ISS28 views
Igniting Next Level Productivity with AI-Infused Data Integration Workflows by Safe Software
Igniting Next Level Productivity with AI-Infused Data Integration Workflows Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Safe Software225 views
Future of Learning - Yap Aye Wee.pdf by NUS-ISS
Future of Learning - Yap Aye Wee.pdfFuture of Learning - Yap Aye Wee.pdf
Future of Learning - Yap Aye Wee.pdf
NUS-ISS41 views
Combining Orchestration and Choreography for a Clean Architecture by ThomasHeinrichs1
Combining Orchestration and Choreography for a Clean ArchitectureCombining Orchestration and Choreography for a Clean Architecture
Combining Orchestration and Choreography for a Clean Architecture
ThomasHeinrichs169 views

Create A Tabbed Interface Part 1

  • 1. .net technique JavaScript JavaScript create l CD Your essentia uire ’ll req a tabbed interface All the files you al can be for this tutori issue’s CD. found on this Part one In the first of a two-part tutorial, Aaron Gustafson demonstrates how to create a script for a lightweight yet powerful tabbed interface using CSS and basic JavaScript Knowledge needed Basic JavaScript, (x)HTML, CSS Feeling inspired, I set out to write the script we’ll walk through here. As with Requires A text editor any great piece of writing, I needed a goal and a plan of action for reaching it. What was the script going to do exactly? What were the constraints? What Project time 30-45 minutes sort of flexibility needed to be built in? I started with a list of requirements. It needed to: work in any container-type element (most likely a div, but not I’ve always been intrigued by the concept of a tabbed interface. My necessarily); separate the content into tabs based on the first encountered main issue with previous attempts at building one was that they heading level; and offer a good deal of flexibility for styling. almost always required you to add extra markup to the document – With those guidelines, I decided on the following steps for the code to use such as section wrappers and a list of links that would become the tabs – so as a blueprint: find any element classified as tabbed (that single class would be they could be leveraged by JavaScript when constructing the interface. the hook that triggers the script); parse the contents of that element, breaking I wanted to find a way around manually adding that cruft. After all, that the content into chunks based on the first heading level encountered within it; extra markup was useless without JavaScript on, so it made no sense for it to generate the wrapper markup for each section and insert the content; generate be hard-coded into the document to begin with. JavaScript is perfectly adept the tab allowing access to that content; assign the appropriate event handlers to at manipulating the DOM, so why not use it to inject all of the code necessary the tabs; and append it all back into the document. to drive the tabbed interface when we know it can actually be used? In other The steps seemed pretty straightforward except the chunking bit; that would words, I was yearning for an unobtrusive way to construct a tabbed interface. require a little regular expression magic, but we’ll get to that shortly. Generating the markup needed for a widget via script is nothing new, but With a plan in place, I threw together a simple page with a recipe for the challenge lies in determining where to break the content up. I’d always felt making a pumpkin pie. Here’s a rough overview of its markup: the need for some sort of markup-based hook to indicate where the content chunks should start and end. Then I realised that headings (h1-h6) – because they create a document outline – were exactly the kind of natural language section divider that I was looking for. JavaScript is perfectly adept at manipulating the DOM, so we’ll use it to inject the code <h1>Pumpkin Pie</h1> <div class="tabbed"> <h2>Overview</h2> <img src="pie.jpg" alt="" /> <p>Whether you're hosting a festive party or a casual get-together with friends, our Pumpkin Pie will make entertaining easy!</p> ... <h2>Ingredients</h2> ... </div> Next, I manually tweaked the page to determine what markup I wanted to use in the final markup and what hooks I needed for styling. I opted for a fairly traditional structure of wrapper divs, with the whole thing contained within that initial container classified as tabbed. I decided the script would use variable names based upon the concept of a filing cabinet (folders, tabs, etc) and I carried it through to the generated markup as well, classifying each division as a ‘folder’, with the first one receiving a class of visible since it would be the one shown by default. I also classified each h2 as ‘hidden’ so they could be removed from view, and then added the list of tabs to the end as ul.tab-list, giving the currently active tab a class of, well, active. Off the hook This script relies only on a single hook: a class of tabbed. All other Finally, I changed the overall container’s class from tabbed to tabbed- necessary markup is generated by the script when it runs on. The script itself will look for tabbed and then swap it for this new class 70 .net october 2009
  • 2. .net technique JavaScript Reign in your styles Don’t let them run amok! If you’ve worked a lot with CSS, you’ll know there’s a balance to be struck with selectors. Make a selector too generic and you could unintentionally set a property on an element you didn’t mean to target; make it too specific and your whole style sheet can erupt into an arms race of increasingly specific selectors when you want to override a property. When JavaScript enters the picture, things get a bit more complex. For one, you have to make sure the styles needed for the JavaScript widget don’t bleed over and begin to affect other elements on the page (ones that, for instance, share the same class). This can be avoided by hanging your styles off of a class or id that’s specific to the JavaScript object. For example, if your script is WickedCool.js, make the wrapper element .WickedCool or #WickedCool and preface all related selectors with that unique hook. Bare bones With no styles applied, the page looks pretty ugly, but is still usable Be wary of timing when you apply your widget-related styles. For example, if a user has JavaScript turned off, you don’t want to have when it’s ready to run in order to ensure styles are not applied prematurely. styles applied to the page that hide content the widget was supposed This technique, known as class-swapping, is a great strategy for maintaining to make visible; the user will never be able to see it. This issue is easily separation between presentation and behaviour. With the markup in place, I resolved by hanging all of your styles off of a class that’s not used wrote some basic styles to lay out the tabbed interface. Of particular note is directly in the markup, instead using one that’s set by JavaScript. how I chose to hide the inactive content sections: This can be done in a few different ways: change the form of the employed class (eg from change-me to changed); append -on to the .tabbed-on .folder { employed class (eg from tabbed to tabbed-on); add a second class position: absolute; (eg on). All of these options act as a switch that your script can trigger top: 0; when it knows it’s safe for the styles to be applied. It should be noted, left: -999em; however, that compound class selectors are not supported by IE6, so if } you need to support that browser, don’t consider the third option. and then make them visible again: $(document).ready(function(){ .tabbed-on .folder.visible { $(".tabbed").each(function(){ position: static; new TabInterface(); } }); }); Armed with a plan and the knowledge of the markup I’d need to make it all work, I set about writing the script that we’ll now build together. To start things This little bit of code (which could be replicated easily in the library of your off, create an object constructor called TabInterface: choice or via native JS methods) runs when the DOM is loaded and scans through the document, creating a new TabInterface instance for every element function TabInterface(){} it encounters with a class of tabbed. Of course, as we only have the skeleton of a constructor, the TabInterface object isn’t set up to capture and manipulate It’s really nothing more than a function but, as everything in JavaScript is an the content that needs tabbing, but we’ll get there. object, it’s also an object and it can be instantiated as many times as necessary on a page. Construction time So, using a library like jQuery, we could say: There’s no reason to expose TabInterface object’s inner workings, so we’ll build them all as private members of this object. As we’ll be operating inside of a function, we’ll need to create the object’s properties as variables and its methods as functions. (And yes, functions can contain other functions.) Each property and method will remain private to TabInterface unless we expose it by directly assigning it as a property of this. The only member we’ll expose is the version of the script (available as TabInterface.Version and set as this.Version), in case anyone wants to implement an extension to the script and wants to see which version it is. Here’s a basic outline of the properties we need to create: Version – the current script version _active – the ID of the active folder (false by default) _index – a DOM-generated unordered list that will contain the tabs _els – an object literal containing two properties of its own li – a DOM-generated list item div – a DOM-generated division. Of note is the final property, _els. JavaScript can create elements on the fly, but cloning an element (using element.cloneNode()) is much faster than generating a new element (using document.createElement()) each time you need it. As we’ll be generating multiple divisions around the content chunks Styled up With basic typographic and colour styles applied, the page looks a little and multiple list items for our tab list, it makes sense to create those better and is completely functional, even in the absence of JavaScript elements before we need them so we can clone them easily later on. .net october 2009 71 next>
  • 3. .net technique JavaScript Once you’ve added those properties, create two new functions to serve as the core methods for TabInterface: initialize – the function that will build the tabbed interface swap – the method that will handle changing currently displayed content Because we’ll be doing some class manipulation, include two helper methods, addClassName and removeClassName, using the following code: function addClassName( e, c ){ var classes = ( !e.className ) ? [] : e.className.split( ' ' ); classes.push( c ); e.className = classes.join( ' ' ); } function removeClassName( e, c ){ var classes = e.className.split( ' ' ); for( var i=classes.length-1; i>=0; i-- ){ if( classes[i] == c ) classes.splice( i, 1 ); } e.className = classes.join( ' ' ); } Tab happy With the widget’s styles applied, the pumpkin pie recipe is transformed into With the preliminaries out of the way, let’s start building out TabInterface in an elegant, compact, tabbed interface earnest. The first thing we need to do is provide a means for the element that needs tabbing to be passed into the object so we can manipulate it. To do that, The next step is to divvy up the content by determining the heading level add an argument to the TabInterface function itself. While you’re at it, add a (h1-h6) upon which to base the chunking. But first, let’s make the node second argument to accept an iteration number (which we’ll put to good use iteration a little easier (and more cross-browser compatible) by stripping out in a moment). Name these two arguments _cabinet and _i, respectively, as you any nodes of whitespace that are children of _cabinet: will need to reference them later. Your code should look (roughly) like this: function TabInterface( _cabinet, _i ){ this.Version = '0.3'; We must provide a means for Now let’s tackle initialize(). You’ll want to update the initialisation of TabInterface the element that needs tabbing (above) to pass in the arguments we just added, if you want to see your work in action as you build. To allow the script to work efficiently, we’ll generate unique to be passed into the object ids for each of the sections as well as the tabs. To keep things organised and keep the ids sensible, each should relate back to the id of the containing element (passed as _cabinet). Of course, _cabinet may not have an id, so we may need to var node = _cabinet.firstChild; generate one. We can do that using the passed iterator ('cabinet-' + _i). Once you while( node ){ have the id, store it to a local variable called _id so you can reference it later: var nextNode = node.nextSibling; if( node.nodeType == 3 && function initialize(){ !/S/.test( node.nodeValue ) ){ _cabinet.removeChild( node ); } // set the id node = nextNode; var _id = el.getAttribute( 'id' ) || 'cabinet-' + _i; } if( !el.getAttribute( 'id' ) ) el.setAttribute( 'id', _id ); Then, you can determine the heading level of _cabinet’s firstChild by testing it against a list of known heading levels: Resources Where to find out more var headers = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ]; for( var i=0, len=headers.length; i<len; i++ ){ if( _cabinet.firstChild.nodeName.toLowerCase() == headers[i] ){ var _tag = headers[i]; break; } } This code block loops through the header types and tests each against the Understanding Progressive Unobtrusive JavaScript for lower-cased tag name (nodeName) of the first child element (firstChild) of Enhancement Progressive Enhancement the container (_cabinet). When a match is encountered, the script assigns the For a bit more background Phil Hawksworth put together this correct heading level to _tag and the loop is broken. on the concept of progressive wonderful demonstration of how Now that we have the heading level, we can chunk the content of _cabinet enhancement, check out this JavaScript can be used in smart using regular expressions and innerHTML, storing the chunks as an array in the primer in A List Apart, as well as ways, and helpfully it comes local variable arr. The split I employ uses a regular expression replacement that its two companion articles. with an in-depth explanation of inserts an arbitrary, yet uncommon series of characters (||||) in front of the alistapart.com/articles/ how to apply the same techniques opening header tag before the content is split (creating an array) using those understandingprogressive to your projects. very same characters. enhancement unobtrusify.com The first member of the array will be empty (because it comes before the first instance of the heading element), but shift() removes it: <prev 72 .net october 2009
  • 4. .net technique JavaScript var rexp = new RegExp( '<(' + _tag + ')', 'ig' ); var arr = _cabinet.innerHTML.replace( rexp, "||||<$1" ).split( '||||' ); arr.shift(); With the content of _cabinet tucked safely away in arr, you can empty the container’s contents and do a little class-swapping to turn on your styles: _cabinet.innerHTML = ''; removeClassName( _cabinet, 'tabbed' ); addClassName( _cabinet, 'tabbed-on' ); Next, loop through arr’s members and create both a folder and a tab for each content chunk. The folders will be clones of _els.div and should be appended directly to _cabinet. The tabs will be cloned from _els.li and should be appended to the ul we created and stored as _index. The text for the tab will come from the heading element, and classifying the heading as hidden will allow you to hide it with CSS, keeping users from seeing the same text twice. Along the way, assign unique ids to the tabs and folders using _id. Button it An early version of this script was used on Nestlé’s Idol Elimination site. The While you’re at it, add an onclick event handler to the tab (a reference to id-based hooks generated by this script enabled the addition of Next and Previous buttons the swap method we’ll get to in a moment), classify the first folder as visible and store its id in the _active variable, and then activate the current tab by _index.className = 'index'; giving it a class of active. Your code should look similar to this: _cabinet.appendChild( _index ); for( i=0, len=arr.length; i<len; i++ ){ With the markup adjustments complete, all that’s left to do is fill in the logic var folder = _els.div.cloneNode( true ); for the swap event handler. The logic doesn’t need to be very complex; it folder.setAttribute( 'id', _id + '-' + i ); simply needs to swap visible folders and activated tabs. folder.innerHTML = arr[i]; addClassName( folder, 'folder' ); Almost there _cabinet.appendChild( folder ); To get the currently active folder, tap into the _active property of TabInterface, var heading = folder.getElementsByTagName( _tag )[0]; as it contains the active folder’s id. Then use the helper methods to move the addClassName( heading, 'hidden' ); appropriate classes from the currently active folder and tab to the newly activated var tab = _els.li.cloneNode( true ); ones, and update the _active property to refer to the newly opened folder: tab.setAttribute( 'id', 'id', _id + '-' + i + '-tab' ); tab.innerHTML = heading.innerHTML; function swap( e ){ tab.onclick = swap; e = ( e ) ? e : event; _index.appendChild( tab ); var tab = e.target || e.srcElement; if( i === 0 ){ var folder_id = tab.getAttribute( 'id' ).replace( '-tab', '' ); addClassName( folder, 'visible' ); removeClassName( document.getElementById( _active + '-tab' ), 'active' ); _active = _id + '-' + i; removeClassName( document.getElementById( _active ), 'visible' ); addClassName( tab, 'active' ); addClassName( tab, 'active' ); } addClassName( document.getElementById( folder_id ), 'visible' ); } _active = folder_id; } Finally, with the loop complete, classify _index as an index and add it to _cabinet: The final step is to trigger initialize() to run when a new TabInterface is created. To do that, add a call to it at the end of the TabInterface function: initialize(); In less than 100 lines of code, you’ve build a powerful, yet simple tabbed interface script, and you did it using a combination of regular expressions, object-orientation and unobtrusive scripting. Don’t miss part two of this article in next month’s issue, in which we’ll improve the flexibility of this script and make it more accessible using WAI-ARIA roles and states. l Note: TabInterface is available under the liberal MIT licence. The complete latest version of the script can be downloaded from GitHub at: easy-designs.github.com/ tabinterface.js. About the author Name Aaron Gustafson Site easy-designs.net Areas of expertise Front- and back-end development, strategy Clients Brighter Planet, Yahoo, Artbox.com Out of the box In The Amanda Project (bit.ly/JKze6), Jason Santa Maria took the tab metaphor out of its traditional box and integrated it perfectly with the site’s aesthetic Favourite ice cream Mint chocolate chip .net october 2009 73