backbone-paginator 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +30 -0
  6. data/Rakefile +19 -0
  7. data/backbone-paginator.gemspec +26 -0
  8. data/lib/backbone-paginator.rb +2 -0
  9. data/lib/backbone-paginator/engine.rb +4 -0
  10. data/lib/backbone-paginator/version.rb +3 -0
  11. data/test/backbone.paginator_test.rb +12 -0
  12. data/test/dummy/.gitignore +16 -0
  13. data/test/dummy/README.rdoc +28 -0
  14. data/test/dummy/Rakefile +6 -0
  15. data/test/dummy/app/assets/images/.keep +0 -0
  16. data/test/dummy/app/assets/javascripts/application.js +16 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  18. data/test/dummy/app/controllers/application_controller.rb +5 -0
  19. data/test/dummy/app/controllers/concerns/.keep +0 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/mailers/.keep +0 -0
  22. data/test/dummy/app/models/.keep +0 -0
  23. data/test/dummy/app/models/concerns/.keep +0 -0
  24. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  25. data/test/dummy/config.ru +4 -0
  26. data/test/dummy/config/application.rb +24 -0
  27. data/test/dummy/config/boot.rb +4 -0
  28. data/test/dummy/config/database.yml +25 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +29 -0
  31. data/test/dummy/config/environments/production.rb +80 -0
  32. data/test/dummy/config/environments/test.rb +36 -0
  33. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  35. data/test/dummy/config/initializers/inflections.rb +16 -0
  36. data/test/dummy/config/initializers/mime_types.rb +5 -0
  37. data/test/dummy/config/initializers/secret_token.rb +12 -0
  38. data/test/dummy/config/initializers/session_store.rb +3 -0
  39. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  40. data/test/dummy/config/locales/en.yml +23 -0
  41. data/test/dummy/config/routes.rb +56 -0
  42. data/test/dummy/db/seeds.rb +7 -0
  43. data/test/dummy/lib/assets/.keep +0 -0
  44. data/test/dummy/lib/tasks/.keep +0 -0
  45. data/test/dummy/log/.keep +0 -0
  46. data/test/dummy/public/404.html +58 -0
  47. data/test/dummy/public/422.html +58 -0
  48. data/test/dummy/public/500.html +57 -0
  49. data/test/dummy/public/favicon.ico +0 -0
  50. data/test/dummy/public/robots.txt +5 -0
  51. data/test/dummy/test/controllers/.keep +0 -0
  52. data/test/dummy/test/fixtures/.keep +0 -0
  53. data/test/dummy/test/helpers/.keep +0 -0
  54. data/test/dummy/test/integration/.keep +0 -0
  55. data/test/dummy/test/mailers/.keep +0 -0
  56. data/test/dummy/test/models/.keep +0 -0
  57. data/test/dummy/test/test_helper.rb +15 -0
  58. data/test/dummy/vendor/assets/javascripts/.keep +0 -0
  59. data/test/dummy/vendor/assets/stylesheets/.keep +0 -0
  60. data/test/test_helper.rb +5 -0
  61. data/vendor/assets/javascripts/backbone.paginator.js +1050 -0
  62. metadata +210 -0
