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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a60b10150fbc85539a6081d94fa22531453cecd1
4
- data.tar.gz: 8c72b9aec92541d08d742e5c7ff5235d61c65d5a
3
+ metadata.gz: b8b773ebedfa6333ff9e68f550959e7b6c75d6bb
4
+ data.tar.gz: 478b8ca2b048716e7a67bc3c3db619db7c217a8c
5
5
  SHA512:
6
- metadata.gz: 8d5af02700098e358931772710eb49b6b07b211b1bb9ded35bd5ec43913fe2a2ea6746134ef9ea5b750f62f983b749cc8cdd8022d808e195f7ebcbed2a5b55a9
7
- data.tar.gz: f563f3ac772cc65bb7105f71c6636fe415e39dd4b3504d3c7142f100c68e152d1b4b3b1cedb55afa4d88d897a906bc852aac6efa44dfb321ca50a2270e3f9f5f
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 https://github.com/codebrew/backbone-rails
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
- # RailsyBackbone
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
- Questions/Suggestions &nbsp; => &nbsp; [open a GitHub issue - 24 hr response time](https://github.com/westonplatter/railsy_backbone/issues/new)
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 &nbsp; `paramRoot` &nbsp;, 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 `underscore`, `backbone`, and JS customizations to make Backbone play nice with Rails (see Javscript files with `rails_backbone.` prefix regarding what changed).
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/<your_application_name_here>
68
+ //= require backbone/<your_rails_application_name>
31
69
  //= require_tree .
32
70
 
33
- ### Generators
34
- Rails Install
35
- Backbone Model
36
- Backbone Router
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
- ### ERB
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
- ## Features
124
+ # BROKEN -- default rails generated show.json.jbuilder
125
+ json.extract! @book, :title, :author, :created_at, :updated_at
87
126
 
88
- 1. [Nested Model Attributes](#nested-model-attributes)
89
- 2. [Automatic Rails CSRF Integration](#automatic-rails-csrf-integration)
127
+ # FIXED --- after adding `id`
128
+ json.extract! @book, :id, :title, :author, :created_at, :updated_at
90
129
 
91
- ### Nested Model Attributes
92
- Allows you to specify a namespace for model attributes by defining a ```paramRoot``` attribute. For example,
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
- [Here's the link to our docs](http://westonplatter.github.io/railsy_backbone/).
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
- ## Contributions
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(#{plural_name}: @#{plural_name})" %>
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
- @options.<%= plural_model_name %>.bind('reset', @addAll)
7
+ @collection.bind('reset', @addAll)
8
8
 
9
9
  addAll: () =>
10
- @options.<%= plural_model_name %>.each(@addOne)
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 %>: @options.<%= plural_model_name %>.toJSON() ))
17
+ @$el.html(@template(<%= plural_model_name %>: @collection.toJSON() ))
18
18
  @addAll()
19
19
 
20
20
  return this
@@ -1,3 +1,3 @@
1
1
  module RailsyBackbone
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -1,6 +1,7 @@
1
- // Backbone.js 1.0.0
1
+ // Backbone.js 1.1.0
2
2
 
3
- // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
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.0.0';
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 listeners = this._listeners;
155
- if (!listeners) return this;
156
- var deleteListener = !name && !callback;
157
- if (typeof name === 'object') callback = this;
158
- if (obj) (listeners = {})[obj._listenerId] = obj;
159
- for (var id in listeners) {
160
- listeners[id].off(name, callback, this);
161
- if (deleteListener) delete this._listeners[id];
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 listeners = this._listeners || (this._listeners = {});
219
- var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
220
- listeners[id] = obj;
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
- _.extend(this, _.pick(options, modelOptions));
251
+ if (options.collection) this.collection = options.collection;
252
252
  if (options.parse) attrs = this.parse(attrs, options) || {};
253
- if (defaults = _.result(this, 'defaults')) {
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
- // Do not persist invalid models.
465
- if (!this._validate(attrs, options)) return false;
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 || {}, {validationError: error}));
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, merge: false, remove: false};
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, _.defaults(options || {}, addOptions));
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
- models = _.isArray(models) ? models.slice() : [models];
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 this;
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(options || {}, setOptions);
663
+ options = _.defaults({}, options, setOptions);
666
664
  if (options.parse) models = this.parse(models, options);
667
- if (!_.isArray(models)) models = models ? [models] : [];
668
- var i, l, model, attrs, existing, sort;
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
- if (!(model = this._prepareModel(models[i], options))) continue;
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(model)) {
682
- if (options.remove) modelMap[existing.cid] = true;
683
- if (options.merge) {
684
- existing.set(model.attributes, options);
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
- // This is a new model, push it to the `toAdd` list.
689
- } else if (options.add) {
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 (options.remove) {
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
- splice.apply(this.models, [at, 0].concat(toAdd));
726
+ for (i = 0, l = toAdd.length; i < l; i++) {
727
+ this.models.splice(at + i, 0, toAdd[i]);
728
+ }
714
729
  } else {
715
- push.apply(this.models, toAdd);
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
- if (options.silent) return this;
723
-
724
- // Trigger `add` events.
725
- for (i = 0, l = toAdd.length; i < l; i++) {
726
- (model = toAdd[i]).trigger('add', model, this, options);
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
- // Trigger `sort` if the collection was sorted.
730
- if (sort) this.trigger('sort', this, options);
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 this;
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
- model = this._prepareModel(model, options);
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
- model = this._prepareModel(model, options);
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(begin, end) {
780
- return this.models.slice(begin, end);
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 != null ? obj.id : obj.cid || obj];
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 || (options = {});
911
+ options = options ? _.clone(options) : {};
907
912
  options.collection = this;
908
913
  var model = new this.model(attrs, options);
909
- if (!model._validate(attrs, options)) {
910
- this.trigger('invalid', this, attrs, options);
911
- return false;
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', 'indexOf', 'shuffle', 'lastIndexOf',
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
- this._configure(options || {});
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 prefered to global lookups where possible.
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' && window.ActiveXObject &&
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.substr(root.length);
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({}, {root: '/'}, this.options, options);
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
- // If we've started off with a route from a `pushState`-enabled browser,
1402
- // but we're currently in a browser that doesn't support it...
1403
- if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1404
- this.fragment = this.getFragment(null, true);
1405
- this.location.replace(this.root + this.location.search + '#' + this.fragment);
1406
- // Return immediately as browser will do redirect to new url
1407
- return true;
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() || this.loadUrl(this.getHash());
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(fragmentOverride) {
1449
- var fragment = this.fragment = this.getFragment(fragmentOverride);
1450
- var matched = _.any(this.handlers, function(handler) {
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
- fragment = this.getFragment(fragment || '');
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
- var url = this.root + fragment;
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 (model, options) {
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
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 `global` on the server.
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.1';
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, l = obj.length; i < l; i++) {
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
- for (var key in obj) {
86
- if (_.has(obj, key)) {
87
- if (iterator.call(context, obj[key], key, obj) === breaker) return;
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 : value,
311
- index : index,
312
- criteria : iterator.call(context, value, index, list)
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 < right.index ? -1 : 1;
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(obj, value, context, behavior) {
327
- var result = {};
328
- var iterator = lookupIterator(value == null ? _.identity : value);
329
- each(obj, function(value, index) {
330
- var key = iterator.call(context, value, index, obj);
331
- behavior(result, key, value);
332
- });
333
- return result;
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(obj, value, context) {
339
- return group(obj, value, context, function(result, key, value) {
340
- (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
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(obj, value, context) {
348
- return group(obj, value, context, function(result, key) {
349
- if (!_.has(result, key)) result[key] = 0;
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 != null) && !guard ? slice.call(array, 0, n) : array[0];
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 != null) && !guard) {
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
- // Return a completely flattened version of an array.
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, l = list.length; i < l; i++) {
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, l = array.length;
542
+ var i = 0, length = array.length;
530
543
  if (isSorted) {
531
544
  if (typeof isSorted == 'number') {
532
- i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
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 < l; i++) if (array[i] === item) return 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 len = Math.max(Math.ceil((stop - start) / step), 0);
578
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
566
579
  var idx = 0;
567
- var range = new Array(len);
580
+ var range = new Array(length);
568
581
 
569
- while(idx < len) {
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
- var context = this, args = arguments;
696
+ context = this;
697
+ args = arguments;
698
+ timestamp = new Date();
685
699
  var later = function() {
686
- timeout = null;
687
- if (!immediate) result = func.apply(context, args);
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
- clearTimeout(timeout);
691
- timeout = setTimeout(later, wait);
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 values = [];
758
- for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
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 pairs = [];
765
- for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
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
- for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
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
  '<': '&lt;',
1054
1085
  '>': '&gt;',
1055
1086
  '"': '&quot;',
1056
- "'": '&#x27;',
1057
- '/': '&#x2F;'
1087
+ "'": '&#x27;'
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
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-02 00:00:00.000000000 Z
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.6
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