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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +30 -0
- data/Rakefile +19 -0
- data/backbone-paginator.gemspec +26 -0
- data/lib/backbone-paginator.rb +2 -0
- data/lib/backbone-paginator/engine.rb +4 -0
- data/lib/backbone-paginator/version.rb +3 -0
- data/test/backbone.paginator_test.rb +12 -0
- data/test/dummy/.gitignore +16 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +16 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +24 -0
- data/test/dummy/config/boot.rb +4 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/lib/tasks/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/robots.txt +5 -0
- data/test/dummy/test/controllers/.keep +0 -0
- data/test/dummy/test/fixtures/.keep +0 -0
- data/test/dummy/test/helpers/.keep +0 -0
- data/test/dummy/test/integration/.keep +0 -0
- data/test/dummy/test/mailers/.keep +0 -0
- data/test/dummy/test/models/.keep +0 -0
- data/test/dummy/test/test_helper.rb +15 -0
- data/test/dummy/vendor/assets/javascripts/.keep +0 -0
- data/test/dummy/vendor/assets/stylesheets/.keep +0 -0
- data/test/test_helper.rb +5 -0
- data/vendor/assets/javascripts/backbone.paginator.js +1050 -0
- 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,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,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,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
|
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
|
File without changes
|
File without changes
|
data/test/test_helper.rb
ADDED
@@ -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 ));
|