<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Paul Bennett - User Interface Developer &#187; Tabs</title>
	<atom:link href="http://pmbennett.net/tag/tabs/feed/" rel="self" type="application/rss+xml" />
	<link>http://pmbennett.net</link>
	<description></description>
	<lastBuildDate>Thu, 27 Oct 2011 15:10:11 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Auto-fit Tabs</title>
		<link>http://pmbennett.net/2010/03/26/auto-fit-tabs/</link>
		<comments>http://pmbennett.net/2010/03/26/auto-fit-tabs/#comments</comments>
		<pubDate>Fri, 26 Mar 2010 18:57:44 +0000</pubDate>
		<dc:creator>paul</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Tabs]]></category>

		<guid isPermaLink="false">http://pmbennett.net/?p=229</guid>
		<description><![CDATA[<p>A while ago, over at <a href="http://www.bright-interactive.co.uk">Bright Interactive</a>, we had a client with a challenging requirement: they wanted a tabbed area on a page, which would have multiple rows of tabs. The challenging part came about when they requested that the tabs would automatically fill their containing box. That was still pretty easy to achieve, until I realised that the website had a fluid layout, and so the width of the containing box was not defined in pixels in the CSS stylesheet.</p>]]></description>
			<content:encoded><![CDATA[<p>The website couldn&#8217;t use a javascript library, so it had to be done completely from scratch. It was a bit fiddly, especially with browser inconsistencies when it came to rounding widths of containers, but I got there eventually. It got me wondering how much easier this would be to achieve using jQuery.</p>
<div class="img_wrapper"><img src="http://resources.pmbennett.net/demos/images/tabs_diagram.png" alt="Diagram of autofit tabs concept" /></div>
<p>The Markup and CSS is pretty straight forward: We just use a list of links that are floated to achieve the horizontal tabs. This is a pretty standard appraoch, so I won&#8217;t list that code here as it is unecessary.</p>
<p>Now the tricky part: the jQuery. Before writing the JavaScript, I wanted to have a clear approach to solving this problem. Basically, every time the page loads or the window is resized, the following steps must take place:</p>
<ol>
<li>Find the width of the tabs&#8217; containing element</li>
<li>Determine how many rows there will be and what row each tab will be in</li>
<li>Determine the amount of space left on each row, and from that work out how much padding to add to each tab to make them fill the space</li>
<li>Add the extra padding to each tab</li>
<li>Do any extra +/- of pixels to accommodate browser rounding inconsistencies</li>
</ol>
<p><em>There are also a few other things that needed to be done in order to get around the browser inconsistencies with working out dimensions, but I will explain them when we get to it.</em></p>
<p>To begin with, we setup the load and window resize events and also make any whitespace in the link text a non-breaking space. This is necessary to prevent the text from dropping onto two lines when resizing.</p>
<pre><code>$(function() {
	$("#tab_container li a").html(function(index, htm) {
		return htm.replace(/\s/g, '&amp;nbsp;');
	});
	autoSizeTabs();
	$(window).resize(function() {
		autoSizeTabs();
	});
});
function autoSizeTabs() {
	// add code here...
}</code></pre>
<p>Now for the fun part, the code that actually makes the tabs fit their container. To start with, we just set a few variables which will be used throughout. the main one is the width of the tab container. I have also added some code to reset the tabs to their default state, otherwise it will break if the function is called more than once. (I.e. when someone is testing by constantly resizing the browser).</p>
<pre><code>// Reset the tabs to their default state
$("#tab_container li").css({"width": "auto", "borderRightWidth": "1px"});
$("#tab_container li:last-child").css({'borderRightWidth': '0px'});
$("#tab_container").parent().css({"width": "50%"});

// Setup some variables
var containerWidth = $("#tab_container").innerWidth();
var tabsWidth = 0;
var rowCount = 1;</code></pre>
<p>As outlined in the steps above, we then need to loop through the list-items to determine which row each should be on. In this implementation, I decided to make use of a class name which takes the form of &#8220;tab_row_X&#8221;. I did play with the idea of using jQuery&#8217;s data() method for this, but it seemed more tricky to retrieve that value, so I decided against it.</p>
<pre><code>// Go through tabs and work out how many rows there are and which tabs are on which row
$("#tab_container li").each(function() {
	tabsWidth = tabsWidth + $(this).outerWidth();
	if (tabsWidth &gt; containerWidth) {
		tabsWidth = 0;
		rowCount++;
	}
	$(this).addClass('tab_row_'+rowCount);
});</code></pre>
<p>With the above in place, we are now ready to pad-out the tabs to make them fit to the width of their parent node. To do this we loop through each row of tabs and work out how much space (in pixels) the tabs use up, and subtract it from the width of the parent container to find the amount of space left over. Then we just divide that by the number of tabs. It is important to note, that I used Math.round() here which means that there could be too much or too little padding added, which will need to be addressed later. (Browser inconsistencies with rounding decimal dimensions meant that I had to make these whole numbers at this stage).</p>
<pre><code>// Now go through the tabs in each row and pad them out to fill the container
for (var d=1; d&lt;=rowCount; d++) {
	var row = $(".tab_row_"+d);
	var rowWidth = 0;

	// Work out the space left over in the row
	for (var r=0; r&lt;$(row).length; r++) {
		rowWidth = rowWidth + $(row[r]).outerWidth();
	}
	var pxDiff = containerWidth - rowWidth;
	tabExtraPx = Math.round(pxDiff / $(row).length);

	// Now add the extra padding to each of the tabs in the row
	var newWidth = 0;
	$(".tab_row_"+d+":last").css({'borderRightWidth': '0px'});
	$(row).each(function() {
		var wd = $(this).outerWidth() + tabExtraPx;
		var cssWd = $(this).innerWidth() + tabExtraPx;
		$(this).css({'width': cssWd + 'px'});
		newWidth = newWidth + wd;
	});

	// Rest of code goes here...
}</code></pre>
<p>Once that is complete, we then need to address any differences caused by the rounding. This just works out the overall width of the tabs and compares it to the width of the container, adding or subtracting the difference.</p>
<pre><code>// Now deal with any difference left as a result of rounding
if (newWidth &gt; containerWidth) {
	var diff = newWidth - containerWidth;
	var lastTab = $(".tab_row_"+d+":last");
	var nWidth = Math.round($(lastTab).innerWidth() - diff);
	var row = $(lastTab).css({'width': nWidth+'px'});;
}
if (newWidth &lt; containerWidth) {
	var diff = containerWidth - newWidth;
	var lastTab = $(".tab_row_"+d+":last");
	var nWidth = Math.round($(lastTab).innerWidth() + diff);
	var row = $(lastTab).css({'width': nWidth+'px'});;
}</code></pre>
<p>Once we have resized the tabs, We need to remove the class used to identify which row each was on, so that if the function gets called again, the tabs will be in their default state.</p>
<pre><code>// reset the row class
$("#tab_container li").removeClass("tab_row_"+d);</code></pre>
<p>Lastly, we need to set the width, in pixels, of the parent container, otherwise we will again come across inconsistenices with browser dimension rounding if the width is actually a decimal number.</p>
<pre><code>// Fix the width of the container to get around browser rounding issues
$("#tab_container").parent().css({"width": containerWidth+'px'});</code></pre>
<p>Although this solution does work, it doesnt evenly spread the tabs across rows. It merely works out what row a tab is one and applies the necessary padding to make them fit their container. So there will be circumstances where one tab drops to a new row and fills the entire row. It&#8217;s not ideal, but it satisfied my requirements at the time.<br />
The code is also far from optimal, and I&#8217;m sure there are some improvements that can be made, but I don&#8217;t particularly have the desire to expend any more time on this right now.</p>
<p>Tested in Firefox 3+, IE6+ (IE6 seems to work 99% of the time, but very occaisonally mis-calculates the widths), Chrome, Safari (Win).</p>
<p class="article_links"><a href="/demos/autofit-tabs.html" target="_blank">View demo</a></p>
]]></content:encoded>
			<wfw:commentRss>http://pmbennett.net/2010/03/26/auto-fit-tabs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

