Progressive Enhancement Techniques 3: the JavaScript

web layersIn my previous articles we built a tab control using Plain Old Semantic HTML with unobtrusive CSS styling. This is the final article in a three-part series that illustrates how to build a simple tabbed box using progressive enhancement techniques.

Shaky Scripting

JavaScript is the most risky web page layer. HTML support is guaranteed, CSS is likely, but JavaScript may be unavailable or disabled. Whether that affects your site will depend on your audience but, in general, you can expect 5% of users to not have scripting enabled. That’s a large percentage — certainly higher than the market share of Safari, Chrome or Opera.

There are also differences in browser implementations. Fortunately, JavaScript is a versatile language and we can fix compatibility issues. For example, IE5 does not provide the Array push() method, but we can detect when it’s missing and add it ourselves:


if (!Array.prototype.push) {
	Array.prototype.push = function(item) { this[this.length] = item; };
}

Note: adding new properties or methods to native objects is a little risky, but that’s a topic for another article.

Many JavaScript frameworks provide functions which overcome browser inconsistencies. The tab box control code shown below is built as a jQuery plugin. It provides a reusable component that can be implemented on any page and jQuery will help with the more mundane aspects of client-side coding, such as DOM manipulation and event delegation.

You should be aware that jQuery and other libraries offer limited browser support. Although our tab box could work in IE5 and IE5.5, jQuery does not support those browsers and scripting will fail. It’s not a major problem in this example because the tab box operates without JavaScript. However, you should always check your requirements against your chosen library’s limitations before using it.

Finally, jQuery aficionados will know that tabs are already supported in the UI API and many other pre-built controls are available. Most will be better than this example — the code is purposely kept short to demonstrate progressive enhancement techniques.

JavaScript-enabled CSS

Without JavaScript, our tab box uses a scrolling content section. Our script must remove the scrollbars, but we can still specify this using JavaScript-controlled CSS:


/* tab control CSS: JavaScript progressive enhancements */
.tabcontent.active
{
	overflow: hidden;
}

The plugin will therefore apply a class of “active” to the outer content div when the tab box is initialized.

Developing the jQuery TabControl Plugin

The full code is available in jquery.tabcontrol.js. When the page has loaded, the JavaScript looks for any tag with a class of “tabs” and creates a new instance of a TabControl object for each node found. The node is assigned a TabControlInitialized property to prevent two or more objects pointing at the same HTML code.

Two important variables are initialized:

  • (this.) Tab property will contain the nodes of all tabs and their associated content
  • (this.) Active property stores the currently highlighted tab.

