ultimate-base 0.5.0.0 → 0.6.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 (24) hide show
  1. data/app/assets/javascripts/ultimate/backbone/app.js.coffee +47 -5
  2. data/app/assets/javascripts/ultimate/backbone/base.js.coffee +9 -10
  3. data/app/assets/javascripts/ultimate/backbone/collection.js.coffee +0 -1
  4. data/app/assets/javascripts/ultimate/backbone/lib/backbone.js +765 -673
  5. data/app/assets/javascripts/ultimate/backbone/model.js.coffee +0 -1
  6. data/app/assets/javascripts/ultimate/backbone/router.js.coffee +0 -4
  7. data/app/assets/javascripts/ultimate/backbone/view-mixins/nodes.js.coffee +51 -0
  8. data/app/assets/javascripts/ultimate/backbone/view.js.coffee +18 -104
  9. data/app/assets/javascripts/ultimate/base.js.coffee +1 -11
  10. data/app/assets/javascripts/ultimate/jquery-plugin-adapter.js.coffee +0 -1
  11. data/app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee +3 -25
  12. data/app/assets/javascripts/ultimate/jquery.base.js.coffee +0 -2
  13. data/app/assets/javascripts/ultimate/underscore/underscore.inflection.js +4 -3
  14. data/app/assets/javascripts/ultimate/underscore/underscore.js +103 -80
  15. data/app/assets/javascripts/ultimate/underscore/underscore.string.js +71 -27
  16. data/lib/ultimate/base.rb +0 -1
  17. data/lib/ultimate/base/version.rb +1 -1
  18. metadata +3 -8
  19. data/app/assets/javascripts/ultimate/backbone/extra/jquery-ext.js.coffee +0 -96
  20. data/app/assets/javascripts/ultimate/improves/datepicker.js.coffee +0 -34
  21. data/app/assets/javascripts/ultimate/improves/devise.js.coffee +0 -18
  22. data/app/assets/javascripts/ultimate/improves/form-errors.js.coffee +0 -146
  23. data/app/assets/javascripts/ultimate/improves/tablesorter.js +0 -59
  24. data/lib/ultimate/extensions/directive_processor.rb +0 -64
@@ -8,18 +8,60 @@ class Ultimate.Backbone.App
8
8
  Models: {}
9
9
  Collections: {}
10
10
  Routers: {}
11
+ ViewMixins: {}
12
+ ProtoViews: {}
11
13
  Views: {}
12
-
13
- scopes: ["Models", "Collections", "Routers", "Views"]
14
+ viewInstances: []
14
15
 
15
16
  constructor: (name = null) ->
16
- Ultimate.Backbone.debug ".App.constructor()", @
17
17
  if @constructor.App
18
- throw new Error("Can't create new Ultimate.Backbone.App because the single instance has already been created");
18
+ throw new Error('Can\'t create new Ultimate.Backbone.App because the single instance has already been created')
19
19
  else
20
+ cout 'info', 'Ultimate.Backbone.App.constructor', name, @
20
21
  @constructor.App = @
21
22
  @name = name
22
- _.extend @[scope], Backbone.Events for scope in @scopes
23
+
24
+ start: ->
25
+ @bindViews()
26
+ @bindCustomElements()
27
+
28
+ bindViews: (jRoot = $('html')) ->
29
+ bindedViews = []
30
+ for viewName, viewClass of @Views when viewClass::el
31
+ #cout 'info', "Try bind #{viewName} [#{viewClass::el}]"
32
+ jRoot.find(viewClass::el).each (index, el) =>
33
+ view = new viewClass(el: el)
34
+ cout 'info', "Binded view #{viewName}:", view
35
+ @viewInstances.push view
36
+ bindedViews.push view
37
+ bindedViews
38
+
39
+ unbindViews: (views) ->
40
+ view.undelegateEvents() for view in views
41
+ @viewInstances = _.without(@viewInstances, views...)
42
+
43
+ getFirstView: (viewClass) ->
44
+ for view in @viewInstances
45
+ if view.constructor is viewClass
46
+ return view
47
+ return null
48
+
49
+ getAllViews: (viewClass) ->
50
+ _.filter(@viewInstances, (view) -> view.constructor is viewClass)
51
+
52
+
53
+
54
+ customElementBinders: []
55
+
56
+ registerCustomElementBinder: (binder) ->
57
+ @customElementBinders.push binder
58
+
59
+ bindCustomElements: (jRoot = $('body')) ->
60
+ for binder in @customElementBinders
61
+ if _.isFunction(binder)
62
+ binder jRoot
63
+ else
64
+ jRoot.find(binder['selector'])[binder['method']] binder['arguments']...
23
65
 
24
66
 
25
67
 
@@ -2,18 +2,11 @@
2
2
  # jquery ~> 1.7.0
3
3
  # underscore ~> 1.3.0
4
4
 
5
- #= require ultimate/base
6
- #= require ultimate/helpers
5
+ #= require ../base
7
6
 
8
- @Ultimate.Backbone ||=
7
+ Ultimate.Backbone ||=
9
8
 
10
- debugMode: false
11
-
12
- debug: ->
13
- if @debugMode
14
- a = ["info", "Ultimate.Backbone"]
15
- Array::push.apply a, arguments if arguments.length > 0
16
- cout.apply @, a
9
+ ViewMixins: {}
17
10
 
18
11
  isView: (view) -> view instanceof Backbone.View
19
12
 
@@ -24,3 +17,9 @@
24
17
  isCollection: (collection) -> collection instanceof Backbone.Collection
25
18
 
26
19
  isRouter: (router) -> router instanceof Backbone.Router
20
+
21
+ # MixinSupport:
22
+ # include: (mixin) ->
23
+ # unless mixin?
24
+ # throw new Error('Mixin is undefined')
25
+ # _.extend @::, mixin
@@ -8,7 +8,6 @@ class Ultimate.Backbone.Collection extends Backbone.Collection
8
8
  expireTime: Infinity
9
9
 
10
10
  constructor: (models, options = {}) ->
11
- Ultimate.Backbone.debug ".Collection.constructor()", @
12
11
  @expireTime = options.expireTime if options.expireTime?
13
12
  super
14
13
 
@@ -1,6 +1,6 @@
1
- // Backbone.js 0.9.2
1
+ // Backbone.js 1.0.0
2
2
 
3
- // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
+ // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
4
4
  // Backbone may be freely distributed under the MIT license.
5
5
  // For all details and documentation:
6
6
  // http://backbonejs.org
@@ -10,7 +10,7 @@
10
10
  // Initial Setup
11
11
  // -------------
12
12
 
13
- // Save a reference to the global object (`window` in the browser, `global`
13
+ // Save a reference to the global object (`window` in the browser, `exports`
14
14
  // on the server).
15
15
  var root = this;
16
16
 
@@ -18,14 +18,14 @@
18
18
  // restored later on, if `noConflict` is used.
19
19
  var previousBackbone = root.Backbone;
20
20
 
21
- // Create a local reference to array methods.
22
- var ArrayProto = Array.prototype;
23
- var push = ArrayProto.push;
24
- var slice = ArrayProto.slice;
25
- var splice = ArrayProto.splice;
21
+ // Create local references to array methods we'll want to use later.
22
+ var array = [];
23
+ var push = array.push;
24
+ var slice = array.slice;
25
+ var splice = array.splice;
26
26
 
27
27
  // The top-level namespace. All public Backbone classes and modules will
28
- // be attached to this. Exported for both CommonJS and the browser.
28
+ // be attached to this. Exported for both the browser and the server.
29
29
  var Backbone;
30
30
  if (typeof exports !== 'undefined') {
31
31
  Backbone = exports;
@@ -34,14 +34,15 @@
34
34
  }
35
35
 
36
36
  // Current version of the library. Keep in sync with `package.json`.
37
- Backbone.VERSION = '0.9.2';
37
+ Backbone.VERSION = '1.0.0';
38
38
 
39
39
  // Require Underscore, if we're on the server, and it's not already present.
40
40
  var _ = root._;
41
41
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
42
42
 
43
- // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
44
- Backbone.$ = root.jQuery || root.Zepto || root.ender;
43
+ // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
44
+ // the `$` variable.
45
+ Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
45
46
 
46
47
  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
47
48
  // to its previous owner. Returns a reference to this Backbone object.
@@ -62,14 +63,12 @@
62
63
  Backbone.emulateJSON = false;
63
64
 
64
65
  // Backbone.Events
65
- // -----------------
66
-
67
- // Regular expression used to split event strings
68
- var eventSplitter = /\s+/;
66
+ // ---------------
69
67
 
70
68
  // A module that can be mixed in to *any object* in order to provide it with
71
- // custom events. You may bind with `on` or remove with `off` callback functions
72
- // to an event; `trigger`-ing an event fires all callbacks in succession.
69
+ // custom events. You may bind with `on` or remove with `off` callback
70
+ // functions to an event; `trigger`-ing an event fires all callbacks in
71
+ // succession.
73
72
  //
74
73
  // var object = {};
75
74
  // _.extend(object, Backbone.Events);
@@ -78,49 +77,56 @@
78
77
  //
