List Controller

A powerful Backbone.js library that renders a collection of models as a list with infinite scrolling, sorting, filtering, and search field.







Tips:
• Apply a filter (color or location), then change that filter but hold cmd/ctrl when you do.
• Click on the "count" button to reveal "bulk actions"
• Try searching for name or email.
• Then try typing "loc:" proceeded by a location name. Ex: "loc:oregon".


Overview

In any application dealing with a significant amount of data, displaying a list of data will be necessary. This could be for reporting, work progress, or data review. Whatever the use case, List Controller lessens the burden of generating these list.

List Controller improves DOM responsiveness by lazy loading the rows with infinite scrolling. The library also provides common list actions: sorting, filtering, and bulk actions. Filters are stored in local storage so that the application remembers the last selected filter set. In addition, multiple filters can be saved as a "preset" for quickly using later.

This library has been 3+ years in the making here at Blackstone Audio. It is used everyday in dozens of areas inside our ERP application and is constantly being improved.

List Controller relies heavliy on Dropdown.js for the list actions (sort, filter, and bulk actions).


Basic use

To use List Controller, you at least need these three things:

1) Collection

var Coll = SortableCollection.extend({})

2) List View (view for each row)

var ListView = Backbone.View.extend({
    tagName: 'li',
    className: 'row',
    
    render: function(){
        this.$el.html(this.model.get('label'))
        return this;
    }
})

3) List Controller

var Controller = ListController.extend({
	
	el: '#demo',
	listView: ListView,
	
    // tell infinite scroll to load more when reaching the end of this list
	scrollContext: '#demo .list',
	
	initialize: function(){
		
        var fakeData = [], i=0;
        while(i++<60){ fakeData.push({id: i, label: 'Row '+i})}
        
        this.collection = new Coll(fakeData);
	}	
})

var listController = new Controller();

// later... render the controller
listController.render();

Working Example

Assuming your collection has some data in it, the code above is all you need to render an infinite scrolling list. See demo-2.js


Preparing for other features

Before adding sorts, filters, and other options, it is best to specify the following options on your SortableCollection for things to work right.

key

This is the key used to save selected sorts and filters to local storage. You want this unique so as not to conflict with other ListControllers you may use other places.

defaultSort

How should the collection be sorted upon first loaded?

defaultDesc optional

Should the sort start in descending order?

dbSort optional

Is the sorting done clientside in the browser, or should the server do the sorting instead? If this is set to true, the collection will be refetched when sort changes to allow the server to return the new sorted data.

var Coll = SortableCollection.extend({
    
    key         : 'my-list-controller-collection',
	defaultSort : 'id',
	defaultDesc	: false,
	dbSort      : false,
    
})

Sorting sorts

To allow the user to sort the data, add a list of available sorts to the ListController

var Controller = ListController.extend({
    
    sorts: [
		{label: 'ID', val: 'id'},
		{label: 'Label', val: 'label'},
        {label: 'Custom Label', val: 'custom-label-sort'}
	]
    
})

By default, sorting uses the val, for example "id", and looks for that val on the model: return model.get(val);

If you need a custom sorting function for a particular sort key, like custom-label-sort, you can specify it in the SortableCollection

var Coll = SortableCollection.extend({
    
    sorts: {
		'custom-label-sort': function(model, key, isDesc){
            return model.get('randNum')
		}
	}
    
})

You'll notice sorting by label doesn't use "natural" sorting. You can achieve this by extending backbone collections with this gist.


Filtering filters

This is one of the most powerful features of ListController. It relies on Dropdown.js for rendering and controlling the selection of filters and thus, uses a similar structure.

SortableCollection setup

Filters are saved on the SortableCollection and have filter settings saved there. At bare minimum, you need to specify all the filter keys.

var Coll = SortableCollection.extend({
    
    filters: [
        {key: 'filter-1'},
        {key: 'filter-2'},
        {key: 'filter-3'}
    ]
    
})

If you wish for a filter to default to certain value, you can set that by using val. Note: the value will only be used if the user has not selected another value for that filter.

If you want the filtering to happen on the server for a particular key, add db:true.

var Coll = SortableCollection.extend({
    
    filters: [{ 
        key: 'key-string',
        val: 'preset value',
        db: true
    }]
    
})

Setting up the filters

Filters are setup on the ListController using a hash of filter keys and their settings.

var Controller = ListController.extend({

    filters: {
        'filter-1': { /* settings*/ },
        'filter-2': { /* settings*/ },
        'filter-3': { /* settings*/ },
    }
    
})

Filter settings

label optional: string

The label for the filter will made from the key using _.humanize(). For example, if the key is "filter-1", it will become "Filter 1". If you do not like this default naming convention, you can specify the label to be something else.

prefix optional: string

When a filter value is selected, the value's label will be displayed. Sometimes the value itself conflicts with other filter values. For example, maybe multiple filter keys have a "Many" filter value. It would be confusing the see multiple filters that say "Many" with no other context. Setting a prefix to something like "Filter 1: " will make the selected filter display as "Filter 1: Many".

values hash / hash

This is the core of each filter. It's structure is the same as Dropdown.js. It accepts a hash or a function returning a hash.

values: [
    {label: 'Clear Filter', val: ''},
    'divider',
    {label: 'Even ID', val: 'even'},
    {label: 'Odd ID', val: 'odd'}
]

filterBy optional: string preset / function

When a filter is selected, this is the method that is used to filter the data. There are presets created that can be reviewed under _defaultFilterMethods in ListController.Settings.js

By default, the text preset is used which attempts to compare the filter value on the model itself using a method or attribute of the same name:
model[filterVal].call() || model.get(filterVal)

Other presets include: number, int, array, and model_id.

Or you can use a function for your own logic:
function(model, filterVal, filterKey){}

multi optional: bool

If set to true, multiple values can be selected by holding "cmd/ctrl". It's important to note that this means the filterVal in filterBy will be an array, not a string.

defaultValIndex optional: int

When the filters are "cleared", they go back to the default filter value which is the first (0) filter. This is generally fine most of the time, but if you wish for a different value other than the first to be the default, set the defaultValIndex

w optional: int

Sets the width of the filter dropdown menu.


Filtering Presets filterPresets

For now, see plugins/ListController.Presets.js


Bulk Select w/ Actions bulkActions

For now, see plugins/ListController.BulkSelect.js


License

MIT © Kevin Jantzer


Built by Kevin Jantzer, Blackstone Audio Inc.