Previous Section  < Day Day Up >  Next Section

Laying Out the Menus

Before we get to the point of having the submenus appear and disappear, we're going to style them to look decent. In other words, we'll get the menus and submenus all styled and then take the steps necessary to make them appear and disappear in response to the user's actions.

Planning Ahead

Our first step will be to call a behavior file using the Windows Internet Explorer杘nly property behavior. This lets us bring in a separate file that will add capabilities to IE/Win that it doesn't ordinarily have.


body {background: #EEE; color: #000;

  behavior: url(csshover.htc);} /* WinIE behavior call */

For this to work, you'll need to get the .htc file from the book's Web site or get a copy from its original author, Peter Nederlof. His demonstration of this technique, which uses the same .htc file that we'll be using here, can be found at http://www.xs4all.nl/~peterned/csshover.html.

Before Peter's publication of csshover.htc, IE/Win could only support CSS-driven menu systems if the author added some JavaScript that added capabilities to IE/Win. In a sense, Peter did the same thing, except he put the additional capabilities into a Windows behavior file instead of a JavaScript file. This has one major advantage: Only IE/Win will try to download the behavior, which means that other browsers (which don't need it anyway) won't waste bandwidth getting the file. With the JavaScript solution, all browsers had to download the script.

There is one disadvantage to using the behavior approach: The behavior declaration will prevent the style sheet from validating. If this is a major concern for you, you could move the behavior declaration into a separate style sheet and then @import it. This would allow the main style sheet to validate and would quarantine the nonvalid behavior declaration in a separate file. What you do will probably depend on how you regard validation.

At the moment, the behavior file isn't changing anything about our page, mostly because we haven't gotten to the point where it would be useful. First we need to get our menus styled. The first step is to strip away all of the indentation and set the list widths to equal the width of the div. The result is shown in Figure 6.2.

Figure 6.2. Having removed the indentation from the lists, they fit much better into the sidebar.

graphics/06fig02.jpg

Margins and Padding

graphics/note_icon.jpg

List indentation is handled with margins in some browsers and with padding in others. By zeroing out both, we make sure to cover all our bases.



div#nav {float: left; width: 7em;

  background: #FDD;}

div#nav ul {margin: 0; padding: 0; width: 7em;}

</style>

For the moment, it's become much harder to tell where the submenus start and end. We'll fix that in a big way in the next phase of the project.

Positioning the Submenus

The next step is to place the submenus where we'll want them to appear when the system is fully working. A very common technique is to have a submenu appear to the right of the item that triggered its appearance, lined up along the top. We can very easily do this by positioning the lists with respect to the list items that contain them.

To make this happen, we first need to make sure that every list item will act as a starting point for the position of its descendant submenu by establishing a containing block. That's as easy as relatively positioning the list items with no offsets.


div#nav ul {margin: 0; padding: 0; width: 7em;}

div#nav li {position: relative;}

</style>

By doing this, every list item sets up a containing block (think of it as a positioning context) for any descendant elements that are absolutely positioned. So, if we absolutely positioned the links inside the list items with right: 0;, each link would be positioned against the right edge of the list item that contained it.

Instead of positioning links, of course, we're going to position unordered lists. For any list that's inside another list, we want to place it so that the top of the submenu is aligned with the top of the list item that contains it.


div#nav li {position: relative; list-style: none; margin: 0;}

div#nav ul ul {position: absolute; top: 0;}

</style>

This will place the top edge of each submenu along the top of its containing block. Now all we need to do is put the submenu just to the right of its containing list item. We'll pull a fast one to make this happen. Thanks to our div#nav ul rule, we know that the lists are all 7em wide. This means that the right edges of the list items are 7em to the right of their left edges. If we offset the positioned submenus by the same amount, they'll be placed exactly where we want them, as shown in Figure 6.3.


div#nav ul ul {position: absolute; top: 0; left: 7em;}

Figure 6.3. The submenu lists are positioned with respect to their containing list items.

graphics/06fig03.jpg

