Jeff Lambert
Jeff Lambert

Reputation: 24661

Nested lists - show each ul level on a separate line

I am using jQuery's isotope plugin to filter a list of entries, using the markup below for my listing of possible filters. All top-level list items are initially visible, and if I click a specific filter button then any sub-filters (if any) are then shown.

All of this works great. I am however running into a few issues getting the CSS to display the list correctly. The markup is:

<ul class="filter-list">
  <li><button data-filter="*">show all</button></li>
  <li>
    <button data-filter=".filter1">Filter 1</button>
    <ul class="sub-filter">
      <li>
        <button data-filter=".filter1.filtera">Filter 1,A</button>
        <ul class="sub-filter">
          <li><button data-filter=".filtera.filteri">Filter 1,A,i</button></li>
          <li><button data-filter=".filtera.filterii">Filter 1,A,ii</button></li>
          <li><button data-filter=".filtera.filteriii">Filter 1,A,iii</button></li>
        </ul>
      </li>
      <li><button data-filter=".filter1.filterb">Filter 1,B</button></li>
      <li>
        <button data-filter=".filter1.filterc">Filter 1,C</button>
        <ul class="sub-filter">
          <li><button data-filter=".filterc.filteri">Filter 1,C,i</button></li>
          <li><button data-filter=".filterc.filterii">Filter 1,C,ii</button></li>
        </ul>
      </li>
      <li>
        <button data-filter=".filter1.filterd">Filter 1,D</button>
        <ul class="sub-filter">
          <li><button data-filter=".filterd.filteri">Filter 1,D,i</button></li>
          <li><button data-filter=".filterd.filterii">Filter 1,D,ii</button></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><button data-filter=".filter2">Filter 2</button></li>
  <li><button data-filter=".filter3">Filter 3</button></li>
</ul>

The CSS that I have so far is:

ul.filter-list {
  display:inline-block;
  position: relative;
  width: 100%;
  margin:0;
  padding:0;
}

ul.filter-list ul {
  display: none;
  position:relative;
}

.filter-list ul.active-filter {
  display: block;
  position:absolute;
  top:50px;
  left:0px;
}

ul.filter-list li {
  list-style-type: none;
  display: inline-block;
}

The expected output that I need is to have each 'level' of the list on a single line, followed by the next 'level' of filters if any for the one that is currently active. Initially, it should look like this:

show all    Filter 1    Filter 2    Filter 3

If I click on Filter 1, then the next level of buttons underneath that filter should show on the next line:

show all    Filter 1    Filter 2    Filter 3
Filter 1,A    Filter 1,B    Filter 1,C    Filter 1,D

If I were to then click on Filter 1,C, the third level of buttons will need to display on the third line:

show all    Filter 1    Filter 2    Filter 3
Filter 1,A    Filter 1,B    Filter 1,C    Filter 1,D
Filter 1,C,i    Filter 1,C,ii

One of the biggest issues I still am having is that, since the active lists are absolutely positioned, they are being displayed on top of content that is immediately following this list of filters rather than pushing it down. You can see this behavior in this fiddle. Also, when screen size is reduced, things are not flowing / breaking in the way I would expect them to (as well as having absolutely positioned buttons sitting on top of other absolutely positioned items).

Is there a way to accomplish this using just CSS (perhaps using relative positioning?), or am I going to have to add some inordinate amount of Javascript to what I have? Or should I just take out the nested lists in favor of having multiple 'flat' lists?

Upvotes: 1

Views: 593

Answers (1)

emerson.marini
emerson.marini

Reputation: 9348

You can achieve must of this using just regular CSS and jQuery/Javascript to toggle the items visibility:

$(function() {
  $('button').on('click', function() {
    // The element to be toggled (show/hide).
    var toToggle = $(this).next('.sub-filter');

    // Go through its parents until the element
    // with class sub-filter and find its
    // children with this same class to hide 
    // them, excluding itself.
    $(this).parents('.sub-filter').find('.sub-filter').not(toToggle).hide();
    
    // Toggle the target.
    toToggle.toggle();
  });
});
* {
  box-sizing: border-box;
}
ul.filter-list,
ul.filter-list li {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
ul.sub-filter {
  /* Giving them the full width and
    floating them you guarantee their 
    place in a new line. */
  width: 100%;
  float: left;
  display: none;
  /* Setting their padding to 0 will
    keep them aligned to the line above
    them. */
  padding: 0;
}
ul.filter-list > li,
ul.sub-filter > li {
  /* Setting their display to inline 
    will show them next to each other. */
  display: inline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="filter-list">
  <li>
    <button data-filter="*">show all</button>
  </li>
  <li>
    <button data-filter=".filter1">Filter 1</button>
    <ul class="sub-filter">
      <li>
        <button data-filter=".filter1.filtera">Filter 1,A</button>
        <ul class="sub-filter">
          <li>
            <button data-filter=".filtera.filteri">Filter 1,A,i</button>
          </li>
          <li>
            <button data-filter=".filtera.filterii">Filter 1,A,ii</button>
          </li>
          <li>
            <button data-filter=".filtera.filteriii">Filter 1,A,iii</button>
          </li>
        </ul>
      </li>
      <li>
        <button data-filter=".filter1.filterb">Filter 1,B</button>
      </li>
      <li>
        <button data-filter=".filter1.filterc">Filter 1,C</button>
        <ul class="sub-filter">
          <li>
            <button data-filter=".filterc.filteri">Filter 1,C,i</button>
          </li>
          <li>
            <button data-filter=".filterc.filterii">Filter 1,C,ii</button>
          </li>
        </ul>
      </li>
      <li>
        <button data-filter=".filter1.filterd">Filter 1,D</button>
        <ul class="sub-filter">
          <li>
            <button data-filter=".filterd.filteri">Filter 1,D,i</button>
          </li>
          <li>
            <button data-filter=".filterd.filterii">Filter 1,D,ii</button>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <button data-filter=".filter2">Filter 2</button>
  </li>
  <li>
    <button data-filter=".filter3">Filter 3</button>
  </li>
</ul>

Demo jsFiddle

Upvotes: 1

Related Questions