One of the reasons that I started Simple Primate was to have a public space in which to respond to students of my lynda.com titles. I plan on posting answers to some of the most common questions that I receive as well as using this as a platform for diving a bit deeper into more specific issues raised by student feedback. In today’s post, I want to take a closer look at a question sent to me by Maria Osterhoudt regarding Spry Menus. Maria was having a problem with her horizontal Spry Menus in Firefox and was having a hard time tracking down the source of her problems.
Maria had noticed that when testing her Spry Menu Bars in Firefox that the sub-menus would not reset if the user hit the Back button to return to the page. Unlike other browsers which would show the default menu, Firefox would show the sub-menus still in their hover state. Maria checked my example files to see if she had done something wrong and noticed that my menus did the exact same thing. Now, truthfully, most clients might not care about this behavior, but Maria was wondering what the heck was going on and how she could fix it.
So, the first question is why is this happening? Well, since the behavior tends to happen in Firefox and not other browsers, that’s a good place to start. A quick look at Firefox’s caching policy tells us all we need to know:
Firefox 1.5 uses in-memory caching for entire Web pages, including their JavaScript states, for a single browser session. Going backward and forward between visited pages requires no page loading and the JavaScript states are preserved. This feature, referred to by some as bfcache (for “Back-Forward Cache”), makes page navigation very fast. This caching state is preserved until the user closes the browser.
Since that hasn’t changed since 1.5 we can safely say that Firefox’s caching preference is what is causing the issue. Also, it’s worth pointing out that Opera uses Back-Forward Caching as well, so the issue also appears in Opera.
Now that we know the problem, solving it should be pretty easy! However, as always, the most obvious solution isn’t necessarily the right one. We could simply prevent the page from caching, thereby forcing the menu to reload each time the page is navigated to. We could do this by using an unload event handler (which forces Firefox to reload the page) or by changing the cache-control property for the page. Although this would work, it would also slow site browsing down and could break pages relying on the cache for functionality, so that’s probably not the best solution.
A better solution would be to look at how the Spry Menubar javascript displays and hides the submenus in the first place, and then to figure out the most efficient means of hiding them from the Back-Button inclined user. Like most Ajax-driven menus, Spry shows and hides submenus by dynamically assigning class names to nested unordered lists based on user interaction. The associated CSS then shows or hides the menus based on the class attributes as well as controlling positioning and formatting for the submenus. By removing those dynamic class names when a link has been clicked, the menus should reset themselves to their default states. Although Firefox has the pagehide event (which would allow us to reset the menus as the user navigates away from the page without resetting the cache) it is proprietary to Firefox. Since Opera also has the same issue, tracking a mouse click would give us more cross-browser compatibility.
I’m a firm believer in not re-inventing the wheel, so the first approach I took was to register a click event listener to the Spry menu and then “piggy back” a mouse click function on the existing mouse out function (which hides the menu). To do this I added the following to the SpryMenuBar.js file:
Spry.Widget.MenuBar.prototype.mouseClick = Spry.Widget.MenuBar.prototype.mouseOver = function (listitem, e)
This worked fine except for the built in delay which caused the menus to display briefly as the user navigated back to the page. At that point I was trying to decide whether to add some conditional logic to the mouse out function –adding the delay if it detected the mouse out event and removing it if it didn’t– or just writing another function for the mouse click that left out the delay altogether.
It was at this point that I found out someone had already done just that! Checking Adobe’s forums to see if anyone else had taken a stab at the problem (mental note, check the forums first next time…), I found that Niels Hofkes of Solutions In Balance had indeed taken that very approach and written a nice, compact function that adds the mouse out functionality without the associated delay. So rather than write my own, I tried out Niels and found out it worked great.
Essentially, Niels added an mouse click event listener to the menu bar initialization and then took the existing mouse out function, stripped it down to remove the delay, and added it as a new function. It works great and doesn’t cause any weirdness on any of the browsers I tested it on (IE, Firefox, Chrome, and Opera). I’m guessing it will work well on Safari as well, but be sure to test that one as well before releasing this into the wild.
Kudos to Niels for slogging through this and posting about it in the Adobe forums! This solution won’t be for everyone (in fact, some of you may not see this as an issue), and a few of you javascript superstars might have a more efficient solution (if you do feel free to add it to the comments) but for the most part I think this approach works fine. I also think this approach would work equally as well for any Ajax menu bar experiencing the same issues, just find the function that handles the mouse out event and write a similar function for the click handler.
Try it out! Use the menu to browse to About > Contributors and hit the back button. The default should still show the submenus, while the fix should hide the submenus.
For those of you looking for a more step-by-step approach here’s what you do:
this.addEventListener(listitem, 'click', function(e)
{self.mouseClick(listitem, e);}, false);
This adds a click event listener to each item on the menu bar.
//remove menu bar hover states when back button is pressed in FF/Opera
Spry.Widget.MenuBar.prototype.mouseClick = function (listitem, e) {
link = listitem.getElementsByTagName('a')[0];
var submenus = listitem.getElementsByTagName('ul');
var menu = (submenus.length > 0 ? submenus[0] : null);
var hasSubMenu = (menu) ? true : false;
this.removeClassName(link, hasSubMenu ?
↵this.subHoverClass : this.hoverClass);
if (menu)
this.hideSubmenu(menu);
if (this.hasFocus)
link.blur();
};
This finds all the menus and submenus and removes the subHoverClass and hoverClass when a link is clicked.
That’s it! I’d like to point out that if this is the result you are always looking for, you’ll need to save this SpryMenuBar.js file and reuse it each time you employ a Spry Menu Bar.
Hello,
Not only Opera has the issue, but also WebKit. So its save to say that all modern browsers have this issue.
As I suggest in the topic, it saver and wise to use the Spry methods instead of creating a custom piece of code for that.
If you check the source of the SpryMenu you will see that
.clearMenus
.clearSelection
methods contain the above described functionality. The only part that is missing is removal of the focus of the a element.
Anyways,
It was a great read, and a nice bug hunt.
Thanks V1, and kudos for being my first official comment! Yes, the clearMenus method would work and could easily be triggered by a user click. I agree with Niels however in that testing it I found that top level menu items are harder to clear with this, it’s probably lack of my fine tuning the example and not removing focus. At any rate I appreciate you looking into this!
Hey James!
I’m watching your latest from Lynda.com while eating lunch: Web Design Fundamentals.
Good stuff!
Thanks Phillip! Glad you’re enjoying it, it was a fun course to write!
Hello James,
I’ve been alone in my office with you and your Dreamweaver CS5 Essential Training DVD for over two weeks now. When I emerge to join my family, briefly, for meals and other human needs, they’ve noticed I’ve actually started to pick up your accent. At 57 years old, I don’t suppose it matters much, career-wise, whether I master Dreamweaver or not, but I just can’t quell the urge to know more.
You’ve played such a big part in the de-mystifying process for me, and I’ve so enjoyed your company, I feel like taking you out for a beer.
I have a serious question if I may…
The Spry Menu Bar 0.2 widget, (rebuilt from the ground up) or pixie roll-overs. Which do you recommend?
Thanks Dave!
If I ever get over to New Zealand I’ll take you up on the beer. As for the menu, it depends. If you need a menu that can handle drop-downs and animations, I recommend using Spry or jQuery to power it. For a traditional roll-over menu I’d go with the pixie-background technique (less overhead, no javascript reliance). Of course, there’s nothing preventing you from combining the two!
My experience of the spry menu bar, albeit limited, has left me feeling very wary. My drop-downs dropped UP, and some distance to the North in Internet Explorer. That’s why my ears pricked up when I heard the widget had been re-built, but the pixies still look simpler, and so much more trustworthy!
PS: Can’t honestly say I expected a reply, let alone such a speedy one, as you must be a very busy man. Good on you, mate. It’s great work you’re doing. The beer’s a goer for sure if you’re ever out this way. The world is shrinking all the time.
Hi James
I am a Graphic Designer with 13 experience in the Print and Publishing Industry. I recently relocated from South Africa to Atlanta, Georgia, and since I don’t have a Green Card yet and am not allowed to work, I am using this time to learn Dreamweaver. I have been diligently following your tutorials on Lynda.com and have gotten so used to the sound of your voice that I feel like I know you. One thing is baffling me though. What do I do if my website is constructed with a main content area and a sidebar but for the portfolio pages I want to do away with the side bar completely to have more room to place my image gallery. How do I achieve this? Do I need a completely separate CSS file or what is the secret? I am completely stuck.
Brigitte, it depends on how your pages, and CSS are constructed. Often it might be as simple as leaving out a DIV. More than likely, you’ll need to make an alternate layout in your CSS for a single column layout. This is usually pretty easy to do and can be triggered by a class on the body tag, or a different ID on the content DIVs.
Hey James,
first of all , my English is not so good
It was my dream to speak to you
Nice to sea your site
I have one important question i hope you to reply ok ?
Ok; I’m working in arabic sites and I’d like to use @font-fave tech in one of those , I really tired of using images , and those sites like Cufon and others don’t support arabic language ????
What to do ??
What should i read ?? and do ??