Sunday, 20 April 2008

Flickr horizontal menu with Prototype

Last week, while looking for a drop-down menu for a Grails application, I came across this one which is very nice but uses jQuery. As Grails comes with Prototype as the default javascript framework and I didn't want to add another dependency, nor mess up with jQuery and Prototype incompatibilities, I decided to port it to Prototype. To set up the menu and download the images and the css, please refer to the original menu post, then substitute the jQuery code with the Prototype one below.

/*
* Flickr-like menu ported from jQuery to Prototype.
* The original is at http://www.candesprojects.com/downloads/flickr-horizontal-menu/
*
* @requires prototype 1.6
*
*/
document.observe("dom:loaded", function() {

$$("#nicemenu img.arrow").invoke("observe", "click", function(e){
headMenu = e.findElement('span.head_menu');
//div.sub_menu
submenu = e.findElement('li').down(3);

if(submenu.getStyle('display')=='block'){
headMenu.removeClassName("active");
submenu.hide();
this.writeAttribute('src','arrow_hover.png');
} else {
headMenu.addClassName("active");
submenu.setStyle({'display': 'block'});
this.writeAttribute('src','arrow_select.png');
}

$$("div.sub_menu:visible").each(function(e){
if(e!=submenu){
e.setStyle({'display': 'none'});
}
});

$$("#nicemenu img.arrow").each(function(e) {
if(e != this){
e.writeAttribute('src','arrow.png');
}
});


$$("#nicemenu span.head_menu").each(function(e) {
if(e != headMenu){
e.removeClassName('active');
}
});

});

$$("#nicemenu img.arrow").invoke("observe", "mouseover", function(e){
this.writeAttribute('src','arrow_hover.png');
});

$$("#nicemenu img.arrow").invoke("observe", "mouseout", function(e){
submenu = e.findElement('li').down(3);
if(submenu.getStyle('display')!='block'){
this.writeAttribute('src','arrow.png');
} else {
this.writeAttribute('src','arrow_select.png');
}
});

$$("#nicemenu span.head_menu").invoke("observe", "mouseover", function(){
this.addClassName('over');
});

$$("#nicemenu span.head_menu").invoke("observe", "mouseout", function(){
this.removeClassName('over');
});

document.observe("click", function(e) {
var elt = e.findElement('#nicemenu');
if (elt == undefined){
$$("#nicemenu span.head_menu").invoke('removeClassName','active');
$$("#nicemenu div.sub_menu").invoke('hide');
$$("#nicemenu img.arrow").invoke('writeAttribute','src','arrow.png');
}});

});

15 comments:

Josh said...

I can't thank you enough for your work in converting the previous script to be compatible with prototype!!!! You're an absolute LIFESAVER! Thanks again! Great work!!

Federico Grilli said...

You're welcome :)

Anonymous said...

awesome!!!

Carlos said...

I'm trying to get this working, but I'm stucked because when I click to get the submenu it doesn't do anything , what's the best way to debug this, I use firebug and it doesn't show me anything

Thanks

Carlos said...

I changed:

submenu.setStyle({'display': 'block'});

to:

submenu.setStyle({'display': 'inline'});

and it worked :) I don't why

Carlos said...

I found that this code doen't work right in opera, I think there's a problem with the display attribute

Federico Grilli said...

Hi Carlos, thank you for your feedback. I actually did not tested this code with Opera. When I'll have some spare time, I'll check it. Just curious, did you try with the jQuery version and had the same problem? Cheers, Federico

Carlos said...

Hey Federico, I couldn't manage to get the jquery version working, I prefer to use the prototype version since I've been using the library for other things in the site. I still have some problems with getting the menu to hide when clicked twice, but still it's a menu thanks

Anonymous said...

Hello, thank for your script but i have a question, why the submenu don't open with the fadein effect?

Federico Grilli said...

Hi, it shouldn't be that difficult to achieve the 'fade in' effect. Basically, you should replace
submenu.setStyle({'display': 'block'});
}

with submenu.appear(). Of course you need Scriptaculous to make it work.
Try have a look here http://github.com/madrobby/scriptaculous/wikis/effect-appear
Cheers, Federico

Anonymous said...

Thank you federico, i replace

submenu.setStyle({'display': 'block'});

with

$("div.sub_menu").appear();

but dont'work :-(

you are italian? i'm italian :-)

Federico Grilli said...

Hi fellow compatriot! Yes, I'm Italian too, but I prefer to stick to English, so that the huge international audience of this blog can benefit from our wisdom :) I guess the problem is as stated here in the scriptaculous doc of Effect.Appear() "... [The] display must be set within the style attribute of an object, and not in the CSS in the head of the document or a linked file. In other words, this Effect will not work if display:none is set within style tag or linked CSS file."
So, try and check your css file and see if the problem is there. Hope this helps. Ciao, Federico

ThinkEddy said...

Well done!
I just found your script and in a matter of minutes I had it implemented into a test system.
Thanks for the work!

katana said...

Yesterday, i was looking for a nice horizontal menu for a site i'm working on, and i found this. Great work for translating from jquery into prototype.
As for fade effect, i've made the following modifications:
- i've moved "display:none" from css to html in style attribute
- i've replaced
submenu.setStyle({'display': 'block'}); with submenu.appear({ duration: 0.5 });
- i've replaced
submenu.hide(); with submenu.fade({ duration: 0.5 });

Federico Grilli said...

Hi Katana, thanks for your contribution :)