简体   繁体   中英

jQuery multi-level accordion menu

I found some accordion menus online but either they did not support multiple levels, or the code was quite cryptic and I didn't want to use it. So I decided, that I want to write my own accordion menu, which supports an arbitrary number of sub-menus.

Here is what I have so far:

 $(document).ready(function() { // at the beginning only the top menu items are visible $('.multi-level-accordion-menu > li ul').slideUp(0); console.log('all elements slid up'); // when a menu item which is not active is clicked, show the next lower level // when a menu item which is active is clicked, hide all its sub menu items $('.multi-level-accordion-menu li').click(function() { var menu_item = $(this); if (is_active(menu_item)) { console.log('active menu item clicked'); close_menu_item(menu_item); } else { console.log('inactive menu item clicked'); open_menu_item(menu_item); } }); console.log('READY!'); }); function open_menu_item(menu_item) { menu_item.children('ul').slideDown(500); menu_item.addClass('active-menu-item'); } function close_menu_item(menu_item) { console.log('1x'); menu_item.find('ul').each(function(index, elem) { $(elem).slideUp(); return false; }); menu_item.removeClass('active-menu-item'); } function is_active(menu_item) { return menu_item.hasClass('active-menu-item'); } 
 .multi-level-accordion-menu * { margin: 0px; padding: 0px; } .multi-level-accordion-menu { list-style: none; max-width: 200px; } .multi-level-accordion-menu ul { list-style: none; } .multi-level-accordion-menu li { border-color: #EEEEEE; border-width: 1px; border-style: solid; background-color: #303030; margin: 2px; padding: 2px; line-height: 30px; font-size: 16px; } .multi-level-accordion-menu { color: #DDDDDD; } body { background-color: #202020; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <body> <div> <ul class='multi-level-accordion-menu'> <li>Item 1 <ul> <li>Item 1.1 <ul> <li>Item 1.1.1</li> <li>Item 1.1.2</li> <li>Item 1.1.3</li> <li>Item 1.1.4</li> </ul> </li> <li>Item 1.2</li> <li>Item 1.3</li> <li>Item 1.4</li> <li>Item 1.5</li> <li>Item 1.6</li> </ul> </li> <li>Item 2</li> <li>Item 3 <ul> <li>Item 3.1 <ul> <li>Item 3.1.1</li> <li>Item 3.1.2</li> <li>Item 3.1.3</li> <li>Item 3.1.4</li> </ul> </li> <li>Item 3.2</li> <li>Item 3.3</li> <li>Item 3.4</li> <li>Item 3.5</li> <li>Item 3.6</li> </ul> </li> <li>Item 4</li> <li>Item 5</li> <li>Item 6</li> <li>Item 7</li> </ul> </div> </body> 

http://jsfiddle.net/Zelphir/1fp03oyq/1/

Clicks on top level menu items work, but when I click Item 1 and then Item 1.1 it becomes obvious, that the lower levels don't work yet. The sub-menu opens but somehow at the same time the top level closes again.

How can I achieve the following behavior:

  1. A click on an 'inactive' menu item makes its sub-menu items visible
  2. A click on an 'active' menu item hides its sub menu items and their sub menu items
  3. At the beginning all items are 'inactive'

This should work for you.

$('.multi-level-accordion-menu').click(function(e){
    var menu_item = $(e.target);

    if(is_active(menu_item)) {
        console.log('active menu item clicked');
        close_menu_item(menu_item);

    } else {
        console.log('inactive menu item clicked');
        open_menu_item(menu_item);          
    }
});

function close_menu_item(menu_item) {
    console.log('1x');

    menu_item.find('ul').each(function() {
        $(this).slideUp().find('li').removeClass('active-menu-item');
    });

    menu_item.removeClass('active-menu-item');
}

Or a one/two liner

function close_menu_item(menu_item) {
    console.log('1x');  

    menu_item.removeClass('active-menu-item').find('ul').slideUp().
    find('li').removeClass('active-menu-item');
}

In response to your comment:

In this case bubbling is what causes the 'click' method to fire on the outermost container (.multi-level-accordion-menu).

When you click on a child element, it is stored in the 'target' property of an 'event' object, and the click event bubbles up until it hits the outermost container, which you have defined in the jQuery selector( again - .multi-level-accordion-menu).

The outermost container is sitting there listening for 'click' events, so when this event bubbles up to it, it enters into the function.

You are then selecting the target element that triggered it all (stored in 'target' property of 'event' object) and performing whatever you want on it. Since you are now inside the function and there are no event listeners in there, no more events fire, and thus there is no further bubbling. The function completes and no more 'click' events are triggered since the event has already bubbled to the top.

In your original code, you were listening for click events on all 'li' elements, so when you clicked on a 'li' element it would immediately trigger the click listener and then run your function. Then when the function finished the event would bubble up to an ancestor 'li' element, trigger the listener again and run the method again. And so on.

Hope this clears it up a bit!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM