judge 1.5.0 → 2.0.0
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.
- data/README.md +273 -14
- data/app/assets/javascripts/judge.js +391 -0
- data/app/controllers/judge/validations_controller.rb +9 -0
- data/app/models/judge/validation.rb +53 -0
- data/config/routes.rb +3 -0
- data/lib/generators/judge/install/install_generator.rb +42 -0
- data/lib/judge.rb +13 -8
- data/lib/judge/config.rb +39 -0
- data/lib/judge/controller.rb +35 -0
- data/lib/judge/each_validator.rb +5 -4
- data/lib/judge/engine.rb +5 -0
- data/lib/judge/form_builder.rb +19 -24
- data/lib/judge/html.rb +5 -7
- data/lib/judge/message_collection.rb +2 -1
- data/lib/judge/message_config.rb +2 -1
- data/lib/judge/validator.rb +3 -1
- data/lib/judge/validator_collection.rb +1 -1
- data/lib/judge/version.rb +2 -2
- data/lib/tasks/judge_tasks.rake +4 -0
- data/{lib/generators/judge/templates → vendor/assets/javascripts}/json2.js +4 -5
- data/{lib/generators/judge/templates → vendor/assets/javascripts}/underscore.js +451 -285
- metadata +94 -87
- data/.gitignore +0 -9
- data/.travis.yml +0 -21
- data/Gemfile +0 -3
- data/Rakefile +0 -11
- data/judge.gemspec +0 -23
- data/lib/generators/judge/judge_generator.rb +0 -21
- data/lib/generators/judge/templates/judge.js +0 -330
- data/spec/each_validator_spec.rb +0 -17
- data/spec/form_builder_spec.rb +0 -68
- data/spec/html_spec.rb +0 -14
- data/spec/javascripts/JudgeSpec.js +0 -509
- data/spec/javascripts/fixtures/form.html +0 -538
- data/spec/javascripts/helpers/customMatchers.js +0 -20
- data/spec/javascripts/helpers/jasmine-jquery.js +0 -204
- data/spec/javascripts/helpers/jquery-1.5.1.min.js +0 -18
- data/spec/javascripts/helpers/json2.js +0 -487
- data/spec/javascripts/helpers/underscore.js +0 -1060
- data/spec/javascripts/support/jasmine.yml +0 -79
- data/spec/javascripts/support/jasmine_config.rb +0 -6
- data/spec/javascripts/support/jasmine_runner.rb +0 -21
- data/spec/javascripts/support/runner.js +0 -51
- data/spec/message_collection_spec.rb +0 -73
- data/spec/setup.rb +0 -75
- data/spec/support/factories.rb +0 -23
- data/spec/support/locale/en.yml +0 -18
- data/spec/support/setup.rb +0 -72
- data/spec/support/spec_helper.rb +0 -13
- data/spec/support/validators/city_validator.rb +0 -9
- data/spec/validator_collection_spec.rb +0 -21
- data/spec/validator_spec.rb +0 -33
@@ -0,0 +1,53 @@
|
|
1
|
+
module Judge
|
2
|
+
|
3
|
+
class Validation
|
4
|
+
def initialize(params)
|
5
|
+
@klass = params[:klass]
|
6
|
+
@attribute = params[:attribute]
|
7
|
+
@value = params[:value]
|
8
|
+
@kind = params[:kind]
|
9
|
+
validate!
|
10
|
+
end
|
11
|
+
|
12
|
+
def amv
|
13
|
+
@amv ||= begin
|
14
|
+
validators = @klass.validators_on(@attribute)
|
15
|
+
validators.keep_if { |amv| amv.kind == @kind }
|
16
|
+
validators.first
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def record
|
21
|
+
@record ||= begin
|
22
|
+
rec = @klass.new
|
23
|
+
rec[@attribute] = @value
|
24
|
+
rec
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate!
|
29
|
+
record.errors.delete(@attribute)
|
30
|
+
amv.validate_each(record, @attribute, @value)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def as_json(options = {})
|
35
|
+
record.errors.get(@attribute) || []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class NullValidation
|
40
|
+
def initialize(params)
|
41
|
+
@params = params
|
42
|
+
end
|
43
|
+
|
44
|
+
def as_json(options = {})
|
45
|
+
["Judge validation for #{@params[:klass]}##{@params[:attribute]} not allowed"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(*args)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Judge
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < ::Rails::Generators::Base
|
4
|
+
|
5
|
+
desc %Q{For use where asset pipeline is disabled.
|
6
|
+
Installs judge.js and optionally installs underscore.js and json2.js.
|
7
|
+
|
8
|
+
Copy judge.js to public/javascripts:
|
9
|
+
$ rails generate judge:install
|
10
|
+
|
11
|
+
Copy judge.js to different path:
|
12
|
+
$ rails generate judge:install path
|
13
|
+
|
14
|
+
Copy judge.js and dependencies:
|
15
|
+
$ rails generate judge:install path --underscore --json2
|
16
|
+
}
|
17
|
+
|
18
|
+
argument :path, :type => :string, :default => "public/javascripts"
|
19
|
+
class_option :underscore, :type => :boolean, :default => false, :desc => "Install underscore.js"
|
20
|
+
class_option :json2, :type => :boolean, :default => false, :desc => "Install json2.js"
|
21
|
+
source_root File.expand_path("../../../../..", __FILE__)
|
22
|
+
|
23
|
+
def exec
|
24
|
+
unless !::Rails.application.config.assets.enabled
|
25
|
+
say_status("deprecated", "You don't need to use this generator as your app is running on Rails >= 3.1 with the asset pipeline enabled")
|
26
|
+
return
|
27
|
+
end
|
28
|
+
say_status("copying", "judge.js", :green)
|
29
|
+
copy_file("app/assets/javascripts/judge.js", "#{path}/judge.js")
|
30
|
+
if options.underscore?
|
31
|
+
say_status("copying", "underscore.js", :green)
|
32
|
+
copy_file("vendor/assets/javascripts/underscore.js", "#{path}/underscore.js")
|
33
|
+
end
|
34
|
+
if options.json2?
|
35
|
+
say_status("copying", "json2.js", :green)
|
36
|
+
copy_file("vendor/assets/javascripts/json2.js", "#{path}/json2.js")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/judge.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
files = [
|
2
|
+
'version',
|
3
|
+
'config',
|
4
|
+
'engine',
|
5
|
+
'validator',
|
6
|
+
'validator_collection',
|
7
|
+
'message_collection',
|
8
|
+
'html',
|
9
|
+
'form_builder',
|
10
|
+
'each_validator',
|
11
|
+
'controller'
|
12
|
+
]
|
13
|
+
files.each { |filename| require "judge/#{filename}" }
|
data/lib/judge/config.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Judge
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
@@exposed = {}
|
8
|
+
|
9
|
+
def expose(klass, *attributes)
|
10
|
+
attrs = (@@exposed[klass] ||= [])
|
11
|
+
attrs.concat(attributes).uniq!
|
12
|
+
end
|
13
|
+
|
14
|
+
def exposed
|
15
|
+
@@exposed
|
16
|
+
end
|
17
|
+
|
18
|
+
def exposed?(klass, attribute)
|
19
|
+
@@exposed.has_key?(klass) && @@exposed[klass].include?(attribute)
|
20
|
+
end
|
21
|
+
|
22
|
+
def unexpose(klass, *attributes)
|
23
|
+
attributes.each do |a|
|
24
|
+
@@exposed[klass].delete(a)
|
25
|
+
end
|
26
|
+
if attributes.empty? || @@exposed[klass].empty?
|
27
|
+
@@exposed.delete(klass)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.config
|
33
|
+
Config.instance
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.configure(&block)
|
37
|
+
Config.instance.instance_eval(&block)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Judge
|
2
|
+
module Controller
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.clear_respond_to
|
6
|
+
base.respond_to(:json)
|
7
|
+
end
|
8
|
+
|
9
|
+
def validation(params)
|
10
|
+
params = normalize_validation_params(params)
|
11
|
+
if params[:klass] && Judge.config.exposed?(params[:klass], params[:attribute])
|
12
|
+
Validation.new(params)
|
13
|
+
else
|
14
|
+
NullValidation.new(params)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def normalize_validation_params(params)
|
21
|
+
params.keep_if { |key| %w{klass attribute value kind}.include?(key) }
|
22
|
+
params[:klass] = find_klass(params[:klass])
|
23
|
+
params[:attribute] = params[:attribute].to_sym
|
24
|
+
params[:kind] = params[:kind].to_sym
|
25
|
+
params
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_klass(name)
|
29
|
+
Module.const_get(name.classify)
|
30
|
+
rescue NameError
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/judge/each_validator.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
module Judge
|
2
2
|
module EachValidator
|
3
3
|
|
4
|
+
require 'set'
|
5
|
+
|
4
6
|
def self.included(base)
|
7
|
+
base.send(:cattr_accessor, :messages_to_lookup) { Set.new }
|
5
8
|
base.send(:extend, ClassMethods)
|
6
9
|
end
|
7
10
|
|
8
11
|
module ClassMethods
|
9
12
|
|
10
|
-
def
|
11
|
-
|
12
|
-
keys
|
13
|
-
end
|
13
|
+
def uses_messages(*keys)
|
14
|
+
self.messages_to_lookup.merge(keys)
|
14
15
|
end
|
15
16
|
|
16
17
|
end
|
data/lib/judge/engine.rb
ADDED
data/lib/judge/form_builder.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Judge
|
2
2
|
|
3
3
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
4
|
+
|
5
|
+
include Judge::Html
|
4
6
|
|
5
7
|
%w{text_field text_area password_field}.each do |type|
|
6
8
|
helper = <<-END
|
7
9
|
def #{type}(method, options = {})
|
8
|
-
|
9
|
-
options = Judge::HTML.attrs_for(self.object, method).merge(options)
|
10
|
-
end
|
10
|
+
add_validate_attr!(self.object, method, options)
|
11
11
|
super
|
12
12
|
end
|
13
13
|
END
|
@@ -15,46 +15,34 @@ module Judge
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def radio_button(method, tag_value, options = {})
|
18
|
-
|
19
|
-
options = Judge::HTML.attrs_for(self.object, method).merge(options)
|
20
|
-
end
|
18
|
+
add_validate_attr!(self.object, method, options)
|
21
19
|
super
|
22
20
|
end
|
23
21
|
|
24
22
|
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
25
|
-
|
26
|
-
options = Judge::HTML.attrs_for(self.object, method).merge(options)
|
27
|
-
end
|
23
|
+
add_validate_attr!(self.object, method, options)
|
28
24
|
super
|
29
25
|
end
|
30
26
|
|
31
27
|
def select(method, choices, options = {}, html_options = {})
|
32
|
-
|
33
|
-
html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
|
34
|
-
end
|
28
|
+
add_validate_attr!(self.object, method, options, html_options)
|
35
29
|
super
|
36
30
|
end
|
37
31
|
|
38
32
|
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
39
|
-
|
40
|
-
html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
|
41
|
-
end
|
33
|
+
add_validate_attr!(self.object, method, options, html_options)
|
42
34
|
super
|
43
35
|
end
|
44
36
|
|
45
37
|
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
|
46
|
-
|
47
|
-
html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
|
48
|
-
end
|
38
|
+
add_validate_attr!(self.object, method, options, html_options)
|
49
39
|
super
|
50
40
|
end
|
51
41
|
|
52
42
|
%w{date_select datetime_select time_select}.each do |type|
|
53
43
|
helper = <<-END
|
54
44
|
def #{type}(method, options = {}, html_options = {})
|
55
|
-
|
56
|
-
html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
|
57
|
-
end
|
45
|
+
add_validate_attr!(self.object, method, options, html_options)
|
58
46
|
super
|
59
47
|
end
|
60
48
|
END
|
@@ -62,11 +50,18 @@ module Judge
|
|
62
50
|
end
|
63
51
|
|
64
52
|
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
65
|
-
|
66
|
-
html_options = Judge::HTML.attrs_for(self.object, method).merge(html_options)
|
67
|
-
end
|
53
|
+
add_validate_attr!(self.object, method, options, html_options)
|
68
54
|
super
|
69
55
|
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def add_validate_attr!(object, method, options, html_options = nil)
|
60
|
+
options_to_merge = html_options || options
|
61
|
+
if options.delete(:validate)
|
62
|
+
options_to_merge.merge! attrs_for(object, method)
|
63
|
+
end
|
64
|
+
end
|
70
65
|
|
71
66
|
end
|
72
67
|
|
data/lib/judge/html.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
module Judge
|
2
|
-
|
3
|
-
module HTML
|
4
|
-
|
2
|
+
module Html
|
5
3
|
extend self
|
6
|
-
|
4
|
+
|
7
5
|
def attrs_for(object, method)
|
8
|
-
{
|
6
|
+
{
|
7
|
+
"data-validate" => ValidatorCollection.new(object, method).to_json
|
8
|
+
}
|
9
9
|
end
|
10
|
-
|
11
10
|
end
|
12
|
-
|
13
11
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "judge/message_config"
|
2
2
|
|
3
3
|
module Judge
|
4
4
|
|
@@ -19,6 +19,7 @@ module Judge
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def generate_messages!
|
22
|
+
return if @kind == :uniqueness
|
22
23
|
%w{base options integer custom blank}.each do |type|
|
23
24
|
@messages = @messages.merge(self.send(:"#{type}_messages"))
|
24
25
|
end
|
data/lib/judge/message_config.rb
CHANGED
data/lib/judge/validator.rb
CHANGED
@@ -4,9 +4,11 @@ module Judge
|
|
4
4
|
|
5
5
|
attr_reader :active_model_validator, :kind, :options, :method, :messages
|
6
6
|
|
7
|
+
REJECTED_OPTIONS = [:if, :on, :unless, :tokenizer, :scope, :case_sensitive]
|
8
|
+
|
7
9
|
def initialize(object, method, amv)
|
8
10
|
@kind = amv.kind
|
9
|
-
@options = amv.options.reject { |key|
|
11
|
+
@options = amv.options.reject { |key| REJECTED_OPTIONS.include?(key) }
|
10
12
|
@method = method
|
11
13
|
@messages = Judge::MessageCollection.new(object, method, amv)
|
12
14
|
end
|
@@ -7,7 +7,7 @@ module Judge
|
|
7
7
|
attr_reader :validators
|
8
8
|
|
9
9
|
def initialize(object, method)
|
10
|
-
amvs = object.class.validators_on(method)
|
10
|
+
amvs = object.class.validators_on(method)
|
11
11
|
@validators = amvs.map { |amv| Judge::Validator.new(object, method, amv) }
|
12
12
|
end
|
13
13
|
|
data/lib/judge/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Judge
|
2
|
-
VERSION = "
|
3
|
-
end
|
2
|
+
VERSION = "2.0.0"
|
3
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
|
-
|
3
|
-
|
2
|
+
json2.js
|
3
|
+
2012-10-08
|
4
4
|
|
5
5
|
Public Domain.
|
6
6
|
|
@@ -159,8 +159,7 @@
|
|
159
159
|
// Create a JSON object only if one does not already exist. We create the
|
160
160
|
// methods in a closure to avoid creating global variables.
|
161
161
|
|
162
|
-
|
163
|
-
if (!JSON) {
|
162
|
+
if (typeof JSON !== 'object') {
|
164
163
|
JSON = {};
|
165
164
|
}
|
166
165
|
|
@@ -484,4 +483,4 @@ if (!JSON) {
|
|
484
483
|
throw new SyntaxError('JSON.parse');
|
485
484
|
};
|
486
485
|
}
|
487
|
-
}());
|
486
|
+
}());
|
@@ -1,11 +1,7 @@
|
|
1
|
-
|
2
|
-
//
|
3
|
-
// (c) 2009-
|
4
|
-
// Underscore
|
5
|
-
// Portions of Underscore are inspired or borrowed from Prototype,
|
6
|
-
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
7
|
-
// For all details and documentation:
|
8
|
-
// http://documentcloud.github.com/underscore
|
1
|
+
// Underscore.js 1.4.4
|
2
|
+
// http://underscorejs.org
|
3
|
+
// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
|
4
|
+
// Underscore may be freely distributed under the MIT license.
|
9
5
|
|
10
6
|
(function() {
|
11
7
|
|
@@ -25,8 +21,9 @@
|
|
25
21
|
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
|
26
22
|
|
27
23
|
// Create quick reference variables for speed access to core prototypes.
|
28
|
-
var
|
29
|
-
|
24
|
+
var push = ArrayProto.push,
|
25
|
+
slice = ArrayProto.slice,
|
26
|
+
concat = ArrayProto.concat,
|
30
27
|
toString = ObjProto.toString,
|
31
28
|
hasOwnProperty = ObjProto.hasOwnProperty;
|
32
29
|
|
@@ -47,7 +44,11 @@
|
|
47
44
|
nativeBind = FuncProto.bind;
|
48
45
|
|
49
46
|
// Create a safe reference to the Underscore object for use below.
|
50
|
-
var _ = function(obj) {
|
47
|
+
var _ = function(obj) {
|
48
|
+
if (obj instanceof _) return obj;
|
49
|
+
if (!(this instanceof _)) return new _(obj);
|
50
|
+
this._wrapped = obj;
|
51
|
+
};
|
51
52
|
|
52
53
|
// Export the Underscore object for **Node.js**, with
|
53
54
|
// backwards-compatibility for the old `require()` API. If we're in
|
@@ -59,11 +60,11 @@
|
|
59
60
|
}
|
60
61
|
exports._ = _;
|
61
62
|
} else {
|
62
|
-
root
|
63
|
+
root._ = _;
|
63
64
|
}
|
64
65
|
|
65
66
|
// Current version.
|
66
|
-
_.VERSION = '1.
|
67
|
+
_.VERSION = '1.4.4';
|
67
68
|
|
68
69
|
// Collection Functions
|
69
70
|
// --------------------
|
@@ -77,7 +78,7 @@
|
|
77
78
|
obj.forEach(iterator, context);
|
78
79
|
} else if (obj.length === +obj.length) {
|
79
80
|
for (var i = 0, l = obj.length; i < l; i++) {
|
80
|
-
if (
|
81
|
+
if (iterator.call(context, obj[i], i, obj) === breaker) return;
|
81
82
|
}
|
82
83
|
} else {
|
83
84
|
for (var key in obj) {
|
@@ -97,10 +98,11 @@
|
|
97
98
|
each(obj, function(value, index, list) {
|
98
99
|
results[results.length] = iterator.call(context, value, index, list);
|
99
100
|
});
|
100
|
-
if (obj.length === +obj.length) results.length = obj.length;
|
101
101
|
return results;
|
102
102
|
};
|
103
103
|
|
104
|
+
var reduceError = 'Reduce of empty array with no initial value';
|
105
|
+
|
104
106
|
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
105
107
|
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
106
108
|
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
@@ -118,7 +120,7 @@
|
|
118
120
|
memo = iterator.call(context, memo, value, index, list);
|
119
121
|
}
|
120
122
|
});
|
121
|
-
if (!initial) throw new TypeError(
|
123
|
+
if (!initial) throw new TypeError(reduceError);
|
122
124
|
return memo;
|
123
125
|
};
|
124
126
|
|
@@ -131,9 +133,22 @@
|
|
131
133
|
if (context) iterator = _.bind(iterator, context);
|
132
134
|
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
133
135
|
}
|
134
|
-
var
|
135
|
-
if (
|
136
|
-
|
136
|
+
var length = obj.length;
|
137
|
+
if (length !== +length) {
|
138
|
+
var keys = _.keys(obj);
|
139
|
+
length = keys.length;
|
140
|
+
}
|
141
|
+
each(obj, function(value, index, list) {
|
142
|
+
index = keys ? keys[--length] : --length;
|
143
|
+
if (!initial) {
|
144
|
+
memo = obj[index];
|
145
|
+
initial = true;
|
146
|
+
} else {
|
147
|
+
memo = iterator.call(context, memo, obj[index], index, list);
|
148
|
+
}
|
149
|
+
});
|
150
|
+
if (!initial) throw new TypeError(reduceError);
|
151
|
+
return memo;
|
137
152
|
};
|
138
153
|
|
139
154
|
// Return the first value which passes a truth test. Aliased as `detect`.
|
@@ -163,18 +178,16 @@
|
|
163
178
|
|
164
179
|
// Return all the elements for which a truth test fails.
|
165
180
|
_.reject = function(obj, iterator, context) {
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
if (!iterator.call(context, value, index, list)) results[results.length] = value;
|
170
|
-
});
|
171
|
-
return results;
|
181
|
+
return _.filter(obj, function(value, index, list) {
|
182
|
+
return !iterator.call(context, value, index, list);
|
183
|
+
}, context);
|
172
184
|
};
|
173
185
|
|
174
186
|
// Determine whether all of the elements match a truth test.
|
175
187
|
// Delegates to **ECMAScript 5**'s native `every` if available.
|
176
188
|
// Aliased as `all`.
|
177
189
|
_.every = _.all = function(obj, iterator, context) {
|
190
|
+
iterator || (iterator = _.identity);
|
178
191
|
var result = true;
|
179
192
|
if (obj == null) return result;
|
180
193
|
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
@@ -198,23 +211,22 @@
|
|
198
211
|
return !!result;
|
199
212
|
};
|
200
213
|
|
201
|
-
// Determine if
|
202
|
-
// Aliased as `
|
203
|
-
_.
|
204
|
-
|
205
|
-
if (obj == null) return found;
|
214
|
+
// Determine if the array or object contains a given value (using `===`).
|
215
|
+
// Aliased as `include`.
|
216
|
+
_.contains = _.include = function(obj, target) {
|
217
|
+
if (obj == null) return false;
|
206
218
|
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
207
|
-
|
219
|
+
return any(obj, function(value) {
|
208
220
|
return value === target;
|
209
221
|
});
|
210
|
-
return found;
|
211
222
|
};
|
212
223
|
|
213
224
|
// Invoke a method (with arguments) on every item in a collection.
|
214
225
|
_.invoke = function(obj, method) {
|
215
226
|
var args = slice.call(arguments, 2);
|
227
|
+
var isFunc = _.isFunction(method);
|
216
228
|
return _.map(obj, function(value) {
|
217
|
-
return (
|
229
|
+
return (isFunc ? method : value[method]).apply(value, args);
|
218
230
|
});
|
219
231
|
};
|
220
232
|
|
@@ -223,11 +235,33 @@
|
|
223
235
|
return _.map(obj, function(value){ return value[key]; });
|
224
236
|
};
|
225
237
|
|
238
|
+
// Convenience version of a common use case of `filter`: selecting only objects
|
239
|
+
// containing specific `key:value` pairs.
|
240
|
+
_.where = function(obj, attrs, first) {
|
241
|
+
if (_.isEmpty(attrs)) return first ? null : [];
|
242
|
+
return _[first ? 'find' : 'filter'](obj, function(value) {
|
243
|
+
for (var key in attrs) {
|
244
|
+
if (attrs[key] !== value[key]) return false;
|
245
|
+
}
|
246
|
+
return true;
|
247
|
+
});
|
248
|
+
};
|
249
|
+
|
250
|
+
// Convenience version of a common use case of `find`: getting the first object
|
251
|
+
// containing specific `key:value` pairs.
|
252
|
+
_.findWhere = function(obj, attrs) {
|
253
|
+
return _.where(obj, attrs, true);
|
254
|
+
};
|
255
|
+
|
226
256
|
// Return the maximum element or (element-based computation).
|
257
|
+
// Can't optimize arrays of integers longer than 65,535 elements.
|
258
|
+
// See: https://bugs.webkit.org/show_bug.cgi?id=80797
|
227
259
|
_.max = function(obj, iterator, context) {
|
228
|
-
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]
|
260
|
+
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
|
261
|
+
return Math.max.apply(Math, obj);
|
262
|
+
}
|
229
263
|
if (!iterator && _.isEmpty(obj)) return -Infinity;
|
230
|
-
var result = {computed : -Infinity};
|
264
|
+
var result = {computed : -Infinity, value: -Infinity};
|
231
265
|
each(obj, function(value, index, list) {
|
232
266
|
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
233
267
|
computed >= result.computed && (result = {value : value, computed : computed});
|
@@ -237,9 +271,11 @@
|
|
237
271
|
|
238
272
|
// Return the minimum element (or element-based computation).
|
239
273
|
_.min = function(obj, iterator, context) {
|
240
|
-
if (!iterator && _.isArray(obj) && obj[0] === +obj[0]
|
274
|
+
if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
|
275
|
+
return Math.min.apply(Math, obj);
|
276
|
+
}
|
241
277
|
if (!iterator && _.isEmpty(obj)) return Infinity;
|
242
|
-
var result = {computed : Infinity};
|
278
|
+
var result = {computed : Infinity, value: Infinity};
|
243
279
|
each(obj, function(value, index, list) {
|
244
280
|
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
245
281
|
computed < result.computed && (result = {value : value, computed : computed});
|
@@ -249,67 +285,96 @@
|
|
249
285
|
|
250
286
|
// Shuffle an array.
|
251
287
|
_.shuffle = function(obj) {
|
252
|
-
var
|
253
|
-
|
254
|
-
|
255
|
-
|
288
|
+
var rand;
|
289
|
+
var index = 0;
|
290
|
+
var shuffled = [];
|
291
|
+
each(obj, function(value) {
|
292
|
+
rand = _.random(index++);
|
293
|
+
shuffled[index - 1] = shuffled[rand];
|
256
294
|
shuffled[rand] = value;
|
257
295
|
});
|
258
296
|
return shuffled;
|
259
297
|
};
|
260
298
|
|
299
|
+
// An internal function to generate lookup iterators.
|
300
|
+
var lookupIterator = function(value) {
|
301
|
+
return _.isFunction(value) ? value : function(obj){ return obj[value]; };
|
302
|
+
};
|
303
|
+
|
261
304
|
// Sort the object's values by a criterion produced by an iterator.
|
262
|
-
_.sortBy = function(obj,
|
263
|
-
var iterator =
|
305
|
+
_.sortBy = function(obj, value, context) {
|
306
|
+
var iterator = lookupIterator(value);
|
264
307
|
return _.pluck(_.map(obj, function(value, index, list) {
|
265
308
|
return {
|
266
309
|
value : value,
|
310
|
+
index : index,
|
267
311
|
criteria : iterator.call(context, value, index, list)
|
268
312
|
};
|
269
313
|
}).sort(function(left, right) {
|
270
|
-
var a = left.criteria
|
271
|
-
|
272
|
-
if (
|
273
|
-
|
314
|
+
var a = left.criteria;
|
315
|
+
var b = right.criteria;
|
316
|
+
if (a !== b) {
|
317
|
+
if (a > b || a === void 0) return 1;
|
318
|
+
if (a < b || b === void 0) return -1;
|
319
|
+
}
|
320
|
+
return left.index < right.index ? -1 : 1;
|
274
321
|
}), 'value');
|
275
322
|
};
|
276
323
|
|
277
|
-
//
|
278
|
-
|
279
|
-
_.groupBy = function(obj, val) {
|
324
|
+
// An internal function used for aggregate "group by" operations.
|
325
|
+
var group = function(obj, value, context, behavior) {
|
280
326
|
var result = {};
|
281
|
-
var iterator =
|
327
|
+
var iterator = lookupIterator(value || _.identity);
|
282
328
|
each(obj, function(value, index) {
|
283
|
-
var key = iterator(value, index);
|
284
|
-
(result
|
329
|
+
var key = iterator.call(context, value, index, obj);
|
330
|
+
behavior(result, key, value);
|
285
331
|
});
|
286
332
|
return result;
|
287
333
|
};
|
288
334
|
|
289
|
-
//
|
290
|
-
//
|
291
|
-
_.
|
292
|
-
|
335
|
+
// Groups the object's values by a criterion. Pass either a string attribute
|
336
|
+
// to group by, or a function that returns the criterion.
|
337
|
+
_.groupBy = function(obj, value, context) {
|
338
|
+
return group(obj, value, context, function(result, key, value) {
|
339
|
+
(_.has(result, key) ? result[key] : (result[key] = [])).push(value);
|
340
|
+
});
|
341
|
+
};
|
342
|
+
|
343
|
+
// Counts instances of an object that group by a certain criterion. Pass
|
344
|
+
// either a string attribute to count by, or a function that returns the
|
345
|
+
// criterion.
|
346
|
+
_.countBy = function(obj, value, context) {
|
347
|
+
return group(obj, value, context, function(result, key) {
|
348
|
+
if (!_.has(result, key)) result[key] = 0;
|
349
|
+
result[key]++;
|
350
|
+
});
|
351
|
+
};
|
352
|
+
|
353
|
+
// Use a comparator function to figure out the smallest index at which
|
354
|
+
// an object should be inserted so as to maintain order. Uses binary search.
|
355
|
+
_.sortedIndex = function(array, obj, iterator, context) {
|
356
|
+
iterator = iterator == null ? _.identity : lookupIterator(iterator);
|
357
|
+
var value = iterator.call(context, obj);
|
293
358
|
var low = 0, high = array.length;
|
294
359
|
while (low < high) {
|
295
|
-
var mid = (low + high)
|
296
|
-
iterator(array[mid]) <
|
360
|
+
var mid = (low + high) >>> 1;
|
361
|
+
iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
|
297
362
|
}
|
298
363
|
return low;
|
299
364
|
};
|
300
365
|
|
301
366
|
// Safely convert anything iterable into a real, live array.
|
302
367
|
_.toArray = function(obj) {
|
303
|
-
if (!obj)
|
304
|
-
if (_.isArray(obj))
|
305
|
-
if (
|
306
|
-
if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray();
|
368
|
+
if (!obj) return [];
|
369
|
+
if (_.isArray(obj)) return slice.call(obj);
|
370
|
+
if (obj.length === +obj.length) return _.map(obj, _.identity);
|
307
371
|
return _.values(obj);
|
308
372
|
};
|
309
373
|
|
310
374
|
// Return the number of elements in an object.
|
311
375
|
_.size = function(obj) {
|
312
|
-
|
376
|
+
if (obj == null) return 0;
|
377
|
+
return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
|
313
378
|
};
|
314
379
|
|
315
380
|
// Array Functions
|
@@ -319,10 +384,11 @@
|
|
319
384
|
// values in the array. Aliased as `head` and `take`. The **guard** check
|
320
385
|
// allows it to work with `_.map`.
|
321
386
|
_.first = _.head = _.take = function(array, n, guard) {
|
387
|
+
if (array == null) return void 0;
|
322
388
|
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
323
389
|
};
|
324
390
|
|
325
|
-
// Returns everything but the last entry of the array.
|
391
|
+
// Returns everything but the last entry of the array. Especially useful on
|
326
392
|
// the arguments object. Passing **n** will return all the values in
|
327
393
|
// the array, excluding the last N. The **guard** check allows it to work with
|
328
394
|
// `_.map`.
|
@@ -333,6 +399,7 @@
|
|
333
399
|
// Get the last element of an array. Passing **n** will return the last N
|
334
400
|
// values in the array. The **guard** check allows it to work with `_.map`.
|
335
401
|
_.last = function(array, n, guard) {
|
402
|
+
if (array == null) return void 0;
|
336
403
|
if ((n != null) && !guard) {
|
337
404
|
return slice.call(array, Math.max(array.length - n, 0));
|
338
405
|
} else {
|
@@ -340,26 +407,34 @@
|
|
340
407
|
}
|
341
408
|
};
|
342
409
|
|
343
|
-
// Returns everything but the first entry of the array. Aliased as `tail`.
|
344
|
-
// Especially useful on the arguments object. Passing an **
|
345
|
-
// the rest
|
410
|
+
// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
|
411
|
+
// Especially useful on the arguments object. Passing an **n** will return
|
412
|
+
// the rest N values in the array. The **guard**
|
346
413
|
// check allows it to work with `_.map`.
|
347
|
-
_.rest = _.tail = function(array,
|
348
|
-
return slice.call(array, (
|
414
|
+
_.rest = _.tail = _.drop = function(array, n, guard) {
|
415
|
+
return slice.call(array, (n == null) || guard ? 1 : n);
|
349
416
|
};
|
350
417
|
|
351
418
|
// Trim out all falsy values from an array.
|
352
419
|
_.compact = function(array) {
|
353
|
-
return _.filter(array,
|
420
|
+
return _.filter(array, _.identity);
|
421
|
+
};
|
422
|
+
|
423
|
+
// Internal implementation of a recursive `flatten` function.
|
424
|
+
var flatten = function(input, shallow, output) {
|
425
|
+
each(input, function(value) {
|
426
|
+
if (_.isArray(value)) {
|
427
|
+
shallow ? push.apply(output, value) : flatten(value, shallow, output);
|
428
|
+
} else {
|
429
|
+
output.push(value);
|
430
|
+
}
|
431
|
+
});
|
432
|
+
return output;
|
354
433
|
};
|
355
434
|
|
356
435
|
// Return a completely flattened version of an array.
|
357
436
|
_.flatten = function(array, shallow) {
|
358
|
-
return
|
359
|
-
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
|
360
|
-
memo[memo.length] = value;
|
361
|
-
return memo;
|
362
|
-
}, []);
|
437
|
+
return flatten(array, shallow, []);
|
363
438
|
};
|
364
439
|
|
365
440
|
// Return a version of the array that does not contain the specified value(s).
|
@@ -370,30 +445,33 @@
|
|
370
445
|
// Produce a duplicate-free version of the array. If the array has already
|
371
446
|
// been sorted, you have the option of using a faster algorithm.
|
372
447
|
// Aliased as `unique`.
|
373
|
-
_.uniq = _.unique = function(array, isSorted, iterator) {
|
374
|
-
|
448
|
+
_.uniq = _.unique = function(array, isSorted, iterator, context) {
|
449
|
+
if (_.isFunction(isSorted)) {
|
450
|
+
context = iterator;
|
451
|
+
iterator = isSorted;
|
452
|
+
isSorted = false;
|
453
|
+
}
|
454
|
+
var initial = iterator ? _.map(array, iterator, context) : array;
|
375
455
|
var results = [];
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
memo.push(value);
|
456
|
+
var seen = [];
|
457
|
+
each(initial, function(value, index) {
|
458
|
+
if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
|
459
|
+
seen.push(value);
|
381
460
|
results.push(array[index]);
|
382
461
|
}
|
383
|
-
|
384
|
-
}, []);
|
462
|
+
});
|
385
463
|
return results;
|
386
464
|
};
|
387
465
|
|
388
466
|
// Produce an array that contains the union: each distinct element from all of
|
389
467
|
// the passed-in arrays.
|
390
468
|
_.union = function() {
|
391
|
-
return _.uniq(
|
469
|
+
return _.uniq(concat.apply(ArrayProto, arguments));
|
392
470
|
};
|
393
471
|
|
394
472
|
// Produce an array that contains every item shared between all the
|
395
|
-
// passed-in arrays.
|
396
|
-
_.intersection =
|
473
|
+
// passed-in arrays.
|
474
|
+
_.intersection = function(array) {
|
397
475
|
var rest = slice.call(arguments, 1);
|
398
476
|
return _.filter(_.uniq(array), function(item) {
|
399
477
|
return _.every(rest, function(other) {
|
@@ -405,8 +483,8 @@
|
|
405
483
|
// Take the difference between one array and a number of other arrays.
|
406
484
|
// Only the elements present in just the first array will remain.
|
407
485
|
_.difference = function(array) {
|
408
|
-
var rest =
|
409
|
-
return _.filter(array, function(value){ return !_.
|
486
|
+
var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
|
487
|
+
return _.filter(array, function(value){ return !_.contains(rest, value); });
|
410
488
|
};
|
411
489
|
|
412
490
|
// Zip together multiple lists into a single array -- elements that share
|
@@ -415,10 +493,28 @@
|
|
415
493
|
var args = slice.call(arguments);
|
416
494
|
var length = _.max(_.pluck(args, 'length'));
|
417
495
|
var results = new Array(length);
|
418
|
-
for (var i = 0; i < length; i++)
|
496
|
+
for (var i = 0; i < length; i++) {
|
497
|
+
results[i] = _.pluck(args, "" + i);
|
498
|
+
}
|
419
499
|
return results;
|
420
500
|
};
|
421
501
|
|
502
|
+
// Converts lists into objects. Pass either a single array of `[key, value]`
|
503
|
+
// pairs, or two parallel arrays of the same length -- one of keys, and one of
|
504
|
+
// the corresponding values.
|
505
|
+
_.object = function(list, values) {
|
506
|
+
if (list == null) return {};
|
507
|
+
var result = {};
|
508
|
+
for (var i = 0, l = list.length; i < l; i++) {
|
509
|
+
if (values) {
|
510
|
+
result[list[i]] = values[i];
|
511
|
+
} else {
|
512
|
+
result[list[i][0]] = list[i][1];
|
513
|
+
}
|
514
|
+
}
|
515
|
+
return result;
|
516
|
+
};
|
517
|
+
|
422
518
|
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
|
423
519
|
// we need this function. Return the position of the first occurrence of an
|
424
520
|
// item in an array, or -1 if the item is not included in the array.
|
@@ -427,22 +523,29 @@
|
|
427
523
|
// for **isSorted** to use binary search.
|
428
524
|
_.indexOf = function(array, item, isSorted) {
|
429
525
|
if (array == null) return -1;
|
430
|
-
var i, l;
|
526
|
+
var i = 0, l = array.length;
|
431
527
|
if (isSorted) {
|
432
|
-
|
433
|
-
|
528
|
+
if (typeof isSorted == 'number') {
|
529
|
+
i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
|
530
|
+
} else {
|
531
|
+
i = _.sortedIndex(array, item);
|
532
|
+
return array[i] === item ? i : -1;
|
533
|
+
}
|
434
534
|
}
|
435
|
-
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
436
|
-
for (
|
535
|
+
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
|
536
|
+
for (; i < l; i++) if (array[i] === item) return i;
|
437
537
|
return -1;
|
438
538
|
};
|
439
539
|
|
440
540
|
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
441
|
-
_.lastIndexOf = function(array, item) {
|
541
|
+
_.lastIndexOf = function(array, item, from) {
|
442
542
|
if (array == null) return -1;
|
443
|
-
|
444
|
-
|
445
|
-
|
543
|
+
var hasIndex = from != null;
|
544
|
+
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
|
545
|
+
return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
|
546
|
+
}
|
547
|
+
var i = (hasIndex ? from : array.length);
|
548
|
+
while (i--) if (array[i] === item) return i;
|
446
549
|
return -1;
|
447
550
|
};
|
448
551
|
|
@@ -471,25 +574,23 @@
|
|
471
574
|
// Function (ahem) Functions
|
472
575
|
// ------------------
|
473
576
|
|
474
|
-
// Reusable constructor function for prototype setting.
|
475
|
-
var ctor = function(){};
|
476
|
-
|
477
577
|
// Create a function bound to a given object (assigning `this`, and arguments,
|
478
|
-
// optionally).
|
479
|
-
//
|
480
|
-
|
481
|
-
_.bind = function bind(func, context) {
|
482
|
-
var bound, args;
|
578
|
+
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
|
579
|
+
// available.
|
580
|
+
_.bind = function(func, context) {
|
483
581
|
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
582
|
+
var args = slice.call(arguments, 2);
|
583
|
+
return function() {
|
584
|
+
return func.apply(context, args.concat(slice.call(arguments)));
|
585
|
+
};
|
586
|
+
};
|
587
|
+
|
588
|
+
// Partially apply a function by creating a version that has had some of its
|
589
|
+
// arguments pre-filled, without changing its dynamic `this` context.
|
590
|
+
_.partial = function(func) {
|
591
|
+
var args = slice.call(arguments, 1);
|
592
|
+
return function() {
|
593
|
+
return func.apply(this, args.concat(slice.call(arguments)));
|
493
594
|
};
|
494
595
|
};
|
495
596
|
|
@@ -497,7 +598,7 @@
|
|
497
598
|
// all callbacks defined on an object belong to it.
|
498
599
|
_.bindAll = function(obj) {
|
499
600
|
var funcs = slice.call(arguments, 1);
|
500
|
-
if (funcs.length
|
601
|
+
if (funcs.length === 0) funcs = _.functions(obj);
|
501
602
|
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
502
603
|
return obj;
|
503
604
|
};
|
@@ -528,23 +629,26 @@
|
|
528
629
|
// Returns a function, that, when invoked, will only be triggered at most once
|
529
630
|
// during a given window of time.
|
530
631
|
_.throttle = function(func, wait) {
|
531
|
-
var context, args, timeout,
|
532
|
-
var
|
632
|
+
var context, args, timeout, result;
|
633
|
+
var previous = 0;
|
634
|
+
var later = function() {
|
635
|
+
previous = new Date;
|
636
|
+
timeout = null;
|
637
|
+
result = func.apply(context, args);
|
638
|
+
};
|
533
639
|
return function() {
|
534
|
-
|
535
|
-
var
|
640
|
+
var now = new Date;
|
641
|
+
var remaining = wait - (now - previous);
|
642
|
+
context = this;
|
643
|
+
args = arguments;
|
644
|
+
if (remaining <= 0) {
|
645
|
+
clearTimeout(timeout);
|
536
646
|
timeout = null;
|
537
|
-
|
538
|
-
whenDone();
|
539
|
-
};
|
540
|
-
if (!timeout) timeout = setTimeout(later, wait);
|
541
|
-
if (throttling) {
|
542
|
-
more = true;
|
543
|
-
} else {
|
647
|
+
previous = now;
|
544
648
|
result = func.apply(context, args);
|
649
|
+
} else if (!timeout) {
|
650
|
+
timeout = setTimeout(later, remaining);
|
545
651
|
}
|
546
|
-
whenDone();
|
547
|
-
throttling = true;
|
548
652
|
return result;
|
549
653
|
};
|
550
654
|
};
|
@@ -554,16 +658,18 @@
|
|
554
658
|
// N milliseconds. If `immediate` is passed, trigger the function on the
|
555
659
|
// leading edge, instead of the trailing.
|
556
660
|
_.debounce = function(func, wait, immediate) {
|
557
|
-
var timeout;
|
661
|
+
var timeout, result;
|
558
662
|
return function() {
|
559
663
|
var context = this, args = arguments;
|
560
664
|
var later = function() {
|
561
665
|
timeout = null;
|
562
|
-
if (!immediate) func.apply(context, args);
|
666
|
+
if (!immediate) result = func.apply(context, args);
|
563
667
|
};
|
564
|
-
|
668
|
+
var callNow = immediate && !timeout;
|
565
669
|
clearTimeout(timeout);
|
566
670
|
timeout = setTimeout(later, wait);
|
671
|
+
if (callNow) result = func.apply(context, args);
|
672
|
+
return result;
|
567
673
|
};
|
568
674
|
};
|
569
675
|
|
@@ -574,7 +680,9 @@
|
|
574
680
|
return function() {
|
575
681
|
if (ran) return memo;
|
576
682
|
ran = true;
|
577
|
-
|
683
|
+
memo = func.apply(this, arguments);
|
684
|
+
func = null;
|
685
|
+
return memo;
|
578
686
|
};
|
579
687
|
};
|
580
688
|
|
@@ -583,7 +691,8 @@
|
|
583
691
|
// conditionally execute the original function.
|
584
692
|
_.wrap = function(func, wrapper) {
|
585
693
|
return function() {
|
586
|
-
var args = [func]
|
694
|
+
var args = [func];
|
695
|
+
push.apply(args, arguments);
|
587
696
|
return wrapper.apply(this, args);
|
588
697
|
};
|
589
698
|
};
|
@@ -605,7 +714,9 @@
|
|
605
714
|
_.after = function(times, func) {
|
606
715
|
if (times <= 0) return func();
|
607
716
|
return function() {
|
608
|
-
if (--times < 1) {
|
717
|
+
if (--times < 1) {
|
718
|
+
return func.apply(this, arguments);
|
719
|
+
}
|
609
720
|
};
|
610
721
|
};
|
611
722
|
|
@@ -623,7 +734,23 @@
|
|
623
734
|
|
624
735
|
// Retrieve the values of an object's properties.
|
625
736
|
_.values = function(obj) {
|
626
|
-
|
737
|
+
var values = [];
|
738
|
+
for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
|
739
|
+
return values;
|
740
|
+
};
|
741
|
+
|
742
|
+
// Convert an object into a list of `[key, value]` pairs.
|
743
|
+
_.pairs = function(obj) {
|
744
|
+
var pairs = [];
|
745
|
+
for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
|
746
|
+
return pairs;
|
747
|
+
};
|
748
|
+
|
749
|
+
// Invert the keys and values of an object. The values must be serializable.
|
750
|
+
_.invert = function(obj) {
|
751
|
+
var result = {};
|
752
|
+
for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
|
753
|
+
return result;
|
627
754
|
};
|
628
755
|
|
629
756
|
// Return a sorted list of the function names available on the object.
|
@@ -639,8 +766,10 @@
|
|
639
766
|
// Extend a given object with all the properties in passed-in object(s).
|
640
767
|
_.extend = function(obj) {
|
641
768
|
each(slice.call(arguments, 1), function(source) {
|
642
|
-
|
643
|
-
|
769
|
+
if (source) {
|
770
|
+
for (var prop in source) {
|
771
|
+
obj[prop] = source[prop];
|
772
|
+
}
|
644
773
|
}
|
645
774
|
});
|
646
775
|
return obj;
|
@@ -648,18 +777,31 @@
|
|
648
777
|
|
649
778
|
// Return a copy of the object only containing the whitelisted properties.
|
650
779
|
_.pick = function(obj) {
|
651
|
-
var
|
652
|
-
|
653
|
-
|
780
|
+
var copy = {};
|
781
|
+
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
|
782
|
+
each(keys, function(key) {
|
783
|
+
if (key in obj) copy[key] = obj[key];
|
654
784
|
});
|
655
|
-
return
|
785
|
+
return copy;
|
786
|
+
};
|
787
|
+
|
788
|
+
// Return a copy of the object without the blacklisted properties.
|
789
|
+
_.omit = function(obj) {
|
790
|
+
var copy = {};
|
791
|
+
var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
|
792
|
+
for (var key in obj) {
|
793
|
+
if (!_.contains(keys, key)) copy[key] = obj[key];
|
794
|
+
}
|
795
|
+
return copy;
|
656
796
|
};
|
657
797
|
|
658
798
|
// Fill in a given object with default properties.
|
659
799
|
_.defaults = function(obj) {
|
660
800
|
each(slice.call(arguments, 1), function(source) {
|
661
|
-
|
662
|
-
|
801
|
+
if (source) {
|
802
|
+
for (var prop in source) {
|
803
|
+
if (obj[prop] == null) obj[prop] = source[prop];
|
804
|
+
}
|
663
805
|
}
|
664
806
|
});
|
665
807
|
return obj;
|
@@ -679,19 +821,16 @@
|
|
679
821
|
return obj;
|
680
822
|
};
|
681
823
|
|
682
|
-
// Internal recursive comparison function
|
683
|
-
|
824
|
+
// Internal recursive comparison function for `isEqual`.
|
825
|
+
var eq = function(a, b, aStack, bStack) {
|
684
826
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
685
827
|
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
686
828
|
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
687
829
|
// A strict comparison is necessary because `null == undefined`.
|
688
830
|
if (a == null || b == null) return a === b;
|
689
831
|
// Unwrap any wrapped objects.
|
690
|
-
if (a
|
691
|
-
if (b
|
692
|
-
// Invoke a custom `isEqual` method if one is provided.
|
693
|
-
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
694
|
-
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
832
|
+
if (a instanceof _) a = a._wrapped;
|
833
|
+
if (b instanceof _) b = b._wrapped;
|
695
834
|
// Compare `[[Class]]` names.
|
696
835
|
var className = toString.call(a);
|
697
836
|
if (className != toString.call(b)) return false;
|
@@ -721,14 +860,15 @@
|
|
721
860
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
722
861
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
723
862
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
724
|
-
var length =
|
863
|
+
var length = aStack.length;
|
725
864
|
while (length--) {
|
726
865
|
// Linear search. Performance is inversely proportional to the number of
|
727
866
|
// unique nested structures.
|
728
|
-
if (
|
867
|
+
if (aStack[length] == a) return bStack[length] == b;
|
729
868
|
}
|
730
869
|
// Add the first object to the stack of traversed objects.
|
731
|
-
|
870
|
+
aStack.push(a);
|
871
|
+
bStack.push(b);
|
732
872
|
var size = 0, result = true;
|
733
873
|
// Recursively compare objects and arrays.
|
734
874
|
if (className == '[object Array]') {
|
@@ -738,20 +878,24 @@
|
|
738
878
|
if (result) {
|
739
879
|
// Deep compare the contents, ignoring non-numeric properties.
|
740
880
|
while (size--) {
|
741
|
-
|
742
|
-
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
|
881
|
+
if (!(result = eq(a[size], b[size], aStack, bStack))) break;
|
743
882
|
}
|
744
883
|
}
|
745
884
|
} else {
|
746
|
-
// Objects with different constructors are not equivalent
|
747
|
-
|
885
|
+
// Objects with different constructors are not equivalent, but `Object`s
|
886
|
+
// from different frames are.
|
887
|
+
var aCtor = a.constructor, bCtor = b.constructor;
|
888
|
+
if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
|
889
|
+
_.isFunction(bCtor) && (bCtor instanceof bCtor))) {
|
890
|
+
return false;
|
891
|
+
}
|
748
892
|
// Deep compare objects.
|
749
893
|
for (var key in a) {
|
750
894
|
if (_.has(a, key)) {
|
751
895
|
// Count the expected number of properties.
|
752
896
|
size++;
|
753
897
|
// Deep compare each member.
|
754
|
-
if (!(result = _.has(b, key) && eq(a[key], b[key],
|
898
|
+
if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
|
755
899
|
}
|
756
900
|
}
|
757
901
|
// Ensure that both objects contain the same number of properties.
|
@@ -763,13 +907,14 @@
|
|
763
907
|
}
|
764
908
|
}
|
765
909
|
// Remove the first object from the stack of traversed objects.
|
766
|
-
|
910
|
+
aStack.pop();
|
911
|
+
bStack.pop();
|
767
912
|
return result;
|
768
|
-
}
|
913
|
+
};
|
769
914
|
|
770
915
|
// Perform a deep comparison to check if two objects are equal.
|
771
916
|
_.isEqual = function(a, b) {
|
772
|
-
return eq(a, b, []);
|
917
|
+
return eq(a, b, [], []);
|
773
918
|
};
|
774
919
|
|
775
920
|
// Is a given array, string, or object empty?
|
@@ -783,7 +928,7 @@
|
|
783
928
|
|
784
929
|
// Is a given value a DOM element?
|
785
930
|
_.isElement = function(obj) {
|
786
|
-
return !!(obj && obj.nodeType
|
931
|
+
return !!(obj && obj.nodeType === 1);
|
787
932
|
};
|
788
933
|
|
789
934
|
// Is a given value an array?
|
@@ -797,40 +942,36 @@
|
|
797
942
|
return obj === Object(obj);
|
798
943
|
};
|
799
944
|
|
800
|
-
//
|
801
|
-
|
802
|
-
|
803
|
-
|
945
|
+
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
|
946
|
+
each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
|
947
|
+
_['is' + name] = function(obj) {
|
948
|
+
return toString.call(obj) == '[object ' + name + ']';
|
949
|
+
};
|
950
|
+
});
|
951
|
+
|
952
|
+
// Define a fallback version of the method in browsers (ahem, IE), where
|
953
|
+
// there isn't any inspectable "Arguments" type.
|
804
954
|
if (!_.isArguments(arguments)) {
|
805
955
|
_.isArguments = function(obj) {
|
806
956
|
return !!(obj && _.has(obj, 'callee'));
|
807
957
|
};
|
808
958
|
}
|
809
959
|
|
810
|
-
//
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
_.isString = function(obj) {
|
817
|
-
return toString.call(obj) == '[object String]';
|
818
|
-
};
|
819
|
-
|
820
|
-
// Is a given value a number?
|
821
|
-
_.isNumber = function(obj) {
|
822
|
-
return toString.call(obj) == '[object Number]';
|
823
|
-
};
|
960
|
+
// Optimize `isFunction` if appropriate.
|
961
|
+
if (typeof (/./) !== 'function') {
|
962
|
+
_.isFunction = function(obj) {
|
963
|
+
return typeof obj === 'function';
|
964
|
+
};
|
965
|
+
}
|
824
966
|
|
825
967
|
// Is a given object a finite number?
|
826
968
|
_.isFinite = function(obj) {
|
827
|
-
return
|
969
|
+
return isFinite(obj) && !isNaN(parseFloat(obj));
|
828
970
|
};
|
829
971
|
|
830
|
-
// Is the given value `NaN`?
|
972
|
+
// Is the given value `NaN`? (NaN is the only number which does not equal itself).
|
831
973
|
_.isNaN = function(obj) {
|
832
|
-
|
833
|
-
return obj !== obj;
|
974
|
+
return _.isNumber(obj) && obj != +obj;
|
834
975
|
};
|
835
976
|
|
836
977
|
// Is a given value a boolean?
|
@@ -838,16 +979,6 @@
|
|
838
979
|
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
|
839
980
|
};
|
840
981
|
|
841
|
-
// Is a given value a date?
|
842
|
-
_.isDate = function(obj) {
|
843
|
-
return toString.call(obj) == '[object Date]';
|
844
|
-
};
|
845
|
-
|
846
|
-
// Is the given value a regular expression?
|
847
|
-
_.isRegExp = function(obj) {
|
848
|
-
return toString.call(obj) == '[object RegExp]';
|
849
|
-
};
|
850
|
-
|
851
982
|
// Is a given value equal to null?
|
852
983
|
_.isNull = function(obj) {
|
853
984
|
return obj === null;
|
@@ -858,7 +989,8 @@
|
|
858
989
|
return obj === void 0;
|
859
990
|
};
|
860
991
|
|
861
|
-
//
|
992
|
+
// Shortcut function for checking if an object has a given property directly
|
993
|
+
// on itself (in other words, not on a prototype).
|
862
994
|
_.has = function(obj, key) {
|
863
995
|
return hasOwnProperty.call(obj, key);
|
864
996
|
};
|
@@ -879,15 +1011,50 @@
|
|
879
1011
|
};
|
880
1012
|
|
881
1013
|
// Run a function **n** times.
|
882
|
-
_.times = function
|
883
|
-
|
1014
|
+
_.times = function(n, iterator, context) {
|
1015
|
+
var accum = Array(n);
|
1016
|
+
for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
|
1017
|
+
return accum;
|
884
1018
|
};
|
885
1019
|
|
886
|
-
//
|
887
|
-
_.
|
888
|
-
|
1020
|
+
// Return a random integer between min and max (inclusive).
|
1021
|
+
_.random = function(min, max) {
|
1022
|
+
if (max == null) {
|
1023
|
+
max = min;
|
1024
|
+
min = 0;
|
1025
|
+
}
|
1026
|
+
return min + Math.floor(Math.random() * (max - min + 1));
|
1027
|
+
};
|
1028
|
+
|
1029
|
+
// List of HTML entities for escaping.
|
1030
|
+
var entityMap = {
|
1031
|
+
escape: {
|
1032
|
+
'&': '&',
|
1033
|
+
'<': '<',
|
1034
|
+
'>': '>',
|
1035
|
+
'"': '"',
|
1036
|
+
"'": ''',
|
1037
|
+
'/': '/'
|
1038
|
+
}
|
1039
|
+
};
|
1040
|
+
entityMap.unescape = _.invert(entityMap.escape);
|
1041
|
+
|
1042
|
+
// Regexes containing the keys and values listed immediately above.
|
1043
|
+
var entityRegexes = {
|
1044
|
+
escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
|
1045
|
+
unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
|
889
1046
|
};
|
890
1047
|
|
1048
|
+
// Functions for escaping and unescaping strings to/from HTML interpolation.
|
1049
|
+
_.each(['escape', 'unescape'], function(method) {
|
1050
|
+
_[method] = function(string) {
|
1051
|
+
if (string == null) return '';
|
1052
|
+
return ('' + string).replace(entityRegexes[method], function(match) {
|
1053
|
+
return entityMap[method][match];
|
1054
|
+
});
|
1055
|
+
};
|
1056
|
+
});
|
1057
|
+
|
891
1058
|
// If the value of the named property is a function then invoke it;
|
892
1059
|
// otherwise, return it.
|
893
1060
|
_.result = function(object, property) {
|
@@ -896,11 +1063,15 @@
|
|
896
1063
|
return _.isFunction(value) ? value.call(object) : value;
|
897
1064
|
};
|
898
1065
|
|
899
|
-
// Add your own custom functions to the Underscore object
|
900
|
-
// they're correctly added to the OOP wrapper as well.
|
1066
|
+
// Add your own custom functions to the Underscore object.
|
901
1067
|
_.mixin = function(obj) {
|
902
1068
|
each(_.functions(obj), function(name){
|
903
|
-
|
1069
|
+
var func = _[name] = obj[name];
|
1070
|
+
_.prototype[name] = function() {
|
1071
|
+
var args = [this._wrapped];
|
1072
|
+
push.apply(args, arguments);
|
1073
|
+
return result.call(this, func.apply(_, args));
|
1074
|
+
};
|
904
1075
|
});
|
905
1076
|
};
|
906
1077
|
|
@@ -908,7 +1079,7 @@
|
|
908
1079
|
// Useful for temporary DOM ids.
|
909
1080
|
var idCounter = 0;
|
910
1081
|
_.uniqueId = function(prefix) {
|
911
|
-
var id = idCounter
|
1082
|
+
var id = ++idCounter + '';
|
912
1083
|
return prefix ? prefix + id : id;
|
913
1084
|
};
|
914
1085
|
|
@@ -923,72 +1094,78 @@
|
|
923
1094
|
// When customizing `templateSettings`, if you don't want to define an
|
924
1095
|
// interpolation, evaluation or escaping regex, we need one that is
|
925
1096
|
// guaranteed not to match.
|
926
|
-
var noMatch =
|
1097
|
+
var noMatch = /(.)^/;
|
927
1098
|
|
928
1099
|
// Certain characters need to be escaped so that they can be put into a
|
929
1100
|
// string literal.
|
930
1101
|
var escapes = {
|
931
|
-
'
|
932
|
-
|
933
|
-
'r':
|
934
|
-
'n':
|
935
|
-
't':
|
936
|
-
'u2028': '
|
937
|
-
'u2029': '
|
1102
|
+
"'": "'",
|
1103
|
+
'\\': '\\',
|
1104
|
+
'\r': 'r',
|
1105
|
+
'\n': 'n',
|
1106
|
+
'\t': 't',
|
1107
|
+
'\u2028': 'u2028',
|
1108
|
+
'\u2029': 'u2029'
|
938
1109
|
};
|
939
1110
|
|
940
|
-
for (var p in escapes) escapes[escapes[p]] = p;
|
941
1111
|
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
|
942
|
-
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
|
943
|
-
|
944
|
-
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
945
|
-
// that had been previously added.
|
946
|
-
var unescape = function(code) {
|
947
|
-
return code.replace(unescaper, function(match, escape) {
|
948
|
-
return escapes[escape];
|
949
|
-
});
|
950
|
-
};
|
951
1112
|
|
952
1113
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
953
1114
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
954
1115
|
// and correctly escapes quotes within interpolated code.
|
955
1116
|
_.template = function(text, data, settings) {
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
//
|
960
|
-
|
961
|
-
|
962
|
-
.
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
1117
|
+
var render;
|
1118
|
+
settings = _.defaults({}, settings, _.templateSettings);
|
1119
|
+
|
1120
|
+
// Combine delimiters into one regular expression via alternation.
|
1121
|
+
var matcher = new RegExp([
|
1122
|
+
(settings.escape || noMatch).source,
|
1123
|
+
(settings.interpolate || noMatch).source,
|
1124
|
+
(settings.evaluate || noMatch).source
|
1125
|
+
].join('|') + '|$', 'g');
|
1126
|
+
|
1127
|
+
// Compile the template source, escaping string literals appropriately.
|
1128
|
+
var index = 0;
|
1129
|
+
var source = "__p+='";
|
1130
|
+
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
|
1131
|
+
source += text.slice(index, offset)
|
1132
|
+
.replace(escaper, function(match) { return '\\' + escapes[match]; });
|
1133
|
+
|
1134
|
+
if (escape) {
|
1135
|
+
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
|
1136
|
+
}
|
1137
|
+
if (interpolate) {
|
1138
|
+
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
|
1139
|
+
}
|
1140
|
+
if (evaluate) {
|
1141
|
+
source += "';\n" + evaluate + "\n__p+='";
|
1142
|
+
}
|
1143
|
+
index = offset + match.length;
|
1144
|
+
return match;
|
1145
|
+
});
|
1146
|
+
source += "';\n";
|
974
1147
|
|
975
1148
|
// If a variable is not specified, place data values in local scope.
|
976
1149
|
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
|
977
1150
|
|
978
|
-
source = "var __p=''
|
979
|
-
"
|
1151
|
+
source = "var __t,__p='',__j=Array.prototype.join," +
|
1152
|
+
"print=function(){__p+=__j.call(arguments,'');};\n" +
|
980
1153
|
source + "return __p;\n";
|
981
1154
|
|
982
|
-
|
1155
|
+
try {
|
1156
|
+
render = new Function(settings.variable || 'obj', '_', source);
|
1157
|
+
} catch (e) {
|
1158
|
+
e.source = source;
|
1159
|
+
throw e;
|
1160
|
+
}
|
1161
|
+
|
983
1162
|
if (data) return render(data, _);
|
984
1163
|
var template = function(data) {
|
985
1164
|
return render.call(this, data, _);
|
986
1165
|
};
|
987
1166
|
|
988
|
-
// Provide the compiled function source as a convenience for
|
989
|
-
|
990
|
-
template.source = 'function(' + (settings.variable || 'obj') + '){\n' +
|
991
|
-
source + '}';
|
1167
|
+
// Provide the compiled function source as a convenience for precompilation.
|
1168
|
+
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
|
992
1169
|
|
993
1170
|
return template;
|
994
1171
|
};
|
@@ -998,29 +1175,15 @@
|
|
998
1175
|
return _(obj).chain();
|
999
1176
|
};
|
1000
1177
|
|
1001
|
-
//
|
1178
|
+
// OOP
|
1002
1179
|
// ---------------
|
1003
|
-
|
1004
1180
|
// If Underscore is called as a function, it returns a wrapped object that
|
1005
1181
|
// can be used OO-style. This wrapper holds altered versions of all the
|
1006
1182
|
// underscore functions. Wrapped objects may be chained.
|
1007
|
-
var wrapper = function(obj) { this._wrapped = obj; };
|
1008
|
-
|
1009
|
-
// Expose `wrapper.prototype` as `_.prototype`
|
1010
|
-
_.prototype = wrapper.prototype;
|
1011
1183
|
|
1012
1184
|
// Helper function to continue chaining intermediate results.
|
1013
|
-
var result = function(obj
|
1014
|
-
return
|
1015
|
-
};
|
1016
|
-
|
1017
|
-
// A method to easily add functions to the OOP wrapper.
|
1018
|
-
var addToWrapper = function(name, func) {
|
1019
|
-
wrapper.prototype[name] = function() {
|
1020
|
-
var args = slice.call(arguments);
|
1021
|
-
unshift.call(args, this._wrapped);
|
1022
|
-
return result(func.apply(_, args), this._chain);
|
1023
|
-
};
|
1185
|
+
var result = function(obj) {
|
1186
|
+
return this._chain ? _(obj).chain() : obj;
|
1024
1187
|
};
|
1025
1188
|
|
1026
1189
|
// Add all of the Underscore functions to the wrapper object.
|
@@ -1029,32 +1192,35 @@
|
|
1029
1192
|
// Add all mutator Array functions to the wrapper.
|
1030
1193
|
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
1031
1194
|
var method = ArrayProto[name];
|
1032
|
-
|
1033
|
-
var
|
1034
|
-
method.apply(
|
1035
|
-
|
1036
|
-
|
1037
|
-
return result(wrapped, this._chain);
|
1195
|
+
_.prototype[name] = function() {
|
1196
|
+
var obj = this._wrapped;
|
1197
|
+
method.apply(obj, arguments);
|
1198
|
+
if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
|
1199
|
+
return result.call(this, obj);
|
1038
1200
|
};
|
1039
1201
|
});
|
1040
1202
|
|
1041
1203
|
// Add all accessor Array functions to the wrapper.
|
1042
1204
|
each(['concat', 'join', 'slice'], function(name) {
|
1043
1205
|
var method = ArrayProto[name];
|
1044
|
-
|
1045
|
-
return result(method.apply(this._wrapped, arguments)
|
1206
|
+
_.prototype[name] = function() {
|
1207
|
+
return result.call(this, method.apply(this._wrapped, arguments));
|
1046
1208
|
};
|
1047
1209
|
});
|
1048
1210
|
|
1049
|
-
|
1050
|
-
wrapper.prototype.chain = function() {
|
1051
|
-
this._chain = true;
|
1052
|
-
return this;
|
1053
|
-
};
|
1211
|
+
_.extend(_.prototype, {
|
1054
1212
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1213
|
+
// Start chaining a wrapped Underscore object.
|
1214
|
+
chain: function() {
|
1215
|
+
this._chain = true;
|
1216
|
+
return this;
|
1217
|
+
},
|
1218
|
+
|
1219
|
+
// Extracts the result from a wrapped and chained object.
|
1220
|
+
value: function() {
|
1221
|
+
return this._wrapped;
|
1222
|
+
}
|
1223
|
+
|
1224
|
+
});
|
1059
1225
|
|
1060
|
-
}).call(this);
|
1226
|
+
}).call(this);
|