railsy_backbone 0.0.4 → 0.0.5
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 +4 -4
- data/CHANGELOG.md +15 -0
- data/LICENSE +5 -1
- data/README.md +65 -52
- data/lib/generators/backbone/scaffold/templates/router.coffee +1 -1
- data/lib/generators/backbone/scaffold/templates/views/index_view.coffee +3 -3
- data/lib/railsy_backbone/version.rb +1 -1
- data/vendor/assets/javascripts/backbone.js +140 -130
- data/vendor/assets/javascripts/underscore.js +89 -59
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8b773ebedfa6333ff9e68f550959e7b6c75d6bb
|
4
|
+
data.tar.gz: 478b8ca2b048716e7a67bc3c3db619db7c217a8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb8e5d26086583fd14ac7dfc792f311c7ae12779bcdcb2546893fedcd107dc04ada96d4e4abaa8f5594a46696abcb95b9b29bc340d8885f12c40a43cdde12f29
|
7
|
+
data.tar.gz: 0770cdbce7aaadab3097665a8ef171e761350f26713206ae63c7aea68ada87fec696014a19dc0c430e62a99a45d93e5581b4e3d0d7855038e805b8ea17afd725
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## next
|
2
2
|
|
3
|
+
## 0.0.5
|
4
|
+
|
5
|
+
- Updated to Backbone 1.1.0 and Underscore 1.5.2.
|
6
|
+
(westonplatter)
|
7
|
+
|
8
|
+
- Changed from `options.<pluralized_model_name>` to `collection` to store array
|
9
|
+
of Backbone models.
|
10
|
+
(westonplatter)
|
11
|
+
|
12
|
+
- README: Describe how to work with Rails 4 default scaffold generators.
|
13
|
+
(westonplatter)
|
14
|
+
|
15
|
+
- README: Add branching info.
|
16
|
+
(westonplatter)
|
17
|
+
|
3
18
|
## 0.0.4
|
4
19
|
|
5
20
|
- Remove Rails unofficially reserved `created_at` and `updated_at` so they're
|
data/LICENSE
CHANGED
@@ -27,7 +27,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
27
|
|
28
28
|
|
29
29
|
|
30
|
-
For code pulled from
|
30
|
+
For code pulled from:
|
31
|
+
https://github.com/codebrew/backbone-rails
|
32
|
+
|
33
|
+
As of:
|
34
|
+
https://github.com/codebrew/backbone-rails/commit/a49f2ea65e1d70d206f6608bd405fb1968f6d070
|
31
35
|
|
32
36
|
Copyright 2011 Ryan Fitzgerald
|
33
37
|
|
data/README.md
CHANGED
@@ -1,10 +1,44 @@
|
|
1
|
-
#
|
2
|
-
Backbone 1.0.0
|
3
|
-
Underscore 1.5.1
|
1
|
+
# railsy_backbone
|
4
2
|
|
5
3
|
[](https://travis-ci.org/westonplatter/railsy_backbone)
|
6
4
|
|
7
|
-
|
5
|
+
A clone of [codebrew/backbone-rails](https://github.com/codebrew/backbone-rails) with updated Backbone, Underscore, and jquery-rails versions.
|
6
|
+
|
7
|
+
Provides Backbone & Underscore files and modifies Backbone to:
|
8
|
+
- include the Rails authenticity token in HTTP requests
|
9
|
+
- nest model attributes within the declared `paramRoot` , EG,
|
10
|
+
|
11
|
+
```js
|
12
|
+
var Book = Backbone.Model.extend({
|
13
|
+
url: '/books',
|
14
|
+
paramRoot: 'book'
|
15
|
+
});
|
16
|
+
|
17
|
+
var book_instance = new Book({
|
18
|
+
title: 'the illiad',
|
19
|
+
author: 'homer'
|
20
|
+
});
|
21
|
+
|
22
|
+
book_instance.sync();
|
23
|
+
```
|
24
|
+
|
25
|
+
This will cause the resulting HTTP POST to be,
|
26
|
+
|
27
|
+
```sh
|
28
|
+
Started POST "/books" for 127.0.0.1 ...
|
29
|
+
Processing by BooksController#create as JSON
|
30
|
+
Parameters: { "book" => { "title" => "the illiad", "author" => "homer", "id" => 1 } }
|
31
|
+
```
|
32
|
+
|
33
|
+
## Branches
|
34
|
+
|
35
|
+
master:
|
36
|
+
- Backbone 1.1.0
|
37
|
+
- Underscore 1.5.2
|
38
|
+
|
39
|
+
1-0-stable
|
40
|
+
- Backbone 1.0.0
|
41
|
+
- Underscore 1.5.1
|
8
42
|
|
9
43
|
## Rails Setup
|
10
44
|
|
@@ -19,7 +53,11 @@ And then,
|
|
19
53
|
$ bundle install
|
20
54
|
$ rails g backbone:install
|
21
55
|
|
22
|
-
This requires
|
56
|
+
This requires Backbone, Underscore, and the Backbone modifications to implement
|
57
|
+
the Rails authenticity token and nesting model attributes in the paramsRoot
|
58
|
+
(see Javscript files with the `railsy_backbone.` prefix for details).
|
59
|
+
|
60
|
+
These will be added to your `app/assets/javascripts/application.js`:
|
23
61
|
|
24
62
|
//= require jquery
|
25
63
|
//= require jquery_ujs
|
@@ -27,14 +65,13 @@ This requires `underscore`, `backbone`, and JS customizations to make Backbone p
|
|
27
65
|
//= require backbone
|
28
66
|
//= require railsy_backbone.sync
|
29
67
|
//= require railsy_backbone.datalink
|
30
|
-
//= require backbone/<
|
68
|
+
//= require backbone/<your_rails_application_name>
|
31
69
|
//= require_tree .
|
32
70
|
|
33
|
-
### Generators
|
34
|
-
|
35
|
-
Backbone
|
36
|
-
Backbone
|
37
|
-
Backbone Scaffold
|
71
|
+
### Generators
|
72
|
+
Backbone Model `$ rails g backbone:model`
|
73
|
+
Backbone Router `$ rails g backbone:router`
|
74
|
+
Backbone Scaffold `$ rails g backbone:scaffold`
|
38
75
|
|
39
76
|
### Example Usage
|
40
77
|
|
@@ -60,7 +97,7 @@ Generate a `Backbone` scaffold,
|
|
60
97
|
|
61
98
|
Edit `books/index.html` to execute actions through the Backbone scaffold UI rather than routing to different pages.
|
62
99
|
|
63
|
-
|
100
|
+
If you're using ERB, `index.html.erb`
|
64
101
|
|
65
102
|
<div id="books"></div>
|
66
103
|
|
@@ -71,8 +108,7 @@ Edit `books/index.html` to execute actions through the Backbone scaffold UI rath
|
|
71
108
|
});
|
72
109
|
</script>
|
73
110
|
|
74
|
-
|
75
|
-
### HAML
|
111
|
+
Or HAML, `index.html.haml`
|
76
112
|
|
77
113
|
#books
|
78
114
|
|
@@ -82,52 +118,29 @@ Edit `books/index.html` to execute actions through the Backbone scaffold UI rath
|
|
82
118
|
Backbone.history.start();
|
83
119
|
});
|
84
120
|
|
121
|
+
If you're using the default Rails 4 scaffold generators, you'll need to adjust
|
122
|
+
the default JSON show view (IE, `show.json`) to render the `id` attribute.
|
85
123
|
|
86
|
-
|
124
|
+
# BROKEN -- default rails generated show.json.jbuilder
|
125
|
+
json.extract! @book, :title, :author, :created_at, :updated_at
|
87
126
|
|
88
|
-
|
89
|
-
|
127
|
+
# FIXED --- after adding `id`
|
128
|
+
json.extract! @book, :id, :title, :author, :created_at, :updated_at
|
90
129
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
var Book = Backbone.Model.extend({
|
95
|
-
url: '/books',
|
96
|
-
paramRoot: 'book'
|
97
|
-
});
|
98
|
-
|
99
|
-
var book_instance = new Book({
|
100
|
-
title: 'the illiad',
|
101
|
-
author: 'homer'
|
102
|
-
});
|
103
|
-
|
104
|
-
book_instance.sync();
|
105
|
-
|
106
|
-
This will cause the HTTP POST to look like this,
|
107
|
-
|
108
|
-
Started POST "/books" for 127.0.0.1
|
109
|
-
Processing by BooksController#create as JSON
|
110
|
-
Parameters: { "book" => {
|
111
|
-
"title" => "the illiad",
|
112
|
-
"author" => "homer",
|
113
|
-
"id" => 1}
|
114
|
-
}
|
115
|
-
|
116
|
-
|
117
|
-
### Automatic Rails CSRF Integration
|
118
|
-
Automatically handles the Rails `authenticity_token`. Or, more technically, sets the `xhr.setRequestHeader` to the Rails CSRF token supplied in the HTML `header` meta tag.
|
130
|
+
Without adjusting the JSON show view, you will be redirected to a "undefined"
|
131
|
+
url after creating an object.
|
119
132
|
|
120
133
|
|
121
134
|
## Docs
|
135
|
+
[Link to the docs](http://westonplatter.github.io/railsy_backbone/).
|
122
136
|
|
123
|
-
|
124
|
-
|
125
|
-
__I really value clear communication__ (I'm serious!). If you think something is missing in the docs, __please__ let me know via a GitHub issue ([create issues here](https://github.com/westonplatter/railsy_backbone/issues)), and I'll look at adding it.
|
126
|
-
|
137
|
+
I value clear communication __(I'm serious!)__. If you think something is missing in the docs, __please__ open a GitHub issue ([create issues here](https://github.com/westonplatter/railsy_backbone/issues)), and I'd love to add it if it makes sense.
|
127
138
|
|
128
|
-
|
129
|
-
|
130
|
-
[Nicholas Zaillian](https://github.com/nzaillian)
|
139
|
+
## Contributors
|
140
|
+
[These awesome people](https://github.com/westonplatter/railsy_backbone/graphs/contributors) infused their awesome talent in this project.
|
131
141
|
|
132
142
|
## Credits
|
143
|
+
Inspired by and copied from Ryan Fitzgerald's [codebrew/backbone-rails](https://github.com/codebrew/backbone-rails).
|
144
|
+
|
145
|
+
## License
|
133
146
|
See LICENSE
|
@@ -15,7 +15,7 @@ class <%= router_namespace %>Router extends Backbone.Router
|
|
15
15
|
$("#<%= plural_name %>").html(@view.render().el)
|
16
16
|
|
17
17
|
index: ->
|
18
|
-
@view = new <%= "#{view_namespace}.IndexView(
|
18
|
+
@view = new <%= "#{view_namespace}.IndexView(collection: @#{plural_name})" %>
|
19
19
|
$("#<%= plural_name %>").html(@view.render().el)
|
20
20
|
|
21
21
|
show: (id) ->
|
@@ -4,17 +4,17 @@ class <%= view_namespace %>.IndexView extends Backbone.View
|
|
4
4
|
template: JST["<%= jst 'index' %>"]
|
5
5
|
|
6
6
|
initialize: () ->
|
7
|
-
@
|
7
|
+
@collection.bind('reset', @addAll)
|
8
8
|
|
9
9
|
addAll: () =>
|
10
|
-
@
|
10
|
+
@collection.each(@addOne)
|
11
11
|
|
12
12
|
addOne: (<%= singular_model_name %>) =>
|
13
13
|
view = new <%= view_namespace %>.<%= singular_name.camelize %>View({model : <%= singular_model_name %>})
|
14
14
|
@$("tbody").append(view.render().el)
|
15
15
|
|
16
16
|
render: =>
|
17
|
-
@$el.html(@template(<%= plural_model_name %>: @
|
17
|
+
@$el.html(@template(<%= plural_model_name %>: @collection.toJSON() ))
|
18
18
|
@addAll()
|
19
19
|
|
20
20
|
return this
|
@@ -1,6 +1,7 @@
|
|
1
|
-
// Backbone.js 1.
|
1
|
+
// Backbone.js 1.1.0
|
2
2
|
|
3
|
-
// (c) 2010-
|
3
|
+
// (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
|
4
|
+
// (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
4
5
|
// Backbone may be freely distributed under the MIT license.
|
5
6
|
// For all details and documentation:
|
6
7
|
// http://backbonejs.org
|
@@ -34,7 +35,7 @@
|
|
34
35
|
}
|
35
36
|
|
36
37
|
// Current version of the library. Keep in sync with `package.json`.
|
37
|
-
Backbone.VERSION = '1.
|
38
|
+
Backbone.VERSION = '1.1.0';
|
38
39
|
|
39
40
|
// Require Underscore, if we're on the server, and it's not already present.
|
40
41
|
var _ = root._;
|
@@ -52,7 +53,7 @@
|
|
52
53
|
};
|
53
54
|
|
54
55
|
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
|
55
|
-
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
|
56
|
+
// will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
|
56
57
|
// set a `X-Http-Method-Override` header.
|
57
58
|
Backbone.emulateHTTP = false;
|
58
59
|
|
@@ -111,7 +112,6 @@
|
|
111
112
|
this._events = {};
|
112
113
|
return this;
|
113
114
|
}
|
114
|
-
|
115
115
|
names = name ? [name] : _.keys(this._events);
|
116
116
|
for (i = 0, l = names.length; i < l; i++) {
|
117
117
|
name = names[i];
|
@@ -151,14 +151,15 @@
|
|
151
151
|
// Tell this object to stop listening to either specific events ... or
|
152
152
|
// to every object it's currently listening to.
|
153
153
|
stopListening: function(obj, name, callback) {
|
154
|
-
var
|
155
|
-
if (!
|
156
|
-
var
|
157
|
-
if (typeof name === 'object') callback = this;
|
158
|
-
if (obj) (
|
159
|
-
for (var id in
|
160
|
-
|
161
|
-
|
154
|
+
var listeningTo = this._listeningTo;
|
155
|
+
if (!listeningTo) return this;
|
156
|
+
var remove = !name && !callback;
|
157
|
+
if (!callback && typeof name === 'object') callback = this;
|
158
|
+
if (obj) (listeningTo = {})[obj._listenId] = obj;
|
159
|
+
for (var id in listeningTo) {
|
160
|
+
obj = listeningTo[id];
|
161
|
+
obj.off(name, callback, this);
|
162
|
+
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
|
162
163
|
}
|
163
164
|
return this;
|
164
165
|
}
|
@@ -215,10 +216,10 @@
|
|
215
216
|
// listening to.
|
216
217
|
_.each(listenMethods, function(implementation, method) {
|
217
218
|
Events[method] = function(obj, name, callback) {
|
218
|
-
var
|
219
|
-
var id = obj.
|
220
|
-
|
221
|
-
if (typeof name === 'object') callback = this;
|
219
|
+
var listeningTo = this._listeningTo || (this._listeningTo = {});
|
220
|
+
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
|
221
|
+
listeningTo[id] = obj;
|
222
|
+
if (!callback && typeof name === 'object') callback = this;
|
222
223
|
obj[implementation](name, callback, this);
|
223
224
|
return this;
|
224
225
|
};
|
@@ -243,24 +244,18 @@
|
|
243
244
|
// Create a new model with the specified attributes. A client id (`cid`)
|
244
245
|
// is automatically generated and assigned for you.
|
245
246
|
var Model = Backbone.Model = function(attributes, options) {
|
246
|
-
var defaults;
|
247
247
|
var attrs = attributes || {};
|
248
248
|
options || (options = {});
|
249
249
|
this.cid = _.uniqueId('c');
|
250
250
|
this.attributes = {};
|
251
|
-
|
251
|
+
if (options.collection) this.collection = options.collection;
|
252
252
|
if (options.parse) attrs = this.parse(attrs, options) || {};
|
253
|
-
|
254
|
-
attrs = _.defaults({}, attrs, defaults);
|
255
|
-
}
|
253
|
+
attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
|
256
254
|
this.set(attrs, options);
|
257
255
|
this.changed = {};
|
258
256
|
this.initialize.apply(this, arguments);
|
259
257
|
};
|
260
258
|
|
261
|
-
// A list of options to be attached directly to the model, if provided.
|
262
|
-
var modelOptions = ['url', 'urlRoot', 'collection'];
|
263
|
-
|
264
259
|
// Attach all inheritable methods to the Model prototype.
|
265
260
|
_.extend(Model.prototype, Events, {
|
266
261
|
|
@@ -456,13 +451,16 @@
|
|
456
451
|
(attrs = {})[key] = val;
|
457
452
|
}
|
458
453
|
|
459
|
-
// If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
|
460
|
-
if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
|
461
|
-
|
462
454
|
options = _.extend({validate: true}, options);
|
463
455
|
|
464
|
-
//
|
465
|
-
|
456
|
+
// If we're not waiting and attributes exist, save acts as
|
457
|
+
// `set(attr).save(null, opts)` with validation. Otherwise, check if
|
458
|
+
// the model will be valid when the attributes, if any, are set.
|
459
|
+
if (attrs && !options.wait) {
|
460
|
+
if (!this.set(attrs, options)) return false;
|
461
|
+
} else {
|
462
|
+
if (!this._validate(attrs, options)) return false;
|
463
|
+
}
|
466
464
|
|
467
465
|
// Set temporary attributes if `{wait: true}`.
|
468
466
|
if (attrs && options.wait) {
|
@@ -563,7 +561,7 @@
|
|
563
561
|
attrs = _.extend({}, this.attributes, attrs);
|
564
562
|
var error = this.validationError = this.validate(attrs, options) || null;
|
565
563
|
if (!error) return true;
|
566
|
-
this.trigger('invalid', this, error, _.extend(options
|
564
|
+
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
|
567
565
|
return false;
|
568
566
|
}
|
569
567
|
|
@@ -596,7 +594,6 @@
|
|
596
594
|
// its models in sort order, as they're added and removed.
|
597
595
|
var Collection = Backbone.Collection = function(models, options) {
|
598
596
|
options || (options = {});
|
599
|
-
if (options.url) this.url = options.url;
|
600
597
|
if (options.model) this.model = options.model;
|
601
598
|
if (options.comparator !== void 0) this.comparator = options.comparator;
|
602
599
|
this._reset();
|
@@ -606,7 +603,7 @@
|
|
606
603
|
|
607
604
|
// Default options for `Collection#set`.
|
608
605
|
var setOptions = {add: true, remove: true, merge: true};
|
609
|
-
var addOptions = {add: true,
|
606
|
+
var addOptions = {add: true, remove: false};
|
610
607
|
|
611
608
|
// Define the Collection's inheritable methods.
|
612
609
|
_.extend(Collection.prototype, Events, {
|
@@ -632,16 +629,17 @@
|
|
632
629
|
|
633
630
|
// Add a model, or list of models to the set.
|
634
631
|
add: function(models, options) {
|
635
|
-
return this.set(models, _.
|
632
|
+
return this.set(models, _.extend({merge: false}, options, addOptions));
|
636
633
|
},
|
637
634
|
|
638
635
|
// Remove a model, or a list of models from the set.
|
639
636
|
remove: function(models, options) {
|
640
|
-
|
637
|
+
var singular = !_.isArray(models);
|
638
|
+
models = singular ? [models] : _.clone(models);
|
641
639
|
options || (options = {});
|
642
640
|
var i, l, index, model;
|
643
641
|
for (i = 0, l = models.length; i < l; i++) {
|
644
|
-
model = this.get(models[i]);
|
642
|
+
model = models[i] = this.get(models[i]);
|
645
643
|
if (!model) continue;
|
646
644
|
delete this._byId[model.id];
|
647
645
|
delete this._byId[model.cid];
|
@@ -654,7 +652,7 @@
|
|
654
652
|
}
|
655
653
|
this._removeReference(model);
|
656
654
|
}
|
657
|
-
return
|
655
|
+
return singular ? models[0] : models;
|
658
656
|
},
|
659
657
|
|
660
658
|
// Update a collection by `set`-ing a new list of models, adding new ones,
|
@@ -662,31 +660,45 @@
|
|
662
660
|
// already exist in the collection, as necessary. Similar to **Model#set**,
|
663
661
|
// the core operation for updating the data contained by the collection.
|
664
662
|
set: function(models, options) {
|
665
|
-
options = _.defaults(
|
663
|
+
options = _.defaults({}, options, setOptions);
|
666
664
|
if (options.parse) models = this.parse(models, options);
|
667
|
-
|
668
|
-
|
665
|
+
var singular = !_.isArray(models);
|
666
|
+
models = singular ? (models ? [models] : []) : _.clone(models);
|
667
|
+
var i, l, id, model, attrs, existing, sort;
|
669
668
|
var at = options.at;
|
669
|
+
var targetModel = this.model;
|
670
670
|
var sortable = this.comparator && (at == null) && options.sort !== false;
|
671
671
|
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
|
672
672
|
var toAdd = [], toRemove = [], modelMap = {};
|
673
|
+
var add = options.add, merge = options.merge, remove = options.remove;
|
674
|
+
var order = !sortable && add && remove ? [] : false;
|
673
675
|
|
674
676
|
// Turn bare objects into model references, and prevent invalid models
|
675
677
|
// from being added.
|
676
678
|
for (i = 0, l = models.length; i < l; i++) {
|
677
|
-
|
679
|
+
attrs = models[i];
|
680
|
+
if (attrs instanceof Model) {
|
681
|
+
id = model = attrs;
|
682
|
+
} else {
|
683
|
+
id = attrs[targetModel.prototype.idAttribute];
|
684
|
+
}
|
678
685
|
|
679
686
|
// If a duplicate is found, prevent it from being added and
|
680
687
|
// optionally merge it into the existing model.
|
681
|
-
if (existing = this.get(
|
682
|
-
if (
|
683
|
-
if (
|
684
|
-
|
688
|
+
if (existing = this.get(id)) {
|
689
|
+
if (remove) modelMap[existing.cid] = true;
|
690
|
+
if (merge) {
|
691
|
+
attrs = attrs === model ? model.attributes : attrs;
|
692
|
+
if (options.parse) attrs = existing.parse(attrs, options);
|
693
|
+
existing.set(attrs, options);
|
685
694
|
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
|
686
695
|
}
|
696
|
+
models[i] = existing;
|
687
697
|
|
688
|
-
//
|
689
|
-
} else if (
|
698
|
+
// If this is a new, valid model, push it to the `toAdd` list.
|
699
|
+
} else if (add) {
|
700
|
+
model = models[i] = this._prepareModel(attrs, options);
|
701
|
+
if (!model) continue;
|
690
702
|
toAdd.push(model);
|
691
703
|
|
692
704
|
// Listen to added models' events, and index models for lookup by
|
@@ -695,10 +707,11 @@
|
|
695
707
|
this._byId[model.cid] = model;
|
696
708
|
if (model.id != null) this._byId[model.id] = model;
|
697
709
|
}
|
710
|
+
if (order) order.push(existing || model);
|
698
711
|
}
|
699
712
|
|
700
713
|
// Remove nonexistent models if appropriate.
|
701
|
-
if (
|
714
|
+
if (remove) {
|
702
715
|
for (i = 0, l = this.length; i < l; ++i) {
|
703
716
|
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
|
704
717
|
}
|
@@ -706,29 +719,35 @@
|
|
706
719
|
}
|
707
720
|
|
708
721
|
// See if sorting is needed, update `length` and splice in new models.
|
709
|
-
if (toAdd.length) {
|
722
|
+
if (toAdd.length || (order && order.length)) {
|
710
723
|
if (sortable) sort = true;
|
711
724
|
this.length += toAdd.length;
|
712
725
|
if (at != null) {
|
713
|
-
|
726
|
+
for (i = 0, l = toAdd.length; i < l; i++) {
|
727
|
+
this.models.splice(at + i, 0, toAdd[i]);
|
728
|
+
}
|
714
729
|
} else {
|
715
|
-
|
730
|
+
if (order) this.models.length = 0;
|
731
|
+
var orderedModels = order || toAdd;
|
732
|
+
for (i = 0, l = orderedModels.length; i < l; i++) {
|
733
|
+
this.models.push(orderedModels[i]);
|
734
|
+
}
|
716
735
|
}
|
717
736
|
}
|
718
737
|
|
719
738
|
// Silently sort the collection if appropriate.
|
720
739
|
if (sort) this.sort({silent: true});
|
721
740
|
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
741
|
+
// Unless silenced, it's time to fire all appropriate add/sort events.
|
742
|
+
if (!options.silent) {
|
743
|
+
for (i = 0, l = toAdd.length; i < l; i++) {
|
744
|
+
(model = toAdd[i]).trigger('add', model, this, options);
|
745
|
+
}
|
746
|
+
if (sort || (order && order.length)) this.trigger('sort', this, options);
|
727
747
|
}
|
728
|
-
|
729
|
-
//
|
730
|
-
|
731
|
-
return this;
|
748
|
+
|
749
|
+
// Return the added (or merged) model (or models).
|
750
|
+
return singular ? models[0] : models;
|
732
751
|
},
|
733
752
|
|
734
753
|
// When you have more items than you want to add or remove individually,
|
@@ -742,16 +761,14 @@
|
|
742
761
|
}
|
743
762
|
options.previousModels = this.models;
|
744
763
|
this._reset();
|
745
|
-
this.add(models, _.extend({silent: true}, options));
|
764
|
+
models = this.add(models, _.extend({silent: true}, options));
|
746
765
|
if (!options.silent) this.trigger('reset', this, options);
|
747
|
-
return
|
766
|
+
return models;
|
748
767
|
},
|
749
768
|
|
750
769
|
// Add a model to the end of the collection.
|
751
770
|
push: function(model, options) {
|
752
|
-
|
753
|
-
this.add(model, _.extend({at: this.length}, options));
|
754
|
-
return model;
|
771
|
+
return this.add(model, _.extend({at: this.length}, options));
|
755
772
|
},
|
756
773
|
|
757
774
|
// Remove a model from the end of the collection.
|
@@ -763,9 +780,7 @@
|
|
763
780
|
|
764
781
|
// Add a model to the beginning of the collection.
|
765
782
|
unshift: function(model, options) {
|
766
|
-
|
767
|
-
this.add(model, _.extend({at: 0}, options));
|
768
|
-
return model;
|
783
|
+
return this.add(model, _.extend({at: 0}, options));
|
769
784
|
},
|
770
785
|
|
771
786
|
// Remove a model from the beginning of the collection.
|
@@ -776,14 +791,14 @@
|
|
776
791
|
},
|
777
792
|
|
778
793
|
// Slice out a sub-array of models from the collection.
|
779
|
-
slice: function(
|
780
|
-
return this.models
|
794
|
+
slice: function() {
|
795
|
+
return slice.apply(this.models, arguments);
|
781
796
|
},
|
782
797
|
|
783
798
|
// Get a model from the set by id.
|
784
799
|
get: function(obj) {
|
785
800
|
if (obj == null) return void 0;
|
786
|
-
return this._byId[obj.id
|
801
|
+
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
|
787
802
|
},
|
788
803
|
|
789
804
|
// Get the model at the given index.
|
@@ -827,16 +842,6 @@
|
|
827
842
|
return this;
|
828
843
|
},
|
829
844
|
|
830
|
-
// Figure out the smallest index at which a model should be inserted so as
|
831
|
-
// to maintain order.
|
832
|
-
sortedIndex: function(model, value, context) {
|
833
|
-
value || (value = this.comparator);
|
834
|
-
var iterator = _.isFunction(value) ? value : function(model) {
|
835
|
-
return model.get(value);
|
836
|
-
};
|
837
|
-
return _.sortedIndex(this.models, model, iterator, context);
|
838
|
-
},
|
839
|
-
|
840
845
|
// Pluck an attribute from each model in the collection.
|
841
846
|
pluck: function(attr) {
|
842
847
|
return _.invoke(this.models, 'get', attr);
|
@@ -869,7 +874,7 @@
|
|
869
874
|
if (!options.wait) this.add(model, options);
|
870
875
|
var collection = this;
|
871
876
|
var success = options.success;
|
872
|
-
options.success = function(resp) {
|
877
|
+
options.success = function(model, resp, options) {
|
873
878
|
if (options.wait) collection.add(model, options);
|
874
879
|
if (success) success(model, resp, options);
|
875
880
|
};
|
@@ -903,14 +908,12 @@
|
|
903
908
|
if (!attrs.collection) attrs.collection = this;
|
904
909
|
return attrs;
|
905
910
|
}
|
906
|
-
options
|
911
|
+
options = options ? _.clone(options) : {};
|
907
912
|
options.collection = this;
|
908
913
|
var model = new this.model(attrs, options);
|
909
|
-
if (!model.
|
910
|
-
|
911
|
-
|
912
|
-
}
|
913
|
-
return model;
|
914
|
+
if (!model.validationError) return model;
|
915
|
+
this.trigger('invalid', this, model.validationError, options);
|
916
|
+
return false;
|
914
917
|
},
|
915
918
|
|
916
919
|
// Internal method to sever a model's ties to a collection.
|
@@ -942,8 +945,8 @@
|
|
942
945
|
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
|
943
946
|
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
|
944
947
|
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
|
945
|
-
'tail', 'drop', 'last', 'without', '
|
946
|
-
'isEmpty', 'chain'];
|
948
|
+
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
|
949
|
+
'lastIndexOf', 'isEmpty', 'chain'];
|
947
950
|
|
948
951
|
// Mix in each Underscore method as a proxy to `Collection#models`.
|
949
952
|
_.each(methods, function(method) {
|
@@ -982,7 +985,8 @@
|
|
982
985
|
// if an existing element is not provided...
|
983
986
|
var View = Backbone.View = function(options) {
|
984
987
|
this.cid = _.uniqueId('view');
|
985
|
-
|
988
|
+
options || (options = {});
|
989
|
+
_.extend(this, _.pick(options, viewOptions));
|
986
990
|
this._ensureElement();
|
987
991
|
this.initialize.apply(this, arguments);
|
988
992
|
this.delegateEvents();
|
@@ -1001,7 +1005,7 @@
|
|
1001
1005
|
tagName: 'div',
|
1002
1006
|
|
1003
1007
|
// jQuery delegate for element lookup, scoped to DOM elements within the
|
1004
|
-
// current view. This should be
|
1008
|
+
// current view. This should be preferred to global lookups where possible.
|
1005
1009
|
$: function(selector) {
|
1006
1010
|
return this.$el.find(selector);
|
1007
1011
|
},
|
@@ -1041,7 +1045,7 @@
|
|
1041
1045
|
//
|
1042
1046
|
// {
|
1043
1047
|
// 'mousedown .title': 'edit',
|
1044
|
-
// 'click .button': 'save'
|
1048
|
+
// 'click .button': 'save',
|
1045
1049
|
// 'click .open': function(e) { ... }
|
1046
1050
|
// }
|
1047
1051
|
//
|
@@ -1079,16 +1083,6 @@
|
|
1079
1083
|
return this;
|
1080
1084
|
},
|
1081
1085
|
|
1082
|
-
// Performs the initial configuration of a View with a set of options.
|
1083
|
-
// Keys with special meaning *(e.g. model, collection, id, className)* are
|
1084
|
-
// attached directly to the view. See `viewOptions` for an exhaustive
|
1085
|
-
// list.
|
1086
|
-
_configure: function(options) {
|
1087
|
-
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
|
1088
|
-
_.extend(this, _.pick(options, viewOptions));
|
1089
|
-
this.options = options;
|
1090
|
-
},
|
1091
|
-
|
1092
1086
|
// Ensure that the View has a DOM element to render into.
|
1093
1087
|
// If `this.el` is a string, pass it through `$()`, take the first
|
1094
1088
|
// matching element, and re-assign it to `el`. Otherwise, create
|
@@ -1174,8 +1168,7 @@
|
|
1174
1168
|
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
|
1175
1169
|
// that still has ActiveX enabled by default, override jQuery to use that
|
1176
1170
|
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
|
1177
|
-
if (params.type === 'PATCH' &&
|
1178
|
-
!(window.external && window.external.msActiveXFilteringEnabled)) {
|
1171
|
+
if (params.type === 'PATCH' && noXhrPatch) {
|
1179
1172
|
params.xhr = function() {
|
1180
1173
|
return new ActiveXObject("Microsoft.XMLHTTP");
|
1181
1174
|
};
|
@@ -1187,6 +1180,8 @@
|
|
1187
1180
|
return xhr;
|
1188
1181
|
};
|
1189
1182
|
|
1183
|
+
var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
|
1184
|
+
|
1190
1185
|
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
1191
1186
|
var methodMap = {
|
1192
1187
|
'create': 'POST',
|
@@ -1275,7 +1270,7 @@
|
|
1275
1270
|
_routeToRegExp: function(route) {
|
1276
1271
|
route = route.replace(escapeRegExp, '\\$&')
|
1277
1272
|
.replace(optionalParam, '(?:$1)?')
|
1278
|
-
.replace(namedParam, function(match, optional){
|
1273
|
+
.replace(namedParam, function(match, optional) {
|
1279
1274
|
return optional ? match : '([^\/]+)';
|
1280
1275
|
})
|
1281
1276
|
.replace(splatParam, '(.*?)');
|
@@ -1325,6 +1320,9 @@
|
|
1325
1320
|
// Cached regex for removing a trailing slash.
|
1326
1321
|
var trailingSlash = /\/$/;
|
1327
1322
|
|
1323
|
+
// Cached regex for stripping urls of hash and query.
|
1324
|
+
var pathStripper = /[?#].*$/;
|
1325
|
+
|
1328
1326
|
// Has the history handling already been started?
|
1329
1327
|
History.started = false;
|
1330
1328
|
|
@@ -1349,7 +1347,7 @@
|
|
1349
1347
|
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
|
1350
1348
|
fragment = this.location.pathname;
|
1351
1349
|
var root = this.root.replace(trailingSlash, '');
|
1352
|
-
if (!fragment.indexOf(root)) fragment = fragment.
|
1350
|
+
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
|
1353
1351
|
} else {
|
1354
1352
|
fragment = this.getHash();
|
1355
1353
|
}
|
@@ -1365,7 +1363,7 @@
|
|
1365
1363
|
|
1366
1364
|
// Figure out the initial configuration. Do we need an iframe?
|
1367
1365
|
// Is pushState desired ... is it available?
|
1368
|
-
this.options = _.extend({
|
1366
|
+
this.options = _.extend({root: '/'}, this.options, options);
|
1369
1367
|
this.root = this.options.root;
|
1370
1368
|
this._wantsHashChange = this.options.hashChange !== false;
|
1371
1369
|
this._wantsPushState = !!this.options.pushState;
|
@@ -1398,19 +1396,25 @@
|
|
1398
1396
|
var loc = this.location;
|
1399
1397
|
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
|
1400
1398
|
|
1401
|
-
//
|
1402
|
-
//
|
1403
|
-
if (this._wantsHashChange && this._wantsPushState
|
1404
|
-
|
1405
|
-
|
1406
|
-
//
|
1407
|
-
|
1399
|
+
// Transition from hashChange to pushState or vice versa if both are
|
1400
|
+
// requested.
|
1401
|
+
if (this._wantsHashChange && this._wantsPushState) {
|
1402
|
+
|
1403
|
+
// If we've started off with a route from a `pushState`-enabled
|
1404
|
+
// browser, but we're currently in a browser that doesn't support it...
|
1405
|
+
if (!this._hasPushState && !atRoot) {
|
1406
|
+
this.fragment = this.getFragment(null, true);
|
1407
|
+
this.location.replace(this.root + this.location.search + '#' + this.fragment);
|
1408
|
+
// Return immediately as browser will do redirect to new url
|
1409
|
+
return true;
|
1410
|
+
|
1411
|
+
// Or if we've started out with a hash-based route, but we're currently
|
1412
|
+
// in a browser where it could be `pushState`-based instead...
|
1413
|
+
} else if (this._hasPushState && atRoot && loc.hash) {
|
1414
|
+
this.fragment = this.getHash().replace(routeStripper, '');
|
1415
|
+
this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
|
1416
|
+
}
|
1408
1417
|
|
1409
|
-
// Or if we've started out with a hash-based route, but we're currently
|
1410
|
-
// in a browser where it could be `pushState`-based instead...
|
1411
|
-
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
|
1412
|
-
this.fragment = this.getHash().replace(routeStripper, '');
|
1413
|
-
this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
|
1414
1418
|
}
|
1415
1419
|
|
1416
1420
|
if (!this.options.silent) return this.loadUrl();
|
@@ -1439,21 +1443,20 @@
|
|
1439
1443
|
}
|
1440
1444
|
if (current === this.fragment) return false;
|
1441
1445
|
if (this.iframe) this.navigate(current);
|
1442
|
-
this.loadUrl()
|
1446
|
+
this.loadUrl();
|
1443
1447
|
},
|
1444
1448
|
|
1445
1449
|
// Attempt to load the current URL fragment. If a route succeeds with a
|
1446
1450
|
// match, returns `true`. If no defined routes matches the fragment,
|
1447
1451
|
// returns `false`.
|
1448
|
-
loadUrl: function(
|
1449
|
-
|
1450
|
-
|
1452
|
+
loadUrl: function(fragment) {
|
1453
|
+
fragment = this.fragment = this.getFragment(fragment);
|
1454
|
+
return _.any(this.handlers, function(handler) {
|
1451
1455
|
if (handler.route.test(fragment)) {
|
1452
1456
|
handler.callback(fragment);
|
1453
1457
|
return true;
|
1454
1458
|
}
|
1455
1459
|
});
|
1456
|
-
return matched;
|
1457
1460
|
},
|
1458
1461
|
|
1459
1462
|
// Save a fragment into the hash history, or replace the URL state if the
|
@@ -1465,11 +1468,18 @@
|
|
1465
1468
|
// you wish to modify the current URL without adding an entry to the history.
|
1466
1469
|
navigate: function(fragment, options) {
|
1467
1470
|
if (!History.started) return false;
|
1468
|
-
if (!options || options === true) options = {trigger: options};
|
1469
|
-
|
1471
|
+
if (!options || options === true) options = {trigger: !!options};
|
1472
|
+
|
1473
|
+
var url = this.root + (fragment = this.getFragment(fragment || ''));
|
1474
|
+
|
1475
|
+
// Strip the fragment of the query and hash for matching.
|
1476
|
+
fragment = fragment.replace(pathStripper, '');
|
1477
|
+
|
1470
1478
|
if (this.fragment === fragment) return;
|
1471
1479
|
this.fragment = fragment;
|
1472
|
-
|
1480
|
+
|
1481
|
+
// Don't include a trailing slash on the root.
|
1482
|
+
if (fragment === '' && url !== '/') url = url.slice(0, -1);
|
1473
1483
|
|
1474
1484
|
// If pushState is available, we use it to set the fragment as a real URL.
|
1475
1485
|
if (this._hasPushState) {
|
@@ -1492,7 +1502,7 @@
|
|
1492
1502
|
} else {
|
1493
1503
|
return this.location.assign(url);
|
1494
1504
|
}
|
1495
|
-
if (options.trigger) this.loadUrl(fragment);
|
1505
|
+
if (options.trigger) return this.loadUrl(fragment);
|
1496
1506
|
},
|
1497
1507
|
|
1498
1508
|
// Update the hash location, either replacing the current entry, or adding
|
@@ -1560,7 +1570,7 @@
|
|
1560
1570
|
};
|
1561
1571
|
|
1562
1572
|
// Wrap an optional error callback with a fallback error event.
|
1563
|
-
var wrapError = function
|
1573
|
+
var wrapError = function(model, options) {
|
1564
1574
|
var error = options.error;
|
1565
1575
|
options.error = function(resp) {
|
1566
1576
|
if (error) error(model, resp, options);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
// Underscore.js 1.5.
|
1
|
+
// Underscore.js 1.5.2
|
2
2
|
// http://underscorejs.org
|
3
3
|
// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
4
4
|
// Underscore may be freely distributed under the MIT license.
|
@@ -8,7 +8,7 @@
|
|
8
8
|
// Baseline setup
|
9
9
|
// --------------
|
10
10
|
|
11
|
-
// Establish the root object, `window` in the browser, or `
|
11
|
+
// Establish the root object, `window` in the browser, or `exports` on the server.
|
12
12
|
var root = this;
|
13
13
|
|
14
14
|
// Save the previous value of the `_` variable.
|
@@ -65,7 +65,7 @@
|
|
65
65
|
}
|
66
66
|
|
67
67
|
// Current version.
|
68
|
-
_.VERSION = '1.5.
|
68
|
+
_.VERSION = '1.5.2';
|
69
69
|
|
70
70
|
// Collection Functions
|
71
71
|
// --------------------
|
@@ -78,14 +78,13 @@
|
|
78
78
|
if (nativeForEach && obj.forEach === nativeForEach) {
|
79
79
|
obj.forEach(iterator, context);
|
80
80
|
} else if (obj.length === +obj.length) {
|
81
|
-
for (var i = 0,
|
81
|
+
for (var i = 0, length = obj.length; i < length; i++) {
|
82
82
|
if (iterator.call(context, obj[i], i, obj) === breaker) return;
|
83
83
|
}
|
84
84
|
} else {
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
}
|
85
|
+
var keys = _.keys(obj);
|
86
|
+
for (var i = 0, length = keys.length; i < length; i++) {
|
87
|
+
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
|
89
88
|
}
|
90
89
|
}
|
91
90
|
};
|
@@ -284,7 +283,8 @@
|
|
284
283
|
return result.value;
|
285
284
|
};
|
286
285
|
|
287
|
-
// Shuffle an array
|
286
|
+
// Shuffle an array, using the modern version of the
|
287
|
+
// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
|
288
288
|
_.shuffle = function(obj) {
|
289
289
|
var rand;
|
290
290
|
var index = 0;
|
@@ -297,6 +297,16 @@
|
|
297
297
|
return shuffled;
|
298
298
|
};
|
299
299
|
|
300
|
+
// Sample **n** random values from an array.
|
301
|
+
// If **n** is not specified, returns a single random element from the array.
|
302
|
+
// The internal `guard` argument allows it to work with `map`.
|
303
|
+
_.sample = function(obj, n, guard) {
|
304
|
+
if (arguments.length < 2 || guard) {
|
305
|
+
return obj[_.random(obj.length - 1)];
|
306
|
+
}
|
307
|
+
return _.shuffle(obj).slice(0, Math.max(0, n));
|
308
|
+
};
|
309
|
+
|
300
310
|
// An internal function to generate lookup iterators.
|
301
311
|
var lookupIterator = function(value) {
|
302
312
|
return _.isFunction(value) ? value : function(obj){ return obj[value]; };
|
@@ -307,9 +317,9 @@
|
|
307
317
|
var iterator = lookupIterator(value);
|
308
318
|
return _.pluck(_.map(obj, function(value, index, list) {
|
309
319
|
return {
|
310
|
-
value
|
311
|
-
index
|
312
|
-
criteria
|
320
|
+
value: value,
|
321
|
+
index: index,
|
322
|
+
criteria: iterator.call(context, value, index, list)
|
313
323
|
};
|
314
324
|
}).sort(function(left, right) {
|
315
325
|
var a = left.criteria;
|
@@ -318,38 +328,41 @@
|
|
318
328
|
if (a > b || a === void 0) return 1;
|
319
329
|
if (a < b || b === void 0) return -1;
|
320
330
|
}
|
321
|
-
return left.index
|
331
|
+
return left.index - right.index;
|
322
332
|
}), 'value');
|
323
333
|
};
|
324
334
|
|
325
335
|
// An internal function used for aggregate "group by" operations.
|
326
|
-
var group = function(
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
336
|
+
var group = function(behavior) {
|
337
|
+
return function(obj, value, context) {
|
338
|
+
var result = {};
|
339
|
+
var iterator = value == null ? _.identity : lookupIterator(value);
|
340
|
+
each(obj, function(value, index) {
|
341
|
+
var key = iterator.call(context, value, index, obj);
|
342
|
+
behavior(result, key, value);
|
343
|
+
});
|
344
|
+
return result;
|
345
|
+
};
|
334
346
|
};
|
335
347
|
|
336
348
|
// Groups the object's values by a criterion. Pass either a string attribute
|
337
349
|
// to group by, or a function that returns the criterion.
|
338
|
-
_.groupBy = function(
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
350
|
+
_.groupBy = group(function(result, key, value) {
|
351
|
+
(_.has(result, key) ? result[key] : (result[key] = [])).push(value);
|
352
|
+
});
|
353
|
+
|
354
|
+
// Indexes the object's values by a criterion, similar to `groupBy`, but for
|
355
|
+
// when you know that your index values will be unique.
|
356
|
+
_.indexBy = group(function(result, key, value) {
|
357
|
+
result[key] = value;
|
358
|
+
});
|
343
359
|
|
344
360
|
// Counts instances of an object that group by a certain criterion. Pass
|
345
361
|
// either a string attribute to count by, or a function that returns the
|
346
362
|
// criterion.
|
347
|
-
_.countBy = function(
|
348
|
-
|
349
|
-
|
350
|
-
result[key]++;
|
351
|
-
});
|
352
|
-
};
|
363
|
+
_.countBy = group(function(result, key) {
|
364
|
+
_.has(result, key) ? result[key]++ : result[key] = 1;
|
365
|
+
});
|
353
366
|
|
354
367
|
// Use a comparator function to figure out the smallest index at which
|
355
368
|
// an object should be inserted so as to maintain order. Uses binary search.
|
@@ -386,7 +399,7 @@
|
|
386
399
|
// allows it to work with `_.map`.
|
387
400
|
_.first = _.head = _.take = function(array, n, guard) {
|
388
401
|
if (array == null) return void 0;
|
389
|
-
return (n
|
402
|
+
return (n == null) || guard ? array[0] : slice.call(array, 0, n);
|
390
403
|
};
|
391
404
|
|
392
405
|
// Returns everything but the last entry of the array. Especially useful on
|
@@ -401,10 +414,10 @@
|
|
401
414
|
// values in the array. The **guard** check allows it to work with `_.map`.
|
402
415
|
_.last = function(array, n, guard) {
|
403
416
|
if (array == null) return void 0;
|
404
|
-
if ((n
|
405
|
-
return slice.call(array, Math.max(array.length - n, 0));
|
406
|
-
} else {
|
417
|
+
if ((n == null) || guard) {
|
407
418
|
return array[array.length - 1];
|
419
|
+
} else {
|
420
|
+
return slice.call(array, Math.max(array.length - n, 0));
|
408
421
|
}
|
409
422
|
};
|
410
423
|
|
@@ -436,7 +449,7 @@
|
|
436
449
|
return output;
|
437
450
|
};
|
438
451
|
|
439
|
-
//
|
452
|
+
// Flatten out an array, either recursively (by default), or just one level.
|
440
453
|
_.flatten = function(array, shallow) {
|
441
454
|
return flatten(array, shallow, []);
|
442
455
|
};
|
@@ -508,7 +521,7 @@
|
|
508
521
|
_.object = function(list, values) {
|
509
522
|
if (list == null) return {};
|
510
523
|
var result = {};
|
511
|
-
for (var i = 0,
|
524
|
+
for (var i = 0, length = list.length; i < length; i++) {
|
512
525
|
if (values) {
|
513
526
|
result[list[i]] = values[i];
|
514
527
|
} else {
|
@@ -526,17 +539,17 @@
|
|
526
539
|
// for **isSorted** to use binary search.
|
527
540
|
_.indexOf = function(array, item, isSorted) {
|
528
541
|
if (array == null) return -1;
|
529
|
-
var i = 0,
|
542
|
+
var i = 0, length = array.length;
|
530
543
|
if (isSorted) {
|
531
544
|
if (typeof isSorted == 'number') {
|
532
|
-
i = (isSorted < 0 ? Math.max(0,
|
545
|
+
i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
|
533
546
|
} else {
|
534
547
|
i = _.sortedIndex(array, item);
|
535
548
|
return array[i] === item ? i : -1;
|
536
549
|
}
|
537
550
|
}
|
538
551
|
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
|
539
|
-
for (; i <
|
552
|
+
for (; i < length; i++) if (array[i] === item) return i;
|
540
553
|
return -1;
|
541
554
|
};
|
542
555
|
|
@@ -562,11 +575,11 @@
|
|
562
575
|
}
|
563
576
|
step = arguments[2] || 1;
|
564
577
|
|
565
|
-
var
|
578
|
+
var length = Math.max(Math.ceil((stop - start) / step), 0);
|
566
579
|
var idx = 0;
|
567
|
-
var range = new Array(
|
580
|
+
var range = new Array(length);
|
568
581
|
|
569
|
-
while(idx <
|
582
|
+
while(idx < length) {
|
570
583
|
range[idx++] = start;
|
571
584
|
start += step;
|
572
585
|
}
|
@@ -678,17 +691,24 @@
|
|
678
691
|
// N milliseconds. If `immediate` is passed, trigger the function on the
|
679
692
|
// leading edge, instead of the trailing.
|
680
693
|
_.debounce = function(func, wait, immediate) {
|
681
|
-
var result;
|
682
|
-
var timeout = null;
|
694
|
+
var timeout, args, context, timestamp, result;
|
683
695
|
return function() {
|
684
|
-
|
696
|
+
context = this;
|
697
|
+
args = arguments;
|
698
|
+
timestamp = new Date();
|
685
699
|
var later = function() {
|
686
|
-
|
687
|
-
if (
|
700
|
+
var last = (new Date()) - timestamp;
|
701
|
+
if (last < wait) {
|
702
|
+
timeout = setTimeout(later, wait - last);
|
703
|
+
} else {
|
704
|
+
timeout = null;
|
705
|
+
if (!immediate) result = func.apply(context, args);
|
706
|
+
}
|
688
707
|
};
|
689
708
|
var callNow = immediate && !timeout;
|
690
|
-
|
691
|
-
|
709
|
+
if (!timeout) {
|
710
|
+
timeout = setTimeout(later, wait);
|
711
|
+
}
|
692
712
|
if (callNow) result = func.apply(context, args);
|
693
713
|
return result;
|
694
714
|
};
|
@@ -754,22 +774,33 @@
|
|
754
774
|
|
755
775
|
// Retrieve the values of an object's properties.
|
756
776
|
_.values = function(obj) {
|
757
|
-
var
|
758
|
-
|
777
|
+
var keys = _.keys(obj);
|
778
|
+
var length = keys.length;
|
779
|
+
var values = new Array(length);
|
780
|
+
for (var i = 0; i < length; i++) {
|
781
|
+
values[i] = obj[keys[i]];
|
782
|
+
}
|
759
783
|
return values;
|
760
784
|
};
|
761
785
|
|
762
786
|
// Convert an object into a list of `[key, value]` pairs.
|
763
787
|
_.pairs = function(obj) {
|
764
|
-
var
|
765
|
-
|
788
|
+
var keys = _.keys(obj);
|
789
|
+
var length = keys.length;
|
790
|
+
var pairs = new Array(length);
|
791
|
+
for (var i = 0; i < length; i++) {
|
792
|
+
pairs[i] = [keys[i], obj[keys[i]]];
|
793
|
+
}
|
766
794
|
return pairs;
|
767
795
|
};
|
768
796
|
|
769
797
|
// Invert the keys and values of an object. The values must be serializable.
|
770
798
|
_.invert = function(obj) {
|
771
799
|
var result = {};
|
772
|
-
|
800
|
+
var keys = _.keys(obj);
|
801
|
+
for (var i = 0, length = keys.length; i < length; i++) {
|
802
|
+
result[obj[keys[i]]] = keys[i];
|
803
|
+
}
|
773
804
|
return result;
|
774
805
|
};
|
775
806
|
|
@@ -1053,8 +1084,7 @@
|
|
1053
1084
|
'<': '<',
|
1054
1085
|
'>': '>',
|
1055
1086
|
'"': '"',
|
1056
|
-
"'": '''
|
1057
|
-
'/': '/'
|
1087
|
+
"'": '''
|
1058
1088
|
}
|
1059
1089
|
};
|
1060
1090
|
entityMap.unescape = _.invert(entityMap.escape);
|
@@ -1085,7 +1115,7 @@
|
|
1085
1115
|
|
1086
1116
|
// Add your own custom functions to the Underscore object.
|
1087
1117
|
_.mixin = function(obj) {
|
1088
|
-
each(_.functions(obj), function(name){
|
1118
|
+
each(_.functions(obj), function(name) {
|
1089
1119
|
var func = _[name] = obj[name];
|
1090
1120
|
_.prototype[name] = function() {
|
1091
1121
|
var args = [this._wrapped];
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: railsy_backbone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weston Platter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -251,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
251
|
version: '0'
|
252
252
|
requirements: []
|
253
253
|
rubyforge_project:
|
254
|
-
rubygems_version: 2.0.
|
254
|
+
rubygems_version: 2.0.5
|
255
255
|
signing_key:
|
256
256
|
specification_version: 4
|
257
257
|
summary: Inspired by backbone-rails with testing & updated Backbone
|