News

Use jQuery to enhance lists, tables and menus

dynatable

Lists, tables, and select boxes; not the most glamorous parts of web development, but we’ll be looking at three libraries that enhance each of them. That means you can safely use them without worrying about users that aren’t using JavaScript. We’ll use List.js (listjs.com) to enhance <ul>s and <ol>s; Dynatable (www.dynatable.com) to add searching and pagination to tables; and Chosen (bit.ly/1faIhQG) to enrich <select> elements.

These three libraries set out to improve usability, and each of them offers a surprisingly flexible API for complex use cases while being as simple as a single line if you just want to drop them in. Styling inputs to work across browsers can be painful, but Chosen does this for you while making it easy to override (or just not include) its default CSS styles. We’ll use the same data for the table and list, a dataset of endangered species in the UK. We’ll use Chosen to filter this dataset down by type. You’ll learn how to use these libraries and further customise them to fit in with your site’s needs.

Initialise Dynatable

Let’s start with tables. Tables can be unwieldy, boring, and make it hard to find what you’re looking for. Simply by invoking ‘dynatable()’ on a selector it will add pagination and searching to your table using all of the data within it. The best thing is that it just works, and you can style it however you wish as classes are namespaced with ‘dynatable’.

001 $('table').dynatable();

Configure Dynatable

You can also change the default amount of items to show by passing a configuration object where you specify the per-page default, and then how many to increment by (if you know the size of your dataset when you initialise then you could do a calculation to find the most efficient number to show).

001  $('.dynatable').dynatable({
002      dataset: {,
003          perPageDefault: 5,
004          perPageOptions: [5, 10]
005            }
006  });

Selective features

If you didn’t want to use all of Dynatable’s features you can selectively turn them on and off with a Boolean when you initialise it. You could use the global configuration object to do this globally (using ‘$.dynatableSetup({})’), or use a class name approach if you have many tables with different needs.

001  $(‘.dynatable.no-pagination’).dynatable({
002      features: {
003           paginate: false, 
004           sorting: false
005       }
006  });

AJAX the table

Our tables can be populated with data from a server by setting ‘ajax’ to true and providing it the URL to get the data from. ‘records’ needs to be an array, not undefined, else it’ll throw an error. We’re using a hardcoded JSON file but it could be an API call or anything that returns records in the right syntax.

001 dataset: {
002     ajax: true,
003     ajaxUrl: 'js/endangered.json',
004     records: [], 
005     perPageDefault: 5,
006     perPageOptions: [5, 10, 15, 20]
007 }

Expected response

The JSON response must have an array called ‘records’, where each property name will relate to a column (<th>) in the table. ‘queryRecordCount’ and ‘totalRecordCount’ must also be present if used with pagination so that Dynatable is able to show how many results that there are in total and whether or not this set is a filtered dataset.

001 {
002     "records": [{
003         "common_name": "Bottlenose dolphin",
004         "description": "warm and temperate seas     worldwide",
005         "conservation_status": “LC”,
006         "kingdom": "mammal"
007     }],
008     "queryRecordCount": 1,
009     "totalRecordCount": 1
010 }

Working with JSON

The general convention for JSON is ‘an_underscore’ between each word. To configure Dynatable to look for the corresponding <th> we set the ‘defaultColumnIdStyle’ property to ‘underscore’. We can also specify the text to show when the table is loading (when users want to go to the next set of results).

001 table: {
002     defaultColumnIdStyle: 'underscore'
003 },
004 inputs: {
005     processingText: 'Loading <img src="loader.gif" />'
006 }

Pagination requests

Each time the user clicks to go to another page in the table, a request is made with some query parameters. These are meant to be used by your API and return the right set of data (with AJAX tables Dynatable assumes all filtering is done server-side). If you share the URL people will be able to go to that exact point without slogging through a giant table.

001 http://localhost:8000/?perPage=15&page=2&offset=15

Be Chosen

We’ve improved our table, but let’s not stop there – what about the oft-neglected select box? A handy jQuery plug-in called Chosen is our hero here. Again, its initial usage is very straightforward, simply pass the elements that you want to turbocharge and fire up Chosen. It’ll add searching and cross-browser styling to all select elements.

001 <select id="chosen" name="chosen">
002 $('select').chosen();

Multiple Chosen

Chosen takes a lot of clues from the (valid) markup itself without having to add any additional data attributes or classes. Want users to be able to select multiple values from the list? No problem, just use the multiple attribute and all users will get a good experience, whether JavaScript is enabled or not.

001 <select id="chosen" name="chosen" multiple>

Customise text

Chosen adds an inline search bar to the drop-down menu, but if the user types something that doesn’t exist then it’ll show some default micro copy. If you’ve already got a defined voice that you want to keep to then you can customise it with the ‘no_results_text’ property when you initialise Chosen.

001 var chosen = $('select').chosen({no_results_text:     'Oops, nothing found for '});

Respond to change

If you would like to listen for when a user chooses different values then you can attach a callback to the change event. It passes you two objects: the event and what the user selected. This is a slightly nicer way of getting to the real information that you’re (probably) looking for. We’re looping through all values as we’re using ‘multiple’.

001 chosen.change(function(event, value) {
002     list.filter(function (item) {
003         if (chosen.val() !== null) {
004             for (var i = 0, len = chosen.val().length; i < len; i++)
005                  if (item.values().kingdom === chosen.val()[i]) return true;
006         } else return true;
007     });
008 });

