Demo: Dynamic Cross-browser Horizontally Fluid Menu

by Mike Badgley on June 22, 2010

The following is a solution that I came up with create a fluid horizontal navigation structure that met the following requirements:

  • The navigation needed to be completely dynamic and not dependent on an update to a CSS file if items were added or removed from it.
  • Must horizontally span the entire width defined by the page layout.
  • Each navigational item has to be horizontally and vertically centered within it’s ‘cell’.
  • No ‘cheating’ by using a <table> for a layout construct.
  • Support as many browsers as possible – including IE6.

I spent some time trying to find a workable solution and I was surprised that I couldn’t find any that met most of what I needed. Perhaps thats is more of a testament to my searching “skills”? Anyhow, creating my own solution and re-inventing the proverbial wheel seemed more interesting to me anyways!

If browser support wasn’t an issue, this would have been a fairly easy task to complete – after all, you can simulate a table layout by using the display: table operation along with table-row and table-cell. However, neither IE6 or IE7 support these layout types.

Based on the requirements that this navigation had, doing a straight-up HTML and CSS solution was not going to work. It was pretty clear that there would need to be some DOM manipulation required via JavaScript.

The HTML and CSS for the navigation

For the site that I implemented this solution on, there was dropdown navigation for each of the ‘primary’ items. However, in my implementation for this example I’ll be assuming there is no dropdown navigation involved. However, this doesn’t really affect or change the way the elements will be styled – but there is one important thing to keep in mind which I’ll get to later.

<div id="wrapper">
	<ul class="navigation">
	<li><a href="#">Home</a></li>
	<li><a href="#">About the Company</a></li>
	<li><a href="#">News Releases</a></li>
	<li><a href="#">Investors Information</a></li>
	<li><a href="#">Policies and Producedures</a></li>
	<li><a href="#">Contact Us</a></li>
	<li><a href="#">Site Map</a></li>
	</ul>
</div>

Nothing to out of the ordinary – just a simple unordered list within a wrapper which will be used to define the width of the layout.

#wrapper {
	margin: 0 auto;
	width: 969px;
}