79
78
  var Events = Backbone.Events = {
80
79
 
81
- // Bind one or more space separated events, `events`, to a `callback`
82
- // function. Passing `"all"` will bind the callback to all events fired.
83
- on: function(events, callback, context) {
84
- var calls, event, list;
85
- if (!callback) return this;
86
-
87
- events = events.split(eventSplitter);
88
- calls = this._callbacks || (this._callbacks = {});
89
-
90
- while (event = events.shift()) {
91
- list = calls[event] || (calls[event] = []);
92
- list.push(callback, context);
93
- }
94
-
80
+ // Bind an event to a `callback` function. Passing `"all"` will bind
81
+ // the callback to all events fired.
82
+ on: function(name, callback, context) {
83
+ if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
84
+ this._events || (this._events = {});
85
+ var events = this._events[name] || (this._events[name] = []);
86
+ events.push({callback: callback, context: context, ctx: context || this});
95
87
  return this;
96
88
  },
97
89
 
98
- // Remove one or many callbacks. If `context` is null, removes all callbacks
99
- // with that function. If `callback` is null, removes all callbacks for the
100
- // event. If `events` is null, removes all bound callbacks for all events.
101
- off: function(events, callback, context) {
102
- var event, calls, list, i;
103
-
104
- // No events, or removing *all* events.
105
- if (!(calls = this._callbacks)) return this;
106
- if (!(events || callback || context)) {
107
- delete this._callbacks;
90
+ // Bind an event to only be triggered a single time. After the first time
91
+ // the callback is invoked, it will be removed.
92
+ once: function(name, callback, context) {
93
+ if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
94
+ var self = this;
95
+ var once = _.once(function() {
96
+ self.off(name, once);
97
+ callback.apply(this, arguments);
98
+ });
99
+ once._callback = callback;
100
+ return this.on(name, once, context);
101
+ },
102
+
103
+ // Remove one or many callbacks. If `context` is null, removes all
104
+ // callbacks with that function. If `callback` is null, removes all
105
+ // callbacks for the event. If `name` is null, removes all bound
106
+ // callbacks for all events.
107
+ off: function(name, callback, context) {
108
+ var retain, ev, events, names, i, l, j, k;
109
+ if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
110
+ if (!name && !callback && !context) {
111
+ this._events = {};
108
112
  return this;
109
113
  }
110
114
 
111
- events = events ? events.split(eventSplitter) : _.keys(calls);
112
-
113
- // Loop through the callback list, splicing where appropriate.
114
- while (event = events.shift()) {
115
- if (!(list = calls[event]) || !(callback || context)) {
116
- delete calls[event];
117
- continue;
118
- }
119
-
120
- for (i = list.length - 2; i >= 0; i -= 2) {
121
- if (!(callback && list[i] !== callback || context && list[i + 1] !== context)) {
122
- list.splice(i, 2);
115
+ names = name ? [name] : _.keys(this._events);
116
+ for (i = 0, l = names.length; i < l; i++) {
117
+ name = names[i];
118
+ if (events = this._events[name]) {
119
+ this._events[name] = retain = [];
120
+ if (callback || context) {
121
+ for (j = 0, k = events.length; j < k; j++) {
122
+ ev = events[j];
123
+ if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
124
+ (context && context !== ev.context)) {
125
+ retain.push(ev);
126
+ }
127
+ }
123
128
  }
129
+ if (!retain.length) delete this._events[name];
124
130
  }
125
131
  }
126
132
 
@@ -131,96 +137,138 @@
131
137
  // passed the same arguments as `trigger` is, apart from the event name
132
138
  // (unless you're listening on `"all"`, which will cause your callback to
133
139
  // receive the true name of the event as the first argument).
134
- trigger: function(events) {
135
- var event, calls, list, i, length, args, all, rest;
136
- if (!(calls = this._callbacks)) return this;
137
-
138
- rest = [];
139
- events = events.split(eventSplitter);
140
+ trigger: function(name) {
141
+ if (!this._events) return this;
142
+ var args = slice.call(arguments, 1);
143
+ if (!eventsApi(this, 'trigger', name, args)) return this;
144
+ var events = this._events[name];
145
+ var allEvents = this._events.all;
146
+ if (events) triggerEvents(events, args);
147
+ if (allEvents) triggerEvents(allEvents, arguments);
148
+ return this;
149
+ },
140
150
 
141
- // Fill up `rest` with the callback arguments. Since we're only copying
142
- // the tail of `arguments`, a loop is much faster than Array#slice.
143
- for (i = 1, length = arguments.length; i < length; i++) {
144
- rest[i - 1] = arguments[i];
151
+ // Tell this object to stop listening to either specific events ... or
152
+ // to every object it's currently listening to.
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];
145
162
  }
163
+ return this;
164
+ }
146
165
 
147
- // For each event, walk through the list of callbacks twice, first to
148
- // trigger the event, then to trigger any `"all"` callbacks.
149
- while (event = events.shift()) {
150
- // Copy callback lists to prevent modification.
151
- if (all = calls.all) all = all.slice();
152
- if (list = calls[event]) list = list.slice();
153
-
154
- // Execute event callbacks.
155
- if (list) {
156
- for (i = 0, length = list.length; i < length; i += 2) {
157
- list[i].apply(list[i + 1] || this, rest);
158
- }
159
- }
166
+ };
160
167
 
161
- // Execute "all" callbacks.
162
- if (all) {
163
- args = [event].concat(rest);
164
- for (i = 0, length = all.length; i < length; i += 2) {
165
- all[i].apply(all[i + 1] || this, args);
166
- }
167
- }
168
+ // Regular expression used to split event strings.
169
+ var eventSplitter = /\s+/;
170
+
171
+ // Implement fancy features of the Events API such as multiple event
172
+ // names `"change blur"` and jQuery-style event maps `{change: action}`
173
+ // in terms of the existing API.
174
+ var eventsApi = function(obj, action, name, rest) {
175
+ if (!name) return true;
176
+
177
+ // Handle event maps.
178
+ if (typeof name === 'object') {
179
+ for (var key in name) {
180
+ obj[action].apply(obj, [key, name[key]].concat(rest));
168
181
  }
182
+ return false;
183
+ }
169
184
 
170
- return this;
185
+ // Handle space separated event names.
186
+ if (eventSplitter.test(name)) {
187
+ var names = name.split(eventSplitter);
188
+ for (var i = 0, l = names.length; i < l; i++) {
189
+ obj[action].apply(obj, [names[i]].concat(rest));
190
+ }
191
+ return false;
171
192
  }
172
193
 
194
+ return true;
195
+ };
196
+
197
+ // A difficult-to-believe, but optimized internal dispatch function for
198
+ // triggering events. Tries to keep the usual cases speedy (most internal
199
+ // Backbone events have 3 arguments).
200
+ var triggerEvents = function(events, args) {
201
+ var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
202
+ switch (args.length) {
203
+ case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
204
+ case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
205
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
206
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
207
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
208
+ }
173
209
  };
174
210
 
211
+ var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
212
+
213
+ // Inversion-of-control versions of `on` and `once`. Tell *this* object to
214
+ // listen to an event in another object ... keeping track of what it's
215
+ // listening to.
216
+ _.each(listenMethods, function(implementation, method) {
217
+ 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;
222
+ obj[implementation](name, callback, this);
223
+ return this;
224
+ };
225
+ });
226
+
175
227
  // Aliases for backwards compatibility.
176
228
  Events.bind = Events.on;
177
229
  Events.unbind = Events.off;
178
230
 
231
+ // Allow the `Backbone` object to serve as a global event bus, for folks who
232
+ // want global "pubsub" in a convenient place.
233
+ _.extend(Backbone, Events);
234
+
179
235
  // Backbone.Model
180
236
  // --------------
181
237
 
182
- // Create a new model, with defined attributes. A client id (`cid`)
238
+ // Backbone **Models** are the basic data object in the framework --
239
+ // frequently representing a row in a table in a database on your server.
240
+ // A discrete chunk of data and a bunch of useful, related methods for
241
+ // performing computations and transformations on that data.
242
+
243
+ // Create a new model with the specified attributes. A client id (`cid`)
183
244
  // is automatically generated and assigned for you.
184
245
  var Model = Backbone.Model = function(attributes, options) {
185
246
  var defaults;
186
247
  var attrs = attributes || {};
187
- if (options && options.collection) this.collection = options.collection;
188
- if (options && options.parse) attributes = this.parse(attributes);
248
+ options || (options = {});
249
+ this.cid = _.uniqueId('c');
250
+ this.attributes = {};
251
+ _.extend(this, _.pick(options, modelOptions));
252
+ if (options.parse) attrs = this.parse(attrs, options) || {};
189
253
  if (defaults = _.result(this, 'defaults')) {
190
- attrs = _.extend({}, defaults, attrs);
254
+ attrs = _.defaults({}, attrs, defaults);
191
255
  }
192
- this.attributes = {};
193
- this._escapedAttributes = {};
194
- this.cid = _.uniqueId('c');
195
- this.changed = {};
196
- this._changes = {};
197
- this._pending = {};
198
- this.set(attrs, {silent: true});
199
- // Reset change tracking.
256
+ this.set(attrs, options);
200
257
  this.changed = {};
201
- this._changes = {};
202
- this._pending = {};
203
- this._previousAttributes = _.clone(this.attributes);
204
258
  this.initialize.apply(this, arguments);
205
259
  };
206
260
 
261
+ // A list of options to be attached directly to the model, if provided.
262
+ var modelOptions = ['url', 'urlRoot', 'collection'];
263
+
207
264
  // Attach all inheritable methods to the Model prototype.
208
265
  _.extend(Model.prototype, Events, {
209
266
 
210
267
  // A hash of attributes whose current and previous value differ.
211
268
  changed: null,
212
269
 
213
- // A hash of attributes that have changed since the last time `change`
214
- // was called.
215
- _changes: null,
216
-
217
- // A hash of attributes that have changed since the last `change` event
218
- // began.
219
- _pending: null,
220
-
221
- // A hash of attributes with the current model state to determine if
222
- // a `change` should be recorded within a nested `change` block.
223
- _changing : null,
270
+ // The value returned during the last failed validation.
271
+ validationError: null,
224
272
 
225
273
  // The default name for the JSON `id` attribute is `"id"`. MongoDB and
226
274
  // CouchDB users may want to set this to `"_id"`.
@@ -235,7 +283,8 @@
235
283
  return _.clone(this.attributes);
236
284
  },
237
285
 
238
- // Proxy `Backbone.sync` by default.
286
+ // Proxy `Backbone.sync` by default -- but override this if you need
287
+ // custom syncing semantics for *this* particular model.
239
288
  sync: function() {
240
289
  return Backbone.sync.apply(this, arguments);
241
290
  },
@@ -247,10 +296,7 @@
247
296
 
248
297
  // Get the HTML-escaped value of an attribute.
249
298
  escape: function(attr) {
250
- var html;
251
- if (html = this._escapedAttributes[attr]) return html;
252
- var val = this.get(attr);
253
- return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
299
+ return _.escape(this.get(attr));
254
300
  },
255
301
 
256
302
  // Returns `true` if the attribute contains a value that is not null
@@ -259,146 +305,194 @@
259
305
  return this.get(attr) != null;
260
306
  },
261
307
 
262
- // Set a hash of model attributes on the object, firing `"change"` unless
263
- // you choose to silence it.
264
- set: function(attrs, options) {
265
- var attr, key, val;
266
- if (attrs == null) return this;
308
+ // Set a hash of model attributes on the object, firing `"change"`. This is
309
+ // the core primitive operation of a model, updating the data and notifying
310
+ // anyone who needs to know about the change in state. The heart of the beast.
311
+ set: function(key, val, options) {
312
+ var attr, attrs, unset, changes, silent, changing, prev, current;
313
+ if (key == null) return this;
267
314
 
268
315
  // Handle both `"key", value` and `{key: value}` -style arguments.
269
- if (!_.isObject(attrs)) {
270
- key = attrs;
271
- (attrs = {})[key] = options;
272
- options = arguments[2];
316
+ if (typeof key === 'object') {
317
+ attrs = key;
318
+ options = val;
319
+ } else {
320
+ (attrs = {})[key] = val;
273
321
  }
274
322
 
275
- // Extract attributes and options.
276
- var silent = options && options.silent;
277
- var unset = options && options.unset;
278
- if (attrs instanceof Model) attrs = attrs.attributes;
279
- if (unset) for (attr in attrs) attrs[attr] = void 0;
323
+ options || (options = {});
280
324
 
281
325
  // Run validation.
282
326
  if (!this._validate(attrs, options)) return false;
283
327
 
328
+ // Extract attributes and options.
329
+ unset = options.unset;
330
+ silent = options.silent;
331
+ changes = [];
332
+ changing = this._changing;
333
+ this._changing = true;
334
+
335
+ if (!changing) {
336
+ this._previousAttributes = _.clone(this.attributes);
337
+ this.changed = {};
338
+ }
339
+ current = this.attributes, prev = this._previousAttributes;
340
+
284
341
  // Check for changes of `id`.
285
342
  if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
286
343
 
287
- var changing = this._changing;
288
- var now = this.attributes;
289
- var escaped = this._escapedAttributes;
290
- var prev = this._previousAttributes || {};
291
-
292
- // For each `set` attribute...
344
+ // For each `set` attribute, update or delete the current value.
293
345
  for (attr in attrs) {
294
346
  val = attrs[attr];
295
-
296
- // If the new and current value differ, record the change.
297
- if (!_.isEqual(now[attr], val) || (unset && _.has(now, attr))) {
298
- delete escaped[attr];
299
- this._changes[attr] = true;
300
- }
301
-
302
- // Update or delete the current value.
303
- unset ? delete now[attr] : now[attr] = val;
304
-
305
- // If the new and previous value differ, record the change. If not,
306
- // then remove changes for this attribute.
307
- if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
347
+ if (!_.isEqual(current[attr], val)) changes.push(attr);
348
+ if (!_.isEqual(prev[attr], val)) {
308
349
  this.changed[attr] = val;
309
- if (!silent) this._pending[attr] = true;
310
350
  } else {
311
351
  delete this.changed[attr];
312
- delete this._pending[attr];
313
- if (!changing) delete this._changes[attr];
314
352
  }
353
+ unset ? delete current[attr] : current[attr] = val;
354
+ }
315
355
 
316
- if (changing && _.isEqual(now[attr], changing[attr])) delete this._changes[attr];
356
+ // Trigger all relevant attribute changes.
357
+ if (!silent) {
358
+ if (changes.length) this._pending = true;
359
+ for (var i = 0, l = changes.length; i < l; i++) {
360
+ this.trigger('change:' + changes[i], this, current[changes[i]], options);
361
+ }
317
362
  }
318
363
 
319
- // Fire the `"change"` events.
320
- if (!silent) this.change(options);
364
+ // You might be wondering why there's a `while` loop here. Changes can
365
+ // be recursively nested within `"change"` events.
366
+ if (changing) return this;
367
+ if (!silent) {
368
+ while (this._pending) {
369
+ this._pending = false;
370
+ this.trigger('change', this, options);
371
+ }
372
+ }
373
+ this._pending = false;
374
+ this._changing = false;
321
375
  return this;
322
376
  },
323
377
 
324
- // Remove an attribute from the model, firing `"change"` unless you choose
325
- // to silence it. `unset` is a noop if the attribute doesn't exist.
378
+ // Remove an attribute from the model, firing `"change"`. `unset` is a noop
379
+ // if the attribute doesn't exist.
326
380
  unset: function(attr, options) {
327
- options = _.extend({}, options, {unset: true});
328
- return this.set(attr, null, options);
381
+ return this.set(attr, void 0, _.extend({}, options, {unset: true}));
329
382
  },
330
383
 
331
- // Clear all attributes on the model, firing `"change"` unless you choose
332
- // to silence it.
384
+ // Clear all attributes on the model, firing `"change"`.
333
385
  clear: function(options) {
334
- options = _.extend({}, options, {unset: true});
335
- return this.set(_.clone(this.attributes), options);
386
+ var attrs = {};
387
+ for (var key in this.attributes) attrs[key] = void 0;
388
+ return this.set(attrs, _.extend({}, options, {unset: true}));
389
+ },
390
+
391
+ // Determine if the model has changed since the last `"change"` event.
392
+ // If you specify an attribute name, determine if that attribute has changed.
393
+ hasChanged: function(attr) {
394
+ if (attr == null) return !_.isEmpty(this.changed);
395
+ return _.has(this.changed, attr);
396
+ },
397
+
398
+ // Return an object containing all the attributes that have changed, or
399
+ // false if there are no changed attributes. Useful for determining what
400
+ // parts of a view need to be updated and/or what attributes need to be
401
+ // persisted to the server. Unset attributes will be set to undefined.
402
+ // You can also pass an attributes object to diff against the model,
403
+ // determining if there *would be* a change.
404
+ changedAttributes: function(diff) {
405
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
406
+ var val, changed = false;
407
+ var old = this._changing ? this._previousAttributes : this.attributes;
408
+ for (var attr in diff) {
409
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
410
+ (changed || (changed = {}))[attr] = val;
411
+ }
412
+ return changed;
413
+ },
414
+
415
+ // Get the previous value of an attribute, recorded at the time the last
416
+ // `"change"` event was fired.
417
+ previous: function(attr) {
418
+ if (attr == null || !this._previousAttributes) return null;
419
+ return this._previousAttributes[attr];
420
+ },
421
+
422
+ // Get all of the attributes of the model at the time of the previous
423
+ // `"change"` event.
424
+ previousAttributes: function() {
425
+ return _.clone(this._previousAttributes);
336
426
  },
337
427
 
338
428
  // Fetch the model from the server. If the server's representation of the
339
- // model differs from its current attributes, they will be overriden,
429
+ // model differs from its current attributes, they will be overridden,
340
430
  // triggering a `"change"` event.
341
431
  fetch: function(options) {
342
432
  options = options ? _.clone(options) : {};
433
+ if (options.parse === void 0) options.parse = true;
343
434
  var model = this;
344
435
  var success = options.success;
345
- options.success = function(resp, status, xhr) {
346
- if (!model.set(model.parse(resp, xhr), options)) return false;
436
+ options.success = function(resp) {
437
+ if (!model.set(model.parse(resp, options), options)) return false;
347
438
  if (success) success(model, resp, options);
439
+ model.trigger('sync', model, resp, options);
348
440
  };
441
+ wrapError(this, options);
349
442
  return this.sync('read', this, options);
350
443
  },
351
444
 
352
445
  // Set a hash of model attributes, and sync the model to the server.
353
446
  // If the server returns an attributes hash that differs, the model's
354
447
  // state will be `set` again.
355
- save: function(attrs, options) {
356
- var key, current, done;
448
+ save: function(key, val, options) {
449
+ var attrs, method, xhr, attributes = this.attributes;
357
450
 
358
451
  // Handle both `"key", value` and `{key: value}` -style arguments.
359
- if (attrs != null && !_.isObject(attrs)) {
360
- key = attrs;
361
- (attrs = {})[key] = options;
362
- options = arguments[2];
452
+ if (key == null || typeof key === 'object') {
453
+ attrs = key;
454
+ options = val;
455
+ } else {
456
+ (attrs = {})[key] = val;
363
457
  }
364
- options = options ? _.clone(options) : {};
365
458
 
366
- // If we're "wait"-ing to set changed attributes, validate early.
367
- if (options.wait) {
368
- if (!this._validate(attrs, options)) return false;
369
- current = _.clone(this.attributes);
370
- }
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;
371
461
 
372
- // Regular saves `set` attributes before persisting to the server.
373
- var silentOptions = _.extend({}, options, {silent: true});
374
- if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
375
- return false;
376
- }
462
+ options = _.extend({validate: true}, options);
377
463
 
378
464
  // Do not persist invalid models.
379
- if (!attrs && !this._validate(null, options)) return false;
465
+ if (!this._validate(attrs, options)) return false;
466
+
467
+ // Set temporary attributes if `{wait: true}`.
468
+ if (attrs && options.wait) {
469
+ this.attributes = _.extend({}, attributes, attrs);
470
+ }
380
471
 
381
472
  // After a successful server-side save, the client is (optionally)
382
473
  // updated with the server-side state.
474
+ if (options.parse === void 0) options.parse = true;
383
475
  var model = this;
384
476
  var success = options.success;
385
- options.success = function(resp, status, xhr) {
386
- done = true;
387
- var serverAttrs = model.parse(resp, xhr);
477
+ options.success = function(resp) {
478
+ // Ensure attributes are restored during synchronous saves.
479
+ model.attributes = attributes;
480
+ var serverAttrs = model.parse(resp, options);
388
481
  if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
389
- if (!model.set(serverAttrs, options)) return false;
482
+ if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
483
+ return false;
484
+ }
390
485
  if (success) success(model, resp, options);
486
+ model.trigger('sync', model, resp, options);
391
487
  };
488
+ wrapError(this, options);
392
489
 
393
- // Finish configuring and sending the Ajax request.
394
- var xhr = this.sync(this.isNew() ? 'create' : 'update', this, options);
490
+ method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
491
+ if (method === 'patch') options.attrs = attrs;
492
+ xhr = this.sync(method, this, options);
395
493
 
396
- // When using `wait`, reset attributes to original values unless
397
- // `success` has been called already.
398
- if (!done && options.wait) {
399
- this.clear(silentOptions);
400
- this.set(current, silentOptions);
401
- }
494
+ // Restore attributes.
495
+ if (attrs && options.wait) this.attributes = attributes;
402
496
 
403
497
  return xhr;
404
498
  },
@@ -418,12 +512,14 @@
418
512
  options.success = function(resp) {
419
513
  if (options.wait || model.isNew()) destroy();
420
514
  if (success) success(model, resp, options);
515
+ if (!model.isNew()) model.trigger('sync', model, resp, options);
421
516
  };
422
517
 
423
518
  if (this.isNew()) {
424
519
  options.success();
425
520
  return false;
426
521
  }
522
+ wrapError(this, options);
427
523
 
428
524
  var xhr = this.sync('delete', this, options);
429
525
  if (!options.wait) destroy();
@@ -441,7 +537,7 @@
441
537
 
442
538
  // **parse** converts a response into the hash of attributes to be `set` on
443
539
  // the model. The default implementation is just to pass the response along.
444
- parse: function(resp, xhr) {
540
+ parse: function(resp, options) {
445
541
  return resp;
446
542
  },
447
543
 
@@ -455,123 +551,63 @@
455
551
  return this.id == null;
456
552
  },
457
553
 
458
- // Call this method to manually fire a `"change"` event for this model and
459
- // a `"change:attribute"` event for each changed attribute.
460
- // Calling this will cause all objects observing the model to update.
461
- change: function(options) {
462
- var changing = this._changing;
463
- var current = this._changing = {};
464
-
465
- // Silent changes become pending changes.
466
- for (var attr in this._changes) this._pending[attr] = true;
467
-
468
- // Trigger 'change:attr' for any new or silent changes.
469
- var changes = this._changes;
470
- this._changes = {};
471
-
472
- // Set the correct state for this._changing values
473
- var triggers = [];
474
- for (var attr in changes) {
475
- current[attr] = this.get(attr);
476
- triggers.push(attr);
477
- }
478
-
479
- for (var i=0, l=triggers.length; i < l; i++) {
480
- this.trigger('change:' + triggers[i], this, current[triggers[i]], options);
481
- }
482
- if (changing) return this;
483
-
484
- // Continue firing `"change"` events while there are pending changes.
485
- while (!_.isEmpty(this._pending)) {
486
- this._pending = {};
487
- this.trigger('change', this, options);
488
- // Pending and silent changes still remain.
489
- for (var attr in this.changed) {
490
- if (this._pending[attr] || this._changes[attr]) continue;
491
- delete this.changed[attr];
492
- }
493
- this._previousAttributes = _.clone(this.attributes);
494
- }
495
-
496
- this._changing = null;
497
- return this;
498
- },
499
-
500
- // Determine if the model has changed since the last `"change"` event.
501
- // If you specify an attribute name, determine if that attribute has changed.
502
- hasChanged: function(attr) {
503
- if (attr == null) return !_.isEmpty(this.changed);
504
- return _.has(this.changed, attr);
505
- },
506
-
507
- // Return an object containing all the attributes that have changed, or
508
- // false if there are no changed attributes. Useful for determining what
509
- // parts of a view need to be updated and/or what attributes need to be
510
- // persisted to the server. Unset attributes will be set to undefined.
511
- // You can also pass an attributes object to diff against the model,
512
- // determining if there *would be* a change.
513
- changedAttributes: function(diff) {
514
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
515
- var val, changed = false, old = this._previousAttributes;
516
- for (var attr in diff) {
517
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
518
- (changed || (changed = {}))[attr] = val;
519
- }
520
- return changed;
521
- },
522
-
523
- // Get the previous value of an attribute, recorded at the time the last
524
- // `"change"` event was fired.
525
- previous: function(attr) {
526
- if (attr == null || !this._previousAttributes) return null;
527
- return this._previousAttributes[attr];
528
- },
529
-
530
- // Get all of the attributes of the model at the time of the previous
531
- // `"change"` event.
532
- previousAttributes: function() {
533
- return _.clone(this._previousAttributes);
534
- },
535
-
536
- // Check if the model is currently in a valid state. It's only possible to
537
- // get into an *invalid* state if you're using silent changes.
554
+ // Check if the model is currently in a valid state.
538
555
  isValid: function(options) {
539
- return !this.validate || !this.validate(this.attributes, options);
556
+ return this._validate({}, _.extend(options || {}, { validate: true }));
540
557
  },
541
558
 
542
559
  // Run validation against the next complete set of model attributes,
543
- // returning `true` if all is well. If a specific `error` callback has
544
- // been passed, call that instead of firing the general `"error"` event.
560
+ // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
545
561
  _validate: function(attrs, options) {
546
- if (options && options.silent || !this.validate) return true;
562
+ if (!options.validate || !this.validate) return true;
547
563
  attrs = _.extend({}, this.attributes, attrs);
548
- var error = this.validate(attrs, options);
564
+ var error = this.validationError = this.validate(attrs, options) || null;
549
565
  if (!error) return true;
550
- if (options && options.error) options.error(this, error, options);
551
- this.trigger('error', this, error, options);
566
+ this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
552
567
  return false;
553
568
  }
554
569
 
555
570
  });
556
571
 
572
+ // Underscore methods that we want to implement on the Model.
573
+ var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
574
+
575
+ // Mix in each Underscore method as a proxy to `Model#attributes`.
576
+ _.each(modelMethods, function(method) {
577
+ Model.prototype[method] = function() {
578
+ var args = slice.call(arguments);
579
+ args.unshift(this.attributes);
580
+ return _[method].apply(_, args);
581
+ };
582
+ });
583
+
557
584
  // Backbone.Collection
558
585
  // -------------------
559
586
 
560
- // Provides a standard collection class for our sets of models, ordered
561
- // or unordered. If a `comparator` is specified, the Collection will maintain
587
+ // If models tend to represent a single row of data, a Backbone Collection is
588
+ // more analagous to a table full of data ... or a small slice or page of that
589
+ // table, or a collection of rows that belong together for a particular reason
590
+ // -- all of the messages in this particular folder, all of the documents
591
+ // belonging to this particular author, and so on. Collections maintain
592
+ // indexes of their models, both in order, and for lookup by `id`.
593
+
594
+ // Create a new **Collection**, perhaps to contain a specific type of `model`.
595
+ // If a `comparator` is specified, the Collection will maintain
562
596
  // its models in sort order, as they're added and removed.
563
597
  var Collection = Backbone.Collection = function(models, options) {
564
598
  options || (options = {});
599
+ if (options.url) this.url = options.url;
565
600
  if (options.model) this.model = options.model;
566
601
  if (options.comparator !== void 0) this.comparator = options.comparator;
567
602
  this._reset();
568
603
  this.initialize.apply(this, arguments);
569
- if (models) {
570
- if (options.parse) models = this.parse(models);
571
- this.reset(models, {silent: true, parse: options.parse});
572
- }
604
+ if (models) this.reset(models, _.extend({silent: true}, options));
573
605
  };
574
606
 
607
+ // Default options for `Collection#set`.
608
+ var setOptions = {add: true, remove: true, merge: true};
609
+ var addOptions = {add: true, merge: false, remove: false};
610
+
575
611
  // Define the Collection's inheritable methods.
576
612
  _.extend(Collection.prototype, Events, {
577
613
 
@@ -594,87 +630,127 @@
594
630
  return Backbone.sync.apply(this, arguments);
595
631
  },
596
632
 
597
- // Add a model, or list of models to the set. Pass **silent** to avoid
598
- // firing the `add` event for every new model.
633
+ // Add a model, or list of models to the set.
599
634
  add: function(models, options) {
600
- var i, args, length, model, existing;
601
- var at = options && options.at;
602
- models = _.isArray(models) ? models.slice() : [models];
635
+ return this.set(models, _.defaults(options || {}, addOptions));
636
+ },
603
637
 
604
- // Begin by turning bare objects into model references, and preventing
605
- // invalid models from being added.
606
- for (i = 0, length = models.length; i < length; i++) {
607
- if (models[i] = this._prepareModel(models[i], options)) continue;
608
- throw new Error("Can't add an invalid model to a collection");
638
+ // Remove a model, or a list of models from the set.
639
+ remove: function(models, options) {
640
+ models = _.isArray(models) ? models.slice() : [models];
641
+ options || (options = {});
642
+ var i, l, index, model;
643
+ for (i = 0, l = models.length; i < l; i++) {
644
+ model = this.get(models[i]);
645
+ if (!model) continue;
646
+ delete this._byId[model.id];
647
+ delete this._byId[model.cid];
648
+ index = this.indexOf(model);
649
+ this.models.splice(index, 1);
650
+ this.length--;
651
+ if (!options.silent) {
652
+ options.index = index;
653
+ model.trigger('remove', model, this, options);
654
+ }
655
+ this._removeReference(model);
609
656
  }
657
+ return this;
658
+ },
610
659
 
611
- for (i = models.length - 1; i >= 0; i--) {
612
- model = models[i];
613
- existing = model.id != null && this._byId[model.id];
614
-
615
- // If a duplicate is found, splice it out and optionally merge it into
616
- // the existing model.
617
- if (existing || this._byCid[model.cid]) {
618
- if (options && options.merge && existing) {
619
- existing.set(model, options);
660
+ // Update a collection by `set`-ing a new list of models, adding new ones,
661
+ // removing models that are no longer present, and merging models that
662
+ // already exist in the collection, as necessary. Similar to **Model#set**,
663
+ // the core operation for updating the data contained by the collection.
664
+ set: function(models, options) {
665
+ options = _.defaults(options || {}, setOptions);
666
+ if (options.parse) models = this.parse(models, options);
667
+ if (!_.isArray(models)) models = models ? [models] : [];
668
+ var i, l, model, attrs, existing, sort;
669
+ var at = options.at;
670
+ var sortable = this.comparator && (at == null) && options.sort !== false;
671
+ var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672
+ var toAdd = [], toRemove = [], modelMap = {};
673
+
674
+ // Turn bare objects into model references, and prevent invalid models
675
+ // from being added.
676
+ for (i = 0, l = models.length; i < l; i++) {
677
+ if (!(model = this._prepareModel(models[i], options))) continue;
678
+
679
+ // If a duplicate is found, prevent it from being added and
680
+ // 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);
685
+ if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
620
686
  }
621
- models.splice(i, 1);
622
- continue;
687
+
688
+ // This is a new model, push it to the `toAdd` list.
689
+ } else if (options.add) {
690
+ toAdd.push(model);
691
+
692
+ // Listen to added models' events, and index models for lookup by
693
+ // `id` and by `cid`.
694
+ model.on('all', this._onModelEvent, this);
695
+ this._byId[model.cid] = model;
696
+ if (model.id != null) this._byId[model.id] = model;
623
697
  }
698
+ }
624
699
 
625
- // Listen to added models' events, and index models for lookup by
626
- // `id` and by `cid`.
627
- model.on('all', this._onModelEvent, this);
628
- this._byCid[model.cid] = model;
629
- if (model.id != null) this._byId[model.id] = model;
700
+ // Remove nonexistent models if appropriate.
701
+ if (options.remove) {
702
+ for (i = 0, l = this.length; i < l; ++i) {
703
+ if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
704
+ }
705
+ if (toRemove.length) this.remove(toRemove, options);
630
706
  }
631
707
 
632
- // Update `length` and splice in new models.
633
- this.length += models.length;
634
- args = [at != null ? at : this.models.length, 0];
635
- push.apply(args, models);
636
- splice.apply(this.models, args);
708
+ // See if sorting is needed, update `length` and splice in new models.
709
+ if (toAdd.length) {
710
+ if (sortable) sort = true;
711
+ this.length += toAdd.length;
712
+ if (at != null) {
713
+ splice.apply(this.models, [at, 0].concat(toAdd));
714
+ } else {
715
+ push.apply(this.models, toAdd);
716
+ }
717
+ }
637
718
 
638
- // Sort the collection if appropriate.
639
- if (this.comparator && at == null) this.sort({silent: true});
719
+ // Silently sort the collection if appropriate.
720
+ if (sort) this.sort({silent: true});
640
721
 
641
- if (options && options.silent) return this;
722
+ if (options.silent) return this;
642
723
 
643
724
  // Trigger `add` events.
644
- while (model = models.shift()) {
645
- model.trigger('add', model, this, options);
725
+ for (i = 0, l = toAdd.length; i < l; i++) {
726
+ (model = toAdd[i]).trigger('add', model, this, options);
646
727
  }
647
728
 
729
+ // Trigger `sort` if the collection was sorted.
730
+ if (sort) this.trigger('sort', this, options);
648
731
  return this;
649
732
  },
650
733
 
651
- // Remove a model, or a list of models from the set. Pass silent to avoid
652
- // firing the `remove` event for every model removed.
653
- remove: function(models, options) {
654
- var i, l, index, model;
734
+ // When you have more items than you want to add or remove individually,
735
+ // you can reset the entire set with a new list of models, without firing
736
+ // any granular `add` or `remove` events. Fires `reset` when finished.
737
+ // Useful for bulk operations and optimizations.
738
+ reset: function(models, options) {
655
739
  options || (options = {});
656
- models = _.isArray(models) ? models.slice() : [models];
657
- for (i = 0, l = models.length; i < l; i++) {
658
- model = this.getByCid(models[i]) || this.get(models[i]);
659
- if (!model) continue;
660
- delete this._byId[model.id];
661
- delete this._byCid[model.cid];
662
- index = this.indexOf(model);
663
- this.models.splice(index, 1);
664
- this.length--;
665
- if (!options.silent) {
666
- options.index = index;
667
- model.trigger('remove', model, this, options);
668
- }
669
- this._removeReference(model);
740
+ for (var i = 0, l = this.models.length; i < l; i++) {
741
+ this._removeReference(this.models[i]);
670
742
  }
743
+ options.previousModels = this.models;
744
+ this._reset();
745
+ this.add(models, _.extend({silent: true}, options));
746
+ if (!options.silent) this.trigger('reset', this, options);
671
747
  return this;
672
748
  },
673
749
 
674
750
  // Add a model to the end of the collection.
675
751
  push: function(model, options) {
676
752
  model = this._prepareModel(model, options);
677
- this.add(model, options);
753
+ this.add(model, _.extend({at: this.length}, options));
678
754
  return model;
679
755
  },
680
756
 
@@ -705,14 +781,9 @@
705
781
  },
706
782
 
707
783
  // Get a model from the set by id.
708
- get: function(id) {
709
- if (id == null) return void 0;
710
- return this._byId[id.id != null ? id.id : id];
711
- },
712
-
713
- // Get a model from the set by client id.
714
- getByCid: function(cid) {
715
- return cid && this._byCid[cid.cid || cid];
784
+ get: function(obj) {
785
+ if (obj == null) return void 0;
786
+ return this._byId[obj.id != null ? obj.id : obj.cid || obj];
716
787
  },
717
788
 
718
789
  // Get the model at the given index.
@@ -720,10 +791,11 @@
720
791
  return this.models[index];
721
792
  },
722
793
 
723
- // Return models with matching attributes. Useful for simple cases of `filter`.
724
- where: function(attrs) {
725
- if (_.isEmpty(attrs)) return [];
726
- return this.filter(function(model) {
794
+ // Return models with matching attributes. Useful for simple cases of
795
+ // `filter`.
796
+ where: function(attrs, first) {
797
+ if (_.isEmpty(attrs)) return first ? void 0 : [];
798
+ return this[first ? 'find' : 'filter'](function(model) {
727
799
  for (var key in attrs) {
728
800
  if (attrs[key] !== model.get(key)) return false;
729
801
  }
@@ -731,54 +803,60 @@
731
803
  });
732
804
  },
733
805
 
806
+ // Return the first model with matching attributes. Useful for simple cases
807
+ // of `find`.
808
+ findWhere: function(attrs) {
809
+ return this.where(attrs, true);
810
+ },
811
+
734
812
  // Force the collection to re-sort itself. You don't need to call this under
735
813
  // normal circumstances, as the set will maintain sort order as each item
736
814
  // is added.
737
815
  sort: function(options) {
738
- if (!this.comparator) {
739
- throw new Error('Cannot sort a set without a comparator');
740
- }
816
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
817
+ options || (options = {});
741
818
 
819
+ // Run sort based on type of `comparator`.
742
820
  if (_.isString(this.comparator) || this.comparator.length === 1) {
743
821
  this.models = this.sortBy(this.comparator, this);
744
822
  } else {
745
823
  this.models.sort(_.bind(this.comparator, this));
746
824
  }
747
825
 
748
- if (!options || !options.silent) this.trigger('reset', this, options);
826
+ if (!options.silent) this.trigger('sort', this, options);
749
827
  return this;
750
828
  },
751
829
 
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
+
752
840
  // Pluck an attribute from each model in the collection.
753
841
  pluck: function(attr) {
754
842
  return _.invoke(this.models, 'get', attr);
755
843
  },
756
844
 
757
- // When you have more items than you want to add or remove individually,
758
- // you can reset the entire set with a new list of models, without firing
759
- // any `add` or `remove` events. Fires `reset` when finished.
760
- reset: function(models, options) {
761
- for (var i = 0, l = this.models.length; i < l; i++) {
762
- this._removeReference(this.models[i]);
763
- }
764
- this._reset();
765
- if (models) this.add(models, _.extend({silent: true}, options));
766
- if (!options || !options.silent) this.trigger('reset', this, options);
767
- return this;
768
- },
769
-
770
845
  // Fetch the default set of models for this collection, resetting the
771
- // collection when they arrive. If `add: true` is passed, appends the
772
- // models to the collection instead of resetting.
846
+ // collection when they arrive. If `reset: true` is passed, the response
847
+ // data will be passed through the `reset` method instead of `set`.
773
848
  fetch: function(options) {
774
849
  options = options ? _.clone(options) : {};
775
850
  if (options.parse === void 0) options.parse = true;
776
- var collection = this;
777
851
  var success = options.success;
778
- options.success = function(resp, status, xhr) {
779
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
852
+ var collection = this;
853
+ options.success = function(resp) {
854
+ var method = options.reset ? 'reset' : 'set';
855
+ collection[method](resp, options);
780
856
  if (success) success(collection, resp, options);
857
+ collection.trigger('sync', collection, resp, options);
781
858
  };
859
+ wrapError(this, options);
782
860
  return this.sync('read', this, options);
783
861
  },
784
862
 
@@ -786,13 +864,12 @@
786
864
  // collection immediately, unless `wait: true` is passed, in which case we
787
865
  // wait for the server to agree.
788
866
  create: function(model, options) {
789
- var collection = this;
790
867
  options = options ? _.clone(options) : {};
791
- model = this._prepareModel(model, options);
792
- if (!model) return false;
793
- if (!options.wait) collection.add(model, options);
868
+ if (!(model = this._prepareModel(model, options))) return false;
869
+ if (!options.wait) this.add(model, options);
870
+ var collection = this;
794
871
  var success = options.success;
795
- options.success = function(model, resp, options) {
872
+ options.success = function(resp) {
796
873
  if (options.wait) collection.add(model, options);
797
874
  if (success) success(model, resp, options);
798
875
  };
@@ -802,7 +879,7 @@
802
879
 
803
880
  // **parse** converts a response into a list of models to be added to the
804
881
  // collection. The default implementation is just to pass it through.
805
- parse: function(resp, xhr) {
882
+ parse: function(resp, options) {
806
883
  return resp;
807
884
  },
808
885
 
@@ -811,22 +888,16 @@
811
888
  return new this.constructor(this.models);
812
889
  },
813
890
 
814
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
815
- // underscore methods are proxied because it relies on the underscore
816
- // constructor.
817
- chain: function() {
818
- return _(this.models).chain();
819
- },
820
-
821
- // Reset all internal state. Called when the collection is reset.
822
- _reset: function(options) {
891
+ // Private method to reset all internal state. Called when the collection
892
+ // is first initialized or reset.
893
+ _reset: function() {
823
894
  this.length = 0;
824
895
  this.models = [];
825
896
  this._byId = {};
826
- this._byCid = {};
827
897
  },
828
898
 
829
- // Prepare a model or hash of attributes to be added to this collection.
899
+ // Prepare a hash of attributes (or other model) to be added to this
900
+ // collection.
830
901
  _prepareModel: function(attrs, options) {
831
902
  if (attrs instanceof Model) {
832
903
  if (!attrs.collection) attrs.collection = this;
@@ -835,11 +906,14 @@
835
906
  options || (options = {});
836
907
  options.collection = this;
837
908
  var model = new this.model(attrs, options);
838
- if (!model._validate(model.attributes, options)) return false;
909
+ if (!model._validate(attrs, options)) {
910
+ this.trigger('invalid', this, attrs, options);
911
+ return false;
912
+ }
839
913
  return model;
840
914
  },
841
915
 
842
- // Internal method to remove a model's ties to a collection.
916
+ // Internal method to sever a model's ties to a collection.
843
917
  _removeReference: function(model) {
844
918
  if (this === model.collection) delete model.collection;
845
919
  model.off('all', this._onModelEvent, this);
@@ -862,12 +936,14 @@
862
936
  });
863
937
 
864
938
  // Underscore methods that we want to implement on the Collection.
939
+ // 90% of the core usefulness of Backbone Collections is actually implemented
940
+ // right here:
865
941
  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
866
942
  'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
867
943
  'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
868
- 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
869
- 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
870
- 'lastIndexOf', 'isEmpty'];
944
+ 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
945
+ 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
946
+ 'isEmpty', 'chain'];
871
947
 
872
948
  // Mix in each Underscore method as a proxy to `Collection#models`.
873
949
  _.each(methods, function(method) {
@@ -891,8 +967,243 @@
891
967
  };
892
968
  });
893
969
 
970
+ // Backbone.View
971
+ // -------------
972
+
973
+ // Backbone Views are almost more convention than they are actual code. A View
974
+ // is simply a JavaScript object that represents a logical chunk of UI in the
975
+ // DOM. This might be a single item, an entire list, a sidebar or panel, or
976
+ // even the surrounding frame which wraps your whole app. Defining a chunk of
977
+ // UI as a **View** allows you to define your DOM events declaratively, without
978
+ // having to worry about render order ... and makes it easy for the view to
979
+ // react to specific changes in the state of your models.
980
+
981
+ // Creating a Backbone.View creates its initial element outside of the DOM,
982
+ // if an existing element is not provided...
983
+ var View = Backbone.View = function(options) {
984
+ this.cid = _.uniqueId('view');
985
+ this._configure(options || {});
986
+ this._ensureElement();
987
+ this.initialize.apply(this, arguments);
988
+ this.delegateEvents();
989
+ };
990
+
991
+ // Cached regex to split keys for `delegate`.
992
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
993
+
994
+ // List of view options to be merged as properties.
995
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
996
+
997
+ // Set up all inheritable **Backbone.View** properties and methods.
998
+ _.extend(View.prototype, Events, {
999
+
1000
+ // The default `tagName` of a View's element is `"div"`.
1001
+ tagName: 'div',
1002
+
1003
+ // jQuery delegate for element lookup, scoped to DOM elements within the
1004
+ // current view. This should be prefered to global lookups where possible.
1005
+ $: function(selector) {
1006
+ return this.$el.find(selector);
1007
+ },
1008
+
1009
+ // Initialize is an empty function by default. Override it with your own
1010
+ // initialization logic.
1011
+ initialize: function(){},
1012
+
1013
+ // **render** is the core function that your view should override, in order
1014
+ // to populate its element (`this.el`), with the appropriate HTML. The
1015
+ // convention is for **render** to always return `this`.
1016
+ render: function() {
1017
+ return this;
1018
+ },
1019
+
1020
+ // Remove this view by taking the element out of the DOM, and removing any
1021
+ // applicable Backbone.Events listeners.
1022
+ remove: function() {
1023
+ this.$el.remove();
1024
+ this.stopListening();
1025
+ return this;
1026
+ },
1027
+
1028
+ // Change the view's element (`this.el` property), including event
1029
+ // re-delegation.
1030
+ setElement: function(element, delegate) {
1031
+ if (this.$el) this.undelegateEvents();
1032
+ this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1033
+ this.el = this.$el[0];
1034
+ if (delegate !== false) this.delegateEvents();
1035
+ return this;
1036
+ },
1037
+
1038
+ // Set callbacks, where `this.events` is a hash of
1039
+ //
1040
+ // *{"event selector": "callback"}*
1041
+ //
1042
+ // {
1043
+ // 'mousedown .title': 'edit',
1044
+ // 'click .button': 'save'
1045
+ // 'click .open': function(e) { ... }
1046
+ // }
1047
+ //
1048
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
1049
+ // Uses event delegation for efficiency.
1050
+ // Omitting the selector binds the event to `this.el`.
1051
+ // This only works for delegate-able events: not `focus`, `blur`, and
1052
+ // not `change`, `submit`, and `reset` in Internet Explorer.
1053
+ delegateEvents: function(events) {
1054
+ if (!(events || (events = _.result(this, 'events')))) return this;
1055
+ this.undelegateEvents();
1056
+ for (var key in events) {
1057
+ var method = events[key];
1058
+ if (!_.isFunction(method)) method = this[events[key]];
1059
+ if (!method) continue;
1060
+
1061
+ var match = key.match(delegateEventSplitter);
1062
+ var eventName = match[1], selector = match[2];
1063
+ method = _.bind(method, this);
1064
+ eventName += '.delegateEvents' + this.cid;
1065
+ if (selector === '') {
1066
+ this.$el.on(eventName, method);
1067
+ } else {
1068
+ this.$el.on(eventName, selector, method);
1069
+ }
1070
+ }
1071
+ return this;
1072
+ },
1073
+
1074
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
1075
+ // You usually don't need to use this, but may wish to if you have multiple
1076
+ // Backbone views attached to the same DOM element.
1077
+ undelegateEvents: function() {
1078
+ this.$el.off('.delegateEvents' + this.cid);
1079
+ return this;
1080
+ },
1081
+
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
+ // Ensure that the View has a DOM element to render into.
1093
+ // If `this.el` is a string, pass it through `$()`, take the first
1094
+ // matching element, and re-assign it to `el`. Otherwise, create
1095
+ // an element from the `id`, `className` and `tagName` properties.
1096
+ _ensureElement: function() {
1097
+ if (!this.el) {
1098
+ var attrs = _.extend({}, _.result(this, 'attributes'));
1099
+ if (this.id) attrs.id = _.result(this, 'id');
1100
+ if (this.className) attrs['class'] = _.result(this, 'className');
1101
+ var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1102
+ this.setElement($el, false);
1103
+ } else {
1104
+ this.setElement(_.result(this, 'el'), false);
1105
+ }
1106
+ }
1107
+
1108
+ });
1109
+
1110
+ // Backbone.sync
1111
+ // -------------
1112
+
1113
+ // Override this function to change the manner in which Backbone persists
1114
+ // models to the server. You will be passed the type of request, and the
1115
+ // model in question. By default, makes a RESTful Ajax request
1116
+ // to the model's `url()`. Some possible customizations could be:
1117
+ //
1118
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
1119
+ // * Send up the models as XML instead of JSON.
1120
+ // * Persist models via WebSockets instead of Ajax.
1121
+ //
1122
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1123
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
1124
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
1125
+ // instead of `application/json` with the model in a param named `model`.
1126
+ // Useful when interfacing with server-side languages like **PHP** that make
1127
+ // it difficult to read the body of `PUT` requests.
1128
+ Backbone.sync = function(method, model, options) {
1129
+ var type = methodMap[method];
1130
+
1131
+ // Default options, unless specified.
1132
+ _.defaults(options || (options = {}), {
1133
+ emulateHTTP: Backbone.emulateHTTP,
1134
+ emulateJSON: Backbone.emulateJSON
1135
+ });
1136
+
1137
+ // Default JSON-request options.
1138
+ var params = {type: type, dataType: 'json'};
1139
+
1140
+ // Ensure that we have a URL.
1141
+ if (!options.url) {
1142
+ params.url = _.result(model, 'url') || urlError();
1143
+ }
1144
+
1145
+ // Ensure that we have the appropriate request data.
1146
+ if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1147
+ params.contentType = 'application/json';
1148
+ params.data = JSON.stringify(options.attrs || model.toJSON(options));
1149
+ }
1150
+
1151
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
1152
+ if (options.emulateJSON) {
1153
+ params.contentType = 'application/x-www-form-urlencoded';
1154
+ params.data = params.data ? {model: params.data} : {};
1155
+ }
1156
+
1157
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1158
+ // And an `X-HTTP-Method-Override` header.
1159
+ if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1160
+ params.type = 'POST';
1161
+ if (options.emulateJSON) params.data._method = type;
1162
+ var beforeSend = options.beforeSend;
1163
+ options.beforeSend = function(xhr) {
1164
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
1165
+ if (beforeSend) return beforeSend.apply(this, arguments);
1166
+ };
1167
+ }
1168
+
1169
+ // Don't process data on a non-GET request.
1170
+ if (params.type !== 'GET' && !options.emulateJSON) {
1171
+ params.processData = false;
1172
+ }
1173
+
1174
+ // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1175
+ // that still has ActiveX enabled by default, override jQuery to use that
1176
+ // 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)) {
1179
+ params.xhr = function() {
1180
+ return new ActiveXObject("Microsoft.XMLHTTP");
1181
+ };
1182
+ }
1183
+
1184
+ // Make the request, allowing the user to override any Ajax options.
1185
+ var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1186
+ model.trigger('request', model, xhr, options);
1187
+ return xhr;
1188
+ };
1189
+
1190
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1191
+ var methodMap = {
1192
+ 'create': 'POST',
1193
+ 'update': 'PUT',
1194
+ 'patch': 'PATCH',
1195
+ 'delete': 'DELETE',
1196
+ 'read': 'GET'
1197
+ };
1198
+
1199
+ // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1200
+ // Override this if you'd like to use a different library.
1201
+ Backbone.ajax = function() {
1202
+ return Backbone.$.ajax.apply(Backbone.$, arguments);
1203
+ };
1204
+
894
1205
  // Backbone.Router
895
- // -------------------
1206
+ // ---------------
896
1207
 
897
1208
  // Routers map faux-URLs to actions, and fire events when routes are
898
1209
  // matched. Creating a new one sets its `routes` hash, if not set statically.
@@ -906,9 +1217,9 @@
906
1217
  // Cached regular expressions for matching named param parts and splatted
907
1218
  // parts of route strings.
908
1219
  var optionalParam = /\((.*?)\)/g;
909
- var namedParam = /:\w+/g;
1220
+ var namedParam = /(\(\?)?:\w+/g;
910
1221
  var splatParam = /\*\w+/g;
911
- var escapeRegExp = /[-{}[\]+?.,\\^$|#\s]/g;
1222
+ var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
912
1223
 
913
1224
  // Set up all inheritable **Backbone.Router** properties and methods.
914
1225
  _.extend(Router.prototype, Events, {
@@ -925,13 +1236,19 @@
925
1236
  //
926
1237
  route: function(route, name, callback) {
927
1238
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1239
+ if (_.isFunction(name)) {
1240
+ callback = name;
1241
+ name = '';
1242
+ }
928
1243
  if (!callback) callback = this[name];
929
- Backbone.history.route(route, _.bind(function(fragment) {
930
- var args = this._extractParameters(route, fragment);
931
- callback && callback.apply(this, args);
932
- this.trigger.apply(this, ['route:' + name].concat(args));
933
- Backbone.history.trigger('route', this, name, args);
934
- }, this));
1244
+ var router = this;
1245
+ Backbone.history.route(route, function(fragment) {
1246
+ var args = router._extractParameters(route, fragment);
1247
+ callback && callback.apply(router, args);
1248
+ router.trigger.apply(router, ['route:' + name].concat(args));
1249
+ router.trigger('route', name, args);
1250
+ Backbone.history.trigger('route', router, name, args);
1251
+ });
935
1252
  return this;
936
1253
  },
937
1254
 
@@ -946,12 +1263,10 @@
946
1263
  // routes can be defined at the bottom of the route map.
947
1264
  _bindRoutes: function() {
948
1265
  if (!this.routes) return;
949
- var routes = [];
950
- for (var route in this.routes) {
951
- routes.unshift([route, this.routes[route]]);
952
- }
953
- for (var i = 0, l = routes.length; i < l; i++) {
954
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
1266
+ this.routes = _.result(this, 'routes');
1267
+ var route, routes = _.keys(this.routes);
1268
+ while ((route = routes.pop()) != null) {
1269
+ this.route(route, this.routes[route]);
955
1270
  }
956
1271
  },
957
1272
 
@@ -960,15 +1275,21 @@
960
1275
  _routeToRegExp: function(route) {
961
1276
  route = route.replace(escapeRegExp, '\\$&')
962
1277
  .replace(optionalParam, '(?:$1)?')
963
- .replace(namedParam, '([^\/]+)')
1278
+ .replace(namedParam, function(match, optional){
1279
+ return optional ? match : '([^\/]+)';
1280
+ })
964
1281
  .replace(splatParam, '(.*?)');
965
1282
  return new RegExp('^' + route + '$');
966
1283
  },
967
1284
 
968
1285
  // Given a route, and a URL fragment that it matches, return the array of
969
- // extracted parameters.
1286
+ // extracted decoded parameters. Empty or unmatched parameters will be
1287
+ // treated as `null` to normalize cross-browser behavior.
970
1288
  _extractParameters: function(route, fragment) {
971
- return route.exec(fragment).slice(1);
1289
+ var params = route.exec(fragment).slice(1);
1290
+ return _.map(params, function(param) {
1291
+ return param ? decodeURIComponent(param) : null;
1292
+ });
972
1293
  }
973
1294
 
974
1295
  });
@@ -976,21 +1297,24 @@
976
1297
  // Backbone.History
977
1298
  // ----------------
978
1299
 
979
- // Handles cross-browser history management, based on URL fragments. If the
980
- // browser does not support `onhashchange`, falls back to polling.
1300
+ // Handles cross-browser history management, based on either
1301
+ // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1302
+ // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1303
+ // and URL fragments. If the browser supports neither (old IE, natch),
1304
+ // falls back to polling.
981
1305
  var History = Backbone.History = function() {
982
1306
  this.handlers = [];
983
1307
  _.bindAll(this, 'checkUrl');
984
1308
 
985
- // #1653 - Ensure that `History` can be used outside of the browser.
1309
+ // Ensure that `History` can be used outside of the browser.
986
1310
  if (typeof window !== 'undefined') {
987
1311
  this.location = window.location;
988
1312
  this.history = window.history;
989
1313
  }
990
1314
  };
991
1315
 
992
- // Cached regex for cleaning leading hashes and slashes.
993
- var routeStripper = /^[#\/]/;
1316
+ // Cached regex for stripping a leading hash/slash and trailing space.
1317
+ var routeStripper = /^[#\/]|\s+$/g;
994
1318
 
995
1319
  // Cached regex for stripping leading and trailing slashes.
996
1320
  var rootStripper = /^\/+|\/+$/g;
@@ -1030,7 +1354,7 @@
1030
1354
  fragment = this.getHash();
1031
1355
  }
1032
1356
  }
1033
- return decodeURIComponent(fragment.replace(routeStripper, ''));
1357
+ return fragment.replace(routeStripper, '');
1034
1358
  },
1035
1359
 
1036
1360
  // Start the hash change handling, returning `true` if the current URL matches
@@ -1061,9 +1385,9 @@
1061
1385
  // Depending on whether we're using pushState or hashes, and whether
1062
1386
  // 'onhashchange' is supported, determine how we check the URL state.
1063
1387
  if (this._hasPushState) {
1064
- Backbone.$(window).bind('popstate', this.checkUrl);
1388
+ Backbone.$(window).on('popstate', this.checkUrl);
1065
1389
  } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1066
- Backbone.$(window).bind('hashchange', this.checkUrl);
1390
+ Backbone.$(window).on('hashchange', this.checkUrl);
1067
1391
  } else if (this._wantsHashChange) {
1068
1392
  this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1069
1393
  }
@@ -1095,7 +1419,7 @@
1095
1419
  // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1096
1420
  // but possibly useful for unit testing Routers.
1097
1421
  stop: function() {
1098
- Backbone.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
1422
+ Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1099
1423
  clearInterval(this._checkUrlInterval);
1100
1424
  History.started = false;
1101
1425
  },
@@ -1178,7 +1502,7 @@
1178
1502
  var href = location.href.replace(/(javascript:|#).*$/, '');
1179
1503
  location.replace(href + '#' + fragment);
1180
1504
  } else {
1181
- // #1649 - Some browsers require that `hash` contains a leading #.
1505
+ // Some browsers require that `hash` contains a leading #.
1182
1506
  location.hash = '#' + fragment;
1183
1507
  }
1184
1508
  }
@@ -1188,247 +1512,6 @@
1188
1512
  // Create the default Backbone.history.
1189
1513
  Backbone.history = new History;
1190
1514
 
1191
- // Backbone.View
1192
- // -------------
1193
-
1194
- // Creating a Backbone.View creates its initial element outside of the DOM,
1195
- // if an existing element is not provided...
1196
- var View = Backbone.View = function(options) {
1197
- this.cid = _.uniqueId('view');
1198
- this._configure(options || {});
1199
- this._ensureElement();
1200
- this.initialize.apply(this, arguments);
1201
- this.delegateEvents();
1202
- };
1203
-
1204
- // Cached regex to split keys for `delegate`.
1205
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1206
-
1207
- // List of view options to be merged as properties.
1208
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1209
-
1210
- // Set up all inheritable **Backbone.View** properties and methods.
1211
- _.extend(View.prototype, Events, {
1212
-
1213
- // The default `tagName` of a View's element is `"div"`.
1214
- tagName: 'div',
1215
-
1216
- // jQuery delegate for element lookup, scoped to DOM elements within the
1217
- // current view. This should be prefered to global lookups where possible.
1218
- $: function(selector) {
1219
- return this.$el.find(selector);
1220
- },
1221
-
1222
- // Initialize is an empty function by default. Override it with your own
1223
- // initialization logic.
1224
- initialize: function(){},
1225
-
1226
- // **render** is the core function that your view should override, in order
1227
- // to populate its element (`this.el`), with the appropriate HTML. The
1228
- // convention is for **render** to always return `this`.
1229
- render: function() {
1230
- return this;
1231
- },
1232
-
1233
- // Clean up references to this view in order to prevent latent effects and
1234
- // memory leaks.
1235
- dispose: function() {
1236
- this.undelegateEvents();
1237
- if (this.model && this.model.off) this.model.off(null, null, this);
1238
- if (this.collection && this.collection.off) this.collection.off(null, null, this);
1239
- return this;
1240
- },
1241
-
1242
- // Remove this view from the DOM. Note that the view isn't present in the
1243
- // DOM by default, so calling this method may be a no-op.
1244
- remove: function() {
1245
- this.dispose();
1246
- this.$el.remove();
1247
- return this;
1248
- },
1249
-
1250
- // For small amounts of DOM Elements, where a full-blown template isn't
1251
- // needed, use **make** to manufacture elements, one at a time.
1252
- //
1253
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1254
- //
1255
- make: function(tagName, attributes, content) {
1256
- var el = document.createElement(tagName);
1257
- if (attributes) Backbone.$(el).attr(attributes);
1258
- if (content != null) Backbone.$(el).html(content);
1259
- return el;
1260
- },
1261
-
1262
- // Change the view's element (`this.el` property), including event
1263
- // re-delegation.
1264
- setElement: function(element, delegate) {
1265
- if (this.$el) this.undelegateEvents();
1266
- this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1267
- this.el = this.$el[0];
1268
- if (delegate !== false) this.delegateEvents();
1269
- return this;
1270
- },
1271
-
1272
- // Set callbacks, where `this.events` is a hash of
1273
- //
1274
- // *{"event selector": "callback"}*
1275
- //
1276
- // {
1277
- // 'mousedown .title': 'edit',
1278
- // 'click .button': 'save'
1279
- // 'click .open': function(e) { ... }
1280
- // }
1281
- //
1282
- // pairs. Callbacks will be bound to the view, with `this` set properly.
1283
- // Uses event delegation for efficiency.
1284
- // Omitting the selector binds the event to `this.el`.
1285
- // This only works for delegate-able events: not `focus`, `blur`, and
1286
- // not `change`, `submit`, and `reset` in Internet Explorer.
1287
- delegateEvents: function(events) {
1288
- if (!(events || (events = _.result(this, 'events')))) return;
1289
- this.undelegateEvents();
1290
- for (var key in events) {
1291
- var method = events[key];
1292
- if (!_.isFunction(method)) method = this[events[key]];
1293
- if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1294
- var match = key.match(delegateEventSplitter);
1295
- var eventName = match[1], selector = match[2];
1296
- method = _.bind(method, this);
1297
- eventName += '.delegateEvents' + this.cid;
1298
- if (selector === '') {
1299
- this.$el.bind(eventName, method);
1300
- } else {
1301
- this.$el.delegate(selector, eventName, method);
1302
- }
1303
- }
1304
- },
1305
-
1306
- // Clears all callbacks previously bound to the view with `delegateEvents`.
1307
- // You usually don't need to use this, but may wish to if you have multiple
1308
- // Backbone views attached to the same DOM element.
1309
- undelegateEvents: function() {
1310
- this.$el.unbind('.delegateEvents' + this.cid);
1311
- },
1312
-
1313
- // Performs the initial configuration of a View with a set of options.
1314
- // Keys with special meaning *(model, collection, id, className)*, are
1315
- // attached directly to the view.
1316
- _configure: function(options) {
1317
- if (this.options) options = _.extend({}, this.options, options);
1318
- for (var i = 0, l = viewOptions.length; i < l; i++) {
1319
- var attr = viewOptions[i];
1320
- if (options[attr]) this[attr] = options[attr];
1321
- }
1322
- this.options = options;
1323
- },
1324
-
1325
- // Ensure that the View has a DOM element to render into.
1326
- // If `this.el` is a string, pass it through `$()`, take the first
1327
- // matching element, and re-assign it to `el`. Otherwise, create
1328
- // an element from the `id`, `className` and `tagName` properties.
1329
- _ensureElement: function() {
1330
- if (!this.el) {
1331
- var attrs = _.extend({}, _.result(this, 'attributes'));
1332
- if (this.id) attrs.id = _.result(this, 'id');
1333
- if (this.className) attrs['class'] = _.result(this, 'className');
1334
- this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
1335
- } else {
1336
- this.setElement(_.result(this, 'el'), false);
1337
- }
1338
- }
1339
-
1340
- });
1341
-
1342
- // Backbone.sync
1343
- // -------------
1344
-
1345
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1346
- var methodMap = {
1347
- 'create': 'POST',
1348
- 'update': 'PUT',
1349
- 'delete': 'DELETE',
1350
- 'read': 'GET'
1351
- };
1352
-
1353
- // Override this function to change the manner in which Backbone persists
1354
- // models to the server. You will be passed the type of request, and the
1355
- // model in question. By default, makes a RESTful Ajax request
1356
- // to the model's `url()`. Some possible customizations could be:
1357
- //
1358
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1359
- // * Send up the models as XML instead of JSON.
1360
- // * Persist models via WebSockets instead of Ajax.
1361
- //
1362
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1363
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1364
- // as well as all requests with the body as `application/x-www-form-urlencoded`
1365
- // instead of `application/json` with the model in a param named `model`.
1366
- // Useful when interfacing with server-side languages like **PHP** that make
1367
- // it difficult to read the body of `PUT` requests.
1368
- Backbone.sync = function(method, model, options) {
1369
- var type = methodMap[method];
1370
-
1371
- // Default options, unless specified.
1372
- options || (options = {});
1373
-
1374
- // Default JSON-request options.
1375
- var params = {type: type, dataType: 'json'};
1376
-
1377
- // Ensure that we have a URL.
1378
- if (!options.url) {
1379
- params.url = _.result(model, 'url') || urlError();
1380
- }
1381
-
1382
- // Ensure that we have the appropriate request data.
1383
- if (!options.data && model && (method === 'create' || method === 'update')) {
1384
- params.contentType = 'application/json';
1385
- params.data = JSON.stringify(model);
1386
- }
1387
-
1388
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1389
- if (Backbone.emulateJSON) {
1390
- params.contentType = 'application/x-www-form-urlencoded';
1391
- params.data = params.data ? {model: params.data} : {};
1392
- }
1393
-
1394
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1395
- // And an `X-HTTP-Method-Override` header.
1396
- if (Backbone.emulateHTTP) {
1397
- if (type === 'PUT' || type === 'DELETE') {
1398
- if (Backbone.emulateJSON) params.data._method = type;
1399
- params.type = 'POST';
1400
- params.beforeSend = function(xhr) {
1401
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1402
- };
1403
- }
1404
- }
1405
-
1406
- // Don't process data on a non-GET request.
1407
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
1408
- params.processData = false;
1409
- }
1410
-
1411
- var success = options.success;
1412
- options.success = function(resp, status, xhr) {
1413
- if (success) success(resp, status, xhr);
1414
- model.trigger('sync', model, resp, options);
1415
- };
1416
-
1417
- var error = options.error;
1418
- options.error = function(xhr, status, thrown) {
1419
- if (error) error(model, xhr, options);
1420
- model.trigger('error', model, xhr, options);
1421
- };
1422
-
1423
- // Make the request, allowing the user to override any Ajax options.
1424
- return Backbone.ajax(_.extend(params, options));
1425
- };
1426
-
1427
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1428
- Backbone.ajax = function() {
1429
- return Backbone.$.ajax.apply(Backbone.$, arguments);
1430
- };
1431
-
1432
1515
  // Helpers
1433
1516
  // -------
1434
1517
 
@@ -1445,7 +1528,7 @@
1445
1528
  if (protoProps && _.has(protoProps, 'constructor')) {
1446
1529
  child = protoProps.constructor;
1447
1530
  } else {
1448
- child = function(){ parent.apply(this, arguments); };
1531
+ child = function(){ return parent.apply(this, arguments); };
1449
1532
  }
1450
1533
 
1451
1534
  // Add static properties to the constructor function, if supplied.
@@ -1468,12 +1551,21 @@
1468
1551
  return child;
1469
1552
  };
1470
1553
 
1471
- // Set up inheritance for the model, collection, router, and view.
1472
- Model.extend = Collection.extend = Router.extend = View.extend = extend;
1554
+ // Set up inheritance for the model, collection, router, view and history.
1555
+ Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1473
1556
 
1474
1557
  // Throw an error when a URL is needed, and none is supplied.
1475
1558
  var urlError = function() {
1476
1559
  throw new Error('A "url" property or function must be specified');
1477
1560
  };
1478
1561
 
1562
+ // Wrap an optional error callback with a fallback error event.
1563
+ var wrapError = function (model, options) {
1564
+ var error = options.error;
1565
+ options.error = function(resp) {
1566
+ if (error) error(model, resp, options);
1567
+ model.trigger('error', model, resp, options);
1568
+ };
1569
+ };
1570
+
1479
1571
  }).call(this);