PageRenderTime 5ms CodeModel.GetById 4ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 1ms

/README.md

https://github.com/xxxazxxx/backbone.paginator
Markdown | 509 lines | 375 code | 134 blank | 0 comment | 0 complexity | 847def022f3572eaf8e7ff739b6459ff MD5 | raw file
  1# Backbone.Paginator (0.5.1-dev)
  2
  3[![Continuous Integration status](https://secure.travis-ci.org/addyosmani/backbone.paginator.png)](http://travis-ci.org/addyosmani/backbone.paginator)
  4
  5![](https://raw.github.com/addyosmani/backbone.paginator/master/media/logo.png)
  6
  7Backbone.Paginator is a set of opinionated components for paginating collections of data using Backbone.js.
  8
  9It aims to provide both solutions for assisting with pagination of requests to a server (e.g an API) as well as pagination of single-loads of data, where we may wish to further paginate a collection of N results into M pages within a view.
 10
 11## Downloads And Source Code
 12
 13You can either download the raw source code for the project, fork the repository or use one of these links:
 14
 15* Production: [production version][min] 10.2K file size (2.79K gzipped)
 16* Development: [development version][max] 30.1K file size (6.8K gzipped)
 17* Examples: [tarball](https://github.com/addyosmani/backbone.paginator/zipball/)
 18
 19[min]: https://raw.github.com/addyosmani/backbone.paginator/master/dist/backbone.paginator.min.js
 20[max]: https://raw.github.com/addyosmani/backbone.paginator/master/dist/backbone.paginator.js
 21
 22We are also available via [Bower](http://twitter.github.com/bower/):
 23
 24```shell
 25bower install backbone.paginator
 26```
 27
 28## Paginator's pieces
 29
 30Backbone.Paginator supports two main pagination components:
 31
 32* **Backbone.Paginator.requestPager**: For pagination of requests between a client and a server-side API
 33* **Backbone.Paginator.clientPager**: For pagination of data returned from a server which you would like to further paginate within the UI (e.g 60 results are returned, paginate into 3 pages of 20)
 34
 35## Live Examples
 36
 37Live previews of both pagination components using the Netflix API can be found below. Fork the repository to experiment with these examples further.
 38
 39* [Backbone.Paginator.requestPager()](http://addyosmani.github.com/backbone.paginator/examples/netflix-request-paging/index.html)
 40* [Backbone.Paginator.clientPager()](http://addyosmani.github.com/backbone.paginator/examples/netflix-client-paging/index.html)
 41* [Infinite Pagination (Backbone.Paginator.requestPager())](http://addyosmani.github.com/backbone.paginator/examples/netflix-infinite-paging/index.html)
 42* [Diacritic Plugin](http://addyosmani.github.com/backbone.paginator/examples/google-diacritic/index.html)
 43
 44##Paginator.requestPager
 45
 46In this section we're going to walkthrough actually using the requestPager.
 47
 48####1. Create a new Paginated collection
 49First, we define a new Paginated collection using `Backbone.Paginator.requestPager()` as follows:
 50
 51```javascript
 52var PaginatedCollection = Backbone.Paginator.requestPager.extend({
 53```
 54####2: Set the model for the collection as normal
 55
 56Within our collection, we then (as normal) specify the model to be used with this collection followed by the URL (or base URL) for the service providing our data (e.g the Netflix API).
 57
 58```javascript
 59        model: model,
 60```
 61####3. Configure the base URL and the type of the request
 62
 63We need to set a base URL. The `type` of the request is `GET` by default, and the `dataType` is `jsonp` in order to enable cross-domain requests.
 64
 65```javascript
 66    paginator_core: {
 67      // the type of the request (GET by default)
 68      type: 'GET',
 69
 70      // the type of reply (jsonp by default)
 71      dataType: 'jsonp',
 72
 73      // the URL (or base URL) for the service
 74      // if you want to have a more dynamic URL, you can make this a function
 75      // that returns a string
 76      url: 'http://odata.netflix.com/Catalog/People(49446)/TitlesActedIn?'
 77    },
 78```
 79
 80## Gotchas!
 81
 82If you use `dataType` **NOT** jsonp, please remove the callback custom parameter inside the `server_api` configuration.
 83
 84####4. Configure how the library will show the results
 85
 86We need to tell the library how many items per page we would like to see, etc...
 87
 88```javascript
 89    paginator_ui: {
 90      // the lowest page index your API allows to be accessed
 91      firstPage: 0,
 92
 93      // which page should the paginator start from
 94      // (also, the actual page the paginator is on)
 95      currentPage: 0,
 96
 97      // how many items per page should be shown
 98      perPage: 3,
 99
100      // a default number of total pages to query in case the API or
101      // service you are using does not support providing the total
102      // number of pages for us.
103      // 10 as a default in case your service doesn't return the total
104      totalPages: 10
105    },
106```
107
108####5. Configure the parameters we want to send to the server
109
110Only the base URL won't be enough for most cases, so you can pass more parameters to the server.
111Note how you can use functions instead of hardcoded values, and you can also refer to the values you specified in `paginator_ui`.
112
113```javascript
114    server_api: {
115      // the query field in the request
116      '$filter': '',
117
118      // number of items to return per request/page
119      '$top': function() { return this.perPage },
120
121      // how many results the request should skip ahead to
122      // customize as needed. For the Netflix API, skipping ahead based on
123      // page * number of results per page was necessary.
124      '$skip': function() { return this.currentPage * this.perPage },
125
126      // field to sort by
127      '$orderby': 'ReleaseYear',
128
129      // what format would you like to request results in?
130      '$format': 'json',
131
132      // custom parameters
133      '$inlinecount': 'allpages',
134      '$callback': 'callback'
135    },
136```
137
138## Gotchas!
139
140If you use `$callback`, please ensure that you did use the jsonp as a `dataType` inside your `paginator_core` configuration.
141
142####6. Finally, configure Collection.parse() and we're done
143
144The last thing we need to do is configure our collection's `parse()` method. We want to ensure we're returning the correct part of our JSON response containing the data our collection will be populated with, which below is `response.d.results` (for the Netflix API).
145
146You might also notice that we're setting `this.totalPages` to the total page count returned by the API. This allows us to define the maximum number of (result) pages available for the current/last request so that we can clearly display this in the UI. It also allows us to infuence whether clicking say, a 'next' button should proceed with a request or not.
147
148```javascript
149        parse: function (response) {
150            // Be sure to change this based on how your results
151            // are structured (e.g d.results is Netflix specific)
152            var tags = response.d.results;
153            //Normally this.totalPages would equal response.d.__count
154            //but as this particular NetFlix request only returns a
155            //total count of items for the search, we divide.
156            this.totalPages = Math.ceil(response.d.__count / this.perPage);
157            return tags;
158        }
159    });
160
161});
162```
163
164####Convenience methods:
165
166For your convenience, the following methods are made available for use in your views to interact with the `requestPager`:
167
168* **Collection.goTo( n, options )** - go to a specific page
169* **Collection.requestNextPage( options )** - go to the next page
170* **Collection.requestPreviousPage( options )** - go to the previous page
171* **Collection.howManyPer( n )** - set the number of items to display per page
172
173**requestPager** collection's methods `.goTo()`, `.requestNextPage()` and `.requestPreviousPage()` are all extension of the original [Backbone Collection.fetch() method](http://documentcloud.github.com/backbone/#Collection-fetch). As so, they all can take the same option object as parameter.
174
175This option object can use `success` and `error` parameters to pass a function to be executed after server answer.
176
177```javascript
178Collection.goTo(n, {
179  success: function( collection, response ) {
180    // called is server request success
181  },
182  error: function( collection, response ) {
183    // called if server request fail
184  }
185});
186```
187
188To manage callback, you could also use the [jqXHR](http://api.jquery.com/jQuery.ajax/#jqXHR) returned by these methods to manage callback.
189
190```javascript
191Collection
192  .requestNextPage()
193  .done(function( data, textStatus, jqXHR ) {
194    // called is server request success
195  })
196  .fail(function( data, textStatus, jqXHR ) {
197    // called if server request fail
198  })
199  .always(function( data, textStatus, jqXHR ) {
200    // do something after server request is complete
201  });
202});
203```
204
205If you'd like to add the incoming models to the current collection, instead of replacing the collection's contents, pass `{update: true, remove: false}` as options to these methods.
206
207```javascript
208Collection.requestPreviousPage({ update: true, remove: false });
209```
210
211##Paginator.clientPager
212
213The `clientPager` works similar to the `requestPager`, except that our configuration values influence the pagination of data already returned at a UI-level. Whilst not shown (yet) there is also a lot more UI logic that ties in with the `clientPager`. An example of this can be seen in 'views/clientPagination.js'.
214
215####1. Create a new paginated collection with a model and URL
216As with `requestPager`, let's first create a new Paginated `Backbone.Paginator.clientPager` collection, with a model:
217
218```javascript
219    var PaginatedCollection = Backbone.Paginator.clientPager.extend({
220
221        model: model,
222```
223
224####2. Configure the base URL and the type of the request
225
226We need to set a base URL. The `type` of the request is `GET` by default, and the `dataType` is `jsonp` in order to enable cross-domain requests.
227
228```javascript
229    paginator_core: {
230      // the type of the request (GET by default)
231      type: 'GET',
232
233      // the type of reply (jsonp by default)
234      dataType: 'jsonp',
235
236      // the URL (or base URL) for the service
237      url: 'http://odata.netflix.com/v2/Catalog/Titles?&'
238    },
239```
240
241####3. Configure how the library will show the results
242
243We need to tell the library how many items per page we would like to see, etc...
244
245```javascript
246    paginator_ui: {
247      // the lowest page index your API allows to be accessed
248      firstPage: 1,
249
250      // which page should the paginator start from
251      // (also, the actual page the paginator is on)
252      currentPage: 1,
253
254      // how many items per page should be shown
255      perPage: 3,
256
257      // a default number of total pages to query in case the API or
258      // service you are using does not support providing the total
259      // number of pages for us.
260      // 10 as a default in case your service doesn't return the total
261      totalPages: 10,
262
263      // The total number of pages to be shown as a pagination
264      // list is calculated by (pagesInRange * 2) + 1.
265      pagesInRange: 4
266    },
267```
268
269####4. Configure the parameters we want to send to the server
270
271Only the base URL won't be enough for most cases, so you can pass more parameters to the server.
272Note how you can use functions instead of hardcoded values, and you can also refer to the values you specified in `paginator_ui`.
273
274```javascript
275    server_api: {
276      // the query field in the request
277      '$filter': 'substringof(\'america\',Name)',
278
279      // number of items to return per request/page
280      '$top': function() { return this.perPage },
281
282      // how many results the request should skip ahead to
283      // customize as needed. For the Netflix API, skipping ahead based on
284      // page * number of results per page was necessary.
285      '$skip': function() { return this.currentPage * this.perPage },
286
287      // field to sort by
288      '$orderby': 'ReleaseYear',
289
290      // what format would you like to request results in?
291      '$format': 'json',
292
293      // custom parameters
294      '$inlinecount': 'allpages',
295      '$callback': 'callback'
296    },
297```
298
299####5. Finally, configure Collection.parse() and we're done
300
301And finally we have our `parse()` method, which in this case isn't concerned with the total number of result pages available on the server as we have our own total count of pages for the paginated data in the UI.
302
303```javascript
304    parse: function (response) {
305            var tags = response.d.results;
306            return tags;
307        }
308
309    });
310```
311
312####Convenience methods:
313
314As mentioned, your views can hook into a number of convenience methods to navigate around UI-paginated data. For `clientPager` these include:
315
316* **Collection.goTo(n)** - go to a specific page
317* **Collection.previousPage()** - go to the previous page
318* **Collection.nextPage()** - go to the next page
319* **Collection.howManyPer(n)** - set how many items to display per page
320* **Collection.setSort(sortBy, sortDirection)** - update sort on the current view. Sorting will automatically detect if you're trying to sort numbers (even if they're strored as strings) and will do the right thing.
321* **Collection.setFilter(filterFields, filterWords)** - filter the current view. Filtering supports multiple words without any specific order, so you'll basically get a full-text search ability. Also, you can pass it only one field from the model, or you can pass an array with fields and all of them will get filtered. Last option is to pass it an object containing a comparison method and rules. Currently, only ```levenshtein``` method is available.
322
323```javascript
324  this.collection.setFilter(
325    {'Name': {cmp_method: 'levenshtein', max_distance: 7}}
326    , "Amreican P" // Note the switched 'r' and 'e', and the 'P' from 'Pie'
327  );
328```
329
330Also note that the Levenshtein plugin should be loaded and enabled using the ```useLevenshteinPlugin``` variable.
331Last but not less important: performing Levenshtein comparison returns the ```distance``` between two strings. It won't let you *search* lengthy text.
332The distance between two strings means the number of characters that should be added, removed or moved to the left or to the right so the strings get equal.
333That means that comparing "Something" in "This is a test that could show something" will return 32, which is bigger than comparing "Something" and "ABCDEFG" (9).
334Use Levenshtein only for short texts (titles, names, etc).
335
336* **Collection.doFakeFilter(filterFields, filterWords)** - returns the models count after fake-applying a call to ```Collection.setFilter```.
337
338* **Collection.setFieldFilter(rules)** - filter each value of each model according to `rules` that you pass as argument. Example: You have a collection of books with 'release year' and 'author'. You can filter only the books that were released between 1999 and 2003. And then you can add another `rule` that will filter those books only to authors who's name start with 'A'. Possible rules: function, required, min, max, range, minLength, maxLength, rangeLength, oneOf, equalTo, containsAllOf, pattern.  Passing this an empty rules set will remove any FieldFilter rules applied.
339```javascript
340  my_collection.setFieldFilter([
341    {field: 'release_year', type: 'range', value: {min: '1999', max: '2003'}},
342    {field: 'author', type: 'pattern', value: new RegExp('A*', 'igm')}
343  ]);
344
345  //Rules:
346  //
347  //var my_var = 'green';
348  //
349  //{field: 'color', type: 'equalTo', value: my_var}
350  //{field: 'color', type: 'function', value: function(field_value){ return field_value == my_var; } }
351  //{field: 'color', type: 'required'}
352  //{field: 'number_of_colors', type: 'min', value: '2'}
353  //{field: 'number_of_colors', type: 'max', value: '4'}
354  //{field: 'number_of_colors', type: 'range', value: {min: '2', max: '4'} }
355  //{field: 'color_name', type: 'minLength', value: '4'}
356  //{field: 'color_name', type: 'maxLength', value: '6'}
357  //{field: 'color_name', type: 'rangeLength', value: {min: '4', max: '6'}}
358  //{field: 'color_name', type: 'oneOf', value: ['green', 'yellow']}
359  //{field: 'color_name', type: 'pattern', value: new RegExp('gre*', 'ig')}
360  //{field: 'color_name', type: 'containsAllOf', value: ['green', 'yellow', 'blue']}
361```
362
363* **Collection.doFakeFieldFilter(rules)** - returns the models count after fake-applying a call to ```Collection.setFieldFilter```.
364
365####Implementation notes:
366
367You can use some variables in your ```View``` to represent the actual state of the paginator.
368
369```totalUnfilteredRecords``` - Contains the number of records, including all records filtered in any way. (Only available in ```clientPager```)
370
371```totalRecords``` - Contains the number of records
372
373```currentPage``` - The actual page were the paginator is at.
374
375```perPage``` - The number of records the paginator will show per page.
376
377```totalPages``` - The number of total pages.
378
379```startRecord``` - The position of the first record shown in the current page (eg 41 to 50 from 2000 records) (Only available in ```clientPager```)
380
381```endRecord``` - The position of the last record shown in the current page (eg 41 to 50 from 2000 records) (Only available in ```clientPager```)
382
383```pagesInRange``` - The number of pages to be drawn on each side of the current page. So if ```pagesInRange``` is 3 and ```currentPage``` is 13 you will get
384the numbers 10, 11, 12, 13(selected), 14, 15, 16.
385
386```html
387<!-- sample template for pagination UI -->
388<script type="text/html" id="tmpServerPagination">
389
390  <div class="row-fluid">
391
392    <div class="pagination span8">
393      <ul>
394        <% _.each (pageSet, function (p) { %>
395        <% if (currentPage == p) { %>
396          <li class="active"><span><%= p %></span></li>
397        <% } else { %>
398          <li><a href="#" class="page"><%= p %></a></li>
399        <% } %>
400        <% }); %>
401      </ul>
402    </div>
403
404    <div class="pagination span4">
405      <ul>
406        <% if (currentPage > firstPage) { %>
407          <li><a href="#" class="serverprevious">Previous</a></li>
408        <% }else{ %>
409          <li><span>Previous</span></li>
410        <% }%>
411        <% if (currentPage < totalPages) { %>
412          <li><a href="#" class="servernext">Next</a></li>
413        <% } else { %>
414          <li><span>Next</span></li>
415        <% } %>
416        <% if (firstPage != currentPage) { %>
417          <li><a href="#" class="serverfirst">First</a></li>
418        <% } else { %>
419          <li><span>First</span></li>
420        <% } %>
421        <% if (totalPages != currentPage) { %>
422          <li><a href="#" class="serverlast">Last</a></li>
423        <% } else { %>
424          <li><span>Last</span></li>
425        <% } %>
426      </ul>
427    </div>
428
429  </div>
430
431  <span class="cell serverhowmany"> Show <a href="#"
432    class="selected">18</a> | <a href="#" class="">9</a> | <a href="#" class="">12</a> per page
433  </span>
434
435  <span class="divider">/</span>
436
437  <span class="cell first records">
438    Page: <span class="label"><%= currentPage %></span> of <span class="label"><%= totalPages %></span> shown
439  </span>
440
441</script>
442```
443
444## Plugins
445
446**Diacritic.js**
447
448A plugin for Backbone.Paginator that replaces diacritic characters (`´`, `˝`, `̏`, `˚`,`~` etc.) with characters that match them most closely. This is particularly useful for filtering.
449
450To enable the plugin, set `this.useDiacriticsPlugin` to true, as can be seen in the example below:
451
452```javascript
453Paginator.clientPager = Backbone.Collection.extend({
454
455    // Default values used when sorting and/or filtering.
456    initialize: function(){
457      this.useDiacriticsPlugin = true; // use diacritics plugin if available
458    ...
459```
460
461## Bootstrapping
462
463By default, both the clientPager and requestPager will make an initial request to the server in order to populate their internal paging data. In order to avoid this additional request, it may be beneficial to bootstrap your Backbone.Paginator instance from data that already exists in the dom.
464
465**Backbone.Paginator.clientPager:**
466```javascript
467// Extend the Backbone.Paginator.clientPager with your own configuration options
468var MyClientPager =  Backbone.Paginator.clientPager.extend({paginator_ui: {}});
469// Create an instance of your class and populate with the models of your entire collection
470var aClientPager = new MyClientPager([{id: 1, title: 'foo'}, {id: 2, title: 'bar'}]);
471// Invoke the bootstrap function
472aClientPager.bootstrap();
473```
474Note: If you intend to bootstrap a clientPager, there is no need to specify a 'paginator_core' object in your configuration (since you should have already populated the clientPager with the entirety of it's necessary data)
475
476**Backbone.Paginator.requestPager:**
477```javascript
478// Extend the Backbone.Paginator.requestPager with your own configuration options
479var MyRequestPager =  Backbone.Paginator.requestPager.extend({paginator_ui: {}});
480// Create an instance of your class with the first page of data
481var aRequestPager = new MyRequestPager([{id: 1, title: 'foo'}, {id: 2, title: 'bar'}]);
482// Invoke the bootstrap function and configure requestPager with 'totalRecords'
483aRequestPager.bootstrap({totalRecords: 50});
484```
485Note: Both the clientPager and requestPager ```bootstrap``` function will accept an options param that will be extended by your Backbone.Paginator instance. However the 'totalRecords' property will be set implicitly by the clientPager.
486
487More on Backbone bootstrapping: http://ricostacruz.com/backbone-patterns/#bootstrapping_data
488
489## Release History
490
491Please check CHANGELOG.md for a complete release history/changelog.
492
493## Team
494
495* [Addy Osmani (addyosmani)](http://github.com/addyosmani) - Developer Programs Engineer, Google
496* [Alexander Nestorov (alexandernst)](http://github.com/alexandernst) - Software Developer, EmeIEme
497* [Srinivas Kusunam (skusunam)](http://github.com/skusunam) - Mobile\Agile Consultant, Liberty Leap Technologies
498* [Leonard Ehrenfried (lenniboy)](http://github.com/lenniboy)
499
500## Contributing
501Indent your code with 2 spaces, strip trailing whitespace and take care to
502maintain the existing coding style. Add unit tests for any new or changed
503functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
504
505_Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "lib" subdirectory!_
506
507## License
508Copyright (c) 2012 Addy Osmani
509Licensed under the MIT license.