UNPKG

backbone-list-controller

Version:

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

368 lines (241 loc) 14.4 kB
<html> <head> <title>Backbone.js ListController View</title> <meta charset="utf-8"> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-44608486-5', 'kjantzer.github.io'); ga('send', 'pageview'); </script> <!-- For creating random list of data --> <script src="lib/chance.min.js"></script> <!-- compiled dependency files; see Gruntfile.js --> <script src="demo-dependencies.js"></script> <link href="style.css" rel="stylesheet" type="text/css"> <script src="list-controller.js"></script> </head> <body> <a href="https://github.com/kjantzer/backbone-list-controller" class="github-corner"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#fff; color:#FFB74D; position: absolute; top: 0; border: 0; right: 0;"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style> <header> <nav class="menu"> <a class="menu-btn" onclick="this.parentNode.classList.toggle('open')"><img src="lib/list.png"></a> <ul id="menu"></ul> </nav> <script> $.getJSON('https://gist.githubusercontent.com/kjantzer/9abbb6cc17d1b6699221/raw', function(menu){ var $menu = $('#menu'); $.each(menu, function(indx, m){ console.log(m); $menu.append('<li>\ <a href="http://kjantzer.github.io/'+m.key+'">'+m.label+'</a>\ <div class="description">'+(m.description||'')+'</div>\ </li>') }) }) </script> <h1>List Controller</h1> <h3>A powerful Backbone.js class that renders a collection of models as a list with infinite scrolling, sorting, filtering, and search field.</h3> <br><br><br><br><br> </header> <section> <div class="demos clearfix panel"> <div id="demo" class="demo"></div> </div> <br> <blockquote> <p><b>Tips:</b> <br>• Apply a filter (color or location), then change that filter but hold cmd/ctrl when you do. <br>• Click on the "count" button to reveal "bulk actions" <br>• Try searching for name or email. <br>• Then try typing "loc:" proceeded by a location name. Ex: "loc:oregon". </p> </blockquote> <hr> <h1>Overview</h1> <p>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.</p> <p>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.</p> <p>This library has been 3+ years in the making here at <a href="http://www.blackstoneaudio.com/">Blackstone Audio</a>. It is used everyday in dozens of areas inside our ERP application and is constantly being improved.</p> <p>List Controller relies heavliy on <a href="http://kjantzer.github.io/backbone-dropdown/">Dropdown.js</a> for the list actions (sort, filter, and bulk actions).</p> <!-- BASIC USE ======================================================== --> <hr> <h1>Basic use</h1> <p>To use List Controller, you at least need these three things:</p> <p><b>1) Collection</b></p> <pre> var Coll = SortableCollection.extend({}) </pre> <p><b>2) List View</b> (view for each row)</p> <pre> var ListView = Backbone.View.extend({ tagName: 'li', className: 'row', render: function(){ this.$el.html(this.model.get('label')) return this; } }) </pre> <p><b>3) List Controller</b></p> <pre> 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(); </pre> <p><b>Working Example</b></p> <p>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</p> <div id="demo-2" class="demo"></div> <!-- PREPARING ======================================================== --> <hr> <h1>Preparing for other features</h1> <p>Before adding sorts, filters, and other options, it is best to specify the following options on your SortableCollection for things to work right.</p> <h3><code>key</code></h3> <p>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.</p> <h3><code>defaultSort</code></h3> <p>How should the collection be sorted upon first loaded?</p> <h3><code>defaultDesc</code> <small>optional</small></h3> <p>Should the sort start in descending order?</p> <h3><code>dbSort</code> <small>optional</small></h3> <p>Is the sorting done clientside in the browser, or should the server do the sorting instead? If this is set to <code>true</code>, the collection will be refetched when sort changes to allow the server to return the new sorted data.</p> <pre> var Coll = SortableCollection.extend({ key : 'my-list-controller-collection', defaultSort : 'id', defaultDesc : false, dbSort : false, }) </pre> <!-- SORTS ======================================================== --> <hr> <h1>Sorting <code>sorts</code></h1> <p>To allow the user to sort the data, add a list of available sorts to the ListController</p> <pre> var Controller = ListController.extend({ sorts: [ {label: 'ID', val: 'id'}, {label: 'Label', val: 'label'}, {label: 'Custom Label', val: 'custom-label-sort'} ] }) </pre> <p>By default, sorting uses the <code>val</code>, for example "id", and looks for that val on the model: <code>return model.get(val);</code></p> <p>If you need a custom sorting function for a particular sort key, like <code>custom-label-sort</code>, you can specify it in the SortableCollection</p> <pre> var Coll = SortableCollection.extend({ sorts: { 'custom-label-sort': function(model, key, isDesc){ return model.get('randNum') } } }) </pre> <div id="demo-3" class="demo"></div> <blockquote> <p>You'll notice sorting by label doesn't use "natural" sorting. You can achieve this by extending backbone collections with <a href="https://gist.github.com/kjantzer/7027717">this gist</a>.</p> </blockquote> <!-- FILTERS ======================================================== --> <hr> <h1>Filtering <code>filters</code></h1> <p>This is one of the most powerful features of ListController. It relies on <a href="http://kjantzer.github.io/backbone-dropdown/">Dropdown.js</a> for rendering and controlling the selection of filters and thus, uses a similar structure.</p> <h4>SortableCollection setup</h4> <p>Filters are saved on the SortableCollection and have filter settings saved there. At bare minimum, you need to specify all the filter keys.</p> <pre> var Coll = SortableCollection.extend({ filters: [ {key: 'filter-1'}, {key: 'filter-2'}, {key: 'filter-3'} ] }) </pre> <p>If you wish for a filter to default to certain value, you can set that by using <code>val</code>. <i>Note: the value will only be used if the user has not selected another value for that filter.</i></p> <p>If you want the filtering to happen on the server for a particular key, add <code>db:true</code>.</p> <pre> var Coll = SortableCollection.extend({ filters: [{ key: 'key-string', val: 'preset value', db: true }] }) </pre> <h4>Setting up the filters</h4> <p>Filters are setup on the ListController using a hash of filter keys and their settings.</p> <pre> var Controller = ListController.extend({ filters: { 'filter-1': { /* settings*/ }, 'filter-2': { /* settings*/ }, 'filter-3': { /* settings*/ }, } }) </pre> <h4>Filter settings</h4> <h3><code>label</code> <small>optional: string</small></h3> <p>The label for the filter will made from the key using <code>_.humanize()</code>. 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 <code>label</code> to be something else.</p> <h3><code>prefix</code> <small>optional: string</small></h3> <p>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".</p> <h3><code>values</code> <small>hash / hash</small></h3> <p>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.</p> <pre> values: [ {label: 'Clear Filter', val: ''}, 'divider', {label: 'Even ID', val: 'even'}, {label: 'Odd ID', val: 'odd'} ] </pre> <h3><code>filterBy</code> <small>optional: string preset / function</small></h3> <p>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 <code>_defaultFilterMethods</code> in ListController.Settings.js</p> <p><b>By default</b>, the <code>text</code> preset is used which attempts to compare the filter value on the model itself using a method or attribute of the same name:<br> <code>model[filterVal].call() || model.get(filterVal)</code></p> <p>Other presets include: <code>number</code>, <code>int</code>, <code>array</code>, and <code>model_id</code>.</p> <p>Or you can use a function for your own logic:<br> <code>function(model, filterVal, filterKey){}</code> </p> <h3><code>multi</code> <small>optional: bool</small></h3> <p>If set to true, multiple values can be selected by holding "cmd/ctrl". It's important to note that this means the <code>filterVal</code> in <code>filterBy</code> <b>will be an array</b>, not a string.</p> <h3><code>defaultValIndex</code> <small>optional: int</small></h3> <p>When the filters are "cleared", they go back to the default filter value which is the <b>first (0)</b> 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 <code>defaultValIndex</code></p> <h3><code>w</code> <small>optional: int</small></h3> <p>Sets the <b>width</b> of the filter dropdown menu.</p> <!-- FILTER PRESETS ======================================================== --> <hr> <h1>Filtering Presets <code>filterPresets</code></h1> <p>For now, see <code>plugins/ListController.Presets.js</code></p> <p><img src="http://i.imgur.com/zxAkAfY.gif"></p> <!-- BULK ACTIONS ======================================================== --> <hr> <h1>Bulk Select w/ Actions <code>bulkActions</code></h1> <p>For now, see <code>plugins/ListController.BulkSelect.js</code></p> <p><img src="http://i.imgur.com/Yf0JQEm.gif"></p> <hr> <h1>License</h1> <p>MIT © <a href="http://kevinjantzer.com">Kevin Jantzer</a></p> <hr> <small>Built by <a href="http://kevinjantzer.com">Kevin Jantzer</a>, <a href="http://blackstoneaudio.com">Blackstone Audio Inc.</a></small> </section> <script id="template-row" type="text/html"> <div class="color-{{color}}"></div> <h4>{{label}} <span>{{email}}</h4> <p>{{location}}</p> <div class="v-compare"><br>I am only displayed when in the comparison mode. More data can be loaded here to compare the records.<br><br>Compare mode is limited to 8 results as it becomes unusable with too many.<br><br>A `view:change` event is triggered for each change.</div> </script> <script type="text/javascript" src="demos/demo.js"></script> <script type="text/javascript" src="demos/demo-2.js"></script> <script type="text/javascript" src="demos/demo-3.js"></script> </body> </html>