tolaria 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -1
  3. data/app/assets/fonts/admin/fontawesome.eot +0 -0
  4. data/app/assets/fonts/admin/fontawesome.svg +39 -24
  5. data/app/assets/fonts/admin/fontawesome.ttf +0 -0
  6. data/app/assets/fonts/admin/fontawesome.woff +0 -0
  7. data/app/assets/fonts/admin/fontawesome.woff2 +0 -0
  8. data/app/assets/javascripts/admin/base.js +1 -0
  9. data/app/assets/javascripts/admin/lib/autosize.js +262 -0
  10. data/app/assets/javascripts/admin/lib/backbone.js +242 -216
  11. data/app/assets/javascripts/admin/lib/jquery.chosen.js +1 -1
  12. data/app/assets/javascripts/admin/lib/jquery.js +1 -1
  13. data/app/assets/javascripts/admin/lib/jquery.selection.js +1 -1
  14. data/app/assets/javascripts/admin/lib/moment.js +301 -189
  15. data/app/assets/javascripts/admin/views/fields/attachment_field.js +1 -0
  16. data/app/assets/javascripts/admin/views/fields/textarea.js +9 -0
  17. data/app/assets/stylesheets/admin/components/_buttons.scss +7 -1
  18. data/app/assets/stylesheets/admin/components/_navigation.scss +7 -12
  19. data/app/assets/stylesheets/admin/settings/_icons.scss +81 -1
  20. data/app/controllers/tolaria/resource_controller.rb +3 -3
  21. data/app/helpers/admin/table_helper.rb +5 -5
  22. data/app/views/admin/administrators/_show.html.erb +2 -2
  23. data/app/views/admin/shared/forms/_markdown_composer.html.erb +3 -1
  24. data/app/views/admin/tolaria_resource/_form_buttons.html.erb +12 -3
  25. data/app/views/admin/tolaria_resource/_search_form.html.erb +1 -1
  26. data/app/views/admin/tolaria_resource/edit.html.erb +7 -1
  27. data/app/views/admin/tolaria_resource/index.html.erb +11 -4
  28. data/app/views/admin/tolaria_resource/show.html.erb +13 -4
  29. data/lib/tasks/admin.rake +16 -7
  30. data/lib/tolaria/default_config.rb +1 -0
  31. data/lib/tolaria/form_buildable.rb +3 -2
  32. data/lib/tolaria/version.rb +1 -1
  33. data/test/integration/{interface_test.rb → crud_test.rb} +19 -3
  34. data/test/integration/filter_preservation_test.rb +61 -0
  35. data/tolaria.gemspec +1 -0
  36. metadata +9 -6
@@ -1,4 +1,4 @@
1
- // Backbone 1.2.0
1
+ // Backbone 1.2.3
2
2
  // https://github.com/jashkenas/backbone
3
3
  //
4
4
  // Copyright (c) Jeremy Ashkenas, DocumentCloud, and
@@ -60,12 +60,11 @@
60
60
  // restored later on, if `noConflict` is used.
61
61
  var previousBackbone = root.Backbone;
62
62
 
63
- // Create local references to array methods we'll want to use later.
64
- var array = [];
65
- var slice = array.slice;
63
+ // Create a local reference to a common array method we'll want to use later.
64
+ var slice = Array.prototype.slice;
66
65
 
67
66
  // Current version of the library. Keep in sync with `package.json`.
68
- Backbone.VERSION = '1.2.0';
67
+ Backbone.VERSION = '1.2.3';
69
68
 
70
69
  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
71
70
  // the `$` variable.
@@ -89,12 +88,60 @@
89
88
  // form param named `model`.
90
89
  Backbone.emulateJSON = false;
91
90
 
91
+ // Proxy Backbone class methods to Underscore functions, wrapping the model's
92
+ // `attributes` object or collection's `models` array behind the scenes.
93
+ //
94
+ // collection.filter(function(model) { return model.get('age') > 10 });
95
+ // collection.each(this.addView);
96
+ //
97
+ // `Function#apply` can be slow so we use the method's arg count, if we know it.
98
+ var addMethod = function(length, method, attribute) {
99
+ switch (length) {
100
+ case 1: return function() {
101
+ return _[method](this[attribute]);
102
+ };
103
+ case 2: return function(value) {
104
+ return _[method](this[attribute], value);
105
+ };
106
+ case 3: return function(iteratee, context) {
107
+ return _[method](this[attribute], cb(iteratee, this), context);
108
+ };
109
+ case 4: return function(iteratee, defaultVal, context) {
110
+ return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
111
+ };
112
+ default: return function() {
113
+ var args = slice.call(arguments);
114
+ args.unshift(this[attribute]);
115
+ return _[method].apply(_, args);
116
+ };
117
+ }
118
+ };
119
+ var addUnderscoreMethods = function(Class, methods, attribute) {
120
+ _.each(methods, function(length, method) {
121
+ if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
122
+ });
123
+ };
124
+
125
+ // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
126
+ var cb = function(iteratee, instance) {
127
+ if (_.isFunction(iteratee)) return iteratee;
128
+ if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
129
+ if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
130
+ return iteratee;
131
+ };
132
+ var modelMatcher = function(attrs) {
133
+ var matcher = _.matches(attrs);
134
+ return function(model) {
135
+ return matcher(model.attributes);
136
+ };
137
+ };
138
+
92
139
  // Backbone.Events
