Small Initiatives

People, Process, Technology ... sensible!

Tapping patience to tab a Web interface

By Jay Small | Wed, 05/12/2004 - 11:07pm

This is Issue 49 of The Sensible Internet Design Letter. In this letter, I explain in some detail how I got an unusual HTML folder-tab interface to work. If you're not used to working in HTML, Cascading Style Sheets or JavaScript, you might want to skip ahead to the example page to decide if you have any use for the resulting interface. If so, read on, grab the HTML source, and feel free to play around with it. If not, thanks for riding along this far.

Though user interface designers keep falling back on "folders" to represent document containers visually, I don't particularly like the metaphor.

Tab interface sample
Remember, it's not how it looks, it's how it works: The tab interface isn't much to look at here, but it could be styled using CSS any number of ways.

Most of the time, see, real-life folders contain undesirable things:

  • At school, an oversize folder holds your "permanent record" until the principal ominously blows the dust off it to review your entire life and determine the severity of punishment for whatever you did wrong.
  • At work, folders contain things you either need to look at, need to do something about or just wish you could ignore. They often become the cages used to pass monkeys from one back to another. The bigger the monkey, the brighter the folder, I've found.
  • At home, you have to keep tax returns and receipts for future tax returns in folders. And no good can come of that.

So when a recent project at my real job required design of a folder-tab style Web search interface, first I sneered. Then I got to work.

Maybe what I learned, or the sample I generated, will help you with a project. Better still, maybe you can help me improve and solidify this work. Either way, here's how it happened:

Folder tabs? Can o'corn!

I had seen great examples of folder-tab interfaces that were Web standards friendly, accessible and quite attractive. Douglas Bowman built a classic example, with three-dimensional appearance, rounded corners and all, for A List Apart. Kalsey Consulting showed how to do tabs with submenus, using Cascading Style Sheets, and maintaining a proper order for nested menu lists. More tutorials? One, two, three.

Should be a snap, right? Unfortunately, the project carried some requirements that threw off most of the prefabricated samples I could find:

  • We wanted all the search forms represented by tabs to be loaded into one HTML document, then switched on and off in the display as each tab was clicked. Most of the tab examples are designed to call a new page from the server when you select a new tab, then highlight that tab on the resulting page.
  • The tab interface would not always appear in the same place, vertically or horizontally, on a page. So we could not use fixed (absolute) positioning of the tabs or the search forms. Every object had to be positioned relative to other objects in the interface and on the page.
  • Surrounding content had to flow smoothly around the different search forms, even if they were different sizes.

Semantics, shlemantics

Most CSS-reliant tab interface examples I found belie at least an attempt to keep the HTML markup and CSS classes semantically sound. Because each tab is one link in a list, for example, conscientious coders formatted the links as an HTML unordered list (<ul>).

Call a list a list. That's marvelous. I endorse the theory. I just couldn't make it work completely in this application.

Examples built from an unordered list all seem to rely on CSS floats to get the links in the list to appear side-by-side, as folder tabs would.

But I needed to repeat the list of links, housed in separate page divisions for each tab/search form combination, so I could highlight the selected tab and remove highlighting from the previously selected tab each time a new one is clicked. Maybe this process could be done in JavaScript, but from my limited scripting knowledge, the only ways I could concoct turned out to be more lines of code than simply repeating the menu lists themselves.

If the tab links are floated beside each other, in at least one browser family (Mozilla), the floats never stop. One whole tab list floats right next to the preceding set, though the elements are contained in separate page divisions. I could clear the float, and it shifts everything back to the left, but that forces the next set of tab links down below the preceding set.

Remember I can't use absolute positioning because content above and below the tab interface will vary in size and shape. So I can't force each list of tab links into the right place.

So I resorted to putting the tab links in a table. Sue me, semantics gurus. Better still, please show me how I could have done it better.

At least I ensured that my HTML and CSS were valid and separated presentation from structure as much as I could. And I used "well-mannered" JavaScript techniques at every turn.

Ultimately, I did it because the table solved the weird Mozilla float problem. Here's the structure I wound up using:


<table class="tabnav">
<tr>
<td><a class="tabselected" href="/" onclick="controlsubmenu('tab1',null);return false;">Tab 1</a></td>
<td><a href="/" onclick="controlsubmenu('tab2',null);return false;">Tab 2</a></td>
<td><a href="/" onclick="controlsubmenu('tab3',null);return false;">Tab 3</a></td>
</tr>
</table>

The table carries class="tabnav" so I can apply visual effects in CSS, including hover effects, to the anchor (<a>) tags within it. The JavaScript function called in the onclick="..." event handlers simply switches on or off the content blocks linked to each tab.

I could put whatever URLs I wanted in the href="..." attributes. Those URLs are just backups for non-JavaScript-capable browsers. So perhaps I'd set each link to a separate page containing a copy of the appropriate search form. In a Javascript-capable browser, meaning for most of us, the link URL just sits there politely, out of the way.

Which tab is shown by default?

That same block-switching JavaScript function comes in handy to set which of the tabs and related search forms will show up by default. I made the document body tag look like this:


