Programming
Article
By Craig Buckler

Progressive Enhancement Techniques 3: the JavaScript

By Craig Buckler
Help us help you! You'll get a... FREE 6-Month Subscription to SitePoint Premium Plus you'll go in the draw to WIN a new Macbook SitePoint 2017 Survey Yes, let's Do this It only takes 5 min

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:

Login or Create Account to Comment
Login Create Account
Recommended
Sponsors
Get the most important and interesting stories in tech. Straight to your inbox, daily.Is it good?