railsy_backbone 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/westonplatter/railsy_backbone.png?branch=master)](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
|