Increasing User Control with Sort and Filter using List.js

List.js is a lightweight javascript plugin that adds search, sort, and filter features to HTML elements. The plugin works on <table> elements as well as block-level HTML.

Skip this Tutorial

Click here to go directly to the sample.

In this tutorial, I will use the plugin to create a list of items (using the <a> tag), count them (with jQuery), then make the list sortable and add some dynamic filters.

Do lists improve user experience?

Lists provide a convienient way to consume a large amount of (text-heavy) content. They are great for scanning and finding important information, as the user flow runs in a single vertical direction, and they generally take up less space than a grid so they work on smaller displays.

Also, just about every app contains some form of list, so the user behaviour is established and consistent.

“The time it takes to make a decision increases with the number and complexity of choices.”

William Edmund Hick and Ray Hyman (Hick’s Law)

But, according to Hick’s Law, having many options increases the time taken to make decisions.

By using filter logic on a list users can reduce the amount of items they see. Sorting empowers users to order items in a way that meets their interests and needs. When implemented properly, filters and sorting will increase the likeliness of the user finding what they are looking for in a list.

For further reading, check out the these UX best practices for implementing filter and sort.

Basic setup

Before you start, download and reference the following scripts:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js" type="text/javascript"></script>

Then, we need to build our HTML template for the list and add some simple styling.

  • In this example we will compile a list of #albums.
  • A .list element must be used to contain the items.
  • Later we will use javascript to add all our items, but, to start we will add the first .list__item in the list, each item will have an .album title, .artist and release .date.
  • Note .header contains an empty span.count element, we will populate this later with the number of items in the list.
<div class="container" id="albums">
  <div class="header">British Bangers! <span class="count"></span></div>
  <div class="list">
    <a class="list__item" href="javascript:void(0)">
      <div class="album">Definately Maybe</div>
      <div class="meta">
        <div class="artist">Oasis</div>
        <div class="date">2002</div>
      </div>
    </a>
  </div>
</div>

Global styles

Here we set a few basic variables, such as color and font. As well as the global styles for our list container.

@import url('https://fonts.googleapis.com/css?family=Cabin:400,600');
$base_color: rgba(0,0,0,1);
$accent_color: dimgrey;
$primary_color: #E27;
$bg_color: #ede8e8;

html, 
body {
  min-height: 100%;
}
body {
  margin: 0;
  padding: 0;
  min-width: 200px;
  background: $bg_color;
  color: $base_color;
  font-family: 'Cabin';
  font-family: 16px;
  line-height: 1.6;
}
a {
  text-decoration: none;
}
.container {
  max-width: 500px;
  padding: 80px 1.5rem;
  margin: 0 auto;
}

Header styles

I have used flexbox to quickly position the .count at the top right of the header.

.header {
  display: flex;
  align-items: center;
  font-size: 2rem;
  font-weight: 600;
  border-bottom: 1px solid lighten($base_color, 84%);
  margin-bottom: 2rem;
  padding-bottom: 1rem;
}
.count {
  padding: .3rem;
  border-radius: 1rem;
  font-size: 1rem;
  margin-left: .5rem;
  margin-top: -2rem;
  min-width: 1.5rem;
  line-height: 1.2;
  text-align: center;
  color: white;
  background: $primary_color;
}

List item styles

Of course you can style your list however you like, for simplicity, I have applied a simple ‘card’ effect to each item, grouping the artist and date below the album title. Each item is also a link so I have added a simple shadow hover effect.

.list__item {
  display: block;
  padding: 1rem;
  margin: 0 0 1rem 0;
  background: white;
  border-radius: 1rem;
  color: inherit;
  transition: all .2s ease;
  
  &:hover {
    color: $primary_color !important;
    box-shadow: 0 1px 10px 0 transparentize($base_color, .9);
  }
}
.album {
  font-size: 1.25rem;
  color: inherit !important;
  font-weight: 600;
  margin-bottom: .25rem;
}
.meta {
  display: flex;
  flex-wrap: wrap;
  font-size: .9rem;
  color: $accent_color;
  
  > div {
    padding-right: .5rem;
  }
  > div:not(:last-child):after {
    content: '·';
    padding-left: .5rem;
    display: inline-block;
  }
}

Create the list

Now that the basic list is ready we need to create, populate and run the list using javascript.

  • To populate, filter and sort the list we need to specify the valueNames in the list options.
  • I have used album, artist and date - make sure these values correspond to the element classes used in the HTML item above.
var options = {
  valueNames: [ 'album', 'artist', 'date' ]
};
  • Adding more items to the list can be done with an array of values.
  • Each item should include album, artist, date.
  • This array can be repeated indefinately.
var values = [
  {
    album: 'Sense',
    artist: 'The Lightning Seeds',
    date: 1992
  },
  {
    album: "His 'N' Hers",
    artist: 'Pulp',
    date: 1994
  },
  ... 
];
  • Finally, we can run the list, passing in the options and values above.
  • I want my list to automatically sort on page load, so I have set the list to sort by album name in accending order too.