There are four things to note in Figure 6.3:

  • The navigation div remains just as tall as its content. Now that we've absolutely positioned the submenus, though, they're no longer really a part of its content梟ot from a layout perspective, at any rate.

  • The last two items in the first submenu overlap the first two items in the second. This makes them hard to read at the moment, but overlaps are actually no big deal because, by the time we're done, only one of those submenus will appear at any one time.

  • The first level of submenus is exactly lined up along the right edge of the main-level links (Home, Services, and so on). The bullets, being in some sense attached to the list items, are actually hanging out of the unordered lists. We can't see them on the main links because the bullets are actually to the left of the browser window's left edge.

  • There are still list bullets, at least in most browsers. We need to get rid of them pronto.

Before we move on then:


div#nav li {position: relative; list-style: none; margin: 0;}

Thus, the bullets are removed, as we'll see in the next figure.

Prettier Menu Styles

Now that the submenus are placed where we want them, it's time to start fleshing out their appearance. Let's give them a white background and, for the moment, a one-pixel solid border whose color is taken from the color value of the ul elements.


div#nav ul {margin: 0; padding: 0; width: 7em;

  background: white; border: 1px solid;}

That will "box in" each unordered list, from the main level to the deepest submenu. Because some browsers (like Opera) apply margins to list items by default, we'll zero out their margins as well.


div#nav li {position: relative; list-style: none; margin: 0;}

Class Naming

graphics/note_icon.jpg

Just about any name could be used instead of submenu. We might have used sm, subm, or even reginald. The value submenu was picked because it's unambiguous. When you're doing your own designs, feel free to use a name that appeals to you more.


Now, about those submenus. To make some later styling easier, we'll need to add some structural hooks to the markup. For each of the three list items that contains a submenu, we're adding a class attribute with a value of submenu, as shown here:


<li class="submenu"><a href="/services/">Services</a>



<li class="submenu"><a href="/pubs/">Publications</a>



   <li class="submenu"><a href="/pubs/tuts/">Tutorials</a>

In effect, we're marking the submenu-containing list items with the label submenu. To make sure that we've classed the proper items, we'll add a highlighting rule, with the result shown in Figure 6.4.


div#nav li {position: relative; list-style: none; margin: 0;}

div#nav li.submenu {background: yellow;}

div#nav ul ul {position: absolute; top: 0; left: 7em;}

Figure 6.4. The submenus' appearance is improved, and list items that contain submenus are highlighted.

graphics/06fig04.jpg

Now that the menus all have backgrounds, the overlap between the two first-level submenus is a little easier to understand. They still overlap, but the background of the second submenu obscures the first one entirely. As for the sub-submenu, we can now see that its left edge lines up quite nicely with the right edge of its parent list item.

Blocking Out Links

If you're following along with the project in your own file, you may have noticed something in the recent changes. The clickable area of the links in the menus aren't filling up the whole of each list item. This is because the links are inline elements. If we were to set a border on the links, for example, the border would wrap around just the text.

For the menus to work correctly, and to act like users expect, we really need the links to fill up all of the list items. To do that, we'll change the kind of boxes the links create when they're laid out.

Doubled Heights

graphics/warning_icon.jpg

In IE/Win, the lists get spread apart by the display: block; we just added. This is due to a bug with IE/Win's handling of whitespace and block-box links in lists. But don't worry: We're going to overcome the bug by the time we reach the next figure.



div#nav li.submenu {background: yellow;}

div#nav li a {display: block;}

div#nav ul ul {position: absolute; top: 0; left: 7em;}

Now each link will generate a block-level box, much like a div or paragraph would. Note that we have not changed the nature of the links themselves. The a elements are still inline elements. All that's happened is that the CSS is getting them to generate block boxes. This is a subtle distinction but a good one to keep in mind.

Now we not only can click anywhere within a menu entry and have it work (except in IE/Win, but we'll fix that in a moment), but we can style the links the same as we might a block-level element. To push the link text over to the right a bit, for example, we'd set a left padding for the links. We can also push the links apart a bit with some top and bottom padding.


div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;}

We should also turn off the link underlining since users don't expect menu links to be underlined.


div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;

  text-decoration: none;}

Now for IE/Win's problems. For whatever reason, block-box links like ours don't fill out the whole list item in IE/Win. This is a bug, but fortunately it's one we can work around. If we give the links an explicit width, we'll get the behavior we want. However, in other browsers, we just want them to set an automatic width, like they were doing before.

