views:

2265

answers:

5

I'm trying to build a horizontal accordion with Jquery. It seems to be working "ok" in Firefox. But in Webkit (Safari 3 + 4, and Chrome) the sub-level UL flashes after the Hide function. Any help would be greatly appreciated. To see a working demo: http://ableobject.com/horaccordion1.html

Here is what I'm working on:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"&gt;&lt;/script&gt;

    <title>untitled</title>
    <style type="text/css">
    #container {
     display: table; 
     margin: 0 auto; 
     text-align: center; /* for IE */
    }
            ul{
                    list-style: none;
                    background-color: yellow;
                    margin: 0;
                    padding: 0;
                    float: left;
        height: 20px; /* For testing */  
            }
            ul li {
                   background-color: aqua;
                    float: left;
            }
            ul li ul {
                   background-color: blue;
        display: none;
            }
            ul li ul li {
                   background-color: green;
            }
            a, a:link, a:hover, a:visited, a:active {
                    color: black;
                    text-decoration: none;
                    float: left;
            }
    </style>

   <script type="text/javascript">
/* Care of Hunter Daley */
    var $current = null;
        $(document).ready(function(){
         $("ul li ul").hide();  // hide submenus by default on load

           $("ul li a").click(function(){
              var $sub = $(this).next(); 
              if ($sub.css("display") == "none")
              {
                 if ($current != null)
                    $current.animate({ width: 'hide' }); // if you want to only show one sub at a time
                 $sub.animate({ width: 'show' }); 
                 $current = $sub;
              }
              else
              {
                 $sub.animate({ width: 'hide' });
                 $current = null;
              }
           });
        });
    </script>
</head>

<body>
    <div id="container">
    <ul>
            <li>
                    <a href="#">Top-level 1</a>
            </li>
            <li>
                    <a href="#">Top-level 2</a>

                    <ul>
                            <li><a href="#">Bottom Level A1</a></li>
                            <li><a href="#">Bottom Level A2</a></li>
                            <li><a href="#">Bottom Level A3</a></li>
                            <li><a href="#">Bottom Level A4</a></li>
                    </ul>
            </li>

            <li>
                    <a href="#">Top-level 3</a>
                    <ul>
                            <li><a href="#">Bottom Level B1</a></li>
                            <li><a href="#">Bottom Level B2</a></li>
                    </ul>
            </li>

            <li>
                    <a href="#">Top-level 4</a>
            </li>
    </ul>
</div>
</body>
+1  A: 
patrick dw
I've tried it out http://ableobject.com/horaccordion2.html but when I'm going from Toplevel 4 from an expanded Toplevel 3 it still flashes. I'll try to figure it out and repost... but at the moment the solution escapes me. Maybe: $sub.parent().css({ width: '' }); $current = $sub;?
stapler
BTW thanks a lot patrick
stapler
How important is the use of 'list' elements to you? After finally ditching them and using div's, I came up with a fairly concise accordion that works in webkit.
patrick dw
Its not all that important, I just didn't want to define widths for the list items (or, divs).
stapler
No need to set widths on divs when you float left. Not sure if this is the best way, but you don't have the webkit flashing problem anyway. It's the cleanest and most concise way I could find. Only weird thing was a Firefox fix that was needed (see code comments). Hope its helpful.
patrick dw
great, thanks patrick. There seems to be another issue with webkit about center-aligning the menu. It works in Firefox by using display: table; margin: 0 auto;and ie IE usingtext-align: center;on #containerbut webkit won't doesn't work!Thanks for all your help!
stapler
Yes, I see. Looks like webkit is actually centering it, but the width #container is still expanded as though all the menu items are shown. If you give the #container a background color, and make your browser window wide enough, you'll see that it is actually centered. (If your screen isn't big enough, you would need to delete some menu items to see the effect.) I'll play around some more and see if I can come up with something.
patrick dw
+1  A: 

I'm posting this as a separate answer just in case you still find the previous one useful.

Please note the following:

  • I have not tested this in ie.
  • This goes back to a 'nested' version, so I changed the class and variable names a little.
  • An empty menu is no longer needed when there is no menu to display.
  • The width of each menu's 'container' is now being reduced the same amount as the menu. This is what eliminates webkit's temporary flash (which was the original strategy).
  • You'll notice the the timing of the animation of the menu is slightly different than that of the menu's container. Basically, you want the container to be a little ahead when expanding, and the menu to be a little ahead when reducing. If the timing is set to be equal, you can get some flashing of the menu.
  • As explained in the comments, at the beginning, each menu 'memorizes' its width when fully expanded by setting a previously non-existent attribute called 'fullWidth'. It then retrieves the value of this attribute when needed. You could just as easily use global variables, or jQuery's data() function to store the info. The point is that things are simplified if each menu is aware of how wide it should be when it is expanded.

So here it is. Hope it helps!

CSS

#container {
    margin: 0 auto 0 auto; 
    text-align: center;
    display: table;
}

.menuContainer {
       margin: 0;
       padding: 0;
       float: left;
       height: 32px; /* For testing */
       font-family: helvetica;
       font-size: 18px;
       clip: auto; overflow: hidden;
}
.menu {
       height: 32px; /* For testing */
       clip: auto; overflow: hidden;
       float: left;
}
a, a:link, a:hover, a:visited, a:active {
       color: black;
       text-decoration: none;
       padding: 12px;
       font-weight: 700;
       float: left;
       color: #222;
}

.menu a, .menu a:link, .menu a:hover, .menu a:visited, .menu a:active {
   color: black;
   text-decoration: none;
   padding: 12px;
   font-weight: normal;
   float: left;
}

javascript

var $currentMenuContainer = $('#someFictionalElement');
var $previousMenuContainer = null;

$(document).ready(function() {

// Iterate through each .menu element, setting the full width of each menu to a 'custom'
//        attribute called 'fullWidth'. Since the full width should never change, this
//        makes it easy to recall it quickly. You could use global variables instead.
// After setting 'fullWidth', it then collapses each menu and title.
$(".menu").each(function() {
    var $theMenu = $(this);
    var $theMenuContainer = $theMenu.parent();
    $theMenu.attr({fullWidth: ($theMenu.width() + 3)});   // Add a few pixels for firefox
    var menuContainerWidth = $theMenuContainer.width() - $theMenu.attr('fullWidth') + 6;  // Add DOUBLE the pixels here
    $theMenu.css({width: 0});
    $theMenuContainer.css({width: menuContainerWidth});
});

    $(".menuContainer a").click(
        function() {
// Set the current and previous elements properly
            $previousMenuContainer = $currentMenuContainer;
            $currentMenuContainer = $(this).parent();
            var $previousMenu = $previousMenuContainer.find('.menu');
            var $currentMenu = $currentMenuContainer.find('.menu');

// Collapse the previous menu
            $previousMenu.animate({ width: 0 }, {duration: 480, queue: false} );

// Subtract the width of the previous menuContainer's menu from the menuContainer (only if its menu is displayed)
            if($previousMenu.width() > 0) $previousMenuContainer.animate({width: ('-=' + $previousMenu.attr('fullWidth'))}, 500);

// Expand the current menu and its menuContainer if it's not showing
            if($currentMenu.width() == 0) {
                // Increase the menuContainer width by the full width of its menu
                $currentMenuContainer.animate({width: ('+=' + $currentMenu.attr('fullWidth'))}, 480);
                // Increase the menuContainer to its full width
                $currentMenu.animate({ width: $currentMenu.attr('fullWidth') }, 500);
            }
    });

    $(".menuContainer a").hover(
        function(){$(this).animate ({ opacity: 0.7 }, 200);},
        function(){$(this).animate ({ opacity: 1 }, 600);}
    );
});

HTML

<div id="container">
    <div class='menuContainer'>
        <a href="#">Top-level 1</a>
    </div>
    <div class='menuContainer'>
        <a href="#">Top-level 2</a>
        <div class='menu'>                         
         <a href="#">Bottom Level A1</a>
         <a href="#">Bottom Level A2</a>
         <a href="#">Bottom Level A3</a>
         <a href="#">Bottom Level A4</a>
     </div>
    </div>
    <div class='menuContainer'>
        <a href="#">Top-level 3</a>
     <div class='menu'>
         <a href="#">Bottom Level B1</a>
         <a href="#">Bottom Level B2</a>
     </div>
    </div>
    <div class='menuContainer'>
        <a href="#">Top-level 4</a>
    </div>
</div>

Edit: Add the following DTD to the top of your page-

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"&gt;
patrick dw
This works great, now if i can only figure out how to center it in IE :)
stapler
I'm encountering a strange bug in Firefox... I've changed the link size to 16px, and now the Bottom Level A4 disappears? I've tried adding more (and less) pixels... and playing with the padding... I can't seem to get it to show up
stapler
For ie to center, add the following to the very top of the page. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">I think I should be able to figure out the Firefox issue. Will probably get back to you on Monday.
patrick dw
OK, I actually got it pretty quick. I've corrected the .each() method in the javascript. How ever many pixels are added to the menu, twice as many need to be added to the menu's container.
patrick dw
wow, thanks so much for help
stapler
Everything is working well, I'm converting it to work with Ems as well. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd"> doesn't seem to center it, neither does containing it with <center></center>. I've put a test up at http://ableobject.com/horaccordion.html
stapler
A: 

Any chance of someone posting a working demo of this? I would love to use the same on a site.

I never got it to center in IE, I'll post an example later tonight
stapler
A: 

Hi there! I've been doing something like this and have encountered the same problem. An example of my page can be found here: http://prorevizija.agenda.si/fileadmin/templates/index.html

Can you please tell me if you found a solution and got it working in IE and Safari (i need it centered aswell)? I'd be very grateful if you can post the final working code if so. Thank you so much!

Max