Halhelms
SIGN UP FOR MY NEWSLETTER

www.savorgold.com is top on wow gold and runescape gold and World of Warcraft gold provider list for trusted services. Their reputation seems to growing by the minute, which isn't surprising because they are one of the safest sellers of Gold. Delivery speed and customer service are very good. They aslo are giving some bonus items depending on the amount of gold you purchase.

 
 
Halhelms

Recent Comments

Recent Entries

RSS

Learning jQuery, Day 5 : Enhanced Tabs

Today, let's look at how we can use the tabs that come with the jQuery UI library. I use these for most of the administrative back end applications I write. The jQuery tabs are great, but they lack some enhanced functionality that I need, so we'll look at how some simple JavaScript can provide things like closing tabs and ensuring that we don't have duplicate tabs.

Let's start with a simple page. On it, I want to display a menu. When any menu item is clicked, I want to load the contents of a separate page into a unique tab. You can see a demo of this here.

Here's my "index.cfm" page:

<!DOCTYPE html>
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
      <title>jQuery Tabs Page</title>
   </head>
   <body>
      <ul id="main_nav" class="lavaLampNoImage">
         <li alt="Home" class="current"><a href="#">Home</a></li>
         <li alt="Services"><a href="#">Services</a></li>
         <li alt="Projects"><a href="#">Projects</a></li>
      </ul>

      <div id="tabs">
         <ul id="tabsUl">
         </ul>
      </div>
   </body>
<cfinclude template="index.pgm">
</html>

I've included "index.pgm" at the bottom of my page. Here's that code:

<link type="text/css" href="/jquery-ui-1.7.1.custom/css/smoothness/jquery-ui-1.7.1.custom.css" rel="stylesheet" />   
<link rel="stylesheet" href="/lavalamp-0.2.0/lavalamp_test.css" type="text/css" media="screen">

<style>
.tab_delete:hover{
cursor: pointer;
}
</style>

<script type="text/javascript" src="/jquery-ui-1.7.1.custom/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/jquery-ui-1.7.1.custom/js/jquery-ui-1.7.1.custom.min.js"></script>
<script type="text/javascript" src="halogen.tabs.js"></script>
<script type="text/javascript" src="hash.js"></script>
<script type="text/javascript" src="/lavalamp-0.2.0/jquery.easing.min.js"></script>
<script type="text/javascript" src="/lavalamp-0.2.0/jquery.lavalamp.min.js"></script>

<script type="text/javascript">
   $(function() {
      $('#main_nav').lavaLamp({
         fx: 'backout',
         speed: 700,
         click: function(event, menuItem) {
            return true;
         }
      });
      
      tabs = new Tabs($('#tabs'));
      
      $('ul#main_nav li').click(function(){
         var tabHash = jsHash($(this).attr('alt'));
         tabs.tab($(this).attr('alt'), tabHash);
         $('#' + tabHash).load($(this).attr('alt') + '.cfm');
      });
   });
</script>

I could, of course, have just included this code on my index.cfm file, but as I explained on the blog entry, "Form Processing, the OO Way : The Movie!", there are some advantages to having a separate code/processing page:

  1. My HTML page ("index.cfm") is very clean and is ideal for passing to a designer for applying styles
  2. If I wish to reuse a page in a different context, I can swap out the programming page to provide different behavior to the same HTML page. (I find this useful surprisingly often.)

In "index.pgm", I've included some stylesheets and some jQuery-related JavaScript scripts. I've also included two files: "halogen.tabs.js" and "hash.js". These will be used to provide the extra functionality to the jQuery tabs I need.

In the open "script" tag, I begin using the "$(function(){}" call. This is shorthand for the more typical "$(document).ready(){}" code. It tells jQuery to run a function once the document is ready for processing -- similar to "body.onLoad" but a bit faster.

In that "ready" function, I set up a jQuery plugin, "LavaLamp", that provides a little eye candy to my menu. The next call, "tabs = new Tabs($('#tabs'));" creates an instance of a "Tabs" class (inside my "halogen.tabs.js"), passing a jQuery object that wraps a DOM element. That DOM element comes from "index.cfm":

<div id="tabs">
   <ul id="tabsUl">
   </ul>
</div>