To pull this off, we'll first set an explicit width for the links, and then we'll use a rule IE/Win doesn't understand to unset that width.


div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;

  text-decoration: none; width: 6.5em;}

div#nav>ul a {width: auto;}

div#nav ul ul {position: absolute; top: 0; left: 7em;}

What's That Again?

graphics/note_icon.jpg

The selector div#nav>ul a means "any a element that is descended from a ul element that is itself the child of a div with an id of nav." That will select all the links in our menu system. IE/Win doesn't understand the "child of a div" part of the selector, so it skips the whole rule.


With a content width of 6.5em and a left padding of 0.5em, the link's element box will be 7em wide, just like its parent element. Now we can just add a light gray border to the bottom of each list item, visually separating them.


div#nav li {position: relative; list-style: none; margin: 0;

  border-bottom: 1px solid #CCC;}

We could have added the border to the bottom of the links, too. Given the way we've styled the menus, that would have worked as well as styling the list items does. The cumulative effect of these changes is shown in Figure 6.5.

Figure 6.5. Padding the links spreads out the list items a bit, and the links fill out their list items.

graphics/06fig05.jpg

Box Model Blues

Declaring a width of 6.5em for the links will work fine in IE6, which gets the CSS box model correct. In older versions of IE/Win, however, it won't because they got the model wrong. It's possible to construct a set of rules that takes all of these into account. This would involve using the "box model hack" as well as the child-selector hack we just saw. For example:


div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;

  text-decoration: none; width: 100%;

  voice-family: "\"}\""; voice-family:inherit;

  width: 6.5em;}

div#nav>ul a {width: auto;}

div#nav ul ul {position: absolute; top: 0; left: 7em;}

The first change sets the link width to 100%, which is what older versions of IE/Win require. That's the last width value those older versions will see because the next line (with the voice-family declarations) will make them skip the rest of the rule. IE6 will keep going, though, and see the width: 6.5em; declaration. The same is true of Opera, Mozilla, Safari, and other modern browsers. Then the child-selector line, which sets the width back to auto, is ignored by IE6 and older IE/Win versions but is handled by modern browsers.

Whether or not it's worth going to all this effort depends heavily on your audience demographic. If you have a lot of users on older machines, you'll probably want to go to this extent. For the rest of the project, we're going to assume that our audience members are using the latest version of whatever browsers they use, and we'll leave out the more complicated series of hacks just shown.


Hovering Entries

It's time to add a hover style to the menu entries so that when the user mouses over "Home," its background changes color. We can do this with the following rule:


div#nav li {position: relative; list-style: none; margin: 0;

  border-bottom: 1px solid #CCC;}

div#nav li:hover {background: #EBB;}

div#nav li.submenu {background: yellow;}

No, that isn't a mistake, so hold off on your errata reports. We are very intentionally applying a hover effect to the list items themselves and not the links. This is perfectly acceptable in CSS, which doesn't restrict hover effects to hyperlinks: Any element at all can be given hover styles. So, in this case, the background of any li element that's being hovered and that is descended from a div with an id of nav will change to #EBB.

Why don't more authors do this kind of thing? Because IE/Win actually does restrict hover effects to hyperlinks and doesn't apply them to other kinds of elements. At least, that's how it acts by default. Remember the csshover.htc file we associated with the body element back at the outset? The sole purpose of that file is to add arbitrary-element hover capabilities to IE/Win. In other words, thanks to that behavior file, IE/Win will now allow hover styles on any element, the same as what we see in Figure 6.6.

Figure 6.6. Adding hover effects to list items.

graphics/06fig06.jpg

Again, the background is changing on the list item, not the link inside the list item. Those links all have transparent backgrounds so that we can see the list item backgrounds changing behind them.

It's difficult to understate the importance of this capability. In addition to allowing for nested pop-up menus based on simple markup and CSS, it's possible to use this capability to change information on one part of a page by hovering over other parts梐nd a whole lot more. Being able to add arbitrary-element hovering to IE/Win seems more than worth the price of a small amount of nonvalid CSS.

Special Submenu Styles

