jQuery многоуровневое аккордеонное меню

Я нашел в сети несколько меню-гармошек, но либо они не поддерживали несколько уровней, либо код был довольно загадочным, и я не хотел его использовать. Поэтому я решил, что хочу написать свое собственное меню аккордеона, которое поддерживает произвольное количество подменю.

Вот что у меня есть на данный момент:

$(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/

Щелчки по пунктам меню верхнего уровня работают, но когда я нажимаю Item 1, а затем Item 1.1, становится очевидно, что нижние уровни еще не работают. Подменю открывается, но как-то одновременно с этим снова закрывается верхний уровень.

Как я могу добиться следующего поведения:

  1. Щелчок по «неактивному» пункту меню делает его пункты подменю видимыми.
  2. Щелчок по «активному» пункту меню скрывает его пункты подменю и их пункты подменю.
  3. Вначале все элементы "неактивны"

person Zelphir Kaltstahl    schedule 11.05.2015    source источник
comment
Взгляните на всплытие событий. Кажется, это классический случай.   -  person Paul Graffam    schedule 12.05.2015
comment
Уже сделал это, поэтому вы можете увидеть return false; в коде - это не помогает, или он находится в неправильном месте в коде. Вы можете дать полезный ответ?   -  person Zelphir Kaltstahl    schedule 12.05.2015
comment
В следующий раз вставьте код в вопрос, jsFiddle недостаточно ...   -  person apaul    schedule 12.05.2015
comment
А, хорошо, редактор SO сказал мне, но в какой-то момент уведомление о речевом пузыре исчезло, и я подумал, что хорошо, похоже, это необходимо только для предотвращения однострочников. Давайте сделаем столб тонким.   -  person Zelphir Kaltstahl    schedule 12.05.2015


Ответы (1)


Это должно сработать для вас.

$('.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');
}

Или одно / два лайнера

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

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

В ответ на ваш комментарий:

В этом случае пузыри - это то, что заставляет метод 'click' срабатывать на самом внешнем контейнере (.multi-level-accordion-menu).

Когда вы щелкаете дочерний элемент, он сохраняется в свойстве target объекта event, и событие щелчка всплывает вверх, пока не попадет во внешний контейнер, который вы определили в селекторе jQuery (опять же - .multi -уровневое-аккордеон-меню).

Самый внешний контейнер сидит там, ожидая событий «щелчка», поэтому, когда это событие всплывает до него, оно входит в функцию.

Затем вы выбираете целевой элемент, который инициировал все это (хранится в свойстве 'target' объекта 'event') и выполняете с ним все, что хотите. Поскольку теперь вы находитесь внутри функции и в ней нет прослушивателей событий, события больше не запускаются, и, следовательно, нет дальнейшего всплытия. Функция завершается, и события «щелчок» больше не запускаются, так как событие уже всплыло наверх.

В исходном коде вы прослушивали события щелчка на всех элементах li, поэтому, когда вы щелкали элемент li, он немедленно запускал прослушиватель щелчков, а затем запускал вашу функцию. Затем, когда функция завершится, событие перейдет к элементу-предку li, снова вызовет прослушиватель и снова запустит метод. И так далее.

Надеюсь, это немного проясняет ситуацию!

person GordonWells    schedule 11.05.2015
comment
У меня это работает, спасибо! Я не понимаю только того, почему событие не всплывает, и внешний элемент LI также не затрагивается. Вы можете объяснить? - person Zelphir Kaltstahl; 12.05.2015
comment
Вау, дополнительное объяснение действительно помогает, спасибо! Это означает, что мне никогда не нужно писать .click () для каждого элемента, на который я хочу реагировать, если по нему щелкнули, но вместо этого я мог бы подняться на один уровень (если он есть) и вместо этого проверить целевой член события. . Думаю, это упростит код. - person Zelphir Kaltstahl; 12.05.2015