<body onload="controlsubmenu('tab1',null);return false;">

As you can see, the onload="..." event handler selects Tab 1 as the default when the page first loads.

Multiple forms, one document

I learned another trick mostly by struggling: how to convert an oft-used JavaScript for cascading menus into a switch to change the CSS display status of an object from none to block and back.

The original script, from the book Javascript + CSS + DOM Magic by Makiko Itoh, drove image rollovers commonly used for pull-down or cascading navigation menus.

I'm a JavaScript baby at best, but I'm enamored with two things about the script: the way it standardizes a miniature object model to address browser differences, and the way it easily switches the visibility of objects on a page.

Switching the CSS visibility state from hidden to visible, the way the script handles cascading menus, doesn't completely work for the tab interface. Hidden objects still take up space on the page and push aside other objects, even if I use CSS relative positioning to slide them back up underneath visible elements.

Instead, I converted the script to switch the CSS display state. An object with a display state of none does not show up and does not push aside other objects. Switching that object to display state block makes it visible and pushes aside objects in its way.

JavaScripters among you can study the logic and, I'm sure, could suggest improvements (please feel free!). Since the script originally ran cascading menus, I know it still calls some variables and functions no longer required for this application.

Streamline the script and find a way to get around having to repeat the menu links in separate divs, and I'll tip my hat to you.

How did it turn out?

All other work boiled down to tweaking spacing in CSS, building the search forms themselves and browser checking. The interface runs in Internet Explorer 6 and Mozilla 1.6 on Windows, Mozilla Firefox 0.8 and Safari on Mac. It also works in IE 5 on Mac, though that browser does not play nice with the border-collapse CSS property for table cells, so the tabs don't align perfectly with the table.

Does this tab interface look or sound like something you could use? Do you have ideas to improve on my work? Either way, please check out the sample page, view source (the HTML, CSS and JavaScript are all in the same document, marked in comments) -- then let me know what you think.

I know enough to know what I don't know. That means I'm sure my methods could bear improvements, and I'd love to hear your suggestions. Post 'em right here!

For example: I've taken the code Ethan put into comments below and reassembled it into a second sample page, so you can try it alongside my original sample. It works for me so far, and it's definitely leaner and meaner than my effort. Thanks, Ethan!

Switching from block to none

kim brooks wei (not verified) | Mon, 11/29/1999 - 7:00pm

Switching from block to none in your javascript to adjust your page's display seems like a brilliant hack to me, Jay. I like it a lot.

Kimi

I have to agree with kim

Pat TheCunt (not verified) | Mon, 11/29/1999 - 7:00pm

I have to agree with kim

There's no need to repeat

Ethan (not verified) | Mon, 11/29/1999 - 7:00pm

There's no need to repeat the tabs three times. You can just change the styles on the tabs to reflect which one is active.