Take a moment to catch your breath, and then we'll move on. The yellow highlighting of the submenu links served its purpose, but now it's starting to look distinctly icky. We still want a visual indication of which menu entries will open a new menu, so let's replace the yellow with a background image of a small arrow called submenu.gif.


div#nav li.submenu {background: url(submenu.gif) 95% 50% no-repeat;}

This will place a single instance of the image 95% of the way to the right of the submenu list items and vertically centered, as shown in Figure 6.7.

Figure 6.7. Placing an arrow to show which entries will spawn submenus.

graphics/06fig07.jpg

That's quite a bit nicer. However, it means now that the same exact hover effect will be applied to all list items: They'll all get the same background color whether or not a submenu is present. Let's lighten up the hover color for submenu entries. Again, instead of the links, we'll apply the hover styles directly to the list items.

Explorer Weirdness

graphics/note_icon.jpg

If you're using IE/Win, you may notice some oddities with the hover effects; for example, the arrow image disappearing. This is likely due to glitches in the behavior script, or in Explorer's handling of it. These are minor enough to ignore, but bear in mind the script's imperfections if you intend to use it for a public design.



div#nav li.submenu {background: url(submenu.gif) 95% 50% no-repeat;}

div#nav li.submenu:hover {background-color: #EDD;}

div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;

  text-decoration: none; width: 6.5em;}

We could style the links themselves, of course, but if we did that for the submenu entries, the link's background would obscure the arrow graphic in the background of the list item. If that's what we wanted, it would be a great solution, but it isn't. By changing the background color of the list item, the color and image can exist together, as shown in Figure 6.8.

Figure 6.8. Defining a different hover style for submenu entries.

graphics/06fig08.jpg

Preparing for Pop-Ups

At this point, we've really written all the styles we need for the presentation and placement of the menus, submenus, and individual entries. All that remains is to start the showing and hiding process.

Before we add more CSS, let's look at a few changes that need to be made to the HTML itself. We're adding classes to the various lists so that we can actually control the appearance and disappearance of submenus via the style sheet. These changes are highlighted in Listing 6.3.

Listing 6.3. Adding Level Information

<div id="nav">

<ul class="level1">

 <li><a href="/">Home</a></li>

 <li class="submenu"><a href="/services/">Services</a>

  <ul class="level2">

   <li><a href="/services/strategy/">Strategy</a></li>

   <li><a href="/services/optimize/">Optimization</a></li>

   <li><a href="/services/guidance/">Guidance</a></li>

   <li><a href="/services/training/">Training</a></li>

  </ul>

 </li>

 <li><a href="/events/">Events</a></li>

 <li class="submenu"><a href="/pubs/">Publications</a>

  <ul class="level2">

   <li><a href="/pubs/articles/">Articles</a></li>

   <li class="submenu"><a href="/pubs/tuts/">Tutorials</a>

    <ul class="level3">

     <li><a href="/pubs/tuts/html/">HTML</a></li>

     <li><a href="/pubs/tuts/css/">CSS</a>

     <li><a href="/pubs/tuts/svg/">SVG</a>

     <li><a href="/pubs/tuts/xml/">XML</a>

    </ul>

   </li>

   <li><a href="/pubs/wpapers/">White Papers</a></li>

   <li><a href="/pubs/comment/">Commentary</a></li>

  </ul>

 </li>

 <li><a href="/contact/">Contact</a></li>

</ul>

</div>

Here's why we need these changes: If we simply wrote in our CSS "Show any ul that's descended from a hovered submenu list item," then hovering over "Publications" would cause both the Publications submenu and the Tutorials submenu to appear at once. Instead, what we need to say is "Show any ul that's a child of a hovered submenu list item." The way to do that would be a child selector, which IE/Win doesn't understand. To work around this, we need to class the menus according to their nesting level.

Because our submenus are already visible, we need to hide them. Since we're hiding any ul that's descended from a ul, we can modify an already-existing rule, with the result shown in Figure 6.9.


div#nav ul ul {position: absolute; top: 0; left: 7em;

  display: none;}

Figure 6.9. Hiding the submenus.

graphics/06fig09.jpg

So, by default, all we can see is the top-level menu. That's good! It's exactly what we want.

Now to control the appearance of submenus. We want a second-level menu to appear when its parent list item is hovered, and we make it reappear by changing its display to block.


