PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://github.com/xxxazxxx/backbone.paginator
Markdown | 509 lines | 375 code | 134 blank | 0 comment | 0 complexity | 847def022f3572eaf8e7ff739b6459ff MD5 | raw file
Possible License(s): MIT
  1. # Backbone.Paginator (0.5.1-dev)
  2. [![Continuous Integration status](https://secure.travis-ci.org/addyosmani/backbone.paginator.png)](http://travis-ci.org/addyosmani/backbone.paginator)
  3. ![](https://raw.github.com/addyosmani/backbone.paginator/master/media/logo.png)
  4. Backbone.Paginator is a set of opinionated components for paginating collections of data using Backbone.js.
  5. It 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.
  6. ## Downloads And Source Code
  7. You can either download the raw source code for the project, fork the repository or use one of these links:
  8. * Production: [production version][min] 10.2K file size (2.79K gzipped)
  9. * Development: [development version][max] 30.1K file size (6.8K gzipped)
  10. * Examples: [tarball](https://github.com/addyosmani/backbone.paginator/zipball/)
  11. [min]: https://raw.github.com/addyosmani/backbone.paginator/master/dist/backbone.paginator.min.js
  12. [max]: https://raw.github.com/addyosmani/backbone.paginator/master/dist/backbone.paginator.js
  13. We are also available via [Bower](http://twitter.github.com/bower/):
  14. ```shell
  15. bower install backbone.paginator
  16. ```
  17. ## Paginator's pieces
  18. Backbone.Paginator supports two main pagination components:
  19. * **Backbone.Paginator.requestPager**: For pagination of requests between a client and a server-side API
  20. * **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)
  21. ## Live Examples
  22. Live previews of both pagination components using the Netflix API can be found below. Fork the repository to experiment with these examples further.
  23. * [Backbone.Paginator.requestPager()](http://addyosmani.github.com/backbone.paginator/examples/netflix-request-paging/index.html)
  24. * [Backbone.Paginator.clientPager()](http://addyosmani.github.com/backbone.paginator/examples/netflix-client-paging/index.html)
  25. * [Infinite Pagination (Backbone.Paginator.requestPager())](http://addyosmani.github.com/backbone.paginator/examples/netflix-infinite-paging/index.html)
  26. * [Diacritic Plugin](http://addyosmani.github.com/backbone.paginator/examples/google-diacritic/index.html)
  27. ##Paginator.requestPager
  28. In this section we're going to walkthrough actually using the requestPager.
  29. ####1. Create a new Paginated collection
  30. First, we define a new Paginated collection using `Backbone.Paginator.requestPager()` as follows:
  31. ```javascript
  32. var PaginatedCollection = Backbone.Paginator.requestPager.extend({
  33. ```
  34. ####2: Set the model for the collection as normal
  35. Within 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).
  36. ```javascript
  37. model: model,
  38. ```
  39. ####3. Configure the base URL and the type of the request
  40. We 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.
  41. ```javascript
  42. paginator_core: {
  43. // the type of the request (GET by default)
  44. type: 'GET',
  45. // the type of reply (jsonp by default)
  46. dataType: 'jsonp',
  47. // the URL (or base URL) for the service
  48. // if you want to have a more dynamic URL, you can make this a function
  49. // that returns a string
  50. url: 'http://odata.netflix.com/Catalog/People(49446)/TitlesActedIn?'
  51. },
  52. ```
  53. ## Gotchas!
  54. If you use `dataType` **NOT** jsonp, please remove the callback custom parameter inside the `server_api` configuration.
  55. ####4. Configure how the library will show the results
  56. We need to tell the library how many items per page we would like to see, etc...
  57. ```javascript
  58. paginator_ui: {
  59. // the lowest page index your API allows to be accessed
  60. firstPage: 0,
  61. // which page should the paginator start from
  62. // (also, the actual page the paginator is on)
  63. currentPage: 0,
  64. // how many items per page should be shown
  65. perPage: 3,
  66. // a default number of total pages to query in case the API or
  67. // service you are using does not support providing the total
  68. // number of pages for us.
  69. // 10 as a default in case your service doesn't return the total
  70. totalPages: 10
  71. },
  72. ```
  73. ####5. Configure the parameters we want to send to the server
  74. Only the base URL won't be enough for most cases, so you can pass more parameters to the server.
  75. Note how you can use functions instead of hardcoded values, and you can also refer to the values you specified in `paginator_ui`.
  76. ```javascript
  77. server_api: {
  78. // the query field in the request
  79. '$filter': '',
  80. // number of items to return per request/page
  81. '$top': function() { return this.perPage },
  82. // how many results the request should skip ahead to
  83. // customize as needed. For the Netflix API, skipping ahead based on
  84. // page * number of results per page was necessary.
  85. '$skip': function() { return this.currentPage * this.perPage },
  86. // field to sort by
  87. '$orderby': 'ReleaseYear',
  88. // what format would you like to request results in?
  89. '$format': 'json',
  90. // custom parameters
  91. '$inlinecount': 'allpages',
  92. '$callback': 'callback'
  93. },
  94. ```
  95. ## Gotchas!
  96. If you use `$callback`, please ensure that you did use the jsonp as a `dataType` inside your `paginator_core` configuration.
  97. ####6. Finally, configure Collection.parse() and we're done
  98. The 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).
  99. You 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.
  100. ```javascript
  101. parse: function (response) {
  102. // Be sure to change this based on how your results
  103. // are structured (e.g d.results is Netflix specific)
  104. var tags = response.d.results;
  105. //Normally this.totalPages would equal response.d.__count
  106. //but as this particular NetFlix request only returns a
  107. //total count of items for the search, we divide.
  108. this.totalPages = Math.ceil(response.d.__count / this.perPage);
  109. return tags;
  110. }
  111. });
  112. });
  113. ```
  114. ####Convenience methods:
  115. For your convenience, the following methods are made available for use in your views to interact with the `requestPager`:
  116. * **Collection.goTo( n, options )** - go to a specific page
  117. * **Collection.requestNextPage( options )** - go to the next page
  118. * **Collection.requestPreviousPage( options )** - go to the previous page
  119. * **Collection.howManyPer( n )** - set the number of items to display per page
  120. **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.
  121. This option object can use `success` and `error` parameters to pass a function to be executed after server answer.
  122. ```javascript
  123. Collection.goTo(n, {
  124. success: function( collection, response ) {
  125. // called is server request success
  126. },
  127. error: function( collection, response ) {
  128. // called if server request fail
  129. }
  130. });
  131. ```
  132. To manage callback, you could also use the [jqXHR](http://api.jquery.com/jQuery.ajax/#jqXHR) returned by these methods to manage callback.
  133. ```javascript
  134. Collection
  135. .requestNextPage()
  136. .done(function( data, textStatus, jqXHR ) {
  137. // called is server request success
  138. })
  139. .fail(function( data, textStatus, jqXHR ) {
  140. // called if server request fail
  141. })
  142. .always(function( data, textStatus, jqXHR ) {
  143. // do something after server request is complete
  144. });
  145. });
  146. ```
  147. If 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.
  148. ```javascript
  149. Collection.requestPreviousPage({ update: true, remove: false });
  150. ```
  151. ##Paginator.clientPager
  152. The `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'.
  153. ####1. Create a new paginated collection with a model and URL
  154. As with `requestPager`, let's first create a new Paginated `Backbone.Paginator.clientPager` collection, with a model:
  155. ```javascript
  156. var PaginatedCollection = Backbone.Paginator.clientPager.extend({
  157. model: model,
  158. ```
  159. ####2. Configure the base URL and the type of the request
  160. We 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.
  161. ```javascript
  162. paginator_core: {
  163. // the type of the request (GET by default)
  164. type: 'GET',
  165. // the type of reply (jsonp by default)
  166. dataType: 'jsonp',
  167. // the URL (or base URL) for the service
  168. url: 'http://odata.netflix.com/v2/Catalog/Titles?&'
  169. },
  170. ```
  171. ####3. Configure how the library will show the results
  172. We need to tell the library how many items per page we would like to see, etc...
  173. ```javascript
  174. paginator_ui: {
  175. // the lowest page index your API allows to be accessed
  176. firstPage: 1,
  177. // which page should the paginator start from
  178. // (also, the actual page the paginator is on)
  179. currentPage: 1,
  180. // how many items per page should be shown
  181. perPage: 3,
  182. // a default number of total pages to query in case the API or
  183. // service you are using does not support providing the total
  184. // number of pages for us.
  185. // 10 as a default in case your service doesn't return the total
  186. totalPages: 10,
  187. // The total number of pages to be shown as a pagination
  188. // list is calculated by (pagesInRange * 2) + 1.
  189. pagesInRange: 4
  190. },
  191. ```
  192. ####4. Configure the parameters we want to send to the server
  193. Only the base URL won't be enough for most cases, so you can pass more parameters to the server.
  194. Note how you can use functions instead of hardcoded values, and you can also refer to the values you specified in `paginator_ui`.
  195. ```javascript
  196. server_api: {
  197. // the query field in the request
  198. '$filter': 'substringof(\'america\',Name)',
  199. // number of items to return per request/page
  200. '$top': function() { return this.perPage },
  201. // how many results the request should skip ahead to
  202. // customize as needed. For the Netflix API, skipping ahead based on
  203. // page * number of results per page was necessary.
  204. '$skip': function() { return this.currentPage * this.perPage },
  205. // field to sort by
  206. '$orderby': 'ReleaseYear',
  207. // what format would you like to request results in?
  208. '$format': 'json',
  209. // custom parameters
  210. '$inlinecount': 'allpages',
  211. '$callback': 'callback'
  212. },
  213. ```
  214. ####5. Finally, configure Collection.parse() and we're done
  215. And 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.
  216. ```javascript
  217. parse: function (response) {
  218. var tags = response.d.results;
  219. return tags;
  220. }
  221. });
  222. ```
  223. ####Convenience methods:
  224. As mentioned, your views can hook into a number of convenience methods to navigate around UI-paginated data. For `clientPager` these include:
  225. * **Collection.goTo(n)** - go to a specific page
  226. * **Collection.previousPage()** - go to the previous page
  227. * **Collection.nextPage()** - go to the next page
  228. * **Collection.howManyPer(n)** - set how many items to display per page
  229. * **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.
  230. * **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.
  231. ```javascript
  232. this.collection.setFilter(
  233. {'Name': {cmp_method: 'levenshtein', max_distance: 7}}
  234. , "Amreican P" // Note the switched 'r' and 'e', and the 'P' from 'Pie'
  235. );
  236. ```
  237. Also note that the Levenshtein plugin should be loaded and enabled using the ```useLevenshteinPlugin``` variable.
  238. Last but not less important: performing Levenshtein comparison returns the ```distance``` between two strings. It won't let you *search* lengthy text.
  239. The 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.
  240. That 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).
  241. Use Levenshtein only for short texts (titles, names, etc).
  242. * **Collection.doFakeFilter(filterFields, filterWords)** - returns the models count after fake-applying a call to ```Collection.setFilter```.
  243. * **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.
  244. ```javascript
  245. my_collection.setFieldFilter([
  246. {field: 'release_year', type: 'range', value: {min: '1999', max: '2003'}},
  247. {field: 'author', type: 'pattern', value: new RegExp('A*', 'igm')}
  248. ]);
  249. //Rules:
  250. //
  251. //var my_var = 'green';
  252. //
  253. //{field: 'color', type: 'equalTo', value: my_var}
  254. //{field: 'color', type: 'function', value: function(field_value){ return field_value == my_var; } }
  255. //{field: 'color', type: 'required'}
  256. //{field: 'number_of_colors', type: 'min', value: '2'}
  257. //{field: 'number_of_colors', type: 'max', value: '4'}
  258. //{field: 'number_of_colors', type: 'range', value: {min: '2', max: '4'} }
  259. //{field: 'color_name', type: 'minLength', value: '4'}
  260. //{field: 'color_name', type: 'maxLength', value: '6'}
  261. //{field: 'color_name', type: 'rangeLength', value: {min: '4', max: '6'}}
  262. //{field: 'color_name', type: 'oneOf', value: ['green', 'yellow']}
  263. //{field: 'color_name', type: 'pattern', value: new RegExp('gre*', 'ig')}
  264. //{field: 'color_name', type: 'containsAllOf', value: ['green', 'yellow', 'blue']}
  265. ```
  266. * **Collection.doFakeFieldFilter(rules)** - returns the models count after fake-applying a call to ```Collection.setFieldFilter```.
  267. ####Implementation notes:
  268. You can use some variables in your ```View``` to represent the actual state of the paginator.
  269. ```totalUnfilteredRecords``` - Contains the number of records, including all records filtered in any way. (Only available in ```clientPager```)
  270. ```totalRecords``` - Contains the number of records
  271. ```currentPage``` - The actual page were the paginator is at.
  272. ```perPage``` - The number of records the paginator will show per page.
  273. ```totalPages``` - The number of total pages.
  274. ```startRecord``` - The position of the first record shown in the current page (eg 41 to 50 from 2000 records) (Only available in ```clientPager```)
  275. ```endRecord``` - The position of the last record shown in the current page (eg 41 to 50 from 2000 records) (Only available in ```clientPager```)
  276. ```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
  277. the numbers 10, 11, 12, 13(selected), 14, 15, 16.
  278. ```html
  279. <!-- sample template for pagination UI -->
  280. <script type="text/html" id="tmpServerPagination">
  281. <div class="row-fluid">
  282. <div class="pagination span8">
  283. <ul>
  284. <% _.each (pageSet, function (p) { %>
  285. <% if (currentPage == p) { %>
  286. <li class="active"><span><%= p %></span></li>
  287. <% } else { %>
  288. <li><a href="#" class="page"><%= p %></a></li>
  289. <% } %>
  290. <% }); %>
  291. </ul>
  292. </div>
  293. <div class="pagination span4">
  294. <ul>
  295. <% if (currentPage > firstPage) { %>
  296. <li><a href="#" class="serverprevious">Previous</a></li>
  297. <% }else{ %>
  298. <li><span>Previous</span></li>
  299. <% }%>
  300. <% if (currentPage < totalPages) { %>
  301. <li><a href="#" class="servernext">Next</a></li>
  302. <% } else { %>
  303. <li><span>Next</span></li>
  304. <% } %>
  305. <% if (firstPage != currentPage) { %>
  306. <li><a href="#" class="serverfirst">First</a></li>
  307. <% } else { %>
  308. <li><span>First</span></li>
  309. <% } %>
  310. <% if (totalPages != currentPage) { %>
  311. <li><a href="#" class="serverlast">Last</a></li>
  312. <% } else { %>
  313. <li><span>Last</span></li>
  314. <% } %>
  315. </ul>
  316. </div>
  317. </div>
  318. <span class="cell serverhowmany"> Show <a href="#"
  319. class="selected">18</a> | <a href="#" class="">9</a> | <a href="#" class="">12</a> per page
  320. </span>
  321. <span class="divider">/</span>
  322. <span class="cell first records">
  323. Page: <span class="label"><%= currentPage %></span> of <span class="label"><%= totalPages %></span> shown
  324. </span>
  325. </script>
  326. ```
  327. ## Plugins
  328. **Diacritic.js**
  329. A plugin for Backbone.Paginator that replaces diacritic characters (`´`, `˝`, `̏`, `˚`,`~` etc.) with characters that match them most closely. This is particularly useful for filtering.
  330. To enable the plugin, set `this.useDiacriticsPlugin` to true, as can be seen in the example below:
  331. ```javascript
  332. Paginator.clientPager = Backbone.Collection.extend({
  333. // Default values used when sorting and/or filtering.
  334. initialize: function(){
  335. this.useDiacriticsPlugin = true; // use diacritics plugin if available
  336. ...
  337. ```
  338. ## Bootstrapping
  339. By 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.
  340. **Backbone.Paginator.clientPager:**
  341. ```javascript
  342. // Extend the Backbone.Paginator.clientPager with your own configuration options
  343. var MyClientPager = Backbone.Paginator.clientPager.extend({paginator_ui: {}});
  344. // Create an instance of your class and populate with the models of your entire collection
  345. var aClientPager = new MyClientPager([{id: 1, title: 'foo'}, {id: 2, title: 'bar'}]);
  346. // Invoke the bootstrap function
  347. aClientPager.bootstrap();
  348. ```
  349. Note: 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)
  350. **Backbone.Paginator.requestPager:**
  351. ```javascript
  352. // Extend the Backbone.Paginator.requestPager with your own configuration options
  353. var MyRequestPager = Backbone.Paginator.requestPager.extend({paginator_ui: {}});
  354. // Create an instance of your class with the first page of data
  355. var aRequestPager = new MyRequestPager([{id: 1, title: 'foo'}, {id: 2, title: 'bar'}]);
  356. // Invoke the bootstrap function and configure requestPager with 'totalRecords'
  357. aRequestPager.bootstrap({totalRecords: 50});
  358. ```
  359. Note: 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.
  360. More on Backbone bootstrapping: http://ricostacruz.com/backbone-patterns/#bootstrapping_data
  361. ## Release History
  362. Please check CHANGELOG.md for a complete release history/changelog.
  363. ## Team
  364. * [Addy Osmani (addyosmani)](http://github.com/addyosmani) - Developer Programs Engineer, Google
  365. * [Alexander Nestorov (alexandernst)](http://github.com/alexandernst) - Software Developer, EmeIEme
  366. * [Srinivas Kusunam (skusunam)](http://github.com/skusunam) - Mobile\Agile Consultant, Liberty Leap Technologies
  367. * [Leonard Ehrenfried (lenniboy)](http://github.com/lenniboy)
  368. ## Contributing
  369. Indent your code with 2 spaces, strip trailing whitespace and take care to
  370. maintain the existing coding style. Add unit tests for any new or changed
  371. functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
  372. _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!_
  373. ## License
  374. Copyright (c) 2012 Addy Osmani
  375. Licensed under the MIT license.