SlideShare ist ein Scribd-Unternehmen logo
1 von 4
Downloaden Sie, um offline zu lesen
.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

Weitere ähnliche Inhalte

Ähnlich wie Create A Tabbed Interface Part 1

Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsJoonas Lehtinen
 
DevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerMark Stillwell
 
DevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerMark Stillwell
 
Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsJoonas Lehtinen
 
Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web componentsjojule
 
Better. Faster. UXier. — AToMIC Design
Better. Faster. UXier. — AToMIC DesignBetter. Faster. UXier. — AToMIC Design
Better. Faster. UXier. — AToMIC Designjennifer gergen
 
Maven: Managing Software Projects for Repeatable Results
Maven: Managing Software Projects for Repeatable ResultsMaven: Managing Software Projects for Repeatable Results
Maven: Managing Software Projects for Repeatable ResultsSteve Keener
 
Google compute presentation puppet conf
Google compute presentation puppet confGoogle compute presentation puppet conf
Google compute presentation puppet confbodepd
 
Html5 with Vaadin and Scala
Html5 with Vaadin and ScalaHtml5 with Vaadin and Scala
Html5 with Vaadin and ScalaJoonas Lehtinen
 
Front End Development Automation with Grunt
Front End Development Automation with GruntFront End Development Automation with Grunt
Front End Development Automation with GruntLadies Who Code
 
Toplog candy elves - HOCM Talk
Toplog candy elves - HOCM TalkToplog candy elves - HOCM Talk
Toplog candy elves - HOCM TalkPatrick LaRoche
 
Fullstack Academy - Awesome Web Dev Tips & Tricks
Fullstack Academy - Awesome Web Dev Tips & TricksFullstack Academy - Awesome Web Dev Tips & Tricks
Fullstack Academy - Awesome Web Dev Tips & TricksFrances Coronel
 
New ways to present your images
New ways to present your imagesNew ways to present your images
New ways to present your imagestutorialsruby
 
&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />tutorialsruby
 
Prerendering Chapter 0
Prerendering Chapter 0Prerendering Chapter 0
Prerendering Chapter 0Samael Wang
 

Ähnlich wie Create A Tabbed Interface Part 1 (20)

Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
 
DevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and docker
 
DevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and dockerDevOps introduction with ansible, vagrant, and docker
DevOps introduction with ansible, vagrant, and docker
 
Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
 
Desingning reusable web components
Desingning reusable web componentsDesingning reusable web components
Desingning reusable web components
 
Behavior Driven Development Testing (BDD)
Behavior Driven Development Testing (BDD)Behavior Driven Development Testing (BDD)
Behavior Driven Development Testing (BDD)
 
ART164_tut_dw
ART164_tut_dwART164_tut_dw
ART164_tut_dw
 
ART164_tut_dw
ART164_tut_dwART164_tut_dw
ART164_tut_dw
 
Automate Yo' Self
Automate Yo' SelfAutomate Yo' Self
Automate Yo' Self
 
Better. Faster. UXier. — AToMIC Design
Better. Faster. UXier. — AToMIC DesignBetter. Faster. UXier. — AToMIC Design
Better. Faster. UXier. — AToMIC Design
 
Maven: Managing Software Projects for Repeatable Results
Maven: Managing Software Projects for Repeatable ResultsMaven: Managing Software Projects for Repeatable Results
Maven: Managing Software Projects for Repeatable Results
 
A Hands-on Introduction to Docker
A Hands-on Introduction to DockerA Hands-on Introduction to Docker
A Hands-on Introduction to Docker
 
Google compute presentation puppet conf
Google compute presentation puppet confGoogle compute presentation puppet conf
Google compute presentation puppet conf
 
Html5 with Vaadin and Scala
Html5 with Vaadin and ScalaHtml5 with Vaadin and Scala
Html5 with Vaadin and Scala
 
Front End Development Automation with Grunt
Front End Development Automation with GruntFront End Development Automation with Grunt
Front End Development Automation with Grunt
 
Toplog candy elves - HOCM Talk
Toplog candy elves - HOCM TalkToplog candy elves - HOCM Talk
Toplog candy elves - HOCM Talk
 
Fullstack Academy - Awesome Web Dev Tips & Tricks
Fullstack Academy - Awesome Web Dev Tips & TricksFullstack Academy - Awesome Web Dev Tips & Tricks
Fullstack Academy - Awesome Web Dev Tips & Tricks
 
New ways to present your images
New ways to present your imagesNew ways to present your images
New ways to present your images
 
&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />
 
Prerendering Chapter 0
Prerendering Chapter 0Prerendering Chapter 0
Prerendering Chapter 0
 

Mehr von 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]
Delivering Critical Information and Services [JavaScript & Friends 2021]Aaron Gustafson
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Aaron Gustafson
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]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]
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]Aaron Gustafson
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?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]
Media in the Age of PWAs [ImageCon 2019]Aaron Gustafson
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Aaron Gustafson
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]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]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]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]
PWA: Where Do I Begin? [Microsoft Ignite 2018]Aaron Gustafson
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Aaron Gustafson
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Aaron Gustafson
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Aaron Gustafson
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for EveryoneAaron Gustafson
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]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]
Performance as User Experience [An Event Apart Denver 2017]Aaron Gustafson
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Aaron Gustafson
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Aaron Gustafson
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Aaron Gustafson
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Aaron Gustafson
 

Mehr von Aaron Gustafson (20)

Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]
 
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]
Getting Started with Progressive Web Apps [Beyond Tellerrand 2019]
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?
 
Media in the Age of PWAs [ImageCon 2019]
Media in the Age of PWAs [ImageCon 2019]Media in the Age of PWAs [ImageCon 2019]
Media in the Age of PWAs [ImageCon 2019]
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]
 
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
 
PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for Everyone
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]
 
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]
Performance as User Experience [An Event Apart Denver 2017]
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]
 

Kürzlich hochgeladen

Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Kaya Weers
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integrationmarketing932765
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...itnewsafrica
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Hiroshi SHIBATA
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityIES VE
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkPixlogix Infotech
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024TopCSSGallery
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesManik S Magar
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterMydbops
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 

Kürzlich hochgeladen (20)

Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a reality
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App Framework
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL Router
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 

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