div#nav ul ul {position: absolute; top: 0; left: 7em;

  display: none;}

div#nav ul.level1 li.submenu:hover ul.level2 {display:block;}

</style>

So now any level2 list that's descended from a hovered list item that is itself descended from a level1 list (which is in turn descended from a div with an id of nav) will change its display from none to block. That's fine for second-level menus, but how about third level? We only need to modify the selector.


div#nav ul.level1 li.submenu:hover ul.level2,

div#nav ul.level2 li.submenu:hover ul.level3 {display:block;}

Now the rule will apply to third-level menus just as it does to second-level menus, with the result shown in Figure 6.10.

Figure 6.10. Showing the submenus in response to user actions.

graphics/06fig10.jpg

And that's really all it takes to create nested pop-up menus with CSS and a little bit of behavior scripting.

Display or Visibility?

Rather than use display: none and display: block to hide and show the menus, we might have used visibility instead. In that case, the relevant rules in the style sheet would have read:


div#nav ul ul {position: absolute; top: 0; left: 7em;

  visibility: hidden;}

div#nav ul.level1 li.submenu:hover ul.level2,

div#nav ul.level2 li.submenu:hover ul.level3 {visibility: visible;}

The difference is that an element with display: none generates no element box at all; it is as though the element did not exist. An element set to visibility: hidden still generates an element box of the same size it would have if it were visible, but the element box is completely transparent.

In normal-flow layout, this makes a difference since a transparent (hidden) element box will affect the layout of other elements. Because the menus are absolutely positioned, however, they don't affect other elements' layout anyway. Thus, visibility and display would work equally well in this situation. We're sticking with display because that's what Peter used in his original demo, and it works just fine.


Finishing Touches

With our menu system working, let's add a couple of extra touches to the presentation. As we can see from the preceding figure, the menus stand out kind of starkly with their black borders, and the main-level menu's top and right borders double up with the borders on the main content and page title.

We'll fix the first issue by pulling the menu up one pixel and to the left by the same amount, by way of negative margins.


div#nav {float: left; width: 7em; margin: -1px 0 0 -1px;

  background: #FDD;}

The list's borders can be toned down by making them match the border color already in use for the title. This is illustrated in Figure 6.11.

Figure 6.11. Merging the menu's appearance more closely with the rest of the layout.

graphics/06fig11.jpg

Negative Margin Clip

graphics/warning_icon.jpg

During testing, it was observed that in some copies of IE/Win, the negative top and left margins would pull the menu up and to the left, but also cut off the borders on those sides. If you don't see this, bear in mind that it does happen and should be considered before publicly deploying any layout.



div#nav ul {margin: 0; padding: 0; width: 7em; background: white;

  border: 1px solid #AAA;}

Now everything fits together very neatly, and it's all thanks to the style sheet given in Listing 6.4 (plus the behavior file, which is not listed).

Listing 6.4. The Menu Styles

body {background: #EEE; color: #000;

  behavior: url(csshover.htc);} /* WinIE behavior call */

h1 {color: #AAA; border-bottom: 1px solid; margin-bottom: 0;}

#main {color: #CCC; margin-left: 7em; padding: 1px 0 1px 5%;

  border-left: 1px solid;}

div#nav {float: left; width: 7em; margin: -1px 0 0 -1px;

  background: #FDD;}

div#nav ul {margin: 0; padding: 0; width: 7em; background: white;

  border: 1px solid #AAA;}

div#nav li {position: relative; list-style: none; margin: 0;

  border-bottom: 1px solid #CCC;}

div#nav li:hover {background: #EBB;}

div#nav li.submenu {background: url(submenu.gif) 95% 50% no-repeat;}

div#nav li.submenu:hover {background-color: #EDD;}

div#nav li a {display: block; padding: 0.25em 0 0.25em 0.5em;

  text-decoration: none; width: 6.5em;}

div#nav>ul a {width: auto;}

div#nav ul ul {position: absolute; top: 0; left: 7em;

  display: none;}

div#nav ul.level1 li.submenu:hover ul.level2,

div#nav ul.level2 li.submenu:hover ul.level3 {display:block;}

    Previous Section  < Day Day Up >  Next Section