Working with groups

Chosen also works well with <optgroup>, which groups together related options. Chosen visually represents this with a label breaking up the options. You can optionally use the data-placeholder (because placeholder isn’t a valid attribute on a select element) to add custom text to prompt the user.

001 <select id="chosen" name="chosen" data-        placeholder="Choose a kingdom" multiple>
002     <option value="mammal">Mammal</option>
003     <option value="bird">Bird</option>
004     <optgroup label="Insects">
005         <option value="ant">Ants</option>
006         <option value="bee">Bees</option>
007     </optgroup>
008 </select>
009 

Label support

Chosen will also work with labels, so if you click a corresponding label then the drop-down menu will open. On a separate note, if you’re using box-sizing: border-box then you’ll have to use the older box model for Chosen elements, otherwise placeholder text will appear to be cut off as it uses padding to add to the elements’ height.

001 <label for=“chosen">Select&hellip;</label>
002 /* css */
003 .chosen-container * {        
004     -webkit-box-sizing: content-box;
005     -moz-box-sizing: content-box;
006     box-sizing: content-box;
007 }

List template

Tables, select boxes, and now lists. The aptly named List.js takes a slightly different approach to building a list than you might have expected. We start with a hidden element, inside is the markup for each <li> to display. This works as if we were using a templating framework such as Mustache, just without the placeholders for variables.

001 <div style="display:none;">
002     <li id="species-item">
003         <h3></h3>
004         <p></p>
005     </li>
006 </div>
007 

List markup

Each list must be wrapped in a selector so that when we initialise List.js it knows which elements it’s looking for – by default this is an ID. If you’re not using an ID then it needs to be the actual elements (not just strings). Other than that we’re using classes from Twitter Bootstrap to style it.

001 <div id="species-list">
002    <ul></ul>
003 </div>
004 

Initialise List.js

Initialising List.js is pretty straightforward. We have an options object tell it what the item’s ID is so that it can template each list item; the number to show on each ‘page’; and an array of any plug-ins that we want to use. We’re going to be setting these plug-ins up a few steps on.

001 var options = {
002     item: 'species-item',
003     page: 10,
004     plugins: [
005         ListFuzzySearch(),
006         ListPagination()    007     ]    008 };    009 var list = new List('species-list', options);

Add features

Once we’ve got our basic List.js list working we can take advantage of some of its features. Simply by adding an input with a class of search will add real-time search functionality to your list. Likewise, if you add a class of pagination to an element then you’ll get instant pagination (working with the ‘page’ property we initialised with).

001 <div id="species-list">
002     <input type="search"     placeholder="Search" />
003     <ulpagination"></ol>
005 </div>

Sort lists

If instant searching and pagination weren’t enough, it makes another list task simple: sorting. We’re going to add a button with a class of ‘sort’ (this is what List.js attaches the functionality to) and add an attribute called data-sort. Data-sort is the name of the corresponding field you wish to sort by.

001 <div id="species-list">
 002     <input type="search"     placeholder="Search" />
 003     <button data-        sort="name">Sort by name</button>
 004     <ul></ul>
 005 </div>

Install plug-ins

We’re going to push the searching frontier further and use a List.js plug-in called fuzzy search. Unfortunately it doesn’t add fuzzy bears searching for things to your site, it just means that if a user types ‘dphin’ it will return ‘dolphin’, which allows for some user error in input. You can use Bower or simply download it from the List.js site.

001 # Terminal
 002 $ bower install list.fuzzysearch.js # or download
 003 $ bower install list.pagination.js # or download    
004 <!-- HTML -->    
005 <script src=“path/to/list.fuzzysearch.js"></script>    
006 <script src=“path/to/list.pagination.js"></script>    
007

Fuzzy options

Here we need to do now is change our input that has a class of ‘search’ to ‘fuzzy-search’. It also takes a number of options, including the class to search for; where to start looking in the input string; exactly how far away a matching letter is allowed to be (distance); and when the algorithm should stop looking for a match (threshold).

001 <div id="species-list">
 002     <input type="search"         placeholder="Fuzzy Search">
 003 </div>
 004 var fuzzyOptions = {
 005     searchClass: 'fuzzy-search',
 006     location: 0,
 007     distance: 100,
 008     threshold: 0.4,
 009     multiSearch: true
 010 };
 011 

Adding and sorting

We can programmatically add new items to our list, meaning that we could make an AJAX call to get more items and use this as a callback. We’ll sort the list by name and then fade in each item in turn. There’s also a remove method that takes which value to look for and what it is equal to.

001 var addSpecies = function (species) {
 002     list.add(species, function (items) {
 003         //fired when all items added
 004         list.sort('name', { asc: true });
 005          for (var i = 0, len = items.length; i > len; i++) {
 006             items[i].hide();
 007             $(items[i].elm).fadeIn();
 008         }
 009     });
 010 };

List filtering

Finally, in this step we’re going to filter out all of the species that haven’t got a common name. Similarly to the native JavaScript filter function, List.js’ filter method takes a function that is passed each item in the list. This function must then return true for items that you want to keep in the list; if you don’t return anything, it will be discarded.

001 list.filter(function (item) {
 002     return item.values().common_name.length;
 003 });
×