var albums = new List('albums', options, values);
albums.sort("album", {
  order: "asc"
})

Check out the List.js documentation for more details on how to initialize the list.

Add sort controls

List.js makes it super easy to sort our list, we have already set our list to sort by album name on load, but we can also create some controls that will dynamically sort the list when clicked by the user.

  • No javascript is required to do this, just add a group of buttons inside your list container (#albums in this example).
  • Each button should have the .sort class and a data-sort attribute that matches the valueNames set previously.
<div class="controls">
  <div>Sort by:</div>
  <a class="sort" href="#" data-sort="album">Album</a>
  <a class="sort" href="#" data-sort="artist">Artist</a>
  <a class="sort" href="#" data-sort="date">Date    </a>
</div>
  • Clicking the sort button will automatically add the .asc and .desc classes respectively, so we can add some styles to indicate when the sort button has been triggered.
  • Note, I have used Material Icons here to display the sort-direction arrow at the end of the button.
.controls {
  display: flex;
  align-items: center;
  margin-bottom: 1rem;
  color: $base_color;
  
  > div {
    font-weight: 600;
  }
  > * {
    margin-right: 1rem;
    color: inherit;
    transition: all .2s ease;
  }
  a:hover {
    color: $primary_color;    
  }
  .sort {
    display: flex;
    align-items: flex-end;
    
    &:after {
      display: block;
      opacity: 0;
      content: '';
      margin-left: .25rem;
      font-family: 'Material Icons';
      font-weight: 400;
      font-style: normal;
      font-size: 1.5rem;
      display: inline-block;
      line-height: 1;
      text-transform: none;
      letter-spacing: normal;
      word-wrap: normal;
      white-space: nowrap;
      direction: ltr;
      -webkit-font-smoothing: antialiased;
      text-rendering: optimizeLegibility;
      -moz-osx-font-smoothing: grayscale;
      font-feature-settings: 'liga';
    }  
    &.asc, &.desc {
      color: $primary_color;
    }
    &.asc:after {
      opacity: 1;
      content: 'arrow_drop_up';
    }
    &.desc:after {
      opacity: 1;
      content: 'arrow_drop_down';
    }
  }
}

Add filter buttons

Unlike sort, filters require a little more effort to initiate effectively. The logic I wanto to create here is as follows:

  1. Filter button will filter the list on click
  2. A filter will only be applied to a single list value (e.g. album, artist or date)
  3. Only one filter can be applied at a time
  4. The active filter should be highlighted
  5. All filters can be cleared

To implement this, first we need to build out the HTML for our group of filters and style it.

  • Styling is attributed to the .tags and .tag classes.
  • The .filter class and data-filter attribute are used to control the logic.
<div class="tags">
  <div>Filter by:</div>
  <div class="tag filter" data-filter="1993">'93 </div>
  <div class="tag filter" data-filter="1994">'94 </div>
  <div class="tag filter" data-filter="1995">'95</div>
  <div class="tag filter" data-filter="1996">'96 </div>
</div>
.tags {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  margin-bottom: .75rem;
  
  div {
    font-weight: 600; 
    margin-right: .5rem;
  }
  .tag {
    display: flex;
    cursor: pointer;
    margin-bottom: .5rem;
    margin-right: .5rem;
    color: white;
    background: lighten($base_color, 60%);
    padding: .2rem .6rem .2rem .6rem;
    font-weight: 600;
    font-size: .9rem;
    transition: all .2s ease;
    border-radius: 1rem;
    overflow: hidden;
    
    &:after {
      content: 'x';
      cursor: pointer;
      opacity: 0;
      visibility: hidden;
      max-width: 0;
      transition: all .2s ease;
    }
    
    &.active:after {
      opacity: 1;
      visibility: visible;
      max-width: 2rem;
      margin-left: .4rem;
    }
    
    &:hover,
    &.active {
      background: $primary_color;
    }
  }
}

The filter logic is controled by some simple jQuery. I decided that I only wanted to let users filter by date.

  • When the filter is clicked the script will reference the data-filter value, checking to see if any items in the list return a date value match.
  • The results are returned, and an .active class is applied to the filter button.
$('.filter').on('click',function(){
  var $q = $(this).attr('data-filter');
  if($(this).hasClass('active')){
    albums.filter();
    $('.filter').removeClass('active');
  } else {
    albums.filter(function(item) {
      return (item.values().date == $q);
    });
    $('.filter').removeClass('active');
    $(this).addClass('active');
  }
});

Return the list items

Lastly, I want to show users how many items are in the list. THe HTML and styling for this is already included in our header.

  • A simple script can be used to append the size of the list to the .count element.
  • Using the List.js filterComplete function means we can also return the number of items in the list after it has been filtered
var $count = $('.count')
$count.append(albums.size());
albums.on('filterComplete', function(){
  $count.text(albums.update().matchingItems.length);
});

Demo

Putting all the code togther we can see our generated list of albums! You can get the full code on Codepen

See the Pen Sort and Filter Using List.js by Matthew Elsom (@matthewelsom) on CodePen.