Drop us a line 0116 403 0288

jQuery tabs with Ajax content

We’ve worked on a few projects recently where information on the page is divided up into tabbed areas. There are hundreds of jQuery plugins available for tabbed content, but not many which allow a mixture of on-page and external tabbed content.

The standard Tabs widget in jQuery UI provides this functionality, but it’s a little heavy for our liking. Why did we want to do this? Sometimes the content of a tab is the same on many different pages – for example delivery information – which isn’t ideal from an SEO point of view. By pulling this information into a tab via Ajax we avoid having to repeat it in the markup of every page. There’s also a nice fallback side-effect in the fact that the tab itself will link through to the external delivery page in question when JavaScript is disabled.

If you’re interested, here’s how our solution came together:

The HTML

<div class="tabs">
	<ul class="tab-menu">
		<li><a href="#tab-1">Product Overview</a></li>
		<li><a href="#tab-2">Specification</a></li>
		<li><a href="./delivery.php" data-fragment="#small-print">Delivery Info (ajax)</a></li>
	</ul>
	<div class="tab-content" id="tab-1">
		<h2>Product Overview</h2>
		<p>This is a really fantastic product with the following features:</p>
		<ul>
			<li>Tear resistant</li>
			<li>Shatter resistant</li>
			<li>Wind resistant</li>
		</ul>
		<p><strong>Please note:</strong> This product isn't water resistant. Well, you can't have everything.</p>
	</div>
	<div class="tab-content" id="tab-2">
		<h2>Product Specification</h2>
		<ul>
			<li>Width: 1,000mm</li>
			<li>Height: 25,000mm</li>
			<li>Weight: 25kg</li>
		</ul>
	</div>
	<!-- You'll notice the delivery info is missing, because we get this via Ajax -->
</div>

You might have noticed that on the delivery link we used data-fragment=”#small-print” – this allows us to pull through the contents of a single element on the delivery page, rather than the entire page.

The CSS

.tab-menu {
	list-style-type: none;
	overflow: hidden;
	margin: 2.5em 0 0 0;
	padding: 0;
}

.tab-menu li {
	display: inline;
	float: left;
}

.tab-menu li a {
	display: block;
	padding: 10px 18px;
	border-top: 1px solid #198219;
	border-left: 1px solid #198219;
	border-right: 1px solid #198219;
	color: #ffffff;
	background-color: #198219;
	text-decoration: none;
}

.tab-menu li a:hover {
	background-color: #208c20;
}

.tab-menu li.active a {
	color: #222222;
	background-color: #f5f5f5;
	border-top: 1px solid #ccf;
	border-left: 1px solid #ccf;
	border-right: 1px solid #ccf;
}

.tab-menu li.active a:hover {
	color: #222222;
	background-color: #f5f5f5;
	text-decoration: none;
	cursor: default;
}

.tab-content {
	position: relative;
	width: 500px;
	min-height: 180px;
	max-height: 180px;
	overflow: auto;
	margin-bottom: 2.5em;
	padding: 20px;
	border-left: 1px solid #ccf;
	border-bottom: 1px solid #ccf;
	background-color: #f5f5f5;
}

.tab-loading {
	position: absolute;
	top: 50%;
	left: 50%;
	margin-top: -16px;
	margin-left: -16px;
	width: 32px;
	height: 32px;
	background-image: url(./ajax-loader.gif);
}

The CSS is pretty self explanatory and can be modified to suit your needs.

The jQuery Plugin

(function ( $ ) {
	$.fn.fwd_tabs = function() {
		return this.each(function() {
			var tabs = $(this);
			var tabMenuList = $(".tab-menu", tabs).children();

			/* Create any missing content areas that are required for AJAX requests */
			for (i = $(".tab-content", tabs).length, j = tabMenuList.length; i < j; i++ ) {
				tabs.append('<div class="tab-content"></div>');
	    	}

			var tabContent = $(".tab-content", tabs);

			/* Hide all but the first content area */
			tabContent.slice(1).hide();

			/* Mark the first tab as active by default */
			tabMenuList.eq(0).addClass("active");

			/* Listen for clicks on the tab menu */
			tabMenuList.find("a").click(function(e) {
				var theParent = $(this).parent().index();

				/* Deactivate any other tab menus and make the selected one active */
				tabMenuList.removeClass('active').eq(theParent).addClass('active');

				/* Hide any other content areas and make the selected content area visible */
				tabContent.hide().eq(theParent).show();

				/* If this is an external link and hasn't been called before, load the data into the content area */
				if (tabContent.eq(theParent).html().length == 0 && $(this).attr("href").substr(0, 1) != "#") {
					var fragment = ($(this).data("fragment") ? " " + $(this).data("fragment") : "");
					tabContent.eq(theParent).append('<div class="tab-loading"></div>').load($(this).attr("href") + fragment, function(response, status, xhr) {
						if (status == "error") {
							tabContent.eq(theParent).html("Sorry, the content could not be loaded");
						}
					});
				}
				e.preventDefault();
			});
		});
	}
}(jQuery));

You can then activate the tabs as follows (don’t forget to load the jQuery library first):

$(function() {
   $(".tabs").fwd_tabs();
});

What are the advantages of this plugin?

  • It’s small, at just over 1.5kb in filesize

  • You have control over the markup – all that’s required are two classnames

  • You can load a mixture of on-page and external content via the tabs – perfect for SEO and duplicate content concerns

  • It’s your choice whether you create empty elements for the Ajax content – if not they’re created automatically

  • It degrades gracefully if JavaScript is disabled or jQuery fails to load

What can’t this plugin do?

It doesn’t pre-activate a tab based on a URL fragment. For example… www.yourwebsite.com/product.php#tab-2 …won’t display the second tab by default like some other plugins do.