backbone-paginator 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 ));