ul.navigation {
	background-color: #e1e1e1;
	list-style-type: none;
	margin: 0;
	padding: 0;
	overflow: hidden;
	position: relative;
	width: 100%;
}
	ul.navigation li { float: left; text-align: center; }
		ul.navigation a {
			display: block;
			color: #666;
			padding: 6px 10px;
			text-decoration: none;
		}
		ul.navigation a:hover { background-color: #f1f1f1; }

The styling of the navigation is kept very simple – I’m basically just floating each item in the navigation and then applying the necessary padding to the item’s hyperlink. To keep things simple and clean, it’s best to not apply any borders, padding or margins to the list items. Otherwise, the script which dynamically resizes them will need to account for this (box model), and currently it is not setup for that. I recommend that if you require extra elements with which to style your navigation with that you just use <span> tags within the <li> tag itself.

Demo 1: Simple horizontal navigation

A working example of the navigation created thus far can be viewed here.

This demo works well across all browsers and all the items in the navigation (depending on your default font size) fit on the same horizontal plane. If we disregard the requirement of the navigation spanning the entire width of the wrapper, this solution works fine.

Demo 2: Breaking the layout

Things change, however, when extra items are added and we can see rather quickly that a need for some DOM manipulation will be necessary, as shown in the example here.

True, this is a rather excessive amount of navigational items, but if you discount that fact and consider that the user could have a larger-than-defined-font-size, or, if the site is in multiple languages the labels for each item could be considerably longer, causing it to break the layout. Either way you look at it, this layout does not allow the navigation to be ‘dynamic’ – you would have to manually adjust the width of each of the <li> elements within the CSS.

Using a JavaScript-based solution

Using JavaScript to fix the layout problems is not a preferred way of doing things, but if this layout needs to work across a wide range of browsers that include IE6 and IE7 then it is the only way that it can be accomplished. Besides, this is a purely visual solution and will not impose any accessibility issues with users who do not have JavaScript enabled on their browser or handheld device (they’ll just see the navigation as was shown in the second demo).

The first thing to do is to include a reference to the jQuery library:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

Next, the actual JavaScript that formats the navigation to meet the requirements:

Array.prototype.sum = function() {
  return (! this.length) ? 0 : this.slice(1).sum() +
	  ((typeof this[0] == 'number') ? this[0] : 0);
};

function menu() {
	var nav = $('ul.navigation'),
		nav_item_height = 0,
		numMenuItems = $('> li', nav).size(),
		totalMenuItemWidth = 0,
		menuWidthRemainder = 0,
		wrapperWidth = 969,
		maxNavItemWidth = 200,
		priNavItems = [];

	/* First, determine the total width of each item in the navigation. */
	$('> li', nav).each(function (i) {
		totalMenuItemWidth += $(this).width();
		priNavItems.push(($(this).width() > maxNavItemWidth) ? maxNavItemWidth : $(this).width());
	});

	/* Primary navigation items (combined) are less width than the
	wrapper. */
	if(totalMenuItemWidth < wrapperWidth) {
		menuWidthRemainder = wrapperWidth - totalMenuItemWidth;

		$('> li', nav).each(function() {
			var tmp_width = $(this).width();
			$(this).width(tmp_width + parseInt(menuWidthRemainder / numMenuItems));
		});

		tmp_width = $('> li:first', nav).width();
		$('> li:first', nav).width(tmp_width + (menuWidthRemainder % numMenuItems));
	}

	/* Primary navigation items (combined) are greater than the width of
	the wrapper. */
	else if(totalMenuItemWidth > wrapperWidth) {
		var priResizedNav = [];

		for(var x = 0; x < numMenuItems; x++) {
			/* Always take the lowest value as the sum total of the resized
			navigation items must never exceed the wrapper width. */
			priResizedNav.push(Math.floor((priNavItems[x] / priNavItems.sum()) * wrapperWidth));
		}

		$('> li', nav).each(function(i) {
			$(this).width(priResizedNav[i]).css('wordWrap', 'break-word');
		});

		$('> li:first', nav).width(priResizedNav[0] + (wrapperWidth - priResizedNav.sum()));
	}

	// Ensure top-level navigation spans the available width.
	$('> li', nav).each(function (i) {
		nav_item_height = ($(this).height() > nav_item_height) ? $(this).height() : nav_item_height;
	});

	$('> li', nav).each(function (i) {
		if($(this).height() < nav_item_height) {
			// Default padding: 6/6 (top/bottom).
			var paddingTop = parseInt((nav_item_height - $(this).height()) / 2);
			var paddingBottom = parseInt((nav_item_height - $(this).height()) - paddingTop);

			$('a', this).css({
				paddingTop: paddingTop + 6 + 'px',
				paddingBottom: paddingBottom + 6 + 'px'
			});
		}
	});
}

The following sections (and demos) will take you through each part of the script and breakdown what I was trying to accomplish.

The sum prototype of the Array object (at the top of the source code) is extraneous to this solution and was picked up from a third-party source. I use this as a ‘helper’ throughout my solution.

Determining the width of the navigation

$('> li', nav).each(function (i) {
	totalMenuItemWidth += $(this).width();
	priNavItems.push(($(this).width() > maxNavItemWidth) ? maxNavItemWidth : $(this).width());
});

The first block of the script is used to determine what the actual width of each item in the navigation is at run-time. Each item’s width (pixels) is stored within an array as well as tracking  a running tally of the overall width that is defined in a separate variable.

When tracking the width of each item, the value of the maxNavItemWidth variable is used to determine if the current item’s width is greater than the variable’s value (currently defined as 200 pixels). This ensures that no single item in the navigation will be larger than that pre-defined value (200 pixels). The reason for this is if you have an item that has a very long string/label. Instead of taking up all the available real-estate (and causing the navigation to wrap onto multiple lines or create awkward spacing issues) it instead can span no wider than the value that has been defined with the variable.

If the determined width is less…

There is one of two paths the script will take once the width of the navigation has been calculated. In the case where the navigation has been found to be less than the wrapper’s width (which we defined in this example as being 969 pixels), the following occurs:

if(totalMenuItemWidth < wrapperWidth) {
	menuWidthRemainder = wrapperWidth - totalMenuItemWidth;

	$('> li', nav).each(function() {
		var tmp_width = $(this).width();
		$(this).width(tmp_width + parseInt(menuWidthRemainder / numMenuItems));
	});

	tmp_width = $('> li:first', nav).width();
	$('> li:first', nav).width(tmp_width + (menuWidthRemainder % numMenuItems));
}
  • First, calculate the amount of pixel space that is remaining in the navigation and assign this value to a variable (menuWidthRemainder).
  • Enumerate through each item in the navigation and assign it’s current width plus the extra width (the amount calculated by dividing it by the number of items in the navigation).
  • Note: The first item in the navigation also receives the remainder from the calculation preformed in the step above.

A working example of the navigation created thus far can be viewed here. You will see how the items are all ‘stretched’ so that they fill the entire width of the navigational area.

…if the determined width is greater.

For navigation that is greater in width than what is allowed by the wrapper, a slightly more complicated solution is needed.

else if(totalMenuItemWidth > wrapperWidth) {
	var priResizedNav = [];

	for(var x = 0; x < numMenuItems; x++) {
		priResizedNav.push(Math.floor((priNavItems[x] / priNavItems.sum()) * wrapperWidth));
	}

	$('> li', nav).each(function(i) {
		$(this).width(priResizedNav[i]).css('wordWrap', 'break-word');
	});

	$('> li:first', nav).width(priResizedNav[0] + (wrapperWidth - priResizedNav.sum()));
}
  • The script enumerates through the list of items in the navigation and generates a new width value that is stored within an array called priResizedNav. The value is determined like so:
    • Take the initial width and divide this by the total width (priNavItems.sum) of the navigation, giving us a percentage value. For example, if item 1 is 200 pixels wide and the total width of all the navigational items is 1000 pixels than I’ll get a value of 0.2, or in other words, the width of item 1 is 20% the width of the entire navigation.
    • With this calculated value (i.e. 0.2), I then apply it to the maximum width of the navigation as a whole, which in our demo is 969 pixels. In other words, the width of item 1 is 20% of 969 pixels which is equal to 193.8 pixels.
    • Lastly, since this value in most cases, as in our example, is a decimal, I just take the integer value and round down (Math.floor) to ensure the resized values of the navigation don’t exceed the allowed total width.

A working example of the navigation created thus far can be viewed here.

You’ll recall earlier in this post that I mentioned that I had created this solution for a menu that had dropdown navigation. You may have wondered why I simply didn’t just apply a style of overflow: hidden to the <li> element in order to get around the expanding box-model problem that IE6 has. The reason I don’t is if I do that, the dropdown navigation gets ‘chopped off’ and is no longer visible since the overflow has been set to be hidden.

The last thing that this script needs to accomplish is to resize the height of each item in the navigation so that they are all vertically centered.

Adjusting the vertical height of the navigation

When the navigation spills outside the defined width of the wrapper, there are going to be item(s) that are on multiple lines and item(s) that fit on a single line. To have these items align vertically, the script below does the following:

$('&gt; li', nav).each(function (i) {
nav_item_height = ($(this).height() &gt; nav_item_height) ? $(this).height() : nav_item_height;
});

$('&gt; li', nav).each(function (i) {
if($(this).height() &lt; nav_item_height) {
// Default padding: 6/6 (top/bottom).
var paddingTop = parseInt((nav_item_height - $(this).height()) / 2);
var paddingBottom = parseInt((nav_item_height - $(this).height()) - paddingTop);

$('a', this).css({
paddingTop: paddingTop + 6 + 'px',
paddingBottom: paddingBottom + 6 + 'px'
});
}
});
  • Enumerate through each item in the navigation and determine which one has the greatest height value (in pixels).
  • Re-enumerate through the navigation and apply this height value (obtained from the step above) to each item if it is less than the calculated height.
  • Finally, apply padding-top and padding-bottom on the hyperlink within the item, using the same top and bottom values that are defined by the CSS.

View the final working example of the navigation, based on all the steps we’ve covered in this post.

Final notes and thoughts

So there you have it – a horizontally-fluid navigation that will dynamically resize to fit the items container therein. I look forward to your comments and suggestions on how I can improve this script, because I know there is a lot of room for improvement!

Demo: Using CSS3 to create a rounded corner box

by Mike Badgley on May 17, 2010

As the CSS3 specification continues to take the web by storm, more and more browsers are

With CSS3, much of the slicing work that used to be the norm back in the ‘old’ CSS2/2.1 days is become much less of a necessity. What used to take multiple (background) images to create a rounded corner, drop shadow or gradient effect can now be easily and quickly done with CSS3.

This was a pain on many levels – front-end developer notwithstanding! For example, to create a layout for the feature box mock-up shown below, you would be look (at the bare minimum) four background images:

  • Header gradient
  • RSS icon
  • Rounded-corner box
  • Rounded-corner button

Example of a rounded-corner "feature" box

If you wanted a fluid-width box, there would be extra markup required (<div> element for each corner of the box) and this would also mean extra background images to compensate for this (unless you combined the images into a sprite). Or, if you went with a static-width implementation, you would be forced to create additional images if you wanted to use a smaller or larger version of the box.

Talk about a time consuming and potentially frustrating experience, but that’s the way things were and we got used to it.

However, with CSS3 support coming soon to a browser near you, the way design elements, like the one in the example above, are handled can be done in a completely different – dare I say ‘smart’ way! Using the aforementioned example, I created a nearly identical version of it using only CSS and only one background image (RSS icon). This has been styled to be a fixed width, but changing that requires nothing more than a tweak to the width property of the parent wrapping element.

A screenshot of what the resulting box looks like:

CTC_FeatureBoxCSS3

As you can see, its not as nice looking as the Photoshop version (from the first screenshot), but pretty close.

A demo of the HTML/CSS that was used to generate the box above can be viewed here.

In terms of full support across all the top browser manufacturers, we are not there yet, but we are getting closer and I expect that in a couple of years this will be the standard (CSS3) that all the current browsers will meet.

Site Launch: Council of Turkish Canadians

by Mike Badgley on May 3, 2010

Today marked the launch of a redesign of the Web site for the Council of Turkish Canadians.

Screenshot of the Home page of Council of Turkish Canadians

The site was built using WordPress which gives the content manager the flexibility to be able to easily manage the Web site’s content from within the administrator area of WordPress (Dashboard).

The site was built using Pages with the News and Other News items being Posts. To differentiate the two types of news items, I assigned categories to each and then used that for filtering the results when displaying news items on the Home and News pages.

The rest of the functionality of the site was accomplished via third-party plugins – specifically the items which appear in the sidebar, including:

  • Events
  • Join Our Mailing List
  • Buy Books at Amazon.ca

Events

For the Events – which appears in the sidebar of the Home page and also in the Events section of the site – a plugin called “Events Manager” by Davide Benini was used.

I was very pleased with the customization and functionality that the plugin offers – definitely one of the better ones I’ve seen. There are a good number of short/template tags made available by the plugin, so showing a list of events (past, future or all) on any given page/post is a simple process.

Managing events, from within the WordPress Dashboard, is also a simple and straightforward process. What would normally be a time consuming and arduous task is made simple with Events Manager.

Join our Mailing List

The mailing list is a very simple one-field form that captures a user’s email address. Instead of developing a custom form ourselves to handle this, we instead went the plugin route – mainly due to the fact that this site will be housing at least one more form that will contain a number of fields. Instead of having to create an additional custom form, it made better sense to get a plugin that would allow the user to easily manage/build this – rather than having to create something from scratch.

We used a pluign called Gravity Forms. Gravity Forms allows you to create functional forms easily using a drag-and-drop interface. It doesn’t offer you a whole lot of customization options, but rather keeps things minimal in order to make the process as easy as possible for the end user.

My only gripes with this plugin was that it didn’t offer any methods of allowing custom HTML to be inserted within the form. Also, customizing the validation messages – and the location where they are displayed – was also a no go.

Buy Books at Amazon.ca

Lastly, the plugin we used for the Amazon widget (bottom of the right-hand sidebar) was Amazon Showcase. Like the other two (plugins), implementing this was painless and easy. Adding a book to the showcase is as simple as entering in the book’s ISBN number and then selecting a thumbnail size to use for displaying the cover of the book.

Site Launch: World Hepatitis Alliance

by Mike Badgley on May 3, 2010

This past week we launched a Web site redesign for the World Hepatitis Alliance.

Screenshot of the Home page of the redesigned World Hepatitis Alliance Web site

The World Hepatitis Alliance is a non-governmental organisation that represents approximately 280 hepatitis B and C patient groups from around the world. As a coalition of advocacy groups, the World Hepatitis Alliance is a global voice for the 500 million people worldwide living with chronic viral hepatitis B or C.

This was a large undertaking due to the amount of content and functionality that was required, including:

The architecture that this Web site was built on was Sitefinity, and again it was a case where were extremely happy with the choice in using this production for our site build. The majority of our custom development (based on the required functionality in the list above) were either based or used existing controls that come packaged with the CMS.

During the development we built a custom tool for handling the social media aspect of the site. It works like Yahoo! Pipes but adds an extra level of manageability to allow a content manager the ability to approve each content piece that comes through the ‘pipe’ before its made available to the Web site.

Props go to Kris Blazejewicz, a Senior Developer here at High Road Communictions who was the lead in developing this application.

Inline-Block whitespace workaround

by Mike Badgley on April 28, 2010

In response to an article written yesterday (April 26th, 2010) about the inline-block display property on Impressive Webs, there was some debate on unwanted whitespace that appears to the right of elements that are styled with this property.

If you use inline-block there are some important factors and drawbacks to keep in mind. An inline-block element is white-space dependent, so if you display list items using inline-block (in a navigation bar, for example), the list items will have unwanted space next to each other.

This is an issue that seems to occur in most modern browsers – including IE8 and most versions of Firefox, to name just a couple. The workaround for this problem is actually pretty simple and does not require any crazy HTML markup hacks, as was shown on the site I mentioned above.

First of all, it is important to note that the amount of whitespace that appears to the right of an element that has a display property of inline-block is 4 pixels. Sounds easy to fix, right? Just apply a negative margin-left property of -4px and we’re done, right? Well, not so easy – first, not every browser* applies this 4 pixel gap – and – if the element is marked up as shown below, there will be no whitespace added period – regardless of what browser you are using:

<ul>
	<li>Item One</li><li>Item Two</li><li>Item Three</li><li>Item Four</li><li>Item Five</li>
</ul>

*NOTE: IE6 and IE7 do not understand the inline-block property and as such require additional properties of display: inline and zoom: 1 (zoom is used to force hasLayout) in order for them to mimic inline-block. As such, the 4 pixel gap does not get applied to the elements.

The solution then is to apply the rarely-used letter-spacing property and give it a negative value that is equal to the width of the whitespace gap, which in this case if 4 pixels. The property must be applied to the parent element, using our example above, it would be applied to the <ul> tag.

ul {
	letter-spacing: -4px;
}

Finally, you need to ‘undo’ the negative spacing on each of the inline-block elements so that the text contained within does not get affected by the property we set on it’s parent.

ul {
	letter-spacing: -4px;
}
	ul li {
		letter-spacing: normal;
		display: inline-block;
		*display: inline; /* For IE6/IE7 */
		zoom: 1; /* For IE6/IE7 */
	}

I’ve tested this across IE6-IE8, Chrome, Firefox (v3+) and Safari (v4) and it seemed to be working properly. However, when I tested in Opera (v10), the whitespace gap was still there. The fix for this was to apply a word-spacing property to it, using the same value as before:

ul {
	letter-spacing: -4px;
	word-spacing: -4px;
}
	ul li {
		letter-spacing: normal;
		word-spacing: normal;
		display: inline-block;
		*display: inline; /* For IE6/IE7 */
		zoom: 1; /* For IE6/IE7 */
	}