This article walks you through the steps of retrofitting a Web 1.0 shopping site with Ajax techniques. You can download the before and after source code for an example application. You can also see both versions in action on the author's Web server. In addition to Ajax techniques and best practices, you learn how Ajax can improve your user experience through principles of progressive enhancement, usability, and user experience design (UxD).
This article assumes you have a solid grasp of HTML and CSS and at least a basic understanding of JavaScript and Ajax programming techniques. The example application is built using only client-side code; the techniques demonstrated can be adapted to any server-side application framework. To run the example site, you need at least a basic Web server running on a localhost. Alternatively, you can follow along in the source code and see the example site in action on the author's Web server.
Part 1 and Part 2 of this series introduced the example application, Customize Me Now, and began the process of retrofitting it from a Web 1.0 version to an Ajax-powered Web 2.0 version. They discussed some of the business and usability reasons for doing so. They also helped you set up several open source tools, including the jQuery JavaScript framework and several of its plug-ins. Using these libraries, you streamlined the user flow of Customize Me Now by replacing popups, off-site links, and navigational side streets with modal dialogs, tooltips, and lightboxes. You did so using principles of progressive enhancement; the same pages that powered your retrofitted Web 2.0 application automatically degraded into a Web 1.0 experience when JavaScript was disabled.
In this installment, you'll tame unmanageable product-details pages by placing content inside a tabbed interface. You'll also keep product images under control by displaying them in an image carousel. You'll learn how to employ both techniques using simple Dynamic HTML (DHTML) or more complex Ajax code. Either way, you'll again use the principle of progressive enhancement to keep your pages accessible even when JavaScript isn't enabled. To accomplish all this, you'll use two additional jQuery plug-ins: jCarousel for image slideshows and jQuery UI Tabs for tabs.
To understand the concepts in this installment, refer to Customize Me Now 1.1, a slightly revised version of the original, pre-Ajax example site. As you make changes to 1.1, you'll create Customize Me Now 2.1, which incorporates all changes from the entire series.
Two types of product details: single-page and multipage
One of the most complex sections of any e-commerce Web site is the product-details area. Sites accumulate a lot of information about their products, from simple descriptions and technical specifications to community-generated content such as user reviews. Then, of course, there are product images — often many individual shots of each product. The challenge, from a user-experience perspective, is to present customers with enough data to make a purchasing decision but avoid overwhelming them with too much information.
In some ways, Customize Me Now 1.0 made things easy for an Ajax overhaul. It presented product-details content that fit easily on a single page. In Customize Me Now 2.0, you replaced the original page with a modal dialog using jQuery and Thickbox. This helped streamline the "happy path" that you wanted users to follow from search to purchase.
Now, though, your requirements have changed. Customize Me Now 1.1 presents far more detailed product information than 1.0 did. This content includes many long blocks of text and many large photos. (The images are culled from Flickr, the popular Web 2.0 photo-sharing network.) In the pre-Ajax world, there were two ways to display such a large amount of content: on one long, scrolling page (see Figure 1 and Figure 2), or broken up into multiple smaller pages, one for each text block or photo (see Figure 3 and Figure 4).
If you visit Customize Me Now 1.1 in your Web browser, you can compare versions A and B of the Product Details page with the old version from Customize Me Now 1.0. Links to all three versions appear in the global header and footer. As you can see, versions A and B present much larger usability challenges than the old version. Clearly, a Thickbox modal dialog is less than ideal for these new versions of Product Details.
Figure 1. Product Details page version A: single page, text content
Figure 2. Product Details page version A: single page, image content
Version A runs the danger of overwhelming not only the user, but also the browser and the server. Users are overwhelmed by the sheer volume of information — assuming they even notice any of the content below the fold. Meanwhile, the browser and server are overwhelmed by the sheer volume of data travelling over the wires. With only six photos, the page loads fairly quickly at broadband speeds. But what if there were 16 photos — or 60? What if there were 150 user reviews? What if the user was on a slow connection? If all possible data about a product loads at once, performance will eventually suffer -- as will the user, who has too much content to digest.
Figure 3. Product Details page version B: multiple pages, text content
Figure 4. Product Details page version B: multiple pages, image content
Version B avoids overwhelming users by showing only a small amount of information on each page. But each time users want to view more information, they must click and wait for a new page to load. In addition, each of the subpages within version B contains a confusingly large array of links. The data may not be overwhelming, but the navigation certainly is.
Overhaul the single-page version
Retrofitting version A with an image slideshow and a tabbed interface proves fairly easy because everything is on one page. No Ajax is required, just old-fashioned DHTML. The advantage to this approach is that progressive enhancement is built in. You still serve the long, scrolling page of content to the browser, but JavaScript code transforms it into a more modern, usable interface. Users without a JavaScript-capable browser see the original page. Of course, this approach does nothing to solve the bandwidth problems of the single-page approach.
Download and install open source tools
To begin your Ajax overhaul, download the latest version of jQuery, which forms the foundation for all your new functionality (see the Resources section). If you followed along in Parts 1 and 2 of this series, you already have jQuery 1.2.1. As of this writing, the current version is 1.2.3, which includes a few additional bug fixes.
You also need to download two plug-ins, both available in the Resources section. jQuery UI Tabs is part of jQuery UI, a set of configurable user-interface widgets and components. jQuery UI Tabs turns ul elements into a tabbed interface and turns chunks of inline or Ajax markup into the content for those tabs. jCarousel, meanwhile, is a stand-alone plug-in that turns sets of images into slideshows. As with jQuery UI tabs, the content for these slideshows can be inline or Ajax.
After you've downloaded these components, place them in the appropriate directory structure with your application. Each download includes code examples and other extraneous files that can be saved or deleted at your discretion. The packed or minified version of each JavaScript library is suitable for production, but you may wish to keep the complete source code so you can read through it and better understand each component:
- For jQuery, the only file you need to keep is the minified version of the library itself.
- For jQuery UI Tabs, you should keep the packed version of the library, the accompanying CSS files, and two images: loading.gif and tab.png. Because tab.png assumes your site has a white background, you may wish to modify it in Adobe Photoshop or another image-editing program so that its rounded corners match the actual background color of Customize Me Now.
- For jCarousel, keep the packed version of the library and its accompanying CSS file, plus the entire directory for one of the three prepackaged skins that come with the library. With jCarousel, a skin is a CSS file and a set of images that let you control the visual style of your carousel. For Customize Me Now, keep the Tango skin but rename the directory "tango-modified"; you'll make several changes to the default Tango theme.
Once your files are in place, plug them into the head of detailA.html. The result is shown in Listing 1:
Listing 1. Deploying jQuery and its plug-ins
<!--jquery assets-->
<script type="text/javascript"
src="../js/jquery-1.2.3.minjs"></script>
<!--jquery.ui.tabs assets-->
<script type="text/javascript"
src="./ui.tabs/ui.tabs.pack.js"></script>
<link rel="stylesheet" href="../ui.tabs/ui.tabs.css"
type="text/css" media="print, projection, screen">
<!--jcarousel assets-->
<script type="text/javascript"
src="../jcarousel/lib/jquery.jcarousel.pack.js"></script>
<link rel="stylesheet" type="text/css"
href="../jcarousel/lib/jquery.jcarousel.css" />
<link rel="stylesheet" type="text/css"
href="../jcarousel/skins/tango-modified/skin.css" />
|
Create the DHTML image carousel
To create your DHTML image carousel, you first modify the look and feel of jCarousel. Because jCarousel can be configured to show either a horizontal or a vertical image carousel, its stylesheet contains rules for both types. Your slideshow is horizontal, so you can delete all CSS declarations relating to vertical slideshows. In addition, you need to modify the widths, heights, padding, and margins of many elements. By default, jCarousel creates a slideshow in which three small thumbnail images are visible at any given time. For your slideshow, the images are much larger — 500 pixels wide each — so you show only one image at a time. Once you're done making your changes, skin.css will look like Listing 2:
Listing 2. CSS code for your slideshow
..jcarousel-skin-tango.jcarousel-container {
-moz-border-radius: 10px;
background: #F0F6F9;
border: 1px solid #346F97;
}
..jcarousel-skin-tango.jcarousel-container-horizontal {
width: 502px;
padding: 20px 125px !important;
}
.jcarousel-skin-tango .jcarousel-clip-horizontal {
width: 502px;
height: 410px;
}
..jcarousel-skin-tango .jcarousel-item {
width: 502px;
height: 410px;
}
..jcarousel-skin-tango .jcarousel-item-horizontal {
margin-right: 125px;
}
..jcarousel-skin-tango .jcarousel-item-placeholder {
background: #fff;
color: #000;
}
/**
* Horizontal Buttons
*/
..jcarousel-skin-tango .jcarousel-next-horizontal {
position: absolute;
top: 43px;
right: 5px;
width: 32px;
height: 32px;
cursor: pointer;
background: transparent url(next-horizontal.png) no-repeat 0 0;
}
..jcarousel-skin-tango .jcarousel-next-horizontal:hover {
background-position: -32px 0;
}
..jcarousel-skin-tango .jcarousel-next-horizontal:active {
background-position: -64px 0;
}
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-next-disabled-horizontal:active {
cursor: default;
background-position: -96px 0;
}
..jcarousel-skin-tango .jcarousel-prev-horizontal {
position: absolute;
top: 43px;
left: 5px;
width: 32px;
height: 32px;
cursor: pointer;
background: transparent url(prev-horizontal.png) no-repeat 0 0;
}
..jcarousel-skin-tango .jcarousel-prev-horizontal:hover {
background-position: -32px 0;
}
..jcarousel-skin-tango .jcarousel-prev-horizontal:active {
background-position: -64px 0;
}
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:hover,
..jcarousel-skin-tango .jcarousel-prev-disabled-horizontal:active {
cursor: default;
background-position: -96px 0;
}
|
Next, make one small change to the markup of detailAhtml. The images from the
original version of the page are already structured as a ul element. You need to add a wrapper div around this HTML list. Give the wrapper div a class attribute of jcarousel-skin-tango so the style rules from your modified Tango skin are applied to the carousel. Also give the div an id attribute of imageCarousel so jQuery can parse the Document Object Model (DOM) and find the correct elements to transform. When you're finished, your HTML will look like Listing 3:
Listing 3. HTML code for your slideshow
<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
<li>
<img class="product" alt="product photo"
width="500" height="375" src="../img/pizza1.jpg" />
Photo credit: <a target="_blank"
href="http://www.flickr.com/photos/kankan/">Kanko*</a>,
Flickr, Creative Commons Attribution License
</li>
<!--additional <li> items, images and photo credits here-->
</ul>
</div>
|
Finally, create the JavaScript code that builds your slideshow. Thanks to the magic of jCarousel, it's only a few lines long. In the head of your HTML document, below the included JavaScript and CSS files, add the inline script block shown in Listing 4:
Listing 4. JavaScript code for your slideshow
<script type="text/javascript">
$(document).ready(function() {
$('#imageCarousel').jcarousel({
scroll: 1
});
});
</script>
|
This JavaScript code looks simple, but it does all the heavy lifting. By invoking
jQuery's ready event handler, it instructs jQuery to
wait until the DOM for the page has loaded and then perform additional tasks. By
invoking jQuery's selector mechanism — the $ function — it tells jQuery to locate the DOM element with the id attribute of imageCarousel. By calling the jcarousel method on the object containing that DOM node, it tells jCarousel to transform all the markup inside that node into an image slideshow. You're passing one argument to the jcarousel method: a hash of configuration parameters. Most of jCarousel's default options are fine, so your hash contains only one key-value pair: scroll, with a value of 1, which tells jCarousel to display only one image at a time.
That's it; your image slideshow is complete.
Now you can move on to creating DHTML tabs for each section of content on your page. To begin, wrap each individual section of content in a new div with a unique id attribute and a class attribute of tabContent. The result for each section of content looks like Listing 5. The purpose of these wrappers is to give jQuery UI Tabs hooks so it knows which chunks of markup to turn into tabbed content.
Listing 5. HTML for all five sections of content in detailA.html
<div class="tabContent" id="introduction">
<h2>Introduction</h2>
<!--paragraphs of text content here-->
</div>
<div class="tabContent" id="moreDetails">
<h2>More Details</h2>
<!--paragraphs of text content here-->
</div>
<div class="tabContent" id="userReviews">
<h2>User Reviews</h2>
<!--paragraphs of text content here-->
</div>
<div class="tabContent" id="techSpecs">
<h2>Technical Specifications</h2>
<!--paragraphs of text content here-->
</div>
<div class="tabContent" id="productImages">
<h2>Product Images</h2>
<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
<!--<li> items from your image slideshow here-->
</ul>
</div>
</div>
|
Next, add some additional markup to your HTML document: an unordered list of links.
This goes toward the top of the document, just before the first section of actual content. jQuery UI Tabs transforms this markup into the new tab interface for your content. Although the href attributes of your links look like internal document anchors, they actually match the id attributes of the wrapper divs you created earlier. The markup is shown in Listing 6:
Listing 6. Additional HTML content for detailA.html
<ul class="navTabs">
<li><a href="#introduction"><span>Introduction</span></a></li>
<li><a href="#moreDetails"><span>More Details</span></a></li>
<li><a href="#userReviews"><span>User Reviews</span></a></li>
<li><a href="#techSpecs"><span>Technical Specifications</span></a></li>
<li><a href="#productImages"><span>Product Images</span></a></li>
</ul>
|
Next, create two sets of styles: one set each for users with and without JavaScript enabled. Users with JavaScript enabled will see your new tabbed interface, so you want to make your tabbed content look good. First, add some padding and borders. Then, suppress the display of the h2 element inside each tabbed section; these headings would be redundant inside your new interface, where they'd restate the text label on the tab. To create these style rules, add them to your global stylesheet.
Users JavaScript disabled won't see your tabbed interface. For those users, you need to hide the unordered list you added to the document, remove the borders and padding you added to your content sections, and restore the visibility of your h2 elements. Luckily, the noscript tag makes this easy. To override global styles in a JavaScript-disabled browser, add additional CSS rules inside a noscript tag. These styles are applied only when JavaScript is turned off.
The final result is one set of markup that looks good — but remarkably different — depending on the browser's capabilities. When you're done, your two CSS blocks will look like Listing 7 and Listing 8:
Listing 7. Additional CSS styles for customizemenow.css
#CMN .tabContent {
padding: 14px;
border: 1px solid #97a5b0;
}
#CMN .tabContent h2{
display: none;
}
|
Listing 8. Additional styles in a
noscript block in detailA.html
<noscript>
<style type="text/css">
#CMN .tabContent {
padding: 0;
border: 0;
}
#CMN .tabContent h2 {
display: block;
}
#CMN ul.nav {
display: none;
}
</style>
</noscript>
|
Finally, add the custom JavaScript that creates your DHTML tabs. To do so, add additional code to the inline script block you created earlier (see Listing 4). Listing 9 shows the inline script block:
Listing 9. Custom JavaScript for detailA.html
$(document).ready(function() {
/*earlier jCarousel code goes first*/
/*create tabs from an unordered list using jquery.ui.tabs*/
$('ul.navTabs').tabs(
{ fx: { height: 'toggle', opacity: 'toggle' } }
);
});
|
As with jCarousel, the custom JavaScript for jQuery UI Tabs looks deceptively simple.
Once again, you use jQuery's selector mechanism to locate a DOM element — in
this case, your new unordered list of links. The tabs method of jQuery UI Tabs then transforms that ul and its li children into a tabbed interface. The tabs method looks at the links inside your unordered list and matches each tab with the corresponding div full of content. Each div is hidden until a user clicks the corresponding li element, which gets styled as a tab. As with the jcarousel method, a hash of initialization parameters allows you to customize the behavior of the tabs method. In this case, you add some nice visual effects so that tab activation looks cool in the browser. One finale note: You need to create your jCarousel image slideshow before you create your tab interface; if you create the tab interface first, then jCarousel will get confused about the CSS properties of your image carousel markup and cause problems.
Review the single-page version
If you look at version A of the Product Details page of Customize Me Now 2.1, you can see your DHTML tabs (Figure 5) and your image carousel (Figure 6) in action. To see the visuals, including a fade-out and a window-shade effect, load the page in your Web browser.
Figure 5. Revised Product Details page: tabbed content
Figure 6. Revised Product Details page: image carousel
Product Details version A 2.1 offers several advantages over version 1.1 of the same page. By hiding the complexity and volume of information from the user, it presents a far less daunting interface. The tabbed navigational metaphor allows the user to digest information in bite-sized pieces. If you disable JavaScript in your browser and reload the page, you see a page almost exactly like the 1.1 version. That's the power of progressive enhancement in action. Unfortunately, though, this version does little to solve the bandwidth problems of the single-page approach. The user still ends up downloading a ton of text and images that they may never view. You'll tackle that problem in the next iteration.
Overhaul the multipage version
Now that you've added tabs and image carousels to your single-page Product Details page, it's time to effect a similar transformation on the multipage version. The code this time is more complex because the content of your tabs and carousel is pulled dynamically into the page via Ajax. The payoff for this extra work is faster page loads and saved bandwidth. Additional content is loaded dynamically, but only when it's needed.
Create the Ajax image carousel
The first step toward creating an image carousel from your multipage Product Details section is to build a new JavaScript Object Notation (JSON) document. JSON is a data-transport format similar to XML but more lightweight. It's optimal for Ajax because jQuery and other Ajax frameworks can turn a JSON document into a JavaScript object safely and almost instantly. Your JSON document doesn't contain any markup, just the URLs and metadata for the images your JavaScript code turns into a slide show. Once you've created this document, save it as detailB5-fragment.html; see Listing 10:
Listing 10. JSON data from detailB5-fragment.html
{"items": [
{
"url": "pizza1.jpg",
"width": "500",
"height": "375",
"creditURL": "kankan",
"creditLabel": "Kanko*"
},
{
"url": "pizza2.jpg",
"width": "500",
"height": "374",
"creditURL": "lenore-m",
"creditLabel": "L. Marie"
},
{
"url": "pizza3.jpg",
"width": "500",
"height": "375",
"creditURL": "roadhunter",
"creditLabel": "Topato"
},
{
"url": "pizza4.jpg",
"width": "500",
"height": "369",
"creditURL": "sgt_spanky",
"creditLabel": "Kevitivity"
},
{
"url": "pizza5.jpg",
"width": "500",
"height": "368",
"creditURL": "fooey",
"creditLabel": "foéöÞoooey"
},
{
"url": "pizza6.jpg",
"width": "500",
"height": "334",
"creditURL": "pancakejess",
"creditLabel": "jsLander"
}
]}
|
Next, add all your jQuery, jQuery UI Tabs, and jCarousel files to the header of detailB1.html, the same way you did earlier for detailA.html (see Listing 1). Then, add HTML to the body of detailB1.html that creates a placeholder for your image slideshow; jCarousel will fill the empty unordered list in with image content. The placeholder HTML is shown in Listing 11:
Listing 11. JavaScript code from detailB1.html
<div class="tabContent" id="productImages">
<h2>Product Images</h2>
<div id="imageCarousel" class="jcarousel-skin-tango">
<ul class="productImages">
</ul>
</div>
</div>
|
Finally, add custom JavaScript code in a script block just below these files in the head of your HTML document. This script block looks like Listing 12:
Listing 12. JavaScript code from detailB1.html
<script type="text/javascript">
window.alert = function() {
return;
};
$(document).ready(function() {
/*
create an image slideshow from a JS array of URLs using
jcarousel
*/
var itemLoadCallback = function(carousel, state) {
if (state != 'init') {
return;
}
jQuery.getJSON("detailB5-fragment.html", function(data){
itemAddCallback(carousel, carousel.first,
carousel.last, data.items);
});
};
var itemAddCallback = function(carousel, first, last, data) {
for (i = 0, j = data.length; i < j; i++) {
carousel.add(i, getItemHTML(data[i]));
}
carousel.size(data.length);
};
var getItemHTML = function(d) {
return '<img class="product" alt="product photo" width="' +
d.width + '" height="' +
d.height + '" src="../img/' +
d.url + '" />Photo credit: <a target="_blank"' +
' href="http://www.flickr.com/photos/' +
d.creditURL + 's/">' +
d.creditLabel + '</a>, Flickr, ' +
'Create Commons Attribution License'
;
};
jQuery('#imageCarousel').jcarousel({
itemLoadCallback: itemLoadCallback,
scroll: 1
});
jQuery('ul.productImages').css("width","3012px");
});
</script>
|
As you can see, this JavaScript is more complicated than anything you've created so far. It includes the following functions:
-
itemLoadCallback. Uses Ajax to read the data from the JSON document you created earlier, and then hands that data off toitemAddCallback. -
itemAddCallBack. Parses the JSON data loaded byitemLoadCallback, and adds each individual image to the carousel. -
getItemHTML. Formats the JSON data into markup for jCarousel to insert into the DOM.
To invoke these functions, pass itemLoadCallback to the jcarousel method as part of the options hash. This signals to jCarousel that it should build its slideshow dynamically from Ajax data rather than building it from markup that already exists in the DOM. itemLoadCallback and its helper functions do their work. But a couple of peculiarities about jCarousel get in the way.
First, jCarousel appears to miscalculate the CSS properties of its dynamically loaded
content. The result is a slideshow in which several of the images never appear. A little debugging reveals that jCarousel is confused about the width of the ul that contains the images. You could spend time debugging the issue and extending the jCarousel code to fix it, but the brute-force method is faster: After your carousel is built, adjust its width using jQuery's css method so that it matches the width of the non-dynamically generated carousel in Product Details version A.
The second problem with jCarousel is related to the first: Because jCarousel isn't
properly calculating the width of its DOM elements, it fires off an error message
every time you resize the browser window. This error message appears as a JavaScript
alert box — not exactly an optimal user experience. To override this behavior, replace JavaScript's built-in window.alert method with a dummy function. Problem solved.
To deploy jQuery UI Tabs on your multipage Product Details section, you use a mixture of inline content and dynamic Ajax content. The file detailB1.html serves as the wrapper page for all the content that was previously included on detailB2.html, detailB3.html, and detailB4.html. The problem with pulling the content of these files into detailB1.html via Ajax is that each page includes a complete HTML document; all you really need is a single div from each page. To get around this issue, you create alternate versions of these pages by copying each file, giving it a new filename, and removing extraneous markup. The result is three new files called detailB2-fragment.html, detailB3-fragment.html, and detailB4-fragment.html. Each one looks something like Listing 13. In the real world, of course, you'd use a server-side templating engine to deliver this content in both full-HTML and fragment versions; for instance, a framework such as Ruby on Rails can automatically apply different wrappers to the same content depending on whether the request is a normal one or an Ajax call. For this purely client-side example code, however, you'll simulate that effect with separate files.
Listing 13. Markup from HTML fragment files
<div class="tabContent" id="moreDetails">
<h2>More Details</h2>
<!--paragraphs of text content here-->
</div>
|
Next, you need to alter some of the markup inside detailB1.html. In Customize Me Now 1.1, this page included a secondary navigational menu to help the user move between pages. It is shown in Listing 14. By making a few changes to this markup, you let jQuery UI Tabs transform it into your tab interface. The modified markup looks like Listing 15.
Listing 14. Original markup for the secondary navigation in detailB1.html
<ul class="nav">
<li><a href="detailB1.html">Introduction</a></li>
<li><a href="detailB2.html">More Details</a></li>
<li><a href="detailB3.html">User Reviews</a></li>
<li><a href="detailB4.html">Technical Specifications</a></li>
<li class="last"><a href="detailB5a.html">Photos</a></li>
</ul>
|
Listing 15. Modified markup for Ajax tabs in detailB1.html
<ul class="nav">
<li><a href="detailB1.html"><span>Introduction</span></a></li>
<li><a href="detailB2.html"><span>More Details</span></a></li>
<li><a href="detailB3.html"><span>User Reviews</span></a></li>
<li><a href="detailB4.html"><span>Technical Specifications</span></a></li>
<li class="last"><a href="detailB5a.html"><span>Product Images</span></a></li>
</ul>
|
Next, add additional JavaScript code to the inline script block you added to this page earlier for your slide show. When you're done, the script block will look like Listing 16:
Listing 16. Inline script block for the creation of Ajax tabs
$(document).ready(function() {
/*transform urls for tabs with inline content*/
$('ul.nav > li:first > a').attr("href", "#introduction");
$('ul.nav > li:last > a').attr("href", "#productImages");
/*transform urls for tabs with ajax content*/
$('ul.nav > li:not(:first):not(:last) > a').each(function (i) {
var el = $(this);
el.attr("href", el.attr("href").replace(".html",
"-fragment.html"));
});
/*earlier jCarousel code goes here*/
/*
replace ul classname of "nav" with "navTabs" to
reset styling to a blank state
*/
$('ul.nav').attr({"class":"navTabs"});
/*create tabs from an unordered list using jquery.ui.tabs*/
$('ul.navTabs').tabs(
{ fx: { height: 'toggle', opacity: 'toggle' } }
);
});
|
The JavaScript code for your Ajax tabs is more complicated than the code for the earlier DHTML tabs. Once again, this is because you want to use progressive enhancement. When making markup changes earlier to your secondary nav menu (see Listing 15), you didn't change the link URLs. That way, when JavaScript is turned off, those links still work the way they always have. To create your Ajax tabs, however, those links need to be transformed into a format that jQuery UI Tabs can understand. For your Introduction and Product Images tabs, which have inline content, the href properties need to be in the format #wrapperDivIDAttribute. For the other three tabs, whose content is fetched via Ajax, the href properties need to point to the HTML fragment files you created earlier. Luckily, jQuery makes it easy to transform these links.
jQuery also makes it easy to change the class attribute of your secondary nav menu. A lot of styling rules are attached to the ul element that makes up the menu. When you transform the li elements into tabs, you need to cancel out the original styling. To do so, use jQuery to change the class attribute of the ul element from nav to navTabs. Once you've done so, you can use the jQuery selector mechanism (the $ function) to select your re-classed ul element and apply the tabs method to it.
You're basically done, but you have a few items of unfinished CSS business. First, you
need to override any styles that should only apply to the Ajax version of this page. To do so, you add a noscript style block to the top of your page the same way you did the first time around. This time, however, you need one extra style rule to hide the dummy image slideshow markup you added to the page. When you're done, the noscript style block will look like Listing 17:
Listing 17. Noscript styles for detailB1.html
<noscript>
<style type="text/css">
#CMN .tabContent {
padding: 0;
border: 0;
}
#CMN .tabContent h2 {
display: block;
}
#CMN #productImages {
display: none;
}
</style>
</noscript>
|
Finally, you need to account for a peculiarity in how jQuery Tabs UI plugs your Ajax
content into its tabbed interface. If you look at the page in a Web browser, you see that the border between the tabs and the Ajax tab content is twice as thick as it should be. That's because jQuery UI Tabs places each piece of Ajax content in a wrapper div with a top border; you have already applied the necessary border to the HTML fragment inside that wrapper, so you see a double border. To fix this, add an extra class to the wrapper div of your HTML fragment files, and add a style rule to remove the top border from elements that have this class. The resulting code is shown in Listing 18 and Listing 19:
Listing 18. CSS declaration to fix the double-border problem
#CMN .tabContent.noTop {
border-top: 0;
}
|
Listing 19. CSS class applied to HTML fragment files
<div class="tabContent noTop" id="moreDetails">
<h2>More Details</h2>
<!--paragraphs of text content here-->
</div>
|
You can now view Product Details version B 2.1 in your Web browser It should look and behave exactly like version A (see Figure 5 and Figure 6). But behind the scenes, version B is far more efficient. None of the Ajax content gets loaded until the user clicks the corresponding tab, which saves bandwidth. Unfortunately, you haven't saved much bandwidth on the images. The way your custom Ajax code works, all the images in your jCarousel slideshow are loaded by default. You have, however, improved the load time of your page. The browser doesn't request these images from your server until the rest of the page has been rendered. This improves user experience considerably on slower connections.
Additional work could create a solution that doesn't automatically load images until the Product Images tab is selected. That way, users who don't choose to look at the images won't have to waste bandwidth loading them. Such a solution would add complexity to your JavaScript code, but the more images you have for your products, the more worthwhile this exercise would be.
The final difference between version A and version B of Product Details reveals itself when you turn off JavaScript. Where version A degrades into a single scrolling page, version B degrades into multiple pages.
In Part 3 of this series, you worked hard to apply the principles of progressive enhancement and achieve a modern, attractive, and highly usable Ajax interface. You also learned far more about the power of jQuery and its wide array of plug-ins. Using the skills you learned in this installment, you can continue to improve your site. For instance, you can use the jQuery Cookie plug-in to allow the browser to remember which tab was selected when a user returns to your page. You can also build a tabbed interface on your Purchase Confirmation page: one tab each for cross-sell, order playback, and billing playback. The possibilities are endless.
In the real world, you'd probably never take the time to build three different versions of a Product Details page for your site. For one thing, it takes too much time. For another, the mixture of different interfaces would confuse your users. Regardless, this article demonstrates that Ajax, progressive enhancement, and user-centered design are all easier with the power and flexibility of open source tools such as jQuery.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code for the original demo app | wa-aj-overhaul3OnePointOne.zip | 797KB | HTTP |
| Source code for the retrofitted demo app | wa-aj-overhaul3TwoPointOne.zip | 889KB | HTTP |
Information about download methods
More downloads
- Demo: Customize Me Now 1.11
- Demo: Customize Me Now 2.12
- Demo: Customize Me Now: All versions3
Notes
- See the original demo application in action on the author's Web site.
- See the retrofitted demo application in action on the author's Web site.
- See all versions of the demo application, including the versions from previous installments in this series, in action on the author's Web site.
Learn
-
Learn more about JSON.
-
Participate in the jQuery community and access tutorials and forums at the Learning jQuery Web site.
-
Get to know the jQuery API at the jQuery documentation Web site.
-
Continue your Ajax education with additional articles such as "Simplify Ajax
development with jQuery" (Jesse Skinner, developerWorks, April 2007).
-
Get started with jQuery with the help of the book
Learning jQuery
(Packt Publishing, July 2007).
-
For additional jQuery help, check out the book
jQuery in Action
(Manning Publication Co., February 2008).
-
For a more general jQuery resource, turn to the
jQuery Reference Guide
(Packt Publishing, July 2007).
-
Check out Brian Dillard's blog, Agile Ajax, for more on jQuery and other UI topics.
-
For an exhaustive overview of best practices for the security of Ajax applications, see Billy Hoffman and Bryan Sullivan's recent
Ajax Security
(Addison Wesley Professional, December 2007).
-
Browse the technology bookstore for books on these and other technical topics.
Get products and technologies
-
Download jQuery, learn all about its philosophy, and find additional plug-ins at the main jQuery Web site. The current version as of this writing is 1.2.3.
-
jQuery UI Tabs, a jQuery plug-in, allows you to wrap inline or Ajax content in a tabbed interface. This plug-in is part of the new jQuery UI collection of customizable widgets and user-interface components.
-
jCarousel, another jQuery plug-in, allows you to load inline or Ajax images into a slideshow/carousel interface.
-
jQuery Cookie, another plug-in, can be added to jQuery UI Tabs so that the browser "remembers" the most recently selected tab the next time the user returns to your page.
Discuss
-
Check out developerWorks blogs and get involved in the developerWorks community.

In his 12 years as a Web developer, Brian J. Dillard has built rich user interfaces for companies as diverse as Orbitz Worldwide, Reflect True Custom Beauty, Archipelago LLC, and United Airlines. Now serving as VP of Ajax Development at Pathfinder Development in Chicago, Brian builds rich Internet applications for a variety of clients, participates in open source projects, and contributes to the Agile Ajax blog. He is the project lead on Really Simple History, a popular Ajax history and bookmarking library.





