tolaria 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-