This way, you can use those floated tabs (I haven't implemented them, out of laziness.

I cleaned out the unneeded JavaScript, as well.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1"><title>Tab Interface Sample :: Small Initiatives</title><!-- Styles to drive tab interface - move to a linked file if you prefer -->

<style type="text/css">
<!--
body { margin: 0; padding: 0; }
#tabmargin { margin: 20px 40px 20px 40px; }
table, td { margin: 0; padding: 0; border: 0; border-collapse: collapse; }
.tabnav a, .tabnav a:link, .tabnav a:visited { display: block; height: 16px; font-family: verdana, arial, sans-serif; font-size: 12px; line-height: 11px; background-color: #666; color: #eee; margin-right: 4px; padding: 2px 3px 0 3px; text-decoration: none; }
.tabnav a:hover, .tabnav a:focus { background: #999; color: #ff9; }
.tabnav a:active { background-color: #fff; color: #00f; }
.tabnav a.tabselected, .tabnav a.tabselected:link, .tabnav a.tabselected:visited { background-color: #ffc; color: #000; cursor: text; margin-right: 4px; }
.contentblock { padding: 10px; background-color: #ffc; color: #000; font-family: verdana, arial, sans-serif; }
.contentblock p { font-size: 11px; }
#tab1, #tab2, #tab3 { display: none; }
-->
</style><!-- Javascript permits switching between tab contents - move to linked file if you prefer -->

<script type="text/javascript">
<!--
// START tab script
// Adapted from "Javascript + CSS + DOM Magic" by Makiko Itoh

// define variables for "if n4 (Netscape 4), if IE (IE 4.x),
// and if n6 (if Netscape 6/W3C-DOM compliant)"

var n4, ie, n6;

<body

Ethan (not verified) | Mon, 11/29/1999 - 7:00pm

<body onload="controlsubmenu('tab1','tabA');return false;">
<!-- div below for prototype display only; it sets margin around sample page contents -->
<div id="tabmargin">

<h1>Tab interface you can put anywhere</h1>
<p>The tab interface example is the yellow folder-tab block below. This
text is here merely to show the tab interface can show up anywhere on
the page, at almost any width, and even if its tab contents vary in
length it lets objects around it snap into place.</p>

<!-- TAB INTERFACE SAMPLE HTML BEGINS HERE -->

<!-- HTML block for Tab 1 -->
<div>
<table class="tabnav">
<tbody><tr>
<td><a class="tabselected" id="tabA" href="/" onclick="controlsubmenu('tab1','tabA');return false;">Tab 1</a></td>
<td><a class="n" id="tabB" href="/" onclick="controlsubmenu('tab2','tabB');return false;">Tab 2</a></td>
<td><a class="n" id="tabC" href="/" onclick="controlsubmenu('tab3','tabC');return false;">Tab 3</a></td>
</tr>
</tbody></table>
<div style="display: block;" class="contentblock" id="tab1">
<h3>This is content of Tab 1</h3>
<p>The first tab content is very short.</p>
</div>
<div class="contentblock" id="tab2">
<h3>Tab 2 shows up when you click</h3>
<p>The second tab content is substantially more verbose. It's important
to show that the size of the box displayed can vary but content and
objects below it will snap into place. Now is the time to show lots of
content in this box for a tab interface example.</p>
<p>The second tab content is substantially more verbose. It's important
to show that the size of the box displayed can vary but content and
objects below it will snap into place. Now is the time to show lots of
content in this box for a tab interface example.</p>
</div>
<div class="contentblock" id="tab3">
<h3>Last but not least, here's Tab 3</h3>
<p>Shorter content again to demonstrate flexibility.</p>
</div>
</div>

<!-- TAB INTERFACE SAMPLE HTML ENDS HERE -->

<p>The tab interface example is the yellow folder-tab block above. This
text is here merely to show the tab interface can show up anywhere on
the page, at almost any width, and even if its tab contents vary in
length it lets objects around it snap into place.</p>

<p>Back to the newsletter that explains this sample | SI Home</p>

// detect browser support

Ethan (not verified) | Mon, 11/29/1999 - 7:00pm

// detect browser support for certain key objects/methods
// and assemble a custom document object

var doc,doc2,doc3,sty;

if (document.layers) {
doc = "document.";
doc2 = ".document.";
doc3 = "";
sty = "";
n4 = true;
} else if (document.all) {
doc = "document.all.";
doc2 = "";
doc3 = "";
sty = ".style";
ie = true;
} else if (document.getElementById) {
doc = "document.getElementById('";
doc2 ="')";
doc3 ="')";
sty = "').style";
n6 = true;
}

// display block or none DIV element

function blocknone(divname,tabidname,state,tabcolor,tabbg,cursortype) {
if (n4) {
divObj = eval (doc + divname);
tabObj = eval (doc + tabidname);
}
else {
divObj = eval (doc + divname + sty);
tabObj = eval (doc + tabidname + sty);
}
divObj.display = state;
tabObj.color = tabcolor;
tabObj.backgroundColor = tabbg;
tabObj.cursor = cursortype;
}
// variables that hold the value of the currently active (open) menu

var active_tabcontent = null;
var active_tab1 = null;

// function closes all active menus and turns back to 'off' state

function closeallmenus() {
if (active_tabcontent != null) {
blocknone(active_tabcontent,active_tab1,'none','#eee','#666','pointer');
}
}

// function controls tab content visibility

function controlsubmenu(tabcontent,tabid) {
closeallmenus();
blocknone(tabcontent,tabid,'block','#000','#ffc','text');
active_tabcontent = tabcontent;
active_tab1 = tabid
}

//END guide tab script
-->
</script></head>

<!-- IGNORE THIS PART - it's

Ethan (not verified) | Mon, 11/29/1999 - 7:00pm

<!-- IGNORE THIS PART - it's just a Web page hit counter -->
<!--WEBBOT bot="HTMLMarkup" startspan ALT="Site Meter" -->
<script type="text/javascript">var site="sm6jaysmall"</script>
<script type="text/javascript" src="Tab%20Interface%20Sample%20%20%20Small%20Initiatives_files/counter.js"></script>[img]"Tab%20Interface%20Sample%20%20%20Small%20Initiatives_files/meter.gif" title="Site Meter" border="0"[/img]
<noscript>
<img
src="http://sm6.sitemeter.com/meter.asp?site=sm6jaysmall" alt="Site
Meter" border="0" />

</noscript>
<!-- Copyright (c)2002 Site Meter -->
<!--WEBBOT bot="HTMLMarkup" Endspan -->
</div>
</body></html>

Right at the end of the

Ethan (not verified) | Mon, 11/29/1999 - 7:00pm

Right at the end of the third part, the comment parser decided to turn the link code into real links... Don't know why it didn't do that anywhere else.

Easy enough to change back.

Hey, Ethan, thanks for the

Jay Small (not verified) | Mon, 11/29/1999 - 7:00pm

Hey, Ethan, thanks for the tweaks! I'll have to try this out.

I'm amazed you got as much of the code through the comment parser intact as you did. Wonders never cease!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <h3> <h4> <h5> <h6> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
  • Lines and paragraphs break automatically.
  • You can use BBCode tags in the text. URLs will automatically be converted to links.
  • Potentially problem-causing HTML tags are filtered.
CAPTCHA
Please complete this to show you're a real person and not a spam program.
5 + 13 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.