javascriptjquerycssmagicline

Iterating over multiple <ul> items to make a jQuery script work without repeating code


I have implemented a jQuery 'magic line' in my multi-level navigation menu and it seems to work with the first sub-navigation, but if I add more it breaks.

My question is: How do I iterate over the other sub-navigation menus to get it to work across however many sub-level items I may add? ( <ul class="sy__list__secondary"> )

Please take a look at my code below. To reproduce my problem follow these steps:

  1. Hover over the first child in the main navigation (HOME)
  2. Then hover over the children of the 'HOME' item - the magic line will work perfectly.

  3. Now hover over the second child in the main navigation (ABOUT)

  4. Then hover over the children of the 'ABOUT' list - the magic line will not work.

My question is: How do I iterate over the other sub-navigation menus to get it to work across however many sub-level items I may add?

var $el, leftPos, newWidth;

$('.sy__list__secondary').each(function() {
  $(this).append("<li id='magic-line'></li>");
});

/* Cache it */
var $magicLine = $("#magic-line");

function magicLinePref() {
  $magicLine
    .width($(".sy__list__secondary.active li:first-child").width())
    .css("left", $(".sy__list__secondary.active li:first-child").position().left)
    .data("origLeft", $magicLine.position().left)
    .data("origWidth", $magicLine.width());

  $(".sy__list__secondary li").find("a").hover(function() {
    $el = $(this);
    leftPos = $el.position().left;
    newWidth = $el.parent().width();
    $magicLine.stop().animate({
      left: leftPos,
      width: newWidth
    });
  }, function() {
    $magicLine.stop().animate({
      left: $magicLine.data("origLeft"),
      width: $magicLine.data("origWidth")
    });
  });
}

$('.sy__list__primary > li').hover(function() {
  magicLinePref();
});
.sy__navigation {
  position: relative;
}

.sy__navigation__primary {
  background: #333333;
}

.sy__navigation__secondary {
  position: absolute;
  background: #031f37eb;
  left: 0;
  top: 50px;
  z-index: 10;
  width: 100%;
  display: none;
}

#magic-line {
  position: absolute;
  bottom: 0;
  padding: 0 !important;
  left: 0;
  width: 100px;
  height: 2px;
  background: #31B2E7;
}

.sy__list__primary>li:hover .sy__navigation__secondary {
  display: block;
}

ul.sy__list__secondary {
  justify-content: space-around;
  margin: auto;
  padding: 0;
  color: #ffffff;
}

ul.sy__list__primary {
  margin: auto;
  padding: 0;
  box-sizing: border-box;
}

ul.sy__list__primary span,
a {
  color: #ffffff;
}

ul.sy__list__primary li {
  display: inline-block;
  letter-spacing: 1px;
  font-size: 10px;
  box-sizing: border-box;
}

ul.sy__list__primary>li {
  padding: 20px;
}

ul.sy__list__secondary li a {
  padding: 15px;
  display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">

<div class="sy__navigation">
  <div class="sy__navigation__primary">
    <div class="container">
      <div class="row">
        <ul class="sy__list__primary">
          <li><span>HOME</span>
            <div class="sy__navigation__secondary">
              <ul class="sy__list__secondary active">
                <li><a href="#">SUB 1A</a></li>
                <li><a href="#">SUB 1A</a></li>
                <li><a href="#">SUB 1A</a></li>
              </ul>
            </div>
          </li>
          <li><span>ABOUT</span>
            <div class="sy__navigation__secondary">
              <ul class="sy__list__secondary">
                <li><a href="#">SUB 2A</a></li>
                <li><a href="#">SUB 2A</a></li>
                <li><a href="#">SUB 2A</a></li>
              </ul>
            </div>
          </li>

        </ul>
      </div>
    </div>
  </div>
</div>


Solution

  • Updated magicLinePref() function and passed respective li object. Fetched .sy__list__secondary and .#magic-line using li.find.

    Removed global declaration of var $magicLine = $("#magic-line");. As when jQuery select any object with id selector # it will only return first object found with given id.

    Instead of that I have select element which are inside current li element which is passing from magicLinePref($(this));.

    li.find("#magic-line"); find element with id magic-line inside current li. Similarly for class selector .sy__list__secondary.

    var $el, leftPos, newWidth;
    
    $('.sy__list__secondary').each(function() {
      $(this).append("<li id='magic-line'></li>");
    });
    
    /* Cache it */
    
    function magicLinePref(li) {
    var $magicLine = li.find("#magic-line");
      $magicLine
        .width(li.find(".sy__list__secondary li:first-child").width())
        .css("left", li.find(".sy__list__secondary li:first-child").position().left)
        .data("origLeft", $magicLine.position().left)
        .data("origWidth", $magicLine.width());
    
      li.find(".sy__list__secondary li").find("a").hover(function() {
        $el = $(this);
        leftPos = $el.position().left;
        newWidth = $el.parent().width();
        $magicLine.stop().animate({
          left: leftPos,
          width: newWidth
        });
      }, function() {
        $magicLine.stop().animate({
          left: $magicLine.data("origLeft"),
          width: $magicLine.data("origWidth")
        });
      });
    }
    
    $('.sy__list__primary > li').hover(function() {
      magicLinePref($(this));
    });
    .sy__navigation {
      position: relative;
    }
    
    .sy__navigation__primary {
      background: #333333;
    }
    
    .sy__navigation__secondary {
      position: absolute;
      background: #031f37eb;
      left: 0;
      top: 50px;
      z-index: 10;
      width: 100%;
      display: none;
    }
    
    #magic-line {
      position: absolute;
      bottom: 0;
      padding: 0 !important;
      left: 0;
      width: 100px;
      height: 2px;
      background: #31B2E7;
    }
    
    .sy__list__primary>li:hover .sy__navigation__secondary {
      display: block;
    }
    
    ul.sy__list__secondary {
      justify-content: space-around;
      margin: auto;
      padding: 0;
      color: #ffffff;
    }
    
    ul.sy__list__primary {
      margin: auto;
      padding: 0;
      box-sizing: border-box;
    }
    
    ul.sy__list__primary span,
    a {
      color: #ffffff;
    }
    
    ul.sy__list__primary li {
      display: inline-block;
      letter-spacing: 1px;
      font-size: 10px;
      box-sizing: border-box;
    }
    
    ul.sy__list__primary>li {
      padding: 20px;
    }
    
    ul.sy__list__secondary li a {
      padding: 15px;
      display: inline-block;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css">
    
    <div class="sy__navigation">
      <div class="sy__navigation__primary">
        <div class="container">
          <div class="row">
            <ul class="sy__list__primary">
              <li><span>HOME</span>
                <div class="sy__navigation__secondary">
                  <ul class="sy__list__secondary">
                    <li><a href="#">SUB 1A</a></li>
                    <li><a href="#">SUB 1A</a></li>
                    <li><a href="#">SUB 1A</a></li>
                  </ul>
                </div>
              </li>
              <li><span>ABOUT</span>
                <div class="sy__navigation__secondary">
                  <ul class="sy__list__secondary">
                    <li><a href="#">SUB 2A</a></li>
                    <li><a href="#">SUB 2A</a></li>
                    <li><a href="#">SUB 2A</a></li>
                  </ul>
                </div>
              </li>
    
            </ul>
          </div>
        </div>
      </div>
    </div>