views:

218

answers:

1

I'm trying to implement a simple horizontal navigation menu that just shows a single div for each link. It is kinda like a dropdown menu but instead of a mouseover triggering a dropdown, an onclick event will trigger the showing of a div. I want to make sure I am taking the right approach before going too much further, any help is appreciated. This is what I have so far:

    <ul id="settings_nav">
        <li>
          <a>Theme</a>
          <div id="settings_block"><%= render :partial => 'email_password' %></div>
        </li>              
        <li>
          <a href="index.htm">Lists</a>
          <div id="settings_block"><%= render :partial => 'lists' %></div>
        </li>
    </ul>

      window.onload = function(){
        settingsMenuInit('settings_nav')
      }

function settingsMenuInit(settings_nav){
  $(settings_nav).childElements().each(
    function(node){
      node.onclick= function(){ this.next.show() };    
  })  
}

Something like that, but I am unsure how to get the div that is currently shown and hide it. I could iterate through all the childElements and hide each div and then show the one that is being clicked, but maybe there's a better way?

+1  A: 

Some notes FW(T)W:

  1. With Prototype and similar libraries, you don't want to hook up event handlers by assigning functions to the element's onclick and similar properties; that style has several disadvantages (not least that there can only be one handler for the event on the element). Instead, use Prototype's observe function:

    someElement.observe('click', functionRefHere);
    // or
    Element.observe(someElementOrID, 'click', functionRefHere);
    

    This also lets Prototype work around some IE memory loss bugs for you.

  2. You might look at is Prototype's dom:loaded event, which happens sooner than window.onload (which won't happen until all of your images and other external resources have loaded, which can be a second or two after the page is displayed):

    document.observe('dom:loaded', initFunctionRefHere);
    
  3. You can use event delegation and just watch your settings_nav element, rather than each child node individually.

    $(settings_nav).observe('click', handleNavClick);
    function handleNavClick(event) {
        var elm = event.findElement("some CSS selector here");
        if (elm) {
            event.stop();
            // Handle it
        }
    }
    

    As you can see, Event#findElement accepts a CSS selector. It starts with the actual element that was clicked and tries to match that with the selector; if it matches, it returns the element, otherwise it goes to the parent to see if it matches; etc. So with your HTML you might look for a li (event.findElement('li')) or the link (event.findElement('a')).

    But if you want to watch each one individually, they can share a function (as they do in your example):

    $(settings_nav).childElements().invoke('observe', 'click', handleNavClick);
    function handleNavClick(event) {
        // Prototype makes `this` reference the element being observed, so
        // `this` will be the `li` element in this example.
    }
    

    Whether you watch each element individually or use event delegation depends on what you're doing (and personal preference). Whenever anything is likely to change (adding and removing navigation li elements, for instance) or when there are lots of things to watch, look to event delegation -- it's much easier simpler to deal with changing sets of elements using event delegation and just watching the parent. When dealing with a stable structure of just a few things (as in your example), it may be simpler to just watch the elements individually.

  4. Once inside your handler, you can use Element#down to find child elements (so from the li, you might use li.down('div') to find the div), or Element#next to get to the next sibling element (e.g., going from the link to the div). Either way, once you have a reference to the div, you can use Element#show and Element#hide (or Element#toggle).

  5. I recommend using named functions instead of anonymous ones (see my example above). Named functions help your tools (debuggers, browsers showing errors, etc.) help you. Just be sure not to declare a named function and use it as an expression (e.g., don't immediately assign it to something):

    // Don't do this because of browser implementation bugs:
    someElement.observe('click', function elementClickHandler(event) {
        // ...
    });
    
    
    // Do this instead:
    someElement.observe('click', elementClickHandler);
    function elementClickHandler(event) {
        // ...
    }
    

    ...because although you should be able to do that according to the spec, in reality various bugs in various browsers make it not work reliably (article).

T.J. Crowder
Nice explanation on how to use prototypejs.I wonder how much time you have spent to write this(with links to prototypeJs too).Anyway, thank you so much.
Hoque
@Hoque: :-) About 15 minutes. Useful way to limber up the brain for a long work day ahead.
T.J. Crowder
You are like a supersonic. To write 5 lines it usually takes 15 minutes for me. How can I improve(though this is not relevent question)?.I wonder, if you response. Thanks.
Hoque
Wow, I would like to thank you as well. This is the most I've ever learned from a response! This should be a blog post.
TenJack
@Hoque: LOL Well, my mother did give me some of the best advice ever when I was in High School: "You're into computers? Take typing."
T.J. Crowder
@TenJack: Glad that helped. Re blogging: I've been known to: http://blog.niftysnippets.org/ Been planning a post on the pros/cons of event delegation, haven't got 'round to it yet. Too much actual work to do. :-)
T.J. Crowder
Hey, what about if I want to 're-use' the same div/space for each option...as if I was doing an ajax request and using replace_html? Should I iterate over and hide every div and then show the one that was clicked or is there a better way?
TenJack
@TenJack: I don't quite understand the question. May be worth posting it as its own question, if it's sufficiently different from this one.
T.J. Crowder