// jQuery plugin definition
$.fn.TabControl = function() {

	// tab control object
	function TabControl(list) {

		if (list.TabControlInitialized) return;
		else list.TabControlInitialized = true;

		this.List = list;
		this.Tab = {};
		this.Active = null;

In the next segment, the value of T is set to this (the current object instance) so it can be referenced within the following loop. Each tab is identified and the ID it links to is extracted (see the LinkId method below). The tab and content nodes are then stored in the Tab object:


		var T = this;

		// find and initialize all tabs
		$("li a[href^=#]", list).each(function() {

			var id = T.LinkId(this);
			var content = $("#"+id);
			content = (content.length > 0 ? content[0] : null);

			// link/content object
			T.Tab[id] = {
				link: this,
				content: content
			};

The final part of the loop performs the following operations:

  1. A class of “active” is added to the content’s container (the div with the class “tabcontent”). This will remove the scroll bars, as described above.
  2. A click event handler is applied to all links that reference the tab content, including the tab itself (see the TabSwitch method below).
  3. All tabs and content are made inactive (see the Activate method below).
  4. The currently active tab is determined. This will normally be the first tab unless another ID is specified in the page URL.

The active tab is then activated:


			// set content holder class
			if (content !== null) $(content.parentNode).addClass("active");

			// event delegation
			$("a[href=#"+id+"]").click(function(e) { T.TabSwitch(e) });

			// deactivate tab
			T.Activate(id, false);

			// is tab active?
			if (T.Active === null || "#"+id == location.hash) T.Active = id;

		});

		// show active tab/content
		this.Activate(this.Active);

	}

Finally, we have a three methods. The first, LinkId, returns the in-page ID extracted from a link’s href attribute (the “#” is also removed):


	// returns linked ID
	TabControl.prototype.LinkId = function(link) {
		return link.href.replace(/.*#(.+)$/, "$1");
	};

The TabSwitch method is called when a tab (or any link pointing at the tab’s content) is clicked. It stops the browser’s default action, deactivates the old tab, and reactivates the new one.

The final few lines smoothly scroll the page to the tab box if it’s off the screen when a link to tabbed content is clicked. The link jumped to the content location in normal HTML, but our code has cancelled that event and replaced it with a nicer effect.


	// tab click event handler
	TabControl.prototype.TabSwitch = function(e) {

		e.preventDefault();

		var id = this.LinkId(e.target);
		if (id != this.Active) {

			// hide old tab
			this.Activate(this.Active, false);

			// switch to new tab
			this.Active = id;
			this.Activate(this.Active);

		}

		// scroll to tab box if required
		var html = $('html,body');
		var lt = $(this.List).offset().top, lh = $(this.List).height();
		var st = Math.max(html.scrollTop(), $("body").scrollTop()), wh = $(window).height();
		if (lt < st || lt+lh > st+wh) html.animate({scrollTop: lt}, 500);

		return false;
	};

Finally, the Activate method takes two arguments; the tab ID and either true or false to activate or deactivate the tab and its content:


	// activate or deactivate a tab
	TabControl.prototype.Activate = function(id, show) {

		if (this.Tab[id]) {
			if (show !== false) {
				$(this.Tab[id].link).addClass("active");
				if (this.Tab[id].content) this.Tab[id].content.style.display = "block";
			}
			else {
				$(this.Tab[id].link).removeClass("active");
				if (this.Tab[id].content) this.Tab[id].content.style.display = "none";
			}
		}

	};

Our progressively-enhanced tab box is now complete — view the example page. It should work in all browsers even if CSS, JavaScript or both are disabled or fail. There is little need to publish a browser support list!

HTML, CSS and JS screenshot

Resource links:

Other parts in this series:

Related reading:

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Integralist

    A bit disappointing that this functionality uses jQuery rather than being library agnostic JavaScript code (i.e. use NO framework).

     

    I appreciate that these frameworks such as jQuery and MooTools etc make it easier to build this sort of functionality. But imagine that you only want a ‘tab’ interface like you have built above, no more (e.g. no other need for Javascript on your website). That means you are now forced to download the entire jQuery library + your script above to perform this functionality.

     

    It’s OTT and it also means if you want to learn how to do this yourself you can’t. There are so many tutorials on the internet for JavaScript functionality that sound really interesting and I go to read them only to find out they are using a framework like jQuery! People still want to learn how to code proper full JavaScript without the use of a framework.

     

    Admittedly this current page isn’t the best example of the issue I have as its still using a fair amount of core JavaScript to achieve the results you are after, but after seeing hundreds of tutorials online and literally nearly all of them using frameworks I’m a bit fed up of seeing them in use.

     

    What is everyone elses thoughts on this?

  • appletsauce

    I would’ve just preferred a high-level overview with just a few snipplets here and there with a link to the full script. Don’t necessarily need line-by-line. jQuery in this example isn’t doing too much thinking for you. It definitely makes using the DOM easier though, and keeps it from having any focus in the article. The one negative would be for people who aren’t familiar with jQuery. In that situation, I’d agree plain old JavaScript is better.

  • http://www.optimalworks.net/ Craig Buckler

    I certainly agree that libraries are over-used and not always required. However, this code is an example of progressive enhancement — it’s not meant to be a definitive tab box control.

    jQuery allowed me to keep the code reasonably short without adding cross-browser DOM manipulation functions, event handlers, and other supporting functions which would have doubled the length and may have confused matters. Few jQuery methods are used and most are reasonably obvious (addClass, removeClass, scrollTop, etc.)

    You’ll be pleased to know that I’ll be writing a few JS-only library-free articles very shortly.

  • Integralist

    @Craig I look forward to reading those articles :)

  • littrean

    This was a great series Craig and jQuery is well suited here in my opinion. The truth is most designers are looking to use frameworks to get the job done; it leaves room for less error and that’ll make clients happy. If you’re struggling to build a javaScript library for a site that you’re building for someone else and you have a deadline, you’re better off using a framework than rushing the code and building it incorrectly. Even worse will be building it “correctly” on one browser only to hand the site over and realize your script is broken in IE6 for example.
    <br/>
    The people that build the major frameworks in heavy usage today (jQuery, prototype, mootools, etc.), have an advanced knowledge of javaScript. Their knowledge extends the average javaScript programmer, or the guy that brags he knows a little javaScript but when you sit him in a room with he computer the best script he can come up with is an alert lol. In addition using frameworks creates a sense of healthy uniformity in the web community instead of a bunch of recklessly written obtrusive code snippets.
    <br/>
    Most people want everything done for them and for free that is. With that being said, If you can build a tab functioning java script that is cross browser, framework free, and error free then chances are Craig’s III part series on Progressive Enhancement Techniques was not geared towards you because in my opinion you know your stuff and building scripts like this series is common knowledge, light work for you.