@@ -0,0 +1,36 @@
1
+ Dummy::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Do not eager load code on boot. This avoids loading your whole application
11
+ # just for the purpose of running a single test. If you are using a tool that
12
+ # preloads Rails for running tests, you may have to set it to true.
13
+ config.eager_load = false
14
+
15
+ # Configure static asset server for tests with Cache-Control for performance.
16
+ config.serve_static_assets = true
17
+ config.static_cache_control = "public, max-age=3600"
18
+
19
+ # Show full error reports and disable caching.
20
+ config.consider_all_requests_local = true
21
+ config.action_controller.perform_caching = false
22
+
23
+ # Raise exceptions instead of rendering exception templates.
24
+ config.action_dispatch.show_exceptions = false
25
+
26
+ # Disable request forgery protection in test environment.
27
+ config.action_controller.allow_forgery_protection = false
28
+
29
+ # Tell Action Mailer not to deliver emails to the real world.
30
+ # The :test delivery method accumulates sent emails in the
31
+ # ActionMailer::Base.deliveries array.
32
+ config.action_mailer.delivery_method = :test
33
+
34
+ # Print deprecation notices to the stderr.
35
+ config.active_support.deprecation = :stderr
36
+ end
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Configure sensitive parameters which will be filtered from the log file.
4
+ Rails.application.config.filter_parameters += [:password]
@@ -0,0 +1,16 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format. Inflections
4
+ # are locale specific, and you may define rules for as many different
5
+ # locales as you wish. All of these examples are active by default:
6
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
7
+ # inflect.plural /^(ox)$/i, '\1en'
8
+ # inflect.singular /^(ox)en/i, '\1'
9
+ # inflect.irregular 'person', 'people'
10
+ # inflect.uncountable %w( fish sheep )
11
+ # end
12
+
13
+ # These inflection rules are supported but not enabled by default:
14
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
15
+ # inflect.acronym 'RESTful'
16
+ # end
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
@@ -0,0 +1,12 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key is used for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+
6
+ # Make sure the secret is at least 30 characters and all random,
7
+ # no regular words or you'll be exposed to dictionary attacks.
8
+ # You can use `rake secret` to generate a secure secret key.
9
+
10
+ # Make sure your secret_key_base is kept private
11
+ # if you're sharing your code publicly.
12
+ Dummy::Application.config.secret_key_base = '6a59172706aae2520a82aeb0b01f9be0b25e8202998da91296bf9f282d970725bd64aa00aa292fb3416e89456d56f47cb7f451898930de6da1f670c7c7af6a22'
@@ -0,0 +1,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
@@ -0,0 +1,14 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # This file contains settings for ActionController::ParamsWrapper which
4
+ # is enabled by default.
5
+
6
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7
+ ActiveSupport.on_load(:action_controller) do
8
+ wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9
+ end
10
+
11
+ # To enable root element in JSON for ActiveRecord objects.
12
+ # ActiveSupport.on_load(:active_record) do
13
+ # self.include_root_in_json = true
14
+ # end
@@ -0,0 +1,23 @@
1
+ # Files in the config/locales directory are used for internationalization
2
+ # and are automatically loaded by Rails. If you want to use locales other
3
+ # than English, add the necessary files in this directory.
4
+ #
5
+ # To use the locales, use `I18n.t`:
6
+ #
7
+ # I18n.t 'hello'
8
+ #
9
+ # In views, this is aliased to just `t`:
10
+ #
11
+ # <%= t('hello') %>
12
+ #
13
+ # To use a different locale, set it with `I18n.locale`:
14
+ #
15
+ # I18n.locale = :es
16
+ #
17
+ # This would use the information in config/locales/es.yml.
18
+ #
19
+ # To learn more, please read the Rails Internationalization guide
20
+ # available at http://guides.rubyonrails.org/i18n.html.
21
+
22
+ en:
23
+ hello: "Hello world"
@@ -0,0 +1,56 @@
1
+ Dummy::Application.routes.draw do
2
+ # The priority is based upon order of creation: first created -> highest priority.
3
+ # See how all your routes lay out with "rake routes".
4
+
5
+ # You can have the root of your site routed with "root"
6
+ # root 'welcome#index'
7
+
8
+ # Example of regular route:
9
+ # get 'products/:id' => 'catalog#view'
10
+
11
+ # Example of named route that can be invoked with purchase_url(id: product.id)
12
+ # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13
+
14
+ # Example resource route (maps HTTP verbs to controller actions automatically):
15
+ # resources :products
16
+
17
+ # Example resource route with options:
18
+ # resources :products do
19
+ # member do
20
+ # get 'short'
21
+ # post 'toggle'
22
+ # end
23
+ #
24
+ # collection do
25
+ # get 'sold'
26
+ # end
27
+ # end
28
+
29
+ # Example resource route with sub-resources:
30
+ # resources :products do
31
+ # resources :comments, :sales
32
+ # resource :seller
33
+ # end
34
+
35
+ # Example resource route with more complex sub-resources:
36
+ # resources :products do
37
+ # resources :comments
38
+ # resources :sales do
39
+ # get 'recent', on: :collection
40
+ # end
41
+ # end
42
+
43
+ # Example resource route with concerns:
44
+ # concern :toggleable do
45
+ # post 'toggle'
46
+ # end
47
+ # resources :posts, concerns: :toggleable
48
+ # resources :photos, concerns: :toggleable
49
+
50
+ # Example resource route within a namespace:
51
+ # namespace :admin do
52
+ # # Directs /admin/products/* to Admin::ProductsController
53
+ # # (app/controllers/admin/products_controller.rb)
54
+ # resources :products
55
+ # end
56
+ end
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7
+ # Mayor.create(name: 'Emanuel', city: cities.first)
File without changes
File without changes
File without changes
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/404.html -->
52
+ <div class="dialog">
53
+ <h1>The page you were looking for doesn't exist.</h1>
54
+ <p>You may have mistyped the address or the page may have moved.</p>
55
+ </div>
56
+ <p>If you are the application owner check the logs for more information.</p>
57
+ </body>
58
+ </html>
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/422.html -->
52
+ <div class="dialog">
53
+ <h1>The change you wanted was rejected.</h1>
54
+ <p>Maybe you tried to change something you didn't have access to.</p>
55
+ </div>
56
+ <p>If you are the application owner check the logs for more information.</p>
57
+ </body>
58
+ </html>
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/500.html -->
52
+ <div class="dialog">
53
+ <h1>We're sorry, but something went wrong.</h1>
54
+ </div>
55
+ <p>If you are the application owner check the logs for more information.</p>
56
+ </body>
57
+ </html>
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ ENV["RAILS_ENV"] ||= "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ ActiveRecord::Migration.check_pending!
7
+
8
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
9
+ #
10
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
11
+ # -- they do not yet inherit this setting
12
+ fixtures :all
13
+
14
+ # Add more helper methods to be used by all tests here...
15
+ end
@@ -0,0 +1,5 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
3
+ require "rails/test_help"
4
+ Rails.backtrace_cleaner.remove_silencers!
5
+ require 'rails/generators/test_case'
@@ -0,0 +1,1050 @@
1
+ /*! backbone.paginator - v0.8.1 - 7/3/2013
2
+ * http://github.com/addyosmani/backbone.paginator
3
+ * Copyright (c) 2013 Addy Osmani; Licensed MIT */
4
+ /*globals Backbone:true, _:true, jQuery:true*/
5
+ Backbone.Paginator = (function ( Backbone, _, $ ) {
6
+ "use strict";
7
+
8
+
9
+ var bbVer = _.map(Backbone.VERSION.split('.'), function(digit) {
10
+ return parseInt(digit, 10);
11
+ });
12
+
13
+ var Paginator = {};
14
+ Paginator.version = "0.8.1";
15
+
16
+ // @name: clientPager
17
+ //
18
+ // @tagline: Paginator for client-side data
19
+ //
20
+ // @description:
21
+ // This paginator is responsible for providing pagination
22
+ // and sort capabilities for a single payload of data
23
+ // we wish to paginate by the UI for easier browsering.
24
+ //
25
+ Paginator.clientPager = Backbone.Collection.extend({
26
+
27
+ // DEFAULTS FOR SORTING & FILTERING
28
+ useDiacriticsPlugin: true, // use diacritics plugin if available
29
+ useLevenshteinPlugin: true, // use levenshtein plugin if available
30
+ sortColumn: "",
31
+ sortDirection: "desc",
32
+ lastSortColumn: "",
33
+ fieldFilterRules: [],
34
+ lastFieldFilterRules: [],
35
+ filterFields: "",
36
+ filterExpression: "",
37
+ lastFilterExpression: "",
38
+
39
+ //DEFAULT PAGINATOR UI VALUES
40
+ defaults_ui: {
41
+ firstPage: 0,
42
+ currentPage: 1,
43
+ perPage: 5,
44
+ totalPages: 10,
45
+ pagesInRange: 4
46
+ },
47
+
48
+ // Default values used when sorting and/or filtering.
49
+ initialize: function(){
50
+ //LISTEN FOR ADD & REMOVE EVENTS THEN REMOVE MODELS FROM ORGINAL MODELS
51
+ this.on('add', this.addModel, this);
52
+ this.on('remove', this.removeModel, this);
53
+
54
+ // SET DEFAULT VALUES (ALLOWS YOU TO POPULATE PAGINATOR MAUNALLY)
55
+ this.setDefaults();
56
+ },
57
+
58
+
59
+ setDefaults: function() {
60
+ // SET DEFAULT UI SETTINGS
61
+ var options = _.defaults(this.paginator_ui, this.defaults_ui);
62
+
63
+ //UPDATE GLOBAL UI SETTINGS
64
+ _.defaults(this, options);
65
+ },
66
+
67
+ addModel: function(model) {
68
+ this.origModels.push(model);
69
+ },
70
+
71
+ removeModel: function(model) {
72
+ var index = _.indexOf(this.origModels, model);
73
+
74
+ this.origModels.splice(index, 1);
75
+ },
76
+
77
+ sync: function ( method, model, options ) {
78
+ var self = this;
79
+
80
+ // SET DEFAULT VALUES
81
+ this.setDefaults();
82
+
83
+ // Some values could be functions, let's make sure
84
+ // to change their scope too and run them
85
+ var queryAttributes = {};
86
+ _.each(_.result(self, "server_api"), function(value, key){
87
+ if( _.isFunction(value) ) {
88
+ value = _.bind(value, self);
89
+ value = value();
90
+ }
91
+ queryAttributes[key] = value;
92
+ });
93
+
94
+ var queryOptions = _.clone(self.paginator_core);
95
+ _.each(queryOptions, function(value, key){
96
+ if( _.isFunction(value) ) {
97
+ value = _.bind(value, self);
98
+ value = value();
99
+ }
100
+ queryOptions[key] = value;
101
+ });
102
+
103
+ // Create default values if no others are specified
104
+ queryOptions = _.defaults(queryOptions, {
105
+ timeout: 25000,
106
+ cache: false,
107
+ type: 'GET',
108
+ dataType: 'jsonp'
109
+ });
110
+
111
+ queryOptions = _.extend(queryOptions, {
112
+ data: decodeURIComponent($.param(queryAttributes)),
113
+ processData: false,
114
+ url: _.result(queryOptions, 'url')
115
+ }, options);
116
+
117
+ var promiseSuccessFormat = !(bbVer[0] === 0 &&
118
+ bbVer[1] === 9 &&
119
+ bbVer[2] === 10);
120
+
121
+ var success = queryOptions.success;
122
+ queryOptions.success = function ( resp, status, xhr ) {
123
+ if ( success ) {
124
+ // This is to keep compatibility with Backbone 0.9.10
125
+ if (promiseSuccessFormat) {
126
+ success( resp, status, xhr );
127
+ } else {
128
+ success( model, resp, queryOptions );
129
+ }
130
+ }
131
+ if ( model && model.trigger ) {
132
+ model.trigger( 'sync', model, resp, queryOptions );
133
+ }
134
+ };
135
+
136
+ var error = queryOptions.error;
137
+ queryOptions.error = function ( xhr ) {
138
+ if ( error ) {
139
+ error( model, xhr, queryOptions );
140
+ }
141
+ if ( model && model.trigger ) {
142
+ model.trigger( 'error', model, xhr, queryOptions );
143
+ }
144
+ };
145
+
146
+ var xhr = queryOptions.xhr = Backbone.ajax( queryOptions );
147
+ if ( model && model.trigger ) {
148
+ model.trigger('request', model, xhr, queryOptions);
149
+ }
150
+ return xhr;
151
+ },
152
+
153
+ nextPage: function (options) {
154
+ if(this.currentPage < this.information.totalPages) {
155
+ this.currentPage = ++this.currentPage;
156
+ this.pager(options);
157
+ }
158
+ },
159
+
160
+ previousPage: function (options) {
161
+ if(this.currentPage > 1) {
162
+ this.currentPage = --this.currentPage;
163
+ this.pager(options);
164
+ }
165
+ },
166
+
167
+ goTo: function ( page, options ) {
168
+ if(page !== undefined){
169
+ this.currentPage = parseInt(page, 10);
170
+ this.pager(options);
171
+ }
172
+ },
173
+
174
+ howManyPer: function ( perPage ) {
175
+ if(perPage !== undefined){
176
+ var lastPerPage = this.perPage;
177
+ this.perPage = parseInt(perPage, 10);
178
+ this.currentPage = Math.ceil( ( lastPerPage * ( this.currentPage - 1 ) + 1 ) / perPage);
179
+ this.pager();
180
+ }
181
+ },
182
+
183
+
184
+ // setSort is used to sort the current model. After
185
+ // passing 'column', which is the model's field you want
186
+ // to filter and 'direction', which is the direction
187
+ // desired for the ordering ('asc' or 'desc'), pager()
188
+ // and info() will be called automatically.
189
+ setSort: function ( column, direction ) {
190
+ if(column !== undefined && direction !== undefined){
191
+ this.lastSortColumn = this.sortColumn;
192
+ this.sortColumn = column;
193
+ this.sortDirection = direction;
194
+ this.pager();
195
+ this.info();
196
+ }
197
+ },
198
+
199
+ // setFieldFilter is used to filter each value of each model
200
+ // according to `rules` that you pass as argument.
201
+ // Example: You have a collection of books with 'release year' and 'author'.
202
+ // You can filter only the books that were released between 1999 and 2003
203
+ // And then you can add another `rule` that will filter those books only to
204
+ // authors who's name start with 'A'.
205
+ setFieldFilter: function ( fieldFilterRules ) {
206
+ if( !_.isEmpty( fieldFilterRules ) ) {
207
+ this.lastFieldFilterRules = this.fieldFilterRules;
208
+ this.fieldFilterRules = fieldFilterRules;
209
+ this.pager();
210
+ this.info();
211
+ // if all the filters are removed, we should save the last filter
212
+ // and then let the list reset to it's original state.
213
+ } else {
214
+ this.lastFieldFilterRules = this.fieldFilterRules;
215
+ this.fieldFilterRules = '';
216
+ this.pager();
217
+ this.info();
218
+ }
219
+ },
220
+
221
+ // doFakeFieldFilter can be used to get the number of models that will remain
222
+ // after calling setFieldFilter with a filter rule(s)
223
+ doFakeFieldFilter: function ( rules ) {
224
+ if( !_.isEmpty( rules ) ) {
225
+ var testModels = this.origModels;
226
+ if (testModels === undefined) {
227
+ testModels = this.models;
228
+ }
229
+
230
+ testModels = this._fieldFilter(testModels, rules);
231
+
232
+ // To comply with current behavior, also filter by any previously defined setFilter rules.
233
+ if ( this.filterExpression !== "" ) {
234
+ testModels = this._filter(testModels, this.filterFields, this.filterExpression);
235
+ }
236
+
237
+ // Return size
238
+ return testModels.length;
239
+ }
240
+
241
+ },
242
+
243
+ // setFilter is used to filter the current model. After
244
+ // passing 'fields', which can be a string referring to
245
+ // the model's field, an array of strings representing
246
+ // each of the model's fields or an object with the name
247
+ // of the model's field(s) and comparing options (see docs)
248
+ // you wish to filter by and
249
+ // 'filter', which is the word or words you wish to
250
+ // filter by, pager() and info() will be called automatically.
251
+ setFilter: function ( fields, filter ) {
252
+ if( fields !== undefined && filter !== undefined ){
253
+ this.filterFields = fields;
254
+ this.lastFilterExpression = this.filterExpression;
255
+ this.filterExpression = filter;
256
+ this.pager();
257
+ this.info();
258
+ }
259
+ },
260
+
261
+ // doFakeFilter can be used to get the number of models that will
262
+ // remain after calling setFilter with a `fields` and `filter` args.
263
+ doFakeFilter: function ( fields, filter ) {
264
+ if( fields !== undefined && filter !== undefined ){
265
+ var testModels = this.origModels;
266
+ if (testModels === undefined) {
267
+ testModels = this.models;
268
+ }
269
+
270
+ // To comply with current behavior, first filter by any previously defined setFieldFilter rules.
271
+ if ( !_.isEmpty( this.fieldFilterRules ) ) {
272
+ testModels = this._fieldFilter(testModels, this.fieldFilterRules);
273
+ }
274
+
275
+ testModels = this._filter(testModels, fields, filter);
276
+
277
+ // Return size
278
+ return testModels.length;
279
+ }
280
+ },
281
+
282
+
283
+ // pager is used to sort, filter and show the data
284
+ // you expect the library to display.
285
+ pager: function (options) {
286
+ var self = this,
287
+ disp = this.perPage,
288
+ start = (self.currentPage - 1) * disp,
289
+ stop = start + disp;
290
+ // Saving the original models collection is important
291
+ // as we could need to sort or filter, and we don't want
292
+ // to loose the data we fetched from the server.
293
+ if (self.origModels === undefined) {
294
+ self.origModels = self.models;
295
+ }
296
+
297
+ self.models = self.origModels.slice();
298
+
299
+ // Check if sorting was set using setSort.
300
+ if ( this.sortColumn !== "" ) {
301
+ self.models = self._sort(self.models, this.sortColumn, this.sortDirection);
302
+ }
303
+
304
+ // Check if field-filtering was set using setFieldFilter
305
+ if ( !_.isEmpty( this.fieldFilterRules ) ) {
306
+ self.models = self._fieldFilter(self.models, this.fieldFilterRules);
307
+ }
308
+
309
+ // Check if filtering was set using setFilter.
310
+ if ( this.filterExpression !== "" ) {
311
+ self.models = self._filter(self.models, this.filterFields, this.filterExpression);
312
+ }
313
+
314
+ // If the sorting or the filtering was changed go to the first page
315
+ if ( this.lastSortColumn !== this.sortColumn || this.lastFilterExpression !== this.filterExpression || !_.isEqual(this.fieldFilterRules, this.lastFieldFilterRules) ) {
316
+ start = 0;
317
+ stop = start + disp;
318
+ self.currentPage = 1;
319
+
320
+ this.lastSortColumn = this.sortColumn;
321
+ this.lastFieldFilterRules = this.fieldFilterRules;
322
+ this.lastFilterExpression = this.filterExpression;
323
+ }
324
+
325
+ // We need to save the sorted and filtered models collection
326
+ // because we'll use that sorted and filtered collection in info().
327
+ self.sortedAndFilteredModels = self.models.slice();
328
+ self.info();
329
+ self.reset(self.models.slice(start, stop));
330
+
331
+ // This is somewhat of a hack to get all the nextPage, prevPage, and goTo methods
332
+ // to work with a success callback (as in the requestPager). Realistically there is no failure case here,
333
+ // but maybe we could catch exception and trigger a failure callback?
334
+ _.result(options, 'success');
335
+ },
336
+
337
+ // The actual place where the collection is sorted.
338
+ // Check setSort for arguments explicacion.
339
+ _sort: function ( models, sort, direction ) {
340
+ models = models.sort(function (a, b) {
341
+ var ac = a.get(sort),
342
+ bc = b.get(sort);
343
+
344
+ if ( _.isUndefined(ac) || _.isUndefined(bc) || ac === null || bc === null ) {
345
+ return 0;
346
+ } else {
347
+ /* Make sure that both ac and bc are lowercase strings.
348
+ * .toString() first so we don't have to worry if ac or bc
349
+ * have other String-only methods.
350
+ */
351
+ ac = ac.toString().toLowerCase();
352
+ bc = bc.toString().toLowerCase();
353
+ }
354
+
355
+ if (direction === 'desc') {
356
+
357
+ // We need to know if there aren't any non-number characters
358
+ // and that there are numbers-only characters and maybe a dot
359
+ // if we have a float.
360
+ // Oh, also a '-' for negative numbers!
361
+ if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) &&
362
+ (!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))){
363
+
364
+ if( (ac - 0) < (bc - 0) ) {
365
+ return 1;
366
+ }
367
+ if( (ac - 0) > (bc - 0) ) {
368
+ return -1;
369
+ }
370
+ } else {
371
+ if (ac < bc) {
372
+ return 1;
373
+ }
374
+ if (ac > bc) {
375
+ return -1;
376
+ }
377
+ }
378
+
379
+ } else {
380
+
381
+ //Same as the regexp check in the 'if' part.
382
+ if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) &&
383
+ (!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))){
384
+ if( (ac - 0) < (bc - 0) ) {
385
+ return -1;
386
+ }
387
+ if( (ac - 0) > (bc - 0) ) {
388
+ return 1;
389
+ }
390
+ } else {
391
+ if (ac < bc) {
392
+ return -1;
393
+ }
394
+ if (ac > bc) {
395
+ return 1;
396
+ }
397
+ }
398
+
399
+ }
400
+
401
+ if (a.cid && b.cid){
402
+ var aId = a.cid,
403
+ bId = b.cid;
404
+
405
+ if (aId < bId) {
406
+ return -1;
407
+ }
408
+ if (aId > bId) {
409
+ return 1;
410
+ }
411
+ }
412
+
413
+ return 0;
414
+ });
415
+
416
+ return models;
417
+ },
418
+
419
+ // The actual place where the collection is field-filtered.
420
+ // Check setFieldFilter for arguments explicacion.
421
+ _fieldFilter: function( models, rules ) {
422
+
423
+ // Check if there are any rules
424
+ if ( _.isEmpty(rules) ) {
425
+ return models;
426
+ }
427
+
428
+ var filteredModels = [];
429
+
430
+ // Iterate over each rule
431
+ _.each(models, function(model){
432
+
433
+ var should_push = true;
434
+
435
+ // Apply each rule to each model in the collection
436
+ _.each(rules, function(rule){
437
+
438
+ // Don't go inside the switch if we're already sure that the model won't be included in the results
439
+ if( !should_push ){
440
+ return false;
441
+ }
442
+
443
+ should_push = false;
444
+
445
+ // The field's value will be passed to a custom function, which should
446
+ // return true (if model should be included) or false (model should be ignored)
447
+ if(rule.type === "function"){
448
+ var f = _.wrap(rule.value, function(func){
449
+ return func( model.get(rule.field) );
450
+ });
451
+ if( f() ){
452
+ should_push = true;
453
+ }
454
+
455
+ // The field's value is required to be non-empty
456
+ }else if(rule.type === "required"){
457
+ if( !_.isEmpty( model.get(rule.field).toString() ) ) {
458
+ should_push = true;
459
+ }
460
+
461
+ // The field's value is required to be greater tan N (numbers only)
462
+ }else if(rule.type === "min"){
463
+ if( !_.isNaN( Number( model.get(rule.field) ) ) &&
464
+ !_.isNaN( Number( rule.value ) ) &&
465
+ Number( model.get(rule.field) ) >= Number( rule.value ) ) {
466
+ should_push = true;
467
+ }
468
+
469
+ // The field's value is required to be smaller tan N (numbers only)
470
+ }else if(rule.type === "max"){
471
+ if( !_.isNaN( Number( model.get(rule.field) ) ) &&
472
+ !_.isNaN( Number( rule.value ) ) &&
473
+ Number( model.get(rule.field) ) <= Number( rule.value ) ) {
474
+ should_push = true;
475
+ }
476
+
477
+ // The field's value is required to be between N and M (numbers only)
478
+ }else if(rule.type === "range"){
479
+ if( !_.isNaN( Number( model.get(rule.field) ) ) &&
480
+ _.isObject( rule.value ) &&
481
+ !_.isNaN( Number( rule.value.min ) ) &&
482
+ !_.isNaN( Number( rule.value.max ) ) &&
483
+ Number( model.get(rule.field) ) >= Number( rule.value.min ) &&
484
+ Number( model.get(rule.field) ) <= Number( rule.value.max ) ) {
485
+ should_push = true;
486
+ }
487
+
488
+ // The field's value is required to be more than N chars long
489
+ }else if(rule.type === "minLength"){
490
+ if( model.get(rule.field).toString().length >= rule.value ) {
491
+ should_push = true;
492
+ }
493
+
494
+ // The field's value is required to be no more than N chars long
495
+ }else if(rule.type === "maxLength"){
496
+ if( model.get(rule.field).toString().length <= rule.value ) {
497
+ should_push = true;
498
+ }
499
+
500
+ // The field's value is required to be more than N chars long and no more than M chars long
501
+ }else if(rule.type === "rangeLength"){
502
+ if( _.isObject( rule.value ) &&
503
+ !_.isNaN( Number( rule.value.min ) ) &&
504
+ !_.isNaN( Number( rule.value.max ) ) &&
505
+ model.get(rule.field).toString().length >= rule.value.min &&
506
+ model.get(rule.field).toString().length <= rule.value.max ) {
507
+ should_push = true;
508
+ }
509
+
510
+ // The field's value is required to be equal to one of the values in rules.value
511
+ }else if(rule.type === "oneOf"){
512
+ if( _.isArray( rule.value ) &&
513
+ _.include( rule.value, model.get(rule.field) ) ) {
514
+ should_push = true;
515
+ }
516
+
517
+ // The field's value is required to be equal to the value in rules.value
518
+ }else if(rule.type === "equalTo"){
519
+ if( rule.value === model.get(rule.field) ) {
520
+ should_push = true;
521
+ }
522
+
523
+ }else if(rule.type === "containsAllOf"){
524
+ if( _.isArray( rule.value ) &&
525
+ _.isArray(model.get(rule.field)) &&
526
+ _.intersection( rule.value, model.get(rule.field)).length === rule.value.length) {
527
+ should_push = true;
528
+ }
529
+
530
+ // The field's value is required to match the regular expression
531
+ }else if(rule.type === "pattern"){
532
+ if( model.get(rule.field).toString().match(rule.value) ) {
533
+ should_push = true;
534
+ }
535
+
536
+ //Unknown type
537
+ }else{
538
+ should_push = false;
539
+ }
540
+
541
+ });
542
+
543
+ if( should_push ){
544
+ filteredModels.push(model);
545
+ }
546
+
547
+ });
548
+
549
+ return filteredModels;
550
+ },
551
+
552
+ // The actual place where the collection is filtered.
553
+ // Check setFilter for arguments explicacion.
554
+ _filter: function ( models, fields, filter ) {
555
+
556
+ // For example, if you had a data model containing cars like { color: '', description: '', hp: '' },
557
+ // your fields was set to ['color', 'description', 'hp'] and your filter was set
558
+ // to "Black Mustang 300", the word "Black" will match all the cars that have black color, then
559
+ // "Mustang" in the description and then the HP in the 'hp' field.
560
+ // NOTE: "Black Musta 300" will return the same as "Black Mustang 300"
561
+
562
+ // We accept fields to be a string, an array or an object
563
+ // but if string or array is passed we need to convert it
564
+ // to an object.
565
+
566
+ var self = this;
567
+
568
+ var obj_fields = {};
569
+
570
+ if( _.isString( fields ) ) {
571
+ obj_fields[fields] = {cmp_method: 'regexp'};
572
+ }else if( _.isArray( fields ) ) {
573
+ _.each(fields, function(field){
574
+ obj_fields[field] = {cmp_method: 'regexp'};
575
+ });
576
+ }else{
577
+ _.each(fields, function( cmp_opts, field ) {
578
+ obj_fields[field] = _.defaults(cmp_opts, { cmp_method: 'regexp' });
579
+ });
580
+ }
581
+
582
+ fields = obj_fields;
583
+
584
+ //Remove diacritic characters if diacritic plugin is loaded
585
+ if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
586
+ filter = Backbone.Paginator.removeDiacritics(filter);
587
+ }
588
+
589
+ // 'filter' can be only a string.
590
+ // If 'filter' is string we need to convert it to
591
+ // a regular expression.
592
+ // For example, if 'filter' is 'black dog' we need
593
+ // to find every single word, remove duplicated ones (if any)
594
+ // and transform the result to '(black|dog)'
595
+ if( filter === '' || !_.isString(filter) ) {
596
+ return models;
597
+ } else {
598
+ var words = _.map(filter.match(/\w+/ig), function(element) { return element.toLowerCase(); });
599
+ var pattern = "(" + _.uniq(words).join("|") + ")";
600
+ var regexp = new RegExp(pattern, "igm");
601
+ }
602
+
603
+ var filteredModels = [];
604
+
605
+ // We need to iterate over each model
606
+ _.each( models, function( model ) {
607
+
608
+ var matchesPerModel = [];
609
+
610
+ // and over each field of each model
611
+ _.each( fields, function( cmp_opts, field ) {
612
+
613
+ var value = model.get( field );
614
+
615
+ if( value ) {
616
+
617
+ // The regular expression we created earlier let's us detect if a
618
+ // given string contains each and all of the words in the regular expression
619
+ // or not, but in both cases match() will return an array containing all
620
+ // the words it matched.
621
+ var matchesPerField = [];
622
+
623
+ if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
624
+ value = Backbone.Paginator.removeDiacritics(value.toString());
625
+ }else{
626
+ value = value.toString();
627
+ }
628
+
629
+ // Levenshtein cmp
630
+ if( cmp_opts.cmp_method === 'levenshtein' && _.has(Backbone.Paginator, 'levenshtein') && self.useLevenshteinPlugin ) {
631
+ var distance = Backbone.Paginator.levenshtein(value, filter);
632
+
633
+ _.defaults(cmp_opts, { max_distance: 0 });
634
+
635
+ if( distance <= cmp_opts.max_distance ) {
636
+ matchesPerField = _.uniq(words);
637
+ }
638
+
639
+ // Default (RegExp) cmp
640
+ }else{
641
+ matchesPerField = value.match( regexp );
642
+ }
643
+
644
+ matchesPerField = _.map(matchesPerField, function(match) {
645
+ return match.toString().toLowerCase();
646
+ });
647
+
648
+ _.each(matchesPerField, function(match){
649
+ matchesPerModel.push(match);
650
+ });
651
+
652
+ }
653
+
654
+ });
655
+
656
+ // We just need to check if the returned array contains all the words in our
657
+ // regex, and if it does, it means that we have a match, so we should save it.
658
+ matchesPerModel = _.uniq( _.without(matchesPerModel, "") );
659
+
660
+ if( _.isEmpty( _.difference(words, matchesPerModel) ) ) {
661
+ filteredModels.push(model);
662
+ }
663
+
664
+ });
665
+
666
+ return filteredModels;
667
+ },
668
+
669
+ // You shouldn't need to call info() as this method is used to
670
+ // calculate internal data as first/prev/next/last page...
671
+ info: function () {
672
+ var self = this,
673
+ info = {},
674
+ totalRecords = (self.sortedAndFilteredModels) ? self.sortedAndFilteredModels.length : self.length,
675
+ totalPages = Math.ceil(totalRecords / self.perPage);
676
+
677
+ info = {
678
+ totalUnfilteredRecords: self.origModels.length,
679
+ totalRecords: totalRecords,
680
+ currentPage: self.currentPage,
681
+ perPage: this.perPage,
682
+ totalPages: totalPages,
683
+ lastPage: totalPages,
684
+ previous: false,
685
+ next: false,
686
+ startRecord: totalRecords === 0 ? 0 : (self.currentPage - 1) * this.perPage + 1,
687
+ endRecord: Math.min(totalRecords, self.currentPage * this.perPage)
688
+ };
689
+
690
+ if (self.currentPage > 1) {
691
+ info.previous = self.currentPage - 1;
692
+ }
693
+
694
+ if (self.currentPage < info.totalPages) {
695
+ info.next = self.currentPage + 1;
696
+ }
697
+
698
+ info.pageSet = self.setPagination(info);
699
+
700
+ self.information = info;
701
+ return info;
702
+ },
703
+
704
+
705
+ // setPagination also is an internal function that shouldn't be called directly.
706
+ // It will create an array containing the pages right before and right after the
707
+ // actual page.
708
+ setPagination: function ( info ) {
709
+
710
+ var pages = [], i = 0, l = 0;
711
+
712
+ // How many adjacent pages should be shown on each side?
713
+ var ADJACENTx2 = this.pagesInRange * 2,
714
+ LASTPAGE = Math.ceil(info.totalRecords / info.perPage);
715
+
716
+ if (LASTPAGE > 1) {
717
+
718
+ // not enough pages to bother breaking it up
719
+ if (LASTPAGE <= (1 + ADJACENTx2)) {
720
+ for (i = 1, l = LASTPAGE; i <= l; i++) {
721
+ pages.push(i);
722
+ }
723
+ }
724
+
725
+ // enough pages to hide some
726
+ else {
727
+
728
+ //close to beginning; only hide later pages
729
+ if (info.currentPage <= (this.pagesInRange + 1)) {
730
+ for (i = 1, l = 2 + ADJACENTx2; i < l; i++) {
731
+ pages.push(i);
732
+ }
733
+ }
734
+
735
+ // in middle; hide some front and some back
736
+ else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) {
737
+ for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) {
738
+ pages.push(i);
739
+ }
740
+ }
741
+
742
+ // close to end; only hide early pages
743
+ else {
744
+ for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) {
745
+ pages.push(i);
746
+ }
747
+ }
748
+ }
749
+
750
+ }
751
+
752
+ return pages;
753
+
754
+ },
755
+
756
+ bootstrap: function(options) {
757
+ _.extend(this, options);
758
+ this.goTo(1);
759
+ this.info();
760
+ return this;
761
+ }
762
+
763
+ });
764
+
765
+ // function aliasing
766
+ Paginator.clientPager.prototype.prevPage = Paginator.clientPager.prototype.previousPage;
767
+
768
+ // Helper function to generate rejected Deferred
769
+ var reject = function () {
770
+ var response = new $.Deferred();
771
+ response.reject();
772
+ return response.promise();
773
+ };
774
+
775
+ // @name: requestPager
776
+ //
777
+ // Paginator for server-side data being requested from a backend/API
778
+ //
779
+ // @description:
780
+ // This paginator is responsible for providing pagination
781
+ // and sort capabilities for requests to a server-side
782
+ // data service (e.g an API)
783
+ //
784
+ Paginator.requestPager = Backbone.Collection.extend({
785
+
786
+ sync: function ( method, model, options ) {
787
+
788
+ var self = this;
789
+
790
+ self.setDefaults();
791
+
792
+ // Some values could be functions, let's make sure
793
+ // to change their scope too and run them
794
+ var queryAttributes = {};
795
+ _.each(_.result(self, "server_api"), function(value, key){
796
+ if( _.isFunction(value) ) {
797
+ value = _.bind(value, self);
798
+ value = value();
799
+ }
800
+ queryAttributes[key] = value;
801
+ });
802
+
803
+ var queryOptions = _.clone(self.paginator_core);
804
+ _.each(queryOptions, function(value, key){
805
+ if( _.isFunction(value) ) {
806
+ value = _.bind(value, self);
807
+ value = value();
808
+ }
809
+ queryOptions[key] = value;
810
+ });
811
+
812
+ // Create default values if no others are specified
813
+ queryOptions = _.defaults(queryOptions, {
814
+ timeout: 25000,
815
+ cache: false,
816
+ type: 'GET',
817
+ dataType: 'jsonp'
818
+ });
819
+
820
+ // Allows the passing in of {data: {foo: 'bar'}} at request time to overwrite server_api defaults
821
+ if( options.data ){
822
+ options.data = decodeURIComponent($.param(_.extend(queryAttributes,options.data)));
823
+ }else{
824
+ options.data = decodeURIComponent($.param(queryAttributes));
825
+ }
826
+
827
+ queryOptions = _.extend(queryOptions, {
828
+ data: decodeURIComponent($.param(queryAttributes)),
829
+ processData: false,
830
+ url: _.result(queryOptions, 'url')
831
+ }, options);
832
+
833
+ var promiseSuccessFormat = !(bbVer[0] === 0 &&
834
+ bbVer[1] === 9 &&
835
+ bbVer[2] === 10);
836
+
837
+ var success = queryOptions.success;
838
+ queryOptions.success = function ( resp, status, xhr ) {
839
+
840
+ if ( success ) {
841
+ // This is to keep compatibility with Backbone 0.9.10
842
+ if (promiseSuccessFormat) {
843
+ success( resp, status, xhr );
844
+ } else {
845
+ success( model, resp, queryOptions );
846
+ }
847
+ }
848
+ if (bbVer[0] < 1 && model && model.trigger ) {
849
+ model.trigger( 'sync', model, resp, queryOptions );
850
+ }
851
+ };
852
+
853
+ var error = queryOptions.error;
854
+ queryOptions.error = function ( xhr ) {
855
+ if ( error ) {
856
+ error( xhr );
857
+ }
858
+ if ( model && model.trigger ) {
859
+ model.trigger( 'error', model, xhr, queryOptions );
860
+ }
861
+ };
862
+
863
+ var xhr = queryOptions.xhr = Backbone.ajax( queryOptions );
864
+ if ( model && model.trigger ) {
865
+ model.trigger('request', model, xhr, queryOptions);
866
+ }
867
+ return xhr;
868
+ },
869
+
870
+ setDefaults: function() {
871
+ var self = this;
872
+
873
+ // Create default values if no others are specified
874
+ _.defaults(self.paginator_ui, {
875
+ firstPage: 0,
876
+ currentPage: 1,
877
+ perPage: 5,
878
+ totalPages: 10,
879
+ pagesInRange: 4
880
+ });
881
+
882
+ // Change scope of 'paginator_ui' object values
883
+ _.each(self.paginator_ui, function(value, key) {
884
+ if (_.isUndefined(self[key])) {
885
+ self[key] = self.paginator_ui[key];
886
+ }
887
+ });
888
+ },
889
+
890
+ requestNextPage: function ( options ) {
891
+ if ( this.currentPage !== undefined ) {
892
+ this.currentPage += 1;
893
+ return this.pager( options );
894
+ } else {
895
+ return reject();
896
+ }
897
+ },
898
+
899
+ requestPreviousPage: function ( options ) {
900
+ if ( this.currentPage !== undefined ) {
901
+ this.currentPage -= 1;
902
+ return this.pager( options );
903
+ } else {
904
+ return reject();
905
+ }
906
+ },
907
+
908
+ updateOrder: function ( column, options ) {
909
+ if (column !== undefined) {
910
+ this.sortField = column;
911
+ return this.pager( options );
912
+ } else {
913
+ return reject();
914
+ }
915
+ },
916
+
917
+ goTo: function ( page, options ) {
918
+ if ( page !== undefined ) {
919
+ this.currentPage = parseInt(page, 10);
920
+ return this.pager( options );
921
+ } else {
922
+ return reject();
923
+ }
924
+ },
925
+
926
+ howManyPer: function ( count, options ) {
927
+ if ( count !== undefined ) {
928
+ this.currentPage = this.firstPage;
929
+ this.perPage = count;
930
+ return this.pager( options );
931
+ } else {
932
+ return reject();
933
+ }
934
+ },
935
+
936
+ info: function () {
937
+
938
+ var info = {
939
+ // If parse() method is implemented and totalRecords is set to the length
940
+ // of the records returned, make it available. Else, default it to 0
941
+ totalRecords: this.totalRecords || 0,
942
+
943
+ currentPage: this.currentPage,
944
+ firstPage: this.firstPage,
945
+ totalPages: Math.ceil(this.totalRecords / this.perPage),
946
+ lastPage: this.totalPages, // should use totalPages in template
947
+ perPage: this.perPage,
948
+ previous:false,
949
+ next:false
950
+ };
951
+
952
+ if (this.currentPage > 1) {
953
+ info.previous = this.currentPage - 1;
954
+ }
955
+
956
+ if (this.currentPage < info.totalPages) {
957
+ info.next = this.currentPage + 1;
958
+ }
959
+
960
+ // left around for backwards compatibility
961
+ info.hasNext = info.next;
962
+ info.hasPrevious = info.next;
963
+
964
+ info.pageSet = this.setPagination(info);
965
+
966
+ this.information = info;
967
+ return info;
968
+ },
969
+
970
+ setPagination: function ( info ) {
971
+
972
+ var pages = [], i = 0, l = 0;
973
+
974
+ // How many adjacent pages should be shown on each side?
975
+ var ADJACENTx2 = this.pagesInRange * 2,
976
+ LASTPAGE = Math.ceil(info.totalRecords / info.perPage);
977
+
978
+ if (LASTPAGE > 1) {
979
+
980
+ // not enough pages to bother breaking it up
981
+ if (LASTPAGE <= (1 + ADJACENTx2)) {
982
+ for (i = 1, l = LASTPAGE; i <= l; i++) {
983
+ pages.push(i);
984
+ }
985
+ }
986
+
987
+ // enough pages to hide some
988
+ else {
989
+
990
+ //close to beginning; only hide later pages
991
+ if (info.currentPage <= (this.pagesInRange + 1)) {
992
+ for (i = 1, l = 2 + ADJACENTx2; i < l; i++) {
993
+ pages.push(i);
994
+ }
995
+ }
996
+
997
+ // in middle; hide some front and some back
998
+ else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) {
999
+ for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) {
1000
+ pages.push(i);
1001
+ }
1002
+ }
1003
+
1004
+ // close to end; only hide early pages
1005
+ else {
1006
+ for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) {
1007
+ pages.push(i);
1008
+ }
1009
+ }
1010
+ }
1011
+
1012
+ }
1013
+
1014
+ return pages;
1015
+
1016
+ },
1017
+
1018
+ // fetches the latest results from the server
1019
+ pager: function ( options ) {
1020
+ if ( !_.isObject(options) ) {
1021
+ options = {};
1022
+ }
1023
+ return this.fetch( options );
1024
+ },
1025
+
1026
+ url: function(){
1027
+ // Expose url parameter enclosed in this.paginator_core.url to properly
1028
+ // extend Collection and allow Collection CRUD
1029
+ if(this.paginator_core !== undefined && this.paginator_core.url !== undefined){
1030
+ return this.paginator_core.url;
1031
+ } else {
1032
+ return null;
1033
+ }
1034
+ },
1035
+
1036
+ bootstrap: function(options) {
1037
+ _.extend(this, options);
1038
+ this.setDefaults();
1039
+ this.info();
1040
+ return this;
1041
+ }
1042
+ });
1043
+
1044
+ // function aliasing
1045
+ Paginator.requestPager.prototype.nextPage = Paginator.requestPager.prototype.requestNextPage;
1046
+ Paginator.requestPager.prototype.prevPage = Paginator.requestPager.prototype.requestPreviousPage;
1047
+
1048
+ return Paginator;
1049
+
1050
+ }( Backbone, _, jQuery ));