93
140
  // ---------------
94
141
 
95
142
  // A module that can be mixed in to *any object* in order to provide it with
96
- // custom events. You may bind with `on` or remove with `off` callback
97
- // functions to an event; `trigger`-ing an event fires all callbacks in
143
+ // a custom event channel. You may bind a callback to an event with `on` or
144
+ // remove with `off`; `trigger`-ing an event fires all callbacks in
98
145
  // succession.
99
146
  //
100
147
  // var object = {};
@@ -109,25 +156,25 @@
109
156
 
110
157
  // Iterates over the standard `event, callback` (as well as the fancy multiple
111
158
  // space-separated events `"change blur", callback` and jQuery-style event
112
- // maps `{event: callback}`), reducing them by manipulating `memo`.
113
- // Passes a normalized single event name and callback, as well as any
114
- // optional `opts`.
115
- var eventsApi = function(iteratee, memo, name, callback, opts) {
159
+ // maps `{event: callback}`).
160
+ var eventsApi = function(iteratee, events, name, callback, opts) {
116
161
  var i = 0, names;
117
162
  if (name && typeof name === 'object') {
118
163
  // Handle event maps.
164
+ if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
119
165
  for (names = _.keys(name); i < names.length ; i++) {
120
- memo = iteratee(memo, names[i], name[names[i]], opts);
166
+ events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
121
167
  }
122
168
  } else if (name && eventSplitter.test(name)) {
123
- // Handle space separated event names.
169
+ // Handle space separated event names by delegating them individually.
124
170
  for (names = name.split(eventSplitter); i < names.length; i++) {
125
- memo = iteratee(memo, names[i], callback, opts);
171
+ events = iteratee(events, names[i], callback, opts);
126
172
  }
127
173
  } else {
128
- memo = iteratee(memo, name, callback, opts);
174
+ // Finally, standard events.
175
+ events = iteratee(events, name, callback, opts);
129
176
  }
130
- return memo;
177
+ return events;
131
178
  };
132
179
 
133
180
  // Bind an event to a `callback` function. Passing `"all"` will bind
@@ -136,8 +183,7 @@
136
183
  return internalOn(this, name, callback, context);
137
184
  };
138
185
 
139
- // An internal use `on` function, used to guard the `listening` argument from
140
- // the public API.
186
+ // Guard the `listening` argument from the public API.
141
187
  var internalOn = function(obj, name, callback, context, listening) {
142
188
  obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
143
189
  context: context,
@@ -154,7 +200,8 @@
154
200
  };
155
201
 
156
202
  // Inversion-of-control versions of `on`. Tell *this* object to listen to