As you can see, it's a div with a nested "ul" element, but there's nothing there to display. (We'll handle that with Ajax via jQuery shortly.) By instantiating the "Tabs" JavaScript class inside halogen.tabs.js, I've both enabled jQuery tabs and assigned additional functionality to them. We'll see this shortly, as well.

Back to "index.pgm", the last bit of code tells jQuery that when an "li" element within a with a "ul" element with an id of "main_nav" is clicked, the following should occur:

  1. Create a unique hash from the value of the "alt" tag of the clicked "li" element. This will be used by "halogen.tabs.js" to ensure that we don't have duplicate tabs.
  2. Call the "tab" method in our "tabs" JavaScript object, passing it both the title of the tab (the value of the "alt" attribute in this case), and the unique hash. Within the "tab" method, I check to see if a tab with an id of "tabHash" exists. If it does, I select that tab. If no such existing tab was found, a new tab is created with the given title and unique hash.
  3. Finally, assured that a tab exists with an id of the value of "tabHash", I load a file into that tab using the jQuery "load" method, passing it the appropriate file to load.

I'll show you the contents of "halogen.tabs.js", but it might be best to just treat this as magic code that will handle tabs for you, but for the curious, here it is:

/*
Copyright 2009 by Hal Helms
hal@halhelms.com
941.716.6909
You may use this code for your projects, but please do not...
1. remove this comment, or
2. sell this stand-alone code. (You may incorporate it into your commercial projects freely.)
*/


function Tabs(jQuerySelector){
   $(jQuerySelector).tabs();
   this.bag = new Array();
   
   this.add = function(title, uniqueHash){
      var deleteString = '&nbsp;&nbsp;<a href="#"><img border="0" class="tab_delete" src="delete-img.png" onclick="tabs.remove(';
      deleteString += "
'";
      deleteString += uniqueHash;
      deleteString += "
'";
      deleteString += ')"
>
</a>';
      $(jQuerySelector).tabs(
         'add',
         '#' + uniqueHash,
         title + deleteString);
      $(jQuerySelector).tabs('select', this.bag.length);
   }

   this.tab = function(title, uniqueHash){
      for (i=0; i<this.bag.length; i++){
         if (this.bag[i].uniqueHash == uniqueHash){
            $(jQuerySelector).tabs('select', i);
            return;
         }
      }
            
      this.add(title, uniqueHash);
      
      var str = {};
      str.title = title;
      str.uniqueHash = uniqueHash;
      str.idx = this.bag.length;
      this.bag.push(str);
      
      return true;
   }
   
   this.remove = function(uniqueHash){
      for (i=0; i<this.bag.length; i++){
         if (this.bag[i].uniqueHash == uniqueHash){
            this.bag.splice(i,1);
            $(jQuerySelector).tabs('remove', i);
            return;
         }
      }
   }
}

Zipped files

In the video links below, I've done a walk-through of the code. Sometimes a video can help clarify complexity.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
John Quarto-vonTivadar's Gravatar maybe I'm being nit-picky, but what are the CSS + jquery library scripts doing being loaded after the close of the </body> tag , rather than in the <head> ?
# Posted By John Quarto-vonTivadar | 4/9/09 3:39 AM
Hal's Gravatar @ der Q,

From the YSlow docs:

Put Scripts at the Bottom

tag: javascript

The problem caused by scripts is that they block parallel downloads. The HTTP/1.1 specification suggests that browsers download no more than two components in parallel per hostname. If you serve your images from multiple hostnames, you can get more than two downloads to occur in parallel. While a script is downloading, however, the browser won't start any other downloads, even on different hostnames.
# Posted By Hal | 4/9/09 5:58 AM
anthony's Gravatar @Hal
Scripts should be at the bottom, CSS should be at the top. If the css is at the top, it allows progressive rendering.

http://developer.yahoo.com/performance/rules.html#...

Great tutorial otherwise.
# Posted By anthony | 4/9/09 1:05 PM
anthony's Gravatar Also, if your scripts are on the bottom of the page, you don't need to worry about wrapping anything in the jQuery dom ready function. Since the script is at the bottom, it is the last thing to be processed, so all the dom elements are already on the page
# Posted By anthony | 4/9/09 1:08 PM
Hal's Gravatar Excellent points, Anthony. But I was trying to beat back my friend, John Q, so I neatly omitted the "CSS Should Be At The Top" section of the YSlow docs!

;-)
# Posted By Hal | 4/9/09 1:50 PM
John Quarto-vonTivadar's Gravatar But it can't hurt to still use document ready even at the bottom, I should think. I don't think it means only "I got to the bottom" but also "I'm ready to do dom manipulation now" which may (or may not) be coincident with being at the bottom. It seems like one takes on all the responsibility for any little thing the browser might or might not need to be doing at a critical juncture, whereas just wrapping it in the ready() method, abstracts all that works over to the jquery library, which is written by way smarter people who do know all the little things that count at that moment.

And what's my upside anyway?
# Posted By John Quarto-vonTivadar | 4/9/09 2:17 PM
anthony's Gravatar @John
If your script is at the bottom of the page, what else is left to be rendered on the dom? You should be able to play with all the elements before the script even if the ondomready event hasn't fired. Developers have been sticking JS at the bottom long before all these dom ready events were around.

The only time i can see needing the dom ready event is when you have scripts that depend on each other at the bottom of the page.
# Posted By anthony | 4/9/09 2:28 PM
anthony's Gravatar @John
I was actually reading a good post on another blog about the document ready function that says it actually can hurt to use it.

http://www.artzstudio.com/2009/04/jquery-performan...

I see the author's point, but I'm still going to use the document ready function if I see a need for it.
# Posted By anthony | 4/9/09 2:32 PM
John Quarto-vonTivadar's Gravatar I guess that's the heart of my point: if I knew precisely what was going on under the covers of each particular browser, maybe then I could agree with your statement. From what I can tell each of the browsers handle things, particularly events, in slightly different ways, and I don't want to depend on my assumption of what I think I know, versus what I cannot imagine I don't know. I do have confidence, however, that John Resig and other JS library creators know more than I do, and I do trust my own judgement that the "cost" of using their document.ready() approach is less than the pain I incur if my knowledge comes up short.

here's an example, and this is only something I can guess at: what if there's an event that fires off when </html> is found? (presumably when EOF is encountered the event fires too unless the browser is in STRICT mode). And that's just one I can think of. Note that I'm not arguing against putting scripts at the bottom; I am arguing against what seems to be your suggest of avoiding the document.ready()
method -- that it somehow is not needed if scripts are at the bottom.

Let me turn the question around on you: what does one gain by not using the document.ready() call? I'm supposing you're gonna say X milliseconds of loadtime, cuz I'm sure what else there is to gain except time.
# Posted By John Quarto-vonTivadar | 4/9/09 2:44 PM
anthony's Gravatar I'm not making a point to never use the document ready function, I'm trying to say that you shouldn't use it if you don't need to use it. In Hal's example, everything being used inside the dom ready function is already available before the dom is completely loaded.

I realize that x number of milliseconds is a small gain for this example, but what about if you had to wait for a dom load event when there is a long blocking script on the page? The google maps scripts come to mind, they seem to take forever to finish loading, which delays the dom load event.

I can see your point about using it anyway because it does provide you with many benefits (previous errors don't stop script from loading, you can put the code anywhere you want, backed by and tested by jQuery team, etc).
# Posted By anthony | 4/9/09 3:22 PM
J. Fishwick's Gravatar I probably should just regard the halogen.js as magic, but I write seeking some understanding.

If I wanted the on-the-fly tabs to be loaded from a list of links on an inital, permanant tab, could that be accomplished? In my experiments, a permanent tab as such vanishes when clicking on one of the on-the-fly tags.

Any help appreciated.
# Posted By J. Fishwick | 6/30/09 5:57 PM
Sachin's Gravatar Hal,

I was wondering if you have come across an issue with having a <cfinclude> inside of the coldfusion page that is ajax loaded by jquery tabs?

The page loads fine if all it has is vanilla coldfusion code. But, I try to cfinclude other coldfusion pages "modules" inside it then I get a 500 - template not found error.

Just curious if there something I am doing something wrong or this is a known boo-boo.

Thank you.
# Posted By Sachin | 2/17/10 1:45 PM
 
   
Clicky Web Analytics