157
- // an event in another object... keeping track of what it's listening to.
203
+ // an event in another object... keeping track of what it's listening to
204
+ // for easier unbinding later.
158
205
  Events.listenTo = function(obj, name, callback) {
159
206
  if (!obj) return this;
160
207
  var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
@@ -222,10 +269,9 @@
222
269
 
223
270
  // The reducing API that removes a callback from the `events` object.
224
271
  var offApi = function(events, name, callback, options) {
225
- // No events to consider.
226
272
  if (!events) return;
227
273
 
228
- var i = 0, length, listening;
274
+ var i = 0, listening;
229
275
  var context = options.context, listeners = options.listeners;
230
276
 
231
277
  // Delete all events listeners and "drop" events.
@@ -277,9 +323,9 @@
277
323
  };
278
324
 
279
325
  // Bind an event to only be triggered a single time. After the first time
280
- // the callback is invoked, it will be removed. When multiple events are
281
- // passed in using the space-separated syntax, the event will fire once for every
282
- // event you passed in, not once for a combination of all events
326
+ // the callback is invoked, its listener will be removed. If multiple events
327
+ // are passed in using the space-separated syntax, the handler will fire
328
+ // once for each event, not once for a combination of all events.
283
329
  Events.once = function(name, callback, context) {
284
330
  // Map the event into a `{event: once}` object.
285
331
  var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
@@ -294,7 +340,7 @@
294
340
  };
295
341
 
296
342
  // Reduces the event callbacks into a map of `{event: onceWrapper}`.
297
- // `offer` unbinds the `onceWrapper` after it as been called.
343
+ // `offer` unbinds the `onceWrapper` after it has been called.
298
344
  var onceMap = function(map, name, callback, offer) {
299
345
  if (callback) {
300
346
  var once = map[name] = _.once(function() {
@@ -347,35 +393,6 @@
347
393
  }
348
394
  };
349
395
 
350
- // Proxy Underscore methods to a Backbone class' prototype using a
351
- // particular attribute as the data argument
352
- var addMethod = function(length, method, attribute) {
353
- switch (length) {
354
- case 1: return function() {
355
- return _[method](this[attribute]);
356
- };
357
- case 2: return function(value) {
358
- return _[method](this[attribute], value);
359
- };
360
- case 3: return function(iteratee, context) {
361
- return _[method](this[attribute], iteratee, context);
362
- };
363
- case 4: return function(iteratee, defaultVal, context) {
364
- return _[method](this[attribute], iteratee, defaultVal, context);
365
- };
366
- default: return function() {
367
- var args = slice.call(arguments);
368
- args.unshift(this[attribute]);
369
- return _[method].apply(_, args);
370
- };
371
- }
372
- };
373
- var addUnderscoreMethods = function(Class, methods, attribute) {
374
- _.each(methods, function(length, method) {
375
- if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
376
- });
377
- };
378
-
379
396
  // Aliases for backwards compatibility.
380
397
  Events.bind = Events.on;
381
398
  Events.unbind = Events.off;
@@ -464,10 +481,10 @@
464
481
  // the core primitive operation of a model, updating the data and notifying
465
482
  // anyone who needs to know about the change in state. The heart of the beast.
466
483
  set: function(key, val, options) {
467
- var attr, attrs, unset, changes, silent, changing, prev, current;
468
484
  if (key == null) return this;
469
485
 
470
486
  // Handle both `"key", value` and `{key: value}` -style arguments.
487
+ var attrs;
471
488
  if (typeof key === 'object') {
472
489
  attrs = key;
473
490
  options = val;
@@ -481,33 +498,36 @@
481
498
  if (!this._validate(attrs, options)) return false;
482
499
 
483
500
  // Extract attributes and options.
484
- unset = options.unset;
485
- silent = options.silent;
486
- changes = [];
487
- changing = this._changing;
488
- this._changing = true;
501
+ var unset = options.unset;
502
+ var silent = options.silent;
503
+ var changes = [];
504
+ var changing = this._changing;
505
+ this._changing = true;
489
506
 
490
507
  if (!changing) {
491
508
  this._previousAttributes = _.clone(this.attributes);
492
509
  this.changed = {};
493
510
  }
494
- current = this.attributes, prev = this._previousAttributes;
495
511
 
496
- // Check for changes of `id`.
497
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
512
+ var current = this.attributes;
513
+ var changed = this.changed;
514
+ var prev = this._previousAttributes;
498
515
 
499
516
  // For each `set` attribute, update or delete the current value.
500
- for (attr in attrs) {
517
+ for (var attr in attrs) {
501
518
  val = attrs[attr];
502
519
  if (!_.isEqual(current[attr], val)) changes.push(attr);
503
520
  if (!_.isEqual(prev[attr], val)) {
504
- this.changed[attr] = val;
521
+ changed[attr] = val;
505
522
  } else {
506
- delete this.changed[attr];
523
+ delete changed[attr];
507
524
  }
508
525
  unset ? delete current[attr] : current[attr] = val;
509
526
  }
510
527
 
528
+ // Update the `id`.
529
+ this.id = this.get(this.idAttribute);
530
+
511
531
  // Trigger all relevant attribute changes.
512
532
  if (!silent) {
513
533
  if (changes.length) this._pending = options;
@@ -559,13 +579,14 @@
559
579
  // determining if there *would be* a change.
560
580
  changedAttributes: function(diff) {
561
581
  if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
562
- var val, changed = false;
563
582
  var old = this._changing ? this._previousAttributes : this.attributes;
583
+ var changed = {};
564
584
  for (var attr in diff) {
565
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
566
- (changed || (changed = {}))[attr] = val;
585
+ var val = diff[attr];
586
+ if (_.isEqual(old[attr], val)) continue;
587
+ changed[attr] = val;
567
588
  }
568
- return changed;
589
+ return _.size(changed) ? changed : false;
569
590
  },
570
591
 
571
592
  // Get the previous value of an attribute, recorded at the time the last
@@ -584,12 +605,12 @@
584
605
  // Fetch the model from the server, merging the response with the model's
585
606
  // local attributes. Any changed attributes will trigger a "change" event.
586
607
  fetch: function(options) {
587
- options = options ? _.clone(options) : {};
588
- if (options.parse === void 0) options.parse = true;
608
+ options = _.extend({parse: true}, options);
589
609
  var model = this;
590
610
  var success = options.success;
591
611
  options.success = function(resp) {
592
- if (!model.set(model.parse(resp, options), options)) return false;
612
+ var serverAttrs = options.parse ? model.parse(resp, options) : resp;
613
+ if (!model.set(serverAttrs, options)) return false;
593
614
  if (success) success.call(options.context, model, resp, options);
594
615
  model.trigger('sync', model, resp, options);
595
616
  };
@@ -601,9 +622,8 @@
601
622
  // If the server returns an attributes hash that differs, the model's
602
623
  // state will be `set` again.
603
624
  save: function(key, val, options) {
604
- var attrs, method, xhr, attributes = this.attributes, wait;
605
-
606
625
  // Handle both `"key", value` and `{key: value}` -style arguments.
626
+ var attrs;
607
627
  if (key == null || typeof key === 'object') {
608
628
  attrs = key;
609
629
  options = val;
@@ -611,8 +631,8 @@
611
631
  (attrs = {})[key] = val;
612
632
  }
613
633
 
614
- options = _.extend({validate: true}, options);
615
- wait = options.wait;
634
+ options = _.extend({validate: true, parse: true}, options);
635
+ var wait = options.wait;
616
636
 
617
637
  // If we're not waiting and attributes exist, save acts as
618
638
  // `set(attr).save(null, opts)` with validation. Otherwise, check if
@@ -623,35 +643,31 @@
623
643
  if (!this._validate(attrs, options)) return false;
624
644
  }
625
645
 
626
- // Set temporary attributes if `{wait: true}`.
627
- if (attrs && wait) {
628
- this.attributes = _.extend({}, attributes, attrs);
629
- }
630
-
631
646
  // After a successful server-side save, the client is (optionally)
632
647
  // updated with the server-side state.
633
- if (options.parse === void 0) options.parse = true;
634
648
  var model = this;
635
649
  var success = options.success;
650
+ var attributes = this.attributes;
636
651
  options.success = function(resp) {
637
652
  // Ensure attributes are restored during synchronous saves.
638
653
  model.attributes = attributes;
639
654
  var serverAttrs = options.parse ? model.parse(resp, options) : resp;
640
- if (wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
641
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
642
- return false;
643
- }
655
+ if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
656
+ if (serverAttrs && !model.set(serverAttrs, options)) return false;
644
657
  if (success) success.call(options.context, model, resp, options);
645
658
  model.trigger('sync', model, resp, options);
646
659
  };
647
660
  wrapError(this, options);
648
661
 
649
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
662
+ // Set temporary attributes if `{wait: true}` to properly find new ids.
663
+ if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
664
+
665
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
650
666
  if (method === 'patch' && !options.attrs) options.attrs = attrs;
651
- xhr = this.sync(method, this, options);
667
+ var xhr = this.sync(method, this, options);
652
668
 
653
669
  // Restore attributes.
654
- if (attrs && wait) this.attributes = attributes;
670
+ this.attributes = attributes;
655
671
 
656
672
  return xhr;
657
673
  },
@@ -696,8 +712,8 @@
696
712
  _.result(this.collection, 'url') ||
697
713
  urlError();
698
714
  if (this.isNew()) return base;
699
- var id = this.id || this.attributes[this.idAttribute];
700
- return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(id);
715
+ var id = this.get(this.idAttribute);
716
+ return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
701
717
  },
702
718
 
703
719
  // **parse** converts a response into the hash of attributes to be `set` on
@@ -718,7 +734,7 @@
718
734
 
719
735
  // Check if the model is currently in a valid state.
720
736
  isValid: function(options) {
721
- return this._validate({}, _.extend(options || {}, { validate: true }));
737
+ return this._validate({}, _.defaults({validate: true}, options));
722
738
  },
723
739
 
724
740
  // Run validation against the next complete set of model attributes,
@@ -734,7 +750,8 @@
734
750
 
735
751
  });
736
752
 
737
- // Underscore methods that we want to implement on the Model.
753
+ // Underscore methods that we want to implement on the Model, mapped to the
754
+ // number of arguments they take.
738
755
  var modelMethods = { keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
739
756
  omit: 0, chain: 1, isEmpty: 1 };
740
757
 
@@ -767,6 +784,16 @@
767
784
  var setOptions = {add: true, remove: true, merge: true};
768
785
  var addOptions = {add: true, remove: false};
769
786
 
787
+ // Splices `insert` into `array` at index `at`.
788
+ var splice = function(array, insert, at) {
789
+ at = Math.min(Math.max(at, 0), array.length);
790
+ var tail = Array(array.length - at);
791
+ var length = insert.length;
792
+ for (var i = 0; i < tail.length; i++) tail[i] = array[i + at];
793
+ for (i = 0; i < length; i++) array[i + at] = insert[i];
794
+ for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
795
+ };
796
+
770
797
  // Define the Collection's inheritable methods.
771
798
  _.extend(Collection.prototype, Events, {
772
799
 
@@ -781,7 +808,7 @@
781
808
  // The JSON representation of a Collection is an array of the
782
809
  // models' attributes.
783
810
  toJSON: function(options) {
784
- return this.map(function(model){ return model.toJSON(options); });
811
+ return this.map(function(model) { return model.toJSON(options); });
785
812
  },
786
813
 
787
814
  // Proxy `Backbone.sync` by default.
@@ -789,19 +816,21 @@
789
816
  return Backbone.sync.apply(this, arguments);
790
817
  },
791
818
 
792
- // Add a model, or list of models to the set.
819
+ // Add a model, or list of models to the set. `models` may be Backbone
820
+ // Models or raw JavaScript objects to be converted to Models, or any
821
+ // combination of the two.
793
822
  add: function(models, options) {
794
823
  return this.set(models, _.extend({merge: false}, options, addOptions));
795
824
  },
796
825
 
797
826
  // Remove a model, or a list of models from the set.
798
827
  remove: function(models, options) {
799
- var singular = !_.isArray(models), removed;
828
+ options = _.extend({}, options);
829
+ var singular = !_.isArray(models);
800
830
  models = singular ? [models] : _.clone(models);
801
- options || (options = {});
802
- removed = this._removeModels(models, options);
831
+ var removed = this._removeModels(models, options);
803
832
  if (!options.silent && removed) this.trigger('update', this, options);
804
- return singular ? models[0] : models;
833
+ return singular ? removed[0] : removed;
805
834
  },
806
835
 
807
836
  // Update a collection by `set`-ing a new list of models, adding new ones,
@@ -809,83 +838,88 @@
809
838
  // already exist in the collection, as necessary. Similar to **Model#set**,
810
839
  // the core operation for updating the data contained by the collection.
811
840
  set: function(models, options) {
841
+ if (models == null) return;
842
+
812
843
  options = _.defaults({}, options, setOptions);
813
- if (options.parse) models = this.parse(models, options);
844
+ if (options.parse && !this._isModel(models)) models = this.parse(models, options);
845
+
814
846
  var singular = !_.isArray(models);
815
- models = singular ? (models ? [models] : []) : models.slice();
816
- var id, model, attrs, existing, sort;
847
+ models = singular ? [models] : models.slice();
848
+
817
849
  var at = options.at;
818
850
  if (at != null) at = +at;
819
851
  if (at < 0) at += this.length + 1;
852
+
853
+ var set = [];
854
+ var toAdd = [];
855
+ var toRemove = [];
856
+ var modelMap = {};
857
+
858
+ var add = options.add;
859
+ var merge = options.merge;
860
+ var remove = options.remove;
861
+
862
+ var sort = false;
820
863
  var sortable = this.comparator && (at == null) && options.sort !== false;
821
864
  var sortAttr = _.isString(this.comparator) ? this.comparator : null;
822
- var toAdd = [], toRemove = [], modelMap = {};
823
- var add = options.add, merge = options.merge, remove = options.remove;
824
- var order = !sortable && add && remove ? [] : false;
825
- var orderChanged = false;
826
865
 
827
866
  // Turn bare objects into model references, and prevent invalid models
828
867
  // from being added.
868
+ var model;
829
869
  for (var i = 0; i < models.length; i++) {
830
- attrs = models[i];
870
+ model = models[i];
831
871
 
832
872
  // If a duplicate is found, prevent it from being added and
833
873
  // optionally merge it into the existing model.
834
- if (existing = this.get(attrs)) {
835
- if (remove) modelMap[existing.cid] = true;
836
- if (merge && attrs !== existing) {
837
- attrs = this._isModel(attrs) ? attrs.attributes : attrs;
874
+ var existing = this.get(model);
875
+ if (existing) {
876
+ if (merge && model !== existing) {
877
+ var attrs = this._isModel(model) ? model.attributes : model;
838
878
  if (options.parse) attrs = existing.parse(attrs, options);
839
879
  existing.set(attrs, options);
840
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
880
+ if (sortable && !sort) sort = existing.hasChanged(sortAttr);
881
+ }
882
+ if (!modelMap[existing.cid]) {
883
+ modelMap[existing.cid] = true;
884
+ set.push(existing);
841
885
  }
842
886
  models[i] = existing;
843
887
 
844
888
  // If this is a new, valid model, push it to the `toAdd` list.
845
889
  } else if (add) {
846
- model = models[i] = this._prepareModel(attrs, options);
847
- if (!model) continue;
848
- toAdd.push(model);
849
- this._addReference(model, options);
850
- }
851
-
852
- // Do not add multiple models with the same `id`.
853
- model = existing || model;
854
- if (!model) continue;
855
- id = this.modelId(model.attributes);
856
- if (order && (model.isNew() || !modelMap[id])) {
857
- order.push(model);
858
-
859
- // Check to see if this is actually a new model at this index.
860
- orderChanged = orderChanged || !this.models[i] || model.cid !== this.models[i].cid;
890
+ model = models[i] = this._prepareModel(model, options);
891
+ if (model) {
892
+ toAdd.push(model);
893
+ this._addReference(model, options);
894
+ modelMap[model.cid] = true;
895
+ set.push(model);
896
+ }
861
897
  }
862
-
863
- modelMap[id] = true;
864
898
  }
865
899
 
866
- // Remove nonexistent models if appropriate.
900
+ // Remove stale models.
867
901
  if (remove) {
868
- for (var i = 0; i < this.length; i++) {
869
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
902
+ for (i = 0; i < this.length; i++) {
903
+ model = this.models[i];
904
+ if (!modelMap[model.cid]) toRemove.push(model);
870
905
  }
871
906
  if (toRemove.length) this._removeModels(toRemove, options);
872
907
  }
873
908
 
874
909
  // See if sorting is needed, update `length` and splice in new models.
875
- if (toAdd.length || orderChanged) {
910
+ var orderChanged = false;
911
+ var replace = !sortable && add && remove;
912
+ if (set.length && replace) {
913
+ orderChanged = this.length != set.length || _.some(this.models, function(model, index) {
914
+ return model !== set[index];
915
+ });
916
+ this.models.length = 0;
917
+ splice(this.models, set, 0);
918
+ this.length = this.models.length;
919
+ } else if (toAdd.length) {
876
920
  if (sortable) sort = true;
877
- this.length += toAdd.length;
878
- if (at != null) {
879
- for (var i = 0; i < toAdd.length; i++) {
880
- this.models.splice(at + i, 0, toAdd[i]);
881
- }
882
- } else {
883
- if (order) this.models.length = 0;
884
- var orderedModels = order || toAdd;
885
- for (var i = 0; i < orderedModels.length; i++) {
886
- this.models.push(orderedModels[i]);
887
- }
888
- }
921
+ splice(this.models, toAdd, at == null ? this.length : at);
922
+ this.length = this.models.length;
889
923
  }
890
924
 
891
925
  // Silently sort the collection if appropriate.
@@ -893,10 +927,10 @@
893
927
 
894
928
  // Unless silenced, it's time to fire all appropriate add/sort events.
895
929
  if (!options.silent) {
896
- var addOpts = at != null ? _.clone(options) : options;
897
- for (var i = 0; i < toAdd.length; i++) {
898
- if (at != null) addOpts.index = at + i;
899
- (model = toAdd[i]).trigger('add', model, this, addOpts);
930
+ for (i = 0; i < toAdd.length; i++) {
931
+ if (at != null) options.index = at + i;
932
+ model = toAdd[i];
933
+ model.trigger('add', model, this, options);
900
934
  }
901
935
  if (sort || orderChanged) this.trigger('sort', this, options);
902
936
  if (toAdd.length || toRemove.length) this.trigger('update', this, options);
@@ -930,8 +964,7 @@
930
964
  // Remove a model from the end of the collection.
931
965
  pop: function(options) {
932
966
  var model = this.at(this.length - 1);
933
- this.remove(model, options);
934
- return model;
967
+ return this.remove(model, options);
935
968
  },
936
969
 
937
970
  // Add a model to the beginning of the collection.
@@ -942,8 +975,7 @@
942
975
  // Remove a model from the beginning of the collection.
943
976
  shift: function(options) {
944
977
  var model = this.at(0);
945
- this.remove(model, options);
946
- return model;
978
+ return this.remove(model, options);
947
979
  },
948
980
 
949
981
  // Slice out a sub-array of models from the collection.
@@ -967,10 +999,7 @@
967
999
  // Return models with matching attributes. Useful for simple cases of
968
1000
  // `filter`.
969
1001
  where: function(attrs, first) {
970
- var matches = _.matches(attrs);
971
- return this[first ? 'find' : 'filter'](function(model) {
972
- return matches(model.attributes);
973
- });
1002
+ return this[first ? 'find' : 'filter'](attrs);
974
1003
  },
975
1004
 
976
1005
  // Return the first model with matching attributes. Useful for simple cases
@@ -983,16 +1012,19 @@
983
1012
  // normal circumstances, as the set will maintain sort order as each item
984
1013
  // is added.
985
1014
  sort: function(options) {
986
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
1015
+ var comparator = this.comparator;
1016
+ if (!comparator) throw new Error('Cannot sort a set without a comparator');
987
1017
  options || (options = {});
988
1018
 
1019
+ var length = comparator.length;
1020
+ if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
1021
+
989
1022
  // Run sort based on type of `comparator`.
990
- if (_.isString(this.comparator) || this.comparator.length === 1) {
991
- this.models = this.sortBy(this.comparator, this);
1023
+ if (length === 1 || _.isString(comparator)) {
1024
+ this.models = this.sortBy(comparator);
992
1025
  } else {
993
- this.models.sort(_.bind(this.comparator, this));
1026
+ this.models.sort(comparator);
994
1027
  }
995
-
996
1028
  if (!options.silent) this.trigger('sort', this, options);
997
1029
  return this;
998
1030
  },
@@ -1006,8 +1038,7 @@
1006
1038
  // collection when they arrive. If `reset: true` is passed, the response
1007
1039
  // data will be passed through the `reset` method instead of `set`.
1008
1040
  fetch: function(options) {
1009
- options = options ? _.clone(options) : {};
1010
- if (options.parse === void 0) options.parse = true;
1041
+ options = _.extend({parse: true}, options);
1011
1042
  var success = options.success;
1012
1043
  var collection = this;
1013
1044
  options.success = function(resp) {
@@ -1026,7 +1057,8 @@
1026
1057
  create: function(model, options) {
1027
1058
  options = options ? _.clone(options) : {};
1028
1059
  var wait = options.wait;
1029
- if (!(model = this._prepareModel(model, options))) return false;
1060
+ model = this._prepareModel(model, options);
1061
+ if (!model) return false;
1030
1062
  if (!wait) this.add(model, options);
1031
1063
  var collection = this;
1032
1064
  var success = options.success;
@@ -1080,31 +1112,26 @@
1080
1112
  return false;
1081
1113
  },
1082
1114
 
1083
- // Internal method called by both remove and set. Does not trigger any
1084
- // additional events. Returns true if anything was actually removed.
1115
+ // Internal method called by both remove and set.
1085
1116
  _removeModels: function(models, options) {
1086
- var i, l, index, model, removed = false;
1087
- for (var i = 0, j = 0; i < models.length; i++) {
1088
- var model = models[i] = this.get(models[i]);
1117
+ var removed = [];
1118
+ for (var i = 0; i < models.length; i++) {
1119
+ var model = this.get(models[i]);
1089
1120
  if (!model) continue;
1090
- var id = this.modelId(model.attributes);
1091
- if (id != null) delete this._byId[id];
1092
- delete this._byId[model.cid];
1121
+
1093
1122
  var index = this.indexOf(model);
1094
1123
  this.models.splice(index, 1);
1095
1124
  this.length--;
1125
+
1096
1126
  if (!options.silent) {
1097
1127
  options.index = index;
1098
1128
  model.trigger('remove', model, this, options);
1099
1129
  }
1100
- models[j++] = model;
1130
+
1131
+ removed.push(model);
1101
1132
  this._removeReference(model, options);
1102
- removed = true;
1103
1133
  }
1104
- // We only need to slice if models array should be smaller, which is
1105
- // caused by some models not actually getting removed.
1106
- if (models.length !== j) models = models.slice(0, j);
1107
- return removed;
1134
+ return removed.length ? removed : false;
1108
1135
  },
1109
1136
 
1110
1137
  // Method for checking whether an object should be considered a model for
@@ -1123,6 +1150,9 @@
1123
1150
 
1124
1151
  // Internal method to sever a model's ties to a collection.
1125
1152
  _removeReference: function(model, options) {
1153
+ delete this._byId[model.cid];
1154
+ var id = this.modelId(model.attributes);
1155
+ if (id != null) delete this._byId[id];
1126
1156
  if (this === model.collection) delete model.collection;
1127
1157
  model.off('all', this._onModelEvent, this);
1128
1158
  },
@@ -1152,29 +1182,16 @@
1152
1182
  // right here:
1153
1183
  var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
1154
1184
  foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
1155
- select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 2,
1156
- contains: 2, invoke: 2, max: 3, min: 3, toArray: 1, size: 1, first: 3,
1185
+ select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
1186
+ contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
1157
1187
  head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
1158
1188
  without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
1159
- isEmpty: 1, chain: 1, sample: 3, partition: 3 };
1189
+ isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
1190
+ sortBy: 3, indexBy: 3};
1160
1191
 
1161
1192
  // Mix in each Underscore method as a proxy to `Collection#models`.
1162
1193
  addUnderscoreMethods(Collection, collectionMethods, 'models');
1163
1194
 
1164
- // Underscore methods that take a property name as an argument.
1165
- var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
1166
-
1167
- // Use attributes instead of properties.
1168
- _.each(attributeMethods, function(method) {
1169
- if (!_[method]) return;
1170
- Collection.prototype[method] = function(value, context) {
1171
- var iterator = _.isFunction(value) ? value : function(model) {
1172
- return model.get(value);
1173
- };
1174
- return _[method](this.models, iterator, context);
1175
- };
1176
- });
1177
-
1178
1195
  // Backbone.View
1179
1196
  // -------------
1180
1197
 
@@ -1190,7 +1207,6 @@
1190
1207
  // if an existing element is not provided...
1191
1208
  var View = Backbone.View = function(options) {
1192
1209
  this.cid = _.uniqueId('view');
1193
- options || (options = {});
1194
1210
  _.extend(this, _.pick(options, viewOptions));
1195
1211
  this._ensureElement();
1196
1212
  this.initialize.apply(this, arguments);
@@ -1199,7 +1215,7 @@
1199
1215
  // Cached regex to split keys for `delegate`.
1200
1216
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1201
1217
 
1202
- // List of view options to be merged as properties.
1218
+ // List of view options to be set as properties.
1203
1219
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1204
1220
 
1205
1221
  // Set up all inheritable **Backbone.View** properties and methods.
@@ -1273,11 +1289,12 @@
1273
1289
  // Uses event delegation for efficiency.
1274
1290
  // Omitting the selector binds the event to `this.el`.
1275
1291
  delegateEvents: function(events) {
1276
- if (!(events || (events = _.result(this, 'events')))) return this;
1292
+ events || (events = _.result(this, 'events'));
1293
+ if (!events) return this;
1277
1294
  this.undelegateEvents();
1278
1295
  for (var key in events) {
1279
1296
  var method = events[key];
1280
- if (!_.isFunction(method)) method = this[events[key]];
1297
+ if (!_.isFunction(method)) method = this[method];
1281
1298
  if (!method) continue;
1282
1299
  var match = key.match(delegateEventSplitter);
1283
1300
  this.delegate(match[1], match[2], _.bind(method, this));
@@ -1290,6 +1307,7 @@
1290
1307
  // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
1291
1308
  delegate: function(eventName, selector, listener) {
1292
1309
  this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
1310
+ return this;
1293
1311
  },
1294
1312
 
1295
1313
  // Clears all callbacks previously bound to the view by `delegateEvents`.
@@ -1304,6 +1322,7 @@
1304
1322
  // `selector` and `listener` are both optional.
1305
1323
  undelegate: function(eventName, selector, listener) {
1306
1324
  this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
1325
+ return this;
1307
1326
  },
1308
1327
 
1309
1328
  // Produces a DOM element to be assigned to your view. Exposed for
@@ -1540,7 +1559,7 @@
1540
1559
  // falls back to polling.
1541
1560
  var History = Backbone.History = function() {
1542
1561
  this.handlers = [];
1543
- _.bindAll(this, 'checkUrl');
1562
+ this.checkUrl = _.bind(this.checkUrl, this);
1544
1563
 
1545
1564
  // Ensure that `History` can be used outside of the browser.
1546
1565
  if (typeof window !== 'undefined') {
@@ -1633,7 +1652,7 @@
1633
1652
  this.options = _.extend({root: '/'}, this.options, options);
1634
1653
  this.root = this.options.root;
1635
1654
  this._wantsHashChange = this.options.hashChange !== false;
1636
- this._hasHashChange = 'onhashchange' in window;
1655
+ this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
1637
1656
  this._useHashChange = this._wantsHashChange && this._hasHashChange;
1638
1657
  this._wantsPushState = !!this.options.pushState;
1639
1658
  this._hasPushState = !!(this.history && this.history.pushState);
@@ -1667,15 +1686,16 @@
1667
1686
  // support the `hashchange` event, HTML5 history, or the user wants
1668
1687
  // `hashChange` but not `pushState`.
1669
1688
  if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
1670
- var iframe = document.createElement('iframe');
1671
- iframe.src = 'javascript:0';
1672
- iframe.style.display = 'none';
1673
- iframe.tabIndex = -1;
1689
+ this.iframe = document.createElement('iframe');
1690
+ this.iframe.src = 'javascript:0';
1691
+ this.iframe.style.display = 'none';
1692
+ this.iframe.tabIndex = -1;
1674
1693
  var body = document.body;
1675
1694
  // Using `appendChild` will throw on IE < 9 if the document is not ready.
1676
- this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
1677
- this.iframe.document.open().close();
1678
- this.iframe.location.hash = '#' + this.fragment;
1695
+ var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
1696
+ iWindow.document.open();
1697
+ iWindow.document.close();
1698
+ iWindow.location.hash = '#' + this.fragment;
1679
1699
  }
1680
1700
 
1681
1701
  // Add a cross-platform `addEventListener` shim for older browsers.
@@ -1713,7 +1733,7 @@
1713
1733
 
1714
1734
  // Clean up the iframe if necessary.
1715
1735
  if (this.iframe) {
1716
- document.body.removeChild(this.iframe.frameElement);
1736
+ document.body.removeChild(this.iframe);
1717
1737
  this.iframe = null;
1718
1738
  }
1719
1739
 
@@ -1736,7 +1756,7 @@
1736
1756
  // If the user pressed the back button, the iframe's hash will have
1737
1757
  // changed and we should use that for comparison.
1738
1758
  if (current === this.fragment && this.iframe) {
1739
- current = this.getHash(this.iframe);
1759
+ current = this.getHash(this.iframe.contentWindow);
1740
1760
  }
1741
1761
 
1742
1762
  if (current === this.fragment) return false;
@@ -1751,7 +1771,7 @@
1751
1771
  // If the root doesn't match, no routes can match either.
1752
1772
  if (!this.matchRoot()) return false;
1753
1773
  fragment = this.fragment = this.getFragment(fragment);
1754
- return _.any(this.handlers, function(handler) {
1774
+ return _.some(this.handlers, function(handler) {
1755
1775
  if (handler.route.test(fragment)) {
1756
1776
  handler.callback(fragment);
1757
1777
  return true;
@@ -1794,12 +1814,18 @@
1794
1814
  // fragment to store history.
1795
1815
  } else if (this._wantsHashChange) {
1796
1816
  this._updateHash(this.location, fragment, options.replace);
1797
- if (this.iframe && (fragment !== this.getHash(this.iframe))) {
1817
+ if (this.iframe && (fragment !== this.getHash(this.iframe.contentWindow))) {
1818
+ var iWindow = this.iframe.contentWindow;
1819
+
1798
1820
  // Opening and closing the iframe tricks IE7 and earlier to push a
1799
1821
  // history entry on hash-tag change. When replace is true, we don't
1800
1822
  // want this.
1801
- if (!options.replace) this.iframe.document.open().close();
1802
- this._updateHash(this.iframe.location, fragment, options.replace);
1823
+ if (!options.replace) {
1824
+ iWindow.document.open();
1825
+ iWindow.document.close();
1826
+ }
1827
+
1828
+ this._updateHash(iWindow.location, fragment, options.replace);
1803
1829
  }
1804
1830
 
1805
1831
  // If you've told us that you explicitly don't want fallback hashchange-