Browse code

Pridany backbone.js

Cinan Rakosnik authored on 25/02/2013 at 20:07:31
Showing 13 changed files
... ...
@@ -5,4 +5,4 @@ You are allowed to:
5 5
 2. Remove generators
6 6
 3. Add installed generators
7 7
 To add new installed generators automatically delete this file and reload the project.
8
+--><GeneratorsGroup><Generator name="active_record:migration" /><Generator name="active_record:model" /><Generator name="active_record:observer" /><Generator name="active_record:session_migration" /><Generator name="assets" /><Generator name="backbone:install" /><Generator name="backbone:model" /><Generator name="backbone:router" /><Generator name="backbone:scaffold" /><Generator name="coffee:assets" /><Generator name="controller" /><Generator name="erb:controller" /><Generator name="erb:mailer" /><Generator name="erb:scaffold" /><Generator name="factory_girl:model" /><Generator name="generator" /><Generator name="helper" /><Generator name="integration_test" /><Generator name="jquery:install" /><Generator name="js:assets" /><Generator name="mailer" /><Generator name="migration" /><Generator name="model" /><Generator name="mongoid:config" /><Generator name="observer" /><Generator name="performance_test" /><Generator name="resource" /><Generator name="rspec:controller" /><Generator name="rspec:helper" /><Generator name="rspec:install" /><Generator name="rspec:integration" /><Generator name="rspec:mailer" /><Generator name="rspec:model" /><Generator name="rspec:observer" /><Generator name="rspec:scaffold" /><Generator name="rspec:view" /><Generator name="scaffold" /><Generator name="scaffold_controller" /><Generator name="session_migration" /><Generator name="task" /></GeneratorsGroup></Settings>
... ...
@@ -42,6 +42,7 @@ gem 'jquery-rails'
42 42
 gem "haml", "~> 3.1.7"
43 43
 gem "haml-rails", "~> 0.3.5"
44 44
 gem "xmpp4r", "~> 0.5"
45
+gem "rails-backbone"
45 46
 
46 47
 # To use ActiveModel has_secure_password
47 48
 # gem 'bcrypt-ruby', '~> 3.0.0'
... ...
@@ -62,6 +62,7 @@ GEM
62 62
     compass-rails (1.0.3)
63 63
       compass (>= 0.12.2, < 0.14)
64 64
     diff-lcs (1.1.3)
65
+    ejs (1.0.0)
65 66
     erubis (2.7.0)
66 67
     execjs (1.4.0)
67 68
       multi_json (~> 1.0)
... ...
@@ -136,6 +137,10 @@ GEM
136 136
       activesupport (= 3.2.12)
137 137
       bundler (~> 1.0)
138 138
       railties (= 3.2.12)
139
+    rails-backbone (0.7.2)
140
+      coffee-script (~> 2.2.0)
141
+      ejs (~> 1.0.0)
142
+      railties (>= 3.1.0)
139 143
     railties (3.2.12)
140 144
       actionpack (= 3.2.12)
141 145
       activesupport (= 3.2.12)
... ...
@@ -207,6 +212,7 @@ DEPENDENCIES
207 207
   mongoid (~> 3.0.20)
208 208
   normalize-rails
209 209
   rails (~> 3.2.11)
210
+  rails-backbone
210 211
   rspec-rails
211 212
   sass-rails (~> 3.2.3)
212 213
   test-unit
... ...
@@ -12,4 +12,9 @@
12 12
 //
13 13
 //= require jquery
14 14
 //= require jquery_ujs
15
+//= require underscore
16
+//= require backbone
17
+//= require backbone_rails_sync
18
+//= require backbone_datalink
19
+//= require backbone/xmpp
15 20
 //= require_tree .
16 21
new file mode 100644
17 22
new file mode 100644
18 23
new file mode 100644
19 24
new file mode 100644
20 25
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+#= require_self
1
+#= require_tree ./templates
2
+#= require_tree ./models
3
+#= require_tree ./views
4
+#= require_tree ./routers
5
+
6
+window.Xmpp =
7
+  Models: {}
8
+  Collections: {}
9
+  Routers: {}
10
+  Views: {}
0 11
\ No newline at end of file
... ...
@@ -4,5 +4,6 @@
4 4
     %title= yield(:title) + " | App"
5 5
     %meta{ :charset => "utf-8" }/
6 6
     = stylesheet_link_tag "application"
7
+    = javascript_include_tag "application"
7 8
   %body
8 9
     = yield
9 10
\ No newline at end of file
10 11
new file mode 100644
... ...
@@ -0,0 +1,1498 @@
0
+//     Backbone.js 0.9.10
1
+
2
+//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
+//     Backbone may be freely distributed under the MIT license.
4
+//     For all details and documentation:
5
+//     http://backbonejs.org
6
+
7
+(function(){
8
+
9
+  // Initial Setup
10
+  // -------------
11
+
12
+  // Save a reference to the global object (`window` in the browser, `exports`
13
+  // on the server).
14
+  var root = this;
15
+
16
+  // Save the previous value of the `Backbone` variable, so that it can be
17
+  // restored later on, if `noConflict` is used.
18
+  var previousBackbone = root.Backbone;
19
+
20
+  // Create a local reference to array methods.
21
+  var array = [];
22
+  var push = array.push;
23
+  var slice = array.slice;
24
+  var splice = array.splice;
25
+
26
+  // The top-level namespace. All public Backbone classes and modules will
27
+  // be attached to this. Exported for both CommonJS and the browser.
28
+  var Backbone;
29
+  if (typeof exports !== 'undefined') {
30
+    Backbone = exports;
31
+  } else {
32
+    Backbone = root.Backbone = {};
33
+  }
34
+
35
+  // Current version of the library. Keep in sync with `package.json`.
36
+  Backbone.VERSION = '0.9.10';
37
+
38
+  // Require Underscore, if we're on the server, and it's not already present.
39
+  var _ = root._;
40
+  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
41
+
42
+  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
43
+  Backbone.$ = root.jQuery || root.Zepto || root.ender;
44
+
45
+  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
46
+  // to its previous owner. Returns a reference to this Backbone object.
47
+  Backbone.noConflict = function() {
48
+    root.Backbone = previousBackbone;
49
+    return this;
50
+  };
51
+
52
+  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
53
+  // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
54
+  // set a `X-Http-Method-Override` header.
55
+  Backbone.emulateHTTP = false;
56
+
57
+  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
58
+  // `application/json` requests ... will encode the body as
59
+  // `application/x-www-form-urlencoded` instead and will send the model in a
60
+  // form param named `model`.
61
+  Backbone.emulateJSON = false;
62
+
63
+  // Backbone.Events
64
+  // ---------------
65
+
66
+  // Regular expression used to split event strings.
67
+  var eventSplitter = /\s+/;
68
+
69
+  // Implement fancy features of the Events API such as multiple event
70
+  // names `"change blur"` and jQuery-style event maps `{change: action}`
71
+  // in terms of the existing API.
72
+  var eventsApi = function(obj, action, name, rest) {
73
+    if (!name) return true;
74
+    if (typeof name === 'object') {
75
+      for (var key in name) {
76
+        obj[action].apply(obj, [key, name[key]].concat(rest));
77
+      }
78
+    } else if (eventSplitter.test(name)) {
79
+      var names = name.split(eventSplitter);
80
+      for (var i = 0, l = names.length; i < l; i++) {
81
+        obj[action].apply(obj, [names[i]].concat(rest));
82
+      }
83
+    } else {
84
+      return true;
85
+    }
86
+  };
87
+
88
+  // Optimized internal dispatch function for triggering events. Tries to
89
+  // keep the usual cases speedy (most Backbone events have 3 arguments).
90
+  var triggerEvents = function(events, args) {
91
+    var ev, i = -1, l = events.length;
92
+    switch (args.length) {
93
+    case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
94
+    return;
95
+    case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
96
+    return;
97
+    case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
98
+    return;
99
+    case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
100
+    return;
101
+    default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
102
+    }
103
+  };
104
+
105
+  // A module that can be mixed in to *any object* in order to provide it with
106
+  // custom events. You may bind with `on` or remove with `off` callback
107
+  // functions to an event; `trigger`-ing an event fires all callbacks in
108
+  // succession.
109
+  //
110
+  //     var object = {};
111
+  //     _.extend(object, Backbone.Events);
112
+  //     object.on('expand', function(){ alert('expanded'); });
113
+  //     object.trigger('expand');
114
+  //
115
+  var Events = Backbone.Events = {
116
+
117
+    // Bind one or more space separated events, or an events map,
118
+    // to a `callback` function. Passing `"all"` will bind the callback to
119
+    // all events fired.
120
+    on: function(name, callback, context) {
121
+      if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
122
+      this._events || (this._events = {});
123
+      var list = this._events[name] || (this._events[name] = []);
124
+      list.push({callback: callback, context: context, ctx: context || this});
125
+      return this;
126
+    },
127
+
128
+    // Bind events to only be triggered a single time. After the first time
129
+    // the callback is invoked, it will be removed.
130
+    once: function(name, callback, context) {
131
+      if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
132
+      var self = this;
133
+      var once = _.once(function() {
134
+        self.off(name, once);
135
+        callback.apply(this, arguments);
136
+      });
137
+      once._callback = callback;
138
+      this.on(name, once, context);
139
+      return this;
140
+    },
141
+
142
+    // Remove one or many callbacks. If `context` is null, removes all
143
+    // callbacks with that function. If `callback` is null, removes all
144
+    // callbacks for the event. If `name` is null, removes all bound
145
+    // callbacks for all events.
146
+    off: function(name, callback, context) {
147
+      var list, ev, events, names, i, l, j, k;
148
+      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
149
+      if (!name && !callback && !context) {
150
+        this._events = {};
151
+        return this;
152
+      }
153
+
154
+      names = name ? [name] : _.keys(this._events);
155
+      for (i = 0, l = names.length; i < l; i++) {
156
+        name = names[i];
157
+        if (list = this._events[name]) {
158
+          events = [];
159
+          if (callback || context) {
160
+            for (j = 0, k = list.length; j < k; j++) {
161
+              ev = list[j];
162
+              if ((callback && callback !== ev.callback &&
163
+                               callback !== ev.callback._callback) ||
164
+                  (context && context !== ev.context)) {
165
+                events.push(ev);
166
+              }
167
+            }
168
+          }
169
+          this._events[name] = events;
170
+        }
171
+      }
172
+
173
+      return this;
174
+    },
175
+
176
+    // Trigger one or many events, firing all bound callbacks. Callbacks are
177
+    // passed the same arguments as `trigger` is, apart from the event name
178
+    // (unless you're listening on `"all"`, which will cause your callback to
179
+    // receive the true name of the event as the first argument).
180
+    trigger: function(name) {
181
+      if (!this._events) return this;
182
+      var args = slice.call(arguments, 1);
183
+      if (!eventsApi(this, 'trigger', name, args)) return this;
184
+      var events = this._events[name];
185
+      var allEvents = this._events.all;
186
+      if (events) triggerEvents(events, args);
187
+      if (allEvents) triggerEvents(allEvents, arguments);
188
+      return this;
189
+    },
190
+
191
+    // An inversion-of-control version of `on`. Tell *this* object to listen to
192
+    // an event in another object ... keeping track of what it's listening to.
193
+    listenTo: function(obj, name, callback) {
194
+      var listeners = this._listeners || (this._listeners = {});
195
+      var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
196
+      listeners[id] = obj;
197
+      obj.on(name, typeof name === 'object' ? this : callback, this);
198
+      return this;
199
+    },
200
+
201
+    // Tell this object to stop listening to either specific events ... or
202
+    // to every object it's currently listening to.
203
+    stopListening: function(obj, name, callback) {
204
+      var listeners = this._listeners;
205
+      if (!listeners) return;
206
+      if (obj) {
207
+        obj.off(name, typeof name === 'object' ? this : callback, this);
208
+        if (!name && !callback) delete listeners[obj._listenerId];
209
+      } else {
210
+        if (typeof name === 'object') callback = this;
211
+        for (var id in listeners) {
212
+          listeners[id].off(name, callback, this);
213
+        }
214
+        this._listeners = {};
215
+      }
216
+      return this;
217
+    }
218
+  };
219
+
220
+  // Aliases for backwards compatibility.
221
+  Events.bind   = Events.on;
222
+  Events.unbind = Events.off;
223
+
224
+  // Allow the `Backbone` object to serve as a global event bus, for folks who
225
+  // want global "pubsub" in a convenient place.
226
+  _.extend(Backbone, Events);
227
+
228
+  // Backbone.Model
229
+  // --------------
230
+
231
+  // Create a new model, with defined attributes. A client id (`cid`)
232
+  // is automatically generated and assigned for you.
233
+  var Model = Backbone.Model = function(attributes, options) {
234
+    var defaults;
235
+    var attrs = attributes || {};
236
+    this.cid = _.uniqueId('c');
237
+    this.attributes = {};
238
+    if (options && options.collection) this.collection = options.collection;
239
+    if (options && options.parse) attrs = this.parse(attrs, options) || {};
240
+    if (defaults = _.result(this, 'defaults')) {
241
+      attrs = _.defaults({}, attrs, defaults);
242
+    }
243
+    this.set(attrs, options);
244
+    this.changed = {};
245
+    this.initialize.apply(this, arguments);
246
+  };
247
+
248
+  // Attach all inheritable methods to the Model prototype.
249
+  _.extend(Model.prototype, Events, {
250
+
251
+    // A hash of attributes whose current and previous value differ.
252
+    changed: null,
253
+
254
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
255
+    // CouchDB users may want to set this to `"_id"`.
256
+    idAttribute: 'id',
257
+
258
+    // Initialize is an empty function by default. Override it with your own
259
+    // initialization logic.
260
+    initialize: function(){},
261
+
262
+    // Return a copy of the model's `attributes` object.
263
+    toJSON: function(options) {
264
+      return _.clone(this.attributes);
265
+    },
266
+
267
+    // Proxy `Backbone.sync` by default.
268
+    sync: function() {
269
+      return Backbone.sync.apply(this, arguments);
270
+    },
271
+
272
+    // Get the value of an attribute.
273
+    get: function(attr) {
274
+      return this.attributes[attr];
275
+    },
276
+
277
+    // Get the HTML-escaped value of an attribute.
278
+    escape: function(attr) {
279
+      return _.escape(this.get(attr));
280
+    },
281
+
282
+    // Returns `true` if the attribute contains a value that is not null
283
+    // or undefined.
284
+    has: function(attr) {
285
+      return this.get(attr) != null;
286
+    },
287
+
288
+    // ----------------------------------------------------------------------
289
+
290
+    // Set a hash of model attributes on the object, firing `"change"` unless
291
+    // you choose to silence it.
292
+    set: function(key, val, options) {
293
+      var attr, attrs, unset, changes, silent, changing, prev, current;
294
+      if (key == null) return this;
295
+
296
+      // Handle both `"key", value` and `{key: value}` -style arguments.
297
+      if (typeof key === 'object') {
298
+        attrs = key;
299
+        options = val;
300
+      } else {
301
+        (attrs = {})[key] = val;
302
+      }
303
+
304
+      options || (options = {});
305
+
306
+      // Run validation.
307
+      if (!this._validate(attrs, options)) return false;
308
+
309
+      // Extract attributes and options.
310
+      unset           = options.unset;
311
+      silent          = options.silent;
312
+      changes         = [];
313
+      changing        = this._changing;
314
+      this._changing  = true;
315
+
316
+      if (!changing) {
317
+        this._previousAttributes = _.clone(this.attributes);
318
+        this.changed = {};
319
+      }
320
+      current = this.attributes, prev = this._previousAttributes;
321
+
322
+      // Check for changes of `id`.
323
+      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
324
+
325
+      // For each `set` attribute, update or delete the current value.
326
+      for (attr in attrs) {
327
+        val = attrs[attr];
328
+        if (!_.isEqual(current[attr], val)) changes.push(attr);
329
+        if (!_.isEqual(prev[attr], val)) {
330
+          this.changed[attr] = val;
331
+        } else {
332
+          delete this.changed[attr];
333
+        }
334
+        unset ? delete current[attr] : current[attr] = val;
335
+      }
336
+
337
+      // Trigger all relevant attribute changes.
338
+      if (!silent) {
339
+        if (changes.length) this._pending = true;
340
+        for (var i = 0, l = changes.length; i < l; i++) {
341
+          this.trigger('change:' + changes[i], this, current[changes[i]], options);
342
+        }
343
+      }
344
+
345
+      if (changing) return this;
346
+      if (!silent) {
347
+        while (this._pending) {
348
+          this._pending = false;
349
+          this.trigger('change', this, options);
350
+        }
351
+      }
352
+      this._pending = false;
353
+      this._changing = false;
354
+      return this;
355
+    },
356
+
357
+    // Remove an attribute from the model, firing `"change"` unless you choose
358
+    // to silence it. `unset` is a noop if the attribute doesn't exist.
359
+    unset: function(attr, options) {
360
+      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
361
+    },
362
+
363
+    // Clear all attributes on the model, firing `"change"` unless you choose
364
+    // to silence it.
365
+    clear: function(options) {
366
+      var attrs = {};
367
+      for (var key in this.attributes) attrs[key] = void 0;
368
+      return this.set(attrs, _.extend({}, options, {unset: true}));
369
+    },
370
+
371
+    // Determine if the model has changed since the last `"change"` event.
372
+    // If you specify an attribute name, determine if that attribute has changed.
373
+    hasChanged: function(attr) {
374
+      if (attr == null) return !_.isEmpty(this.changed);
375
+      return _.has(this.changed, attr);
376
+    },
377
+
378
+    // Return an object containing all the attributes that have changed, or
379
+    // false if there are no changed attributes. Useful for determining what
380
+    // parts of a view need to be updated and/or what attributes need to be
381
+    // persisted to the server. Unset attributes will be set to undefined.
382
+    // You can also pass an attributes object to diff against the model,
383
+    // determining if there *would be* a change.
384
+    changedAttributes: function(diff) {
385
+      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
386
+      var val, changed = false;
387
+      var old = this._changing ? this._previousAttributes : this.attributes;
388
+      for (var attr in diff) {
389
+        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
390
+        (changed || (changed = {}))[attr] = val;
391
+      }
392
+      return changed;
393
+    },
394
+
395
+    // Get the previous value of an attribute, recorded at the time the last
396
+    // `"change"` event was fired.
397
+    previous: function(attr) {
398
+      if (attr == null || !this._previousAttributes) return null;
399
+      return this._previousAttributes[attr];
400
+    },
401
+
402
+    // Get all of the attributes of the model at the time of the previous
403
+    // `"change"` event.
404
+    previousAttributes: function() {
405
+      return _.clone(this._previousAttributes);
406
+    },
407
+
408
+    // ---------------------------------------------------------------------
409
+
410
+    // Fetch the model from the server. If the server's representation of the
411
+    // model differs from its current attributes, they will be overriden,
412
+    // triggering a `"change"` event.
413
+    fetch: function(options) {
414
+      options = options ? _.clone(options) : {};
415
+      if (options.parse === void 0) options.parse = true;
416
+      var success = options.success;
417
+      options.success = function(model, resp, options) {
418
+        if (!model.set(model.parse(resp, options), options)) return false;
419
+        if (success) success(model, resp, options);
420
+      };
421
+      return this.sync('read', this, options);
422
+    },
423
+
424
+    // Set a hash of model attributes, and sync the model to the server.
425
+    // If the server returns an attributes hash that differs, the model's
426
+    // state will be `set` again.
427
+    save: function(key, val, options) {
428
+      var attrs, success, method, xhr, attributes = this.attributes;
429
+
430
+      // Handle both `"key", value` and `{key: value}` -style arguments.
431
+      if (key == null || typeof key === 'object') {
432
+        attrs = key;
433
+        options = val;
434
+      } else {
435
+        (attrs = {})[key] = val;
436
+      }
437
+
438
+      // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
439
+      if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
440
+
441
+      options = _.extend({validate: true}, options);
442
+
443
+      // Do not persist invalid models.
444
+      if (!this._validate(attrs, options)) return false;
445
+
446
+      // Set temporary attributes if `{wait: true}`.
447
+      if (attrs && options.wait) {
448
+        this.attributes = _.extend({}, attributes, attrs);
449
+      }
450
+
451
+      // After a successful server-side save, the client is (optionally)
452
+      // updated with the server-side state.
453
+      if (options.parse === void 0) options.parse = true;
454
+      success = options.success;
455
+      options.success = function(model, resp, options) {
456
+        // Ensure attributes are restored during synchronous saves.
457
+        model.attributes = attributes;
458
+        var serverAttrs = model.parse(resp, options);
459
+        if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
460
+        if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
461
+          return false;
462
+        }
463
+        if (success) success(model, resp, options);
464
+      };
465
+
466
+      // Finish configuring and sending the Ajax request.
467
+      method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
468
+      if (method === 'patch') options.attrs = attrs;
469
+      xhr = this.sync(method, this, options);
470
+
471
+      // Restore attributes.
472
+      if (attrs && options.wait) this.attributes = attributes;
473
+
474
+      return xhr;
475
+    },
476
+
477
+    // Destroy this model on the server if it was already persisted.
478
+    // Optimistically removes the model from its collection, if it has one.
479
+    // If `wait: true` is passed, waits for the server to respond before removal.
480
+    destroy: function(options) {
481
+      options = options ? _.clone(options) : {};
482
+      var model = this;
483
+      var success = options.success;
484
+
485
+      var destroy = function() {
486
+        model.trigger('destroy', model, model.collection, options);
487
+      };
488
+
489
+      options.success = function(model, resp, options) {
490
+        if (options.wait || model.isNew()) destroy();
491
+        if (success) success(model, resp, options);
492
+      };
493
+
494
+      if (this.isNew()) {
495
+        options.success(this, null, options);
496
+        return false;
497
+      }
498
+
499
+      var xhr = this.sync('delete', this, options);
500
+      if (!options.wait) destroy();
501
+      return xhr;
502
+    },
503
+
504
+    // Default URL for the model's representation on the server -- if you're
505
+    // using Backbone's restful methods, override this to change the endpoint
506
+    // that will be called.
507
+    url: function() {
508
+      var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
509
+      if (this.isNew()) return base;
510
+      return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
511
+    },
512
+
513
+    // **parse** converts a response into the hash of attributes to be `set` on
514
+    // the model. The default implementation is just to pass the response along.
515
+    parse: function(resp, options) {
516
+      return resp;
517
+    },
518
+
519
+    // Create a new model with identical attributes to this one.
520
+    clone: function() {
521
+      return new this.constructor(this.attributes);
522
+    },
523
+
524
+    // A model is new if it has never been saved to the server, and lacks an id.
525
+    isNew: function() {
526
+      return this.id == null;
527
+    },
528
+
529
+    // Check if the model is currently in a valid state.
530
+    isValid: function(options) {
531
+      return !this.validate || !this.validate(this.attributes, options);
532
+    },
533
+
534
+    // Run validation against the next complete set of model attributes,
535
+    // returning `true` if all is well. Otherwise, fire a general
536
+    // `"error"` event and call the error callback, if specified.
537
+    _validate: function(attrs, options) {
538
+      if (!options.validate || !this.validate) return true;
539
+      attrs = _.extend({}, this.attributes, attrs);
540
+      var error = this.validationError = this.validate(attrs, options) || null;
541
+      if (!error) return true;
542
+      this.trigger('invalid', this, error, options || {});
543
+      return false;
544
+    }
545
+
546
+  });
547
+
548
+  // Backbone.Collection
549
+  // -------------------
550
+
551
+  // Provides a standard collection class for our sets of models, ordered
552
+  // or unordered. If a `comparator` is specified, the Collection will maintain
553
+  // its models in sort order, as they're added and removed.
554
+  var Collection = Backbone.Collection = function(models, options) {
555
+    options || (options = {});
556
+    if (options.model) this.model = options.model;
557
+    if (options.comparator !== void 0) this.comparator = options.comparator;
558
+    this.models = [];
559
+    this._reset();
560
+    this.initialize.apply(this, arguments);
561
+    if (models) this.reset(models, _.extend({silent: true}, options));
562
+  };
563
+
564
+  // Define the Collection's inheritable methods.
565
+  _.extend(Collection.prototype, Events, {
566
+
567
+    // The default model for a collection is just a **Backbone.Model**.
568
+    // This should be overridden in most cases.
569
+    model: Model,
570
+
571
+    // Initialize is an empty function by default. Override it with your own
572
+    // initialization logic.
573
+    initialize: function(){},
574
+
575
+    // The JSON representation of a Collection is an array of the
576
+    // models' attributes.
577
+    toJSON: function(options) {
578
+      return this.map(function(model){ return model.toJSON(options); });
579
+    },
580
+
581
+    // Proxy `Backbone.sync` by default.
582
+    sync: function() {
583
+      return Backbone.sync.apply(this, arguments);
584
+    },
585
+
586
+    // Add a model, or list of models to the set.
587
+    add: function(models, options) {
588
+      models = _.isArray(models) ? models.slice() : [models];
589
+      options || (options = {});
590
+      var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
591
+      add = [];
592
+      at = options.at;
593
+      sort = this.comparator && (at == null) && options.sort != false;
594
+      sortAttr = _.isString(this.comparator) ? this.comparator : null;
595
+
596
+      // Turn bare objects into model references, and prevent invalid models
597
+      // from being added.
598
+      for (i = 0, l = models.length; i < l; i++) {
599
+        if (!(model = this._prepareModel(attrs = models[i], options))) {
600
+          this.trigger('invalid', this, attrs, options);
601
+          continue;
602
+        }
603
+
604
+        // If a duplicate is found, prevent it from being added and
605
+        // optionally merge it into the existing model.
606
+        if (existing = this.get(model)) {
607
+          if (options.merge) {
608
+            existing.set(attrs === model ? model.attributes : attrs, options);
609
+            if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
610
+          }
611
+          continue;
612
+        }
613
+
614
+        // This is a new model, push it to the `add` list.
615
+        add.push(model);
616
+
617
+        // Listen to added models' events, and index models for lookup by
618
+        // `id` and by `cid`.
619
+        model.on('all', this._onModelEvent, this);
620
+        this._byId[model.cid] = model;
621
+        if (model.id != null) this._byId[model.id] = model;
622
+      }
623
+
624
+      // See if sorting is needed, update `length` and splice in new models.
625
+      if (add.length) {
626
+        if (sort) doSort = true;
627
+        this.length += add.length;
628
+        if (at != null) {
629
+          splice.apply(this.models, [at, 0].concat(add));
630
+        } else {
631
+          push.apply(this.models, add);
632
+        }
633
+      }
634
+
635
+      // Silently sort the collection if appropriate.
636
+      if (doSort) this.sort({silent: true});
637
+
638
+      if (options.silent) return this;
639
+
640
+      // Trigger `add` events.
641
+      for (i = 0, l = add.length; i < l; i++) {
642
+        (model = add[i]).trigger('add', model, this, options);
643
+      }
644
+
645
+      // Trigger `sort` if the collection was sorted.
646
+      if (doSort) this.trigger('sort', this, options);
647
+
648
+      return this;
649
+    },
650
+
651
+    // Remove a model, or a list of models from the set.
652
+    remove: function(models, options) {
653
+      models = _.isArray(models) ? models.slice() : [models];
654
+      options || (options = {});
655
+      var i, l, index, model;
656
+      for (i = 0, l = models.length; i < l; i++) {
657
+        model = this.get(models[i]);
658
+        if (!model) continue;
659
+        delete this._byId[model.id];
660
+        delete this._byId[model.cid];
661
+        index = this.indexOf(model);
662
+        this.models.splice(index, 1);
663
+        this.length--;
664
+        if (!options.silent) {
665
+          options.index = index;
666
+          model.trigger('remove', model, this, options);
667
+        }
668
+        this._removeReference(model);
669
+      }
670
+      return this;
671
+    },
672
+
673
+    // Add a model to the end of the collection.
674
+    push: function(model, options) {
675
+      model = this._prepareModel(model, options);
676
+      this.add(model, _.extend({at: this.length}, options));
677
+      return model;
678
+    },
679
+
680
+    // Remove a model from the end of the collection.
681
+    pop: function(options) {
682
+      var model = this.at(this.length - 1);
683
+      this.remove(model, options);
684
+      return model;
685
+    },
686
+
687
+    // Add a model to the beginning of the collection.
688
+    unshift: function(model, options) {
689
+      model = this._prepareModel(model, options);
690
+      this.add(model, _.extend({at: 0}, options));
691
+      return model;
692
+    },
693
+
694
+    // Remove a model from the beginning of the collection.
695
+    shift: function(options) {
696
+      var model = this.at(0);
697
+      this.remove(model, options);
698
+      return model;
699
+    },
700
+
701
+    // Slice out a sub-array of models from the collection.
702
+    slice: function(begin, end) {
703
+      return this.models.slice(begin, end);
704
+    },
705
+
706
+    // Get a model from the set by id.
707
+    get: function(obj) {
708
+      if (obj == null) return void 0;
709
+      this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
710
+      return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
711
+    },
712
+
713
+    // Get the model at the given index.
714
+    at: function(index) {
715
+      return this.models[index];
716
+    },
717
+
718
+    // Return models with matching attributes. Useful for simple cases of `filter`.
719
+    where: function(attrs) {
720
+      if (_.isEmpty(attrs)) return [];
721
+      return this.filter(function(model) {
722
+        for (var key in attrs) {
723
+          if (attrs[key] !== model.get(key)) return false;
724
+        }
725
+        return true;
726
+      });
727
+    },
728
+
729
+    // Force the collection to re-sort itself. You don't need to call this under
730
+    // normal circumstances, as the set will maintain sort order as each item
731
+    // is added.
732
+    sort: function(options) {
733
+      if (!this.comparator) {
734
+        throw new Error('Cannot sort a set without a comparator');
735
+      }
736
+      options || (options = {});
737
+
738
+      // Run sort based on type of `comparator`.
739
+      if (_.isString(this.comparator) || this.comparator.length === 1) {
740
+        this.models = this.sortBy(this.comparator, this);
741
+      } else {
742
+        this.models.sort(_.bind(this.comparator, this));
743
+      }
744
+
745
+      if (!options.silent) this.trigger('sort', this, options);
746
+      return this;
747
+    },
748
+
749
+    // Pluck an attribute from each model in the collection.
750
+    pluck: function(attr) {
751
+      return _.invoke(this.models, 'get', attr);
752
+    },
753
+
754
+    // Smartly update a collection with a change set of models, adding,
755
+    // removing, and merging as necessary.
756
+    update: function(models, options) {
757
+      options = _.extend({add: true, merge: true, remove: true}, options);
758
+      if (options.parse) models = this.parse(models, options);
759
+      var model, i, l, existing;
760
+      var add = [], remove = [], modelMap = {};
761
+
762
+      // Allow a single model (or no argument) to be passed.
763
+      if (!_.isArray(models)) models = models ? [models] : [];
764
+
765
+      // Proxy to `add` for this case, no need to iterate...
766
+      if (options.add && !options.remove) return this.add(models, options);
767
+
768
+      // Determine which models to add and merge, and which to remove.
769
+      for (i = 0, l = models.length; i < l; i++) {
770
+        model = models[i];
771
+        existing = this.get(model);
772
+        if (options.remove && existing) modelMap[existing.cid] = true;
773
+        if ((options.add && !existing) || (options.merge && existing)) {
774
+          add.push(model);
775
+        }
776
+      }
777
+      if (options.remove) {
778
+        for (i = 0, l = this.models.length; i < l; i++) {
779
+          model = this.models[i];
780
+          if (!modelMap[model.cid]) remove.push(model);
781
+        }
782
+      }
783
+
784
+      // Remove models (if applicable) before we add and merge the rest.
785
+      if (remove.length) this.remove(remove, options);
786
+      if (add.length) this.add(add, options);
787
+      return this;
788
+    },
789
+
790
+    // When you have more items than you want to add or remove individually,
791
+    // you can reset the entire set with a new list of models, without firing
792
+    // any `add` or `remove` events. Fires `reset` when finished.
793
+    reset: function(models, options) {
794
+      options || (options = {});
795
+      if (options.parse) models = this.parse(models, options);
796
+      for (var i = 0, l = this.models.length; i < l; i++) {
797
+        this._removeReference(this.models[i]);
798
+      }
799
+      options.previousModels = this.models.slice();
800
+      this._reset();
801
+      if (models) this.add(models, _.extend({silent: true}, options));
802
+      if (!options.silent) this.trigger('reset', this, options);
803
+      return this;
804
+    },
805
+
806
+    // Fetch the default set of models for this collection, resetting the
807
+    // collection when they arrive. If `update: true` is passed, the response
808
+    // data will be passed through the `update` method instead of `reset`.
809
+    fetch: function(options) {
810
+      options = options ? _.clone(options) : {};
811
+      if (options.parse === void 0) options.parse = true;
812
+      var success = options.success;
813
+      options.success = function(collection, resp, options) {
814
+        var method = options.update ? 'update' : 'reset';
815
+        collection[method](resp, options);
816
+        if (success) success(collection, resp, options);
817
+      };
818
+      return this.sync('read', this, options);
819
+    },
820
+
821
+    // Create a new instance of a model in this collection. Add the model to the
822
+    // collection immediately, unless `wait: true` is passed, in which case we
823
+    // wait for the server to agree.
824
+    create: function(model, options) {
825
+      options = options ? _.clone(options) : {};
826
+      if (!(model = this._prepareModel(model, options))) return false;
827
+      if (!options.wait) this.add(model, options);
828
+      var collection = this;
829
+      var success = options.success;
830
+      options.success = function(model, resp, options) {
831
+        if (options.wait) collection.add(model, options);
832
+        if (success) success(model, resp, options);
833
+      };
834
+      model.save(null, options);
835
+      return model;
836
+    },
837
+
838
+    // **parse** converts a response into a list of models to be added to the
839
+    // collection. The default implementation is just to pass it through.
840
+    parse: function(resp, options) {
841
+      return resp;
842
+    },
843
+
844
+    // Create a new collection with an identical list of models as this one.
845
+    clone: function() {
846
+      return new this.constructor(this.models);
847
+    },
848
+
849
+    // Reset all internal state. Called when the collection is reset.
850
+    _reset: function() {
851
+      this.length = 0;
852
+      this.models.length = 0;
853
+      this._byId  = {};
854
+    },
855
+
856
+    // Prepare a model or hash of attributes to be added to this collection.
857
+    _prepareModel: function(attrs, options) {
858
+      if (attrs instanceof Model) {
859
+        if (!attrs.collection) attrs.collection = this;
860
+        return attrs;
861
+      }
862
+      options || (options = {});
863
+      options.collection = this;
864
+      var model = new this.model(attrs, options);
865
+      if (!model._validate(attrs, options)) return false;
866
+      return model;
867
+    },
868
+
869
+    // Internal method to remove a model's ties to a collection.
870
+    _removeReference: function(model) {
871
+      if (this === model.collection) delete model.collection;
872
+      model.off('all', this._onModelEvent, this);
873
+    },
874
+
875
+    // Internal method called every time a model in the set fires an event.
876
+    // Sets need to update their indexes when models change ids. All other
877
+    // events simply proxy through. "add" and "remove" events that originate
878
+    // in other collections are ignored.
879
+    _onModelEvent: function(event, model, collection, options) {
880
+      if ((event === 'add' || event === 'remove') && collection !== this) return;
881
+      if (event === 'destroy') this.remove(model, options);
882
+      if (model && event === 'change:' + model.idAttribute) {
883
+        delete this._byId[model.previous(model.idAttribute)];
884
+        if (model.id != null) this._byId[model.id] = model;
885
+      }
886
+      this.trigger.apply(this, arguments);
887
+    },
888
+
889
+    sortedIndex: function (model, value, context) {
890
+      value || (value = this.comparator);
891
+      var iterator = _.isFunction(value) ? value : function(model) {
892
+        return model.get(value);
893
+      };
894
+      return _.sortedIndex(this.models, model, iterator, context);
895
+    }
896
+
897
+  });
898
+
899
+  // Underscore methods that we want to implement on the Collection.
900
+  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
901
+    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
902
+    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
903
+    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
904
+    'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
905
+    'isEmpty', 'chain'];
906
+
907
+  // Mix in each Underscore method as a proxy to `Collection#models`.
908
+  _.each(methods, function(method) {
909
+    Collection.prototype[method] = function() {
910
+      var args = slice.call(arguments);
911
+      args.unshift(this.models);
912
+      return _[method].apply(_, args);
913
+    };
914
+  });
915
+
916
+  // Underscore methods that take a property name as an argument.
917
+  var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
918
+
919
+  // Use attributes instead of properties.
920
+  _.each(attributeMethods, function(method) {
921
+    Collection.prototype[method] = function(value, context) {
922
+      var iterator = _.isFunction(value) ? value : function(model) {
923
+        return model.get(value);
924
+      };
925
+      return _[method](this.models, iterator, context);
926
+    };
927
+  });
928
+
929
+  // Backbone.Router
930
+  // ---------------
931
+
932
+  // Routers map faux-URLs to actions, and fire events when routes are
933
+  // matched. Creating a new one sets its `routes` hash, if not set statically.
934
+  var Router = Backbone.Router = function(options) {
935
+    options || (options = {});
936
+    if (options.routes) this.routes = options.routes;
937
+    this._bindRoutes();
938
+    this.initialize.apply(this, arguments);
939
+  };
940
+
941
+  // Cached regular expressions for matching named param parts and splatted
942
+  // parts of route strings.
943
+  var optionalParam = /\((.*?)\)/g;
944
+  var namedParam    = /(\(\?)?:\w+/g;
945
+  var splatParam    = /\*\w+/g;
946
+  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
947
+
948
+  // Set up all inheritable **Backbone.Router** properties and methods.
949
+  _.extend(Router.prototype, Events, {
950
+
951
+    // Initialize is an empty function by default. Override it with your own
952
+    // initialization logic.
953
+    initialize: function(){},
954
+
955
+    // Manually bind a single named route to a callback. For example:
956
+    //
957
+    //     this.route('search/:query/p:num', 'search', function(query, num) {
958
+    //       ...
959
+    //     });
960
+    //
961
+    route: function(route, name, callback) {
962
+      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
963
+      if (!callback) callback = this[name];
964
+      Backbone.history.route(route, _.bind(function(fragment) {
965
+        var args = this._extractParameters(route, fragment);
966
+        callback && callback.apply(this, args);
967
+        this.trigger.apply(this, ['route:' + name].concat(args));
968
+        this.trigger('route', name, args);
969
+        Backbone.history.trigger('route', this, name, args);
970
+      }, this));
971
+      return this;
972
+    },
973
+
974
+    // Simple proxy to `Backbone.history` to save a fragment into the history.
975
+    navigate: function(fragment, options) {
976
+      Backbone.history.navigate(fragment, options);
977
+      return this;
978
+    },
979
+
980
+    // Bind all defined routes to `Backbone.history`. We have to reverse the
981
+    // order of the routes here to support behavior where the most general
982
+    // routes can be defined at the bottom of the route map.
983
+    _bindRoutes: function() {
984
+      if (!this.routes) return;
985
+      var route, routes = _.keys(this.routes);
986
+      while ((route = routes.pop()) != null) {
987
+        this.route(route, this.routes[route]);
988
+      }
989
+    },
990
+
991
+    // Convert a route string into a regular expression, suitable for matching
992
+    // against the current location hash.
993
+    _routeToRegExp: function(route) {
994
+      route = route.replace(escapeRegExp, '\\$&')
995
+                   .replace(optionalParam, '(?:$1)?')
996
+                   .replace(namedParam, function(match, optional){
997
+                     return optional ? match : '([^\/]+)';
998
+                   })
999
+                   .replace(splatParam, '(.*?)');
1000
+      return new RegExp('^' + route + '$');
1001
+    },
1002
+
1003
+    // Given a route, and a URL fragment that it matches, return the array of
1004
+    // extracted parameters.
1005
+    _extractParameters: function(route, fragment) {
1006
+      return route.exec(fragment).slice(1);
1007
+    }
1008
+
1009
+  });
1010
+
1011
+  // Backbone.History
1012
+  // ----------------
1013
+
1014
+  // Handles cross-browser history management, based on URL fragments. If the
1015
+  // browser does not support `onhashchange`, falls back to polling.
1016
+  var History = Backbone.History = function() {
1017
+    this.handlers = [];
1018
+    _.bindAll(this, 'checkUrl');
1019
+
1020
+    // Ensure that `History` can be used outside of the browser.
1021
+    if (typeof window !== 'undefined') {
1022
+      this.location = window.location;
1023
+      this.history = window.history;
1024
+    }
1025
+  };
1026
+
1027
+  // Cached regex for stripping a leading hash/slash and trailing space.
1028
+  var routeStripper = /^[#\/]|\s+$/g;
1029
+
1030
+  // Cached regex for stripping leading and trailing slashes.
1031
+  var rootStripper = /^\/+|\/+$/g;
1032
+
1033
+  // Cached regex for detecting MSIE.
1034
+  var isExplorer = /msie [\w.]+/;
1035
+
1036
+  // Cached regex for removing a trailing slash.
1037
+  var trailingSlash = /\/$/;
1038
+
1039
+  // Has the history handling already been started?
1040
+  History.started = false;
1041
+
1042
+  // Set up all inheritable **Backbone.History** properties and methods.
1043
+  _.extend(History.prototype, Events, {
1044
+
1045
+    // The default interval to poll for hash changes, if necessary, is
1046
+    // twenty times a second.
1047
+    interval: 50,
1048
+
1049
+    // Gets the true hash value. Cannot use location.hash directly due to bug
1050
+    // in Firefox where location.hash will always be decoded.
1051
+    getHash: function(window) {
1052
+      var match = (window || this).location.href.match(/#(.*)$/);
1053
+      return match ? match[1] : '';
1054
+    },
1055
+
1056
+    // Get the cross-browser normalized URL fragment, either from the URL,
1057
+    // the hash, or the override.
1058
+    getFragment: function(fragment, forcePushState) {
1059
+      if (fragment == null) {
1060
+        if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1061
+          fragment = this.location.pathname;
1062
+          var root = this.root.replace(trailingSlash, '');
1063
+          if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
1064
+        } else {
1065
+          fragment = this.getHash();
1066
+        }
1067
+      }
1068
+      return fragment.replace(routeStripper, '');
1069
+    },
1070
+
1071
+    // Start the hash change handling, returning `true` if the current URL matches
1072
+    // an existing route, and `false` otherwise.
1073
+    start: function(options) {
1074
+      if (History.started) throw new Error("Backbone.history has already been started");
1075
+      History.started = true;
1076
+
1077
+      // Figure out the initial configuration. Do we need an iframe?
1078
+      // Is pushState desired ... is it available?
1079
+      this.options          = _.extend({}, {root: '/'}, this.options, options);
1080
+      this.root             = this.options.root;
1081
+      this._wantsHashChange = this.options.hashChange !== false;
1082
+      this._wantsPushState  = !!this.options.pushState;
1083
+      this._hasPushState    = !!(this.options.pushState && this.history && this.history.pushState);
1084
+      var fragment          = this.getFragment();
1085
+      var docMode           = document.documentMode;
1086
+      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1087
+
1088
+      // Normalize root to always include a leading and trailing slash.
1089
+      this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1090
+
1091
+      if (oldIE && this._wantsHashChange) {
1092
+        this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1093
+        this.navigate(fragment);
1094
+      }
1095
+
1096
+      // Depending on whether we're using pushState or hashes, and whether
1097
+      // 'onhashchange' is supported, determine how we check the URL state.
1098
+      if (this._hasPushState) {
1099
+        Backbone.$(window).on('popstate', this.checkUrl);
1100
+      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1101
+        Backbone.$(window).on('hashchange', this.checkUrl);
1102
+      } else if (this._wantsHashChange) {
1103
+        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1104
+      }
1105
+
1106
+      // Determine if we need to change the base url, for a pushState link
1107
+      // opened by a non-pushState browser.
1108
+      this.fragment = fragment;
1109
+      var loc = this.location;
1110
+      var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1111
+
1112
+      // If we've started off with a route from a `pushState`-enabled browser,
1113
+      // but we're currently in a browser that doesn't support it...
1114
+      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1115
+        this.fragment = this.getFragment(null, true);
1116
+        this.location.replace(this.root + this.location.search + '#' + this.fragment);
1117
+        // Return immediately as browser will do redirect to new url
1118
+        return true;
1119
+
1120
+      // Or if we've started out with a hash-based route, but we're currently
1121
+      // in a browser where it could be `pushState`-based instead...
1122
+      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1123
+        this.fragment = this.getHash().replace(routeStripper, '');
1124
+        this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1125
+      }
1126
+
1127
+      if (!this.options.silent) return this.loadUrl();
1128
+    },
1129
+
1130
+    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1131
+    // but possibly useful for unit testing Routers.
1132
+    stop: function() {
1133
+      Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1134
+      clearInterval(this._checkUrlInterval);
1135
+      History.started = false;
1136
+    },
1137
+
1138
+    // Add a route to be tested when the fragment changes. Routes added later
1139
+    // may override previous routes.
1140
+    route: function(route, callback) {
1141
+      this.handlers.unshift({route: route, callback: callback});
1142
+    },
1143
+
1144
+    // Checks the current URL to see if it has changed, and if it has,
1145
+    // calls `loadUrl`, normalizing across the hidden iframe.
1146
+    checkUrl: function(e) {
1147
+      var current = this.getFragment();
1148
+      if (current === this.fragment && this.iframe) {
1149
+        current = this.getFragment(this.getHash(this.iframe));
1150
+      }
1151
+      if (current === this.fragment) return false;
1152
+      if (this.iframe) this.navigate(current);
1153
+      this.loadUrl() || this.loadUrl(this.getHash());
1154
+    },
1155
+
1156
+    // Attempt to load the current URL fragment. If a route succeeds with a
1157
+    // match, returns `true`. If no defined routes matches the fragment,
1158
+    // returns `false`.
1159
+    loadUrl: function(fragmentOverride) {
1160
+      var fragment = this.fragment = this.getFragment(fragmentOverride);
1161
+      var matched = _.any(this.handlers, function(handler) {
1162
+        if (handler.route.test(fragment)) {
1163
+          handler.callback(fragment);
1164
+          return true;
1165
+        }
1166
+      });
1167
+      return matched;
1168
+    },
1169
+
1170
+    // Save a fragment into the hash history, or replace the URL state if the
1171
+    // 'replace' option is passed. You are responsible for properly URL-encoding
1172
+    // the fragment in advance.
1173
+    //
1174
+    // The options object can contain `trigger: true` if you wish to have the
1175
+    // route callback be fired (not usually desirable), or `replace: true`, if
1176
+    // you wish to modify the current URL without adding an entry to the history.
1177
+    navigate: function(fragment, options) {
1178
+      if (!History.started) return false;
1179
+      if (!options || options === true) options = {trigger: options};
1180
+      fragment = this.getFragment(fragment || '');
1181
+      if (this.fragment === fragment) return;
1182
+      this.fragment = fragment;
1183
+      var url = this.root + fragment;
1184
+
1185
+      // If pushState is available, we use it to set the fragment as a real URL.
1186
+      if (this._hasPushState) {
1187
+        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1188
+
1189
+      // If hash changes haven't been explicitly disabled, update the hash
1190
+      // fragment to store history.
1191
+      } else if (this._wantsHashChange) {
1192
+        this._updateHash(this.location, fragment, options.replace);
1193
+        if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1194
+          // Opening and closing the iframe tricks IE7 and earlier to push a
1195
+          // history entry on hash-tag change.  When replace is true, we don't
1196
+          // want this.
1197
+          if(!options.replace) this.iframe.document.open().close();
1198
+          this._updateHash(this.iframe.location, fragment, options.replace);
1199
+        }
1200
+
1201
+      // If you've told us that you explicitly don't want fallback hashchange-
1202
+      // based history, then `navigate` becomes a page refresh.
1203
+      } else {
1204
+        return this.location.assign(url);
1205
+      }
1206
+      if (options.trigger) this.loadUrl(fragment);
1207
+    },
1208
+
1209
+    // Update the hash location, either replacing the current entry, or adding
1210
+    // a new one to the browser history.
1211
+    _updateHash: function(location, fragment, replace) {
1212
+      if (replace) {
1213
+        var href = location.href.replace(/(javascript:|#).*$/, '');
1214
+        location.replace(href + '#' + fragment);
1215
+      } else {
1216
+        // Some browsers require that `hash` contains a leading #.
1217
+        location.hash = '#' + fragment;
1218
+      }
1219
+    }
1220
+
1221
+  });
1222
+
1223
+  // Create the default Backbone.history.
1224
+  Backbone.history = new History;
1225
+
1226
+  // Backbone.View
1227
+  // -------------
1228
+
1229
+  // Creating a Backbone.View creates its initial element outside of the DOM,
1230
+  // if an existing element is not provided...
1231
+  var View = Backbone.View = function(options) {
1232
+    this.cid = _.uniqueId('view');
1233
+    this._configure(options || {});
1234
+    this._ensureElement();
1235
+    this.initialize.apply(this, arguments);
1236
+    this.delegateEvents();
1237
+  };
1238
+
1239
+  // Cached regex to split keys for `delegate`.
1240
+  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1241
+
1242
+  // List of view options to be merged as properties.
1243
+  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1244
+
1245
+  // Set up all inheritable **Backbone.View** properties and methods.
1246
+  _.extend(View.prototype, Events, {
1247
+
1248
+    // The default `tagName` of a View's element is `"div"`.
1249
+    tagName: 'div',
1250
+
1251
+    // jQuery delegate for element lookup, scoped to DOM elements within the
1252
+    // current view. This should be prefered to global lookups where possible.
1253
+    $: function(selector) {
1254
+      return this.$el.find(selector);
1255
+    },
1256
+
1257
+    // Initialize is an empty function by default. Override it with your own
1258
+    // initialization logic.
1259
+    initialize: function(){},
1260
+
1261
+    // **render** is the core function that your view should override, in order
1262
+    // to populate its element (`this.el`), with the appropriate HTML. The
1263
+    // convention is for **render** to always return `this`.
1264
+    render: function() {
1265
+      return this;
1266
+    },
1267
+
1268
+    // Remove this view by taking the element out of the DOM, and removing any
1269
+    // applicable Backbone.Events listeners.
1270
+    remove: function() {
1271
+      this.$el.remove();
1272
+      this.stopListening();
1273
+      return this;
1274
+    },
1275
+
1276
+    // Change the view's element (`this.el` property), including event
1277
+    // re-delegation.
1278
+    setElement: function(element, delegate) {
1279
+      if (this.$el) this.undelegateEvents();
1280
+      this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1281
+      this.el = this.$el[0];
1282
+      if (delegate !== false) this.delegateEvents();
1283
+      return this;
1284
+    },
1285
+
1286
+    // Set callbacks, where `this.events` is a hash of
1287
+    //
1288
+    // *{"event selector": "callback"}*
1289
+    //
1290
+    //     {
1291
+    //       'mousedown .title':  'edit',
1292
+    //       'click .button':     'save'
1293
+    //       'click .open':       function(e) { ... }
1294
+    //     }
1295
+    //
1296
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
1297
+    // Uses event delegation for efficiency.
1298
+    // Omitting the selector binds the event to `this.el`.
1299
+    // This only works for delegate-able events: not `focus`, `blur`, and
1300
+    // not `change`, `submit`, and `reset` in Internet Explorer.
1301
+    delegateEvents: function(events) {
1302
+      if (!(events || (events = _.result(this, 'events')))) return;
1303
+      this.undelegateEvents();
1304
+      for (var key in events) {
1305
+        var method = events[key];
1306
+        if (!_.isFunction(method)) method = this[events[key]];
1307
+        if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1308
+        var match = key.match(delegateEventSplitter);
1309
+        var eventName = match[1], selector = match[2];
1310
+        method = _.bind(method, this);
1311
+        eventName += '.delegateEvents' + this.cid;
1312
+        if (selector === '') {
1313
+          this.$el.on(eventName, method);
1314
+        } else {
1315
+          this.$el.on(eventName, selector, method);
1316
+        }
1317
+      }
1318
+    },
1319
+
1320
+    // Clears all callbacks previously bound to the view with `delegateEvents`.
1321
+    // You usually don't need to use this, but may wish to if you have multiple
1322
+    // Backbone views attached to the same DOM element.
1323
+    undelegateEvents: function() {
1324
+      this.$el.off('.delegateEvents' + this.cid);
1325
+    },
1326
+
1327
+    // Performs the initial configuration of a View with a set of options.
1328
+    // Keys with special meaning *(model, collection, id, className)*, are
1329
+    // attached directly to the view.
1330
+    _configure: function(options) {
1331
+      if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1332
+      _.extend(this, _.pick(options, viewOptions));
1333
+      this.options = options;
1334
+    },
1335
+
1336
+    // Ensure that the View has a DOM element to render into.
1337
+    // If `this.el` is a string, pass it through `$()`, take the first
1338
+    // matching element, and re-assign it to `el`. Otherwise, create
1339
+    // an element from the `id`, `className` and `tagName` properties.
1340
+    _ensureElement: function() {
1341
+      if (!this.el) {
1342
+        var attrs = _.extend({}, _.result(this, 'attributes'));
1343
+        if (this.id) attrs.id = _.result(this, 'id');
1344
+        if (this.className) attrs['class'] = _.result(this, 'className');
1345
+        var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1346
+        this.setElement($el, false);
1347
+      } else {
1348
+        this.setElement(_.result(this, 'el'), false);
1349
+      }
1350
+    }
1351
+
1352
+  });
1353
+
1354
+  // Backbone.sync
1355
+  // -------------
1356
+
1357
+  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1358
+  var methodMap = {
1359
+    'create': 'POST',
1360
+    'update': 'PUT',
1361
+    'patch':  'PATCH',
1362
+    'delete': 'DELETE',
1363
+    'read':   'GET'
1364
+  };
1365
+
1366
+  // Override this function to change the manner in which Backbone persists
1367
+  // models to the server. You will be passed the type of request, and the
1368
+  // model in question. By default, makes a RESTful Ajax request
1369
+  // to the model's `url()`. Some possible customizations could be:
1370
+  //
1371
+  // * Use `setTimeout` to batch rapid-fire updates into a single request.
1372
+  // * Send up the models as XML instead of JSON.
1373
+  // * Persist models via WebSockets instead of Ajax.
1374
+  //
1375
+  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1376
+  // as `POST`, with a `_method` parameter containing the true HTTP method,
1377
+  // as well as all requests with the body as `application/x-www-form-urlencoded`
1378
+  // instead of `application/json` with the model in a param named `model`.
1379
+  // Useful when interfacing with server-side languages like **PHP** that make
1380
+  // it difficult to read the body of `PUT` requests.
1381
+  Backbone.sync = function(method, model, options) {
1382
+    var type = methodMap[method];
1383
+
1384
+    // Default options, unless specified.
1385
+    _.defaults(options || (options = {}), {
1386
+      emulateHTTP: Backbone.emulateHTTP,
1387
+      emulateJSON: Backbone.emulateJSON
1388
+    });
1389
+
1390
+    // Default JSON-request options.
1391
+    var params = {type: type, dataType: 'json'};
1392
+
1393
+    // Ensure that we have a URL.
1394
+    if (!options.url) {
1395
+      params.url = _.result(model, 'url') || urlError();
1396
+    }
1397
+
1398
+    // Ensure that we have the appropriate request data.
1399
+    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1400
+      params.contentType = 'application/json';
1401
+      params.data = JSON.stringify(options.attrs || model.toJSON(options));
1402
+    }
1403
+
1404
+    // For older servers, emulate JSON by encoding the request into an HTML-form.
1405
+    if (options.emulateJSON) {
1406
+      params.contentType = 'application/x-www-form-urlencoded';
1407
+      params.data = params.data ? {model: params.data} : {};
1408
+    }
1409
+
1410
+    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1411
+    // And an `X-HTTP-Method-Override` header.
1412
+    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1413
+      params.type = 'POST';
1414
+      if (options.emulateJSON) params.data._method = type;
1415
+      var beforeSend = options.beforeSend;
1416
+      options.beforeSend = function(xhr) {
1417
+        xhr.setRequestHeader('X-HTTP-Method-Override', type);
1418
+        if (beforeSend) return beforeSend.apply(this, arguments);
1419
+      };
1420
+    }
1421
+
1422
+    // Don't process data on a non-GET request.
1423
+    if (params.type !== 'GET' && !options.emulateJSON) {
1424
+      params.processData = false;
1425
+    }
1426
+
1427
+    var success = options.success;
1428
+    options.success = function(resp) {
1429
+      if (success) success(model, resp, options);
1430
+      model.trigger('sync', model, resp, options);
1431
+    };
1432
+
1433
+    var error = options.error;
1434
+    options.error = function(xhr) {
1435
+      if (error) error(model, xhr, options);
1436
+      model.trigger('error', model, xhr, options);
1437
+    };
1438
+
1439
+    // Make the request, allowing the user to override any Ajax options.
1440
+    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1441
+    model.trigger('request', model, xhr, options);
1442
+    return xhr;
1443
+  };
1444
+
1445
+  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1446
+  Backbone.ajax = function() {
1447
+    return Backbone.$.ajax.apply(Backbone.$, arguments);
1448
+  };
1449
+
1450
+  // Helpers
1451
+  // -------
1452
+
1453
+  // Helper function to correctly set up the prototype chain, for subclasses.
1454
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
1455
+  // class properties to be extended.
1456
+  var extend = function(protoProps, staticProps) {
1457
+    var parent = this;
1458
+    var child;
1459
+
1460
+    // The constructor function for the new subclass is either defined by you
1461
+    // (the "constructor" property in your `extend` definition), or defaulted
1462
+    // by us to simply call the parent's constructor.
1463
+    if (protoProps && _.has(protoProps, 'constructor')) {
1464
+      child = protoProps.constructor;
1465
+    } else {
1466
+      child = function(){ return parent.apply(this, arguments); };
1467
+    }
1468
+
1469
+    // Add static properties to the constructor function, if supplied.
1470
+    _.extend(child, parent, staticProps);
1471
+
1472
+    // Set the prototype chain to inherit from `parent`, without calling
1473
+    // `parent`'s constructor function.
1474
+    var Surrogate = function(){ this.constructor = child; };
1475
+    Surrogate.prototype = parent.prototype;
1476
+    child.prototype = new Surrogate;
1477
+
1478
+    // Add prototype properties (instance properties) to the subclass,
1479
+    // if supplied.
1480
+    if (protoProps) _.extend(child.prototype, protoProps);
1481
+
1482
+    // Set a convenience property in case the parent's prototype is needed
1483
+    // later.
1484
+    child.__super__ = parent.prototype;
1485
+
1486
+    return child;
1487
+  };
1488
+
1489
+  // Set up inheritance for the model, collection, router, view and history.
1490
+  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1491
+
1492
+  // Throw an error when a URL is needed, and none is supplied.
1493
+  var urlError = function() {
1494
+    throw new Error('A "url" property or function must be specified');
1495
+  };
1496
+
1497
+}).call(this);
0 1498
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+(function($) {
1
+  var methodMap = {
2
+    'create': 'POST',
3
+    'update': 'PUT',
4
+    'delete': 'DELETE',
5
+    'read'  : 'GET'
6
+  };
7
+  
8
+  var getUrl = function(object) {
9
+    if (!(object && object.url)) return null;
10
+    return _.isFunction(object.url) ? object.url() : object.url;
11
+  };
12
+  
13
+  var urlError = function() {
14
+    throw new Error("A 'url' property or function must be specified");
15
+  };
16
+
17
+  Backbone.sync = function(method, model, options) {
18
+    var type = methodMap[method];
19
+
20
+    // Default JSON-request options.
21
+    var params = _.extend({
22
+      type:         type,
23
+      dataType:     'json',
24
+      beforeSend: function( xhr ) {
25
+        if (!options.noCSRF) {
26
+          var token = $('meta[name="csrf-token"]').attr('content');
27
+          if (token) xhr.setRequestHeader('X-CSRF-Token', token);  
28
+        }
29
+        model.trigger('sync:start');
30
+      }
31
+    }, options);
32
+
33
+    if (!params.url) {
34
+      params.url = getUrl(model) || urlError();
35
+    }
36
+
37
+    // Ensure that we have the appropriate request data.
38
+    if (!params.data && model && (method == 'create' || method == 'update')) {
39
+      params.contentType = 'application/json';
40
+
41
+      var data = {}
42
+
43
+      if(model.paramRoot) {
44
+        data[model.paramRoot] = model.toJSON();
45
+      } else {
46
+        data = model.toJSON();
47
+      }
48
+
49
+      params.data = JSON.stringify(data)
50
+    }
51
+
52
+    // Don't process data on a non-GET request.
53
+    if (params.type !== 'GET') {
54
+      params.processData = false;
55
+    }
56
+
57
+    // Trigger the sync end event
58
+    var complete = options.complete;
59
+    params.complete = function(jqXHR, textStatus) {
60
+      model.trigger('sync:end');
61
+      if (complete) complete(jqXHR, textStatus);
62
+    };
63
+    
64
+    var success = options.success;
65
+    params.success = function(resp) {
66
+      if (success) success(model, resp, options);
67
+      model.trigger('sync', model, resp, options);
68
+    };
69
+
70
+    var error = options.error;
71
+    params.error = function(xhr) {
72
+      if (error) error(model, xhr, options);
73
+      model.trigger('error', model, xhr, options);
74
+    };
75
+    
76
+    // Make the request.
77
+    return $.ajax(params);
78
+  }
79
+  
80
+})(jQuery);
0 81
new file mode 100644
... ...
@@ -0,0 +1,1226 @@
0
+//     Underscore.js 1.4.4
1
+//     http://underscorejs.org
2
+//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
3
+//     Underscore may be freely distributed under the MIT license.
4
+
5
+(function() {
6
+
7
+  // Baseline setup
8
+  // --------------
9
+
10
+  // Establish the root object, `window` in the browser, or `global` on the server.
11
+  var root = this;
12
+
13
+  // Save the previous value of the `_` variable.
14
+  var previousUnderscore = root._;
15
+
16
+  // Establish the object that gets returned to break out of a loop iteration.
17
+  var breaker = {};
18
+
19
+  // Save bytes in the minified (but not gzipped) version:
20
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
21
+
22
+  // Create quick reference variables for speed access to core prototypes.
23
+  var push             = ArrayProto.push,
24
+      slice            = ArrayProto.slice,
25
+      concat           = ArrayProto.concat,
26
+      toString         = ObjProto.toString,
27
+      hasOwnProperty   = ObjProto.hasOwnProperty;
28
+
29
+  // All **ECMAScript 5** native function implementations that we hope to use
30
+  // are declared here.
31
+  var
32
+    nativeForEach      = ArrayProto.forEach,
33
+    nativeMap          = ArrayProto.map,
34
+    nativeReduce       = ArrayProto.reduce,
35
+    nativeReduceRight  = ArrayProto.reduceRight,
36
+    nativeFilter       = ArrayProto.filter,
37
+    nativeEvery        = ArrayProto.every,
38
+    nativeSome         = ArrayProto.some,
39
+    nativeIndexOf      = ArrayProto.indexOf,
40
+    nativeLastIndexOf  = ArrayProto.lastIndexOf,
41
+    nativeIsArray      = Array.isArray,
42
+    nativeKeys         = Object.keys,
43
+    nativeBind         = FuncProto.bind;
44
+
45
+  // Create a safe reference to the Underscore object for use below.
46
+  var _ = function(obj) {
47
+    if (obj instanceof _) return obj;
48
+    if (!(this instanceof _)) return new _(obj);
49
+    this._wrapped = obj;
50
+  };
51
+
52
+  // Export the Underscore object for **Node.js**, with
53
+  // backwards-compatibility for the old `require()` API. If we're in
54
+  // the browser, add `_` as a global object via a string identifier,
55
+  // for Closure Compiler "advanced" mode.
56
+  if (typeof exports !== 'undefined') {
57
+    if (typeof module !== 'undefined' && module.exports) {
58
+      exports = module.exports = _;
59
+    }
60
+    exports._ = _;
61
+  } else {
62
+    root._ = _;
63
+  }
64
+
65
+  // Current version.
66
+  _.VERSION = '1.4.4';
67
+
68
+  // Collection Functions
69
+  // --------------------
70
+
71
+  // The cornerstone, an `each` implementation, aka `forEach`.
72
+  // Handles objects with the built-in `forEach`, arrays, and raw objects.
73
+  // Delegates to **ECMAScript 5**'s native `forEach` if available.
74
+  var each = _.each = _.forEach = function(obj, iterator, context) {
75
+    if (obj == null) return;
76
+    if (nativeForEach && obj.forEach === nativeForEach) {
77
+      obj.forEach(iterator, context);
78
+    } else if (obj.length === +obj.length) {
79
+      for (var i = 0, l = obj.length; i < l; i++) {
80
+        if (iterator.call(context, obj[i], i, obj) === breaker) return;
81
+      }
82
+    } else {
83
+      for (var key in obj) {
84
+        if (_.has(obj, key)) {
85
+          if (iterator.call(context, obj[key], key, obj) === breaker) return;
86
+        }
87
+      }
88
+    }
89
+  };
90
+
91
+  // Return the results of applying the iterator to each element.
92
+  // Delegates to **ECMAScript 5**'s native `map` if available.
93
+  _.map = _.collect = function(obj, iterator, context) {
94
+    var results = [];
95
+    if (obj == null) return results;
96
+    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
97
+    each(obj, function(value, index, list) {
98
+      results[results.length] = iterator.call(context, value, index, list);
99
+    });
100
+    return results;
101
+  };
102
+
103
+  var reduceError = 'Reduce of empty array with no initial value';
104
+
105
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
106
+  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
107
+  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
108
+    var initial = arguments.length > 2;
109
+    if (obj == null) obj = [];
110
+    if (nativeReduce && obj.reduce === nativeReduce) {
111
+      if (context) iterator = _.bind(iterator, context);
112
+      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
113
+    }
114
+    each(obj, function(value, index, list) {
115
+      if (!initial) {
116
+        memo = value;
117
+        initial = true;
118
+      } else {
119
+        memo = iterator.call(context, memo, value, index, list);
120
+      }
121
+    });
122
+    if (!initial) throw new TypeError(reduceError);
123
+    return memo;
124
+  };
125
+
126
+  // The right-associative version of reduce, also known as `foldr`.
127
+  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
128
+  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
129
+    var initial = arguments.length > 2;
130
+    if (obj == null) obj = [];
131
+    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
132
+      if (context) iterator = _.bind(iterator, context);
133
+      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
134
+    }
135
+    var length = obj.length;
136
+    if (length !== +length) {
137
+      var keys = _.keys(obj);
138
+      length = keys.length;
139
+    }
140
+    each(obj, function(value, index, list) {
141
+      index = keys ? keys[--length] : --length;
142
+      if (!initial) {
143
+        memo = obj[index];
144
+        initial = true;
145
+      } else {
146
+        memo = iterator.call(context, memo, obj[index], index, list);
147
+      }
148
+    });
149
+    if (!initial) throw new TypeError(reduceError);
150
+    return memo;
151
+  };
152
+
153
+  // Return the first value which passes a truth test. Aliased as `detect`.
154
+  _.find = _.detect = function(obj, iterator, context) {
155
+    var result;
156
+    any(obj, function(value, index, list) {
157
+      if (iterator.call(context, value, index, list)) {
158
+        result = value;
159
+        return true;
160
+      }
161
+    });
162
+    return result;
163
+  };
164
+
165
+  // Return all the elements that pass a truth test.
166
+  // Delegates to **ECMAScript 5**'s native `filter` if available.
167
+  // Aliased as `select`.
168
+  _.filter = _.select = function(obj, iterator, context) {
169
+    var results = [];
170
+    if (obj == null) return results;
171
+    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
172
+    each(obj, function(value, index, list) {
173
+      if (iterator.call(context, value, index, list)) results[results.length] = value;
174
+    });
175
+    return results;
176
+  };
177
+
178
+  // Return all the elements for which a truth test fails.
179
+  _.reject = function(obj, iterator, context) {
180
+    return _.filter(obj, function(value, index, list) {
181
+      return !iterator.call(context, value, index, list);
182
+    }, context);
183
+  };
184
+
185
+  // Determine whether all of the elements match a truth test.
186
+  // Delegates to **ECMAScript 5**'s native `every` if available.
187
+  // Aliased as `all`.
188
+  _.every = _.all = function(obj, iterator, context) {
189
+    iterator || (iterator = _.identity);
190
+    var result = true;
191
+    if (obj == null) return result;
192
+    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
193
+    each(obj, function(value, index, list) {
194
+      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
195
+    });
196
+    return !!result;
197
+  };
198
+
199
+  // Determine if at least one element in the object matches a truth test.
200
+  // Delegates to **ECMAScript 5**'s native `some` if available.
201
+  // Aliased as `any`.
202
+  var any = _.some = _.any = function(obj, iterator, context) {
203
+    iterator || (iterator = _.identity);
204
+    var result = false;
205
+    if (obj == null) return result;
206
+    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
207
+    each(obj, function(value, index, list) {
208
+      if (result || (result = iterator.call(context, value, index, list))) return breaker;
209
+    });
210
+    return !!result;
211
+  };
212
+
213
+  // Determine if the array or object contains a given value (using `===`).
214
+  // Aliased as `include`.
215
+  _.contains = _.include = function(obj, target) {
216
+    if (obj == null) return false;
217
+    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
218
+    return any(obj, function(value) {
219
+      return value === target;
220
+    });
221
+  };
222
+
223
+  // Invoke a method (with arguments) on every item in a collection.
224
+  _.invoke = function(obj, method) {
225
+    var args = slice.call(arguments, 2);
226
+    var isFunc = _.isFunction(method);
227
+    return _.map(obj, function(value) {
228
+      return (isFunc ? method : value[method]).apply(value, args);
229
+    });
230
+  };
231
+
232
+  // Convenience version of a common use case of `map`: fetching a property.
233
+  _.pluck = function(obj, key) {
234
+    return _.map(obj, function(value){ return value[key]; });
235
+  };
236
+
237
+  // Convenience version of a common use case of `filter`: selecting only objects
238
+  // containing specific `key:value` pairs.
239
+  _.where = function(obj, attrs, first) {
240
+    if (_.isEmpty(attrs)) return first ? null : [];
241
+    return _[first ? 'find' : 'filter'](obj, function(value) {
242
+      for (var key in attrs) {
243
+        if (attrs[key] !== value[key]) return false;
244
+      }
245
+      return true;
246
+    });
247
+  };
248
+
249
+  // Convenience version of a common use case of `find`: getting the first object
250
+  // containing specific `key:value` pairs.
251
+  _.findWhere = function(obj, attrs) {
252
+    return _.where(obj, attrs, true);
253
+  };
254
+
255
+  // Return the maximum element or (element-based computation).
256
+  // Can't optimize arrays of integers longer than 65,535 elements.
257
+  // See: https://bugs.webkit.org/show_bug.cgi?id=80797
258
+  _.max = function(obj, iterator, context) {
259
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
260
+      return Math.max.apply(Math, obj);
261
+    }
262
+    if (!iterator && _.isEmpty(obj)) return -Infinity;
263
+    var result = {computed : -Infinity, value: -Infinity};
264
+    each(obj, function(value, index, list) {
265
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
266
+      computed >= result.computed && (result = {value : value, computed : computed});
267
+    });
268
+    return result.value;
269
+  };
270
+
271
+  // Return the minimum element (or element-based computation).
272
+  _.min = function(obj, iterator, context) {
273
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
274
+      return Math.min.apply(Math, obj);
275
+    }
276
+    if (!iterator && _.isEmpty(obj)) return Infinity;
277
+    var result = {computed : Infinity, value: Infinity};
278
+    each(obj, function(value, index, list) {
279
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
280
+      computed < result.computed && (result = {value : value, computed : computed});
281
+    });
282
+    return result.value;
283
+  };
284
+
285
+  // Shuffle an array.
286
+  _.shuffle = function(obj) {
287
+    var rand;
288
+    var index = 0;
289
+    var shuffled = [];
290
+    each(obj, function(value) {
291
+      rand = _.random(index++);
292
+      shuffled[index - 1] = shuffled[rand];
293
+      shuffled[rand] = value;
294
+    });
295
+    return shuffled;
296
+  };
297
+
298
+  // An internal function to generate lookup iterators.
299
+  var lookupIterator = function(value) {
300
+    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
301
+  };
302
+
303
+  // Sort the object's values by a criterion produced by an iterator.
304
+  _.sortBy = function(obj, value, context) {
305
+    var iterator = lookupIterator(value);
306
+    return _.pluck(_.map(obj, function(value, index, list) {
307
+      return {
308
+        value : value,
309
+        index : index,
310
+        criteria : iterator.call(context, value, index, list)
311
+      };
312
+    }).sort(function(left, right) {
313
+      var a = left.criteria;
314
+      var b = right.criteria;
315
+      if (a !== b) {
316
+        if (a > b || a === void 0) return 1;
317
+        if (a < b || b === void 0) return -1;
318
+      }
319
+      return left.index < right.index ? -1 : 1;
320
+    }), 'value');
321
+  };
322
+
323
+  // An internal function used for aggregate "group by" operations.
324
+  var group = function(obj, value, context, behavior) {
325
+    var result = {};
326
+    var iterator = lookupIterator(value || _.identity);
327
+    each(obj, function(value, index) {
328
+      var key = iterator.call(context, value, index, obj);
329
+      behavior(result, key, value);
330
+    });
331
+    return result;
332
+  };
333
+
334
+  // Groups the object's values by a criterion. Pass either a string attribute
335
+  // to group by, or a function that returns the criterion.
336
+  _.groupBy = function(obj, value, context) {
337
+    return group(obj, value, context, function(result, key, value) {
338
+      (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
339
+    });
340
+  };
341
+
342
+  // Counts instances of an object that group by a certain criterion. Pass
343
+  // either a string attribute to count by, or a function that returns the
344
+  // criterion.
345
+  _.countBy = function(obj, value, context) {
346
+    return group(obj, value, context, function(result, key) {
347
+      if (!_.has(result, key)) result[key] = 0;
348
+      result[key]++;
349
+    });
350
+  };
351
+
352
+  // Use a comparator function to figure out the smallest index at which
353
+  // an object should be inserted so as to maintain order. Uses binary search.
354
+  _.sortedIndex = function(array, obj, iterator, context) {
355
+    iterator = iterator == null ? _.identity : lookupIterator(iterator);
356
+    var value = iterator.call(context, obj);
357
+    var low = 0, high = array.length;
358
+    while (low < high) {
359
+      var mid = (low + high) >>> 1;
360
+      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
361
+    }
362
+    return low;
363
+  };
364
+
365
+  // Safely convert anything iterable into a real, live array.
366
+  _.toArray = function(obj) {
367
+    if (!obj) return [];
368
+    if (_.isArray(obj)) return slice.call(obj);
369
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
370
+    return _.values(obj);
371
+  };
372
+
373
+  // Return the number of elements in an object.
374
+  _.size = function(obj) {
375
+    if (obj == null) return 0;
376
+    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
377
+  };
378
+
379
+  // Array Functions
380
+  // ---------------
381
+
382
+  // Get the first element of an array. Passing **n** will return the first N
383
+  // values in the array. Aliased as `head` and `take`. The **guard** check
384
+  // allows it to work with `_.map`.
385
+  _.first = _.head = _.take = function(array, n, guard) {
386
+    if (array == null) return void 0;
387
+    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
388
+  };
389
+
390
+  // Returns everything but the last entry of the array. Especially useful on
391
+  // the arguments object. Passing **n** will return all the values in
392
+  // the array, excluding the last N. The **guard** check allows it to work with
393
+  // `_.map`.
394
+  _.initial = function(array, n, guard) {
395
+    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
396
+  };
397
+
398
+  // Get the last element of an array. Passing **n** will return the last N
399
+  // values in the array. The **guard** check allows it to work with `_.map`.
400
+  _.last = function(array, n, guard) {
401
+    if (array == null) return void 0;
402
+    if ((n != null) && !guard) {
403
+      return slice.call(array, Math.max(array.length - n, 0));
404
+    } else {
405
+      return array[array.length - 1];
406
+    }
407
+  };
408
+
409
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
410
+  // Especially useful on the arguments object. Passing an **n** will return
411
+  // the rest N values in the array. The **guard**
412
+  // check allows it to work with `_.map`.
413
+  _.rest = _.tail = _.drop = function(array, n, guard) {
414
+    return slice.call(array, (n == null) || guard ? 1 : n);
415
+  };
416
+
417
+  // Trim out all falsy values from an array.
418
+  _.compact = function(array) {
419
+    return _.filter(array, _.identity);
420
+  };
421
+
422
+  // Internal implementation of a recursive `flatten` function.
423
+  var flatten = function(input, shallow, output) {
424
+    each(input, function(value) {
425
+      if (_.isArray(value)) {
426
+        shallow ? push.apply(output, value) : flatten(value, shallow, output);
427
+      } else {
428
+        output.push(value);
429
+      }
430
+    });
431
+    return output;
432
+  };
433
+
434
+  // Return a completely flattened version of an array.
435
+  _.flatten = function(array, shallow) {
436
+    return flatten(array, shallow, []);
437
+  };
438
+
439
+  // Return a version of the array that does not contain the specified value(s).
440
+  _.without = function(array) {
441
+    return _.difference(array, slice.call(arguments, 1));
442
+  };
443
+
444
+  // Produce a duplicate-free version of the array. If the array has already
445
+  // been sorted, you have the option of using a faster algorithm.
446
+  // Aliased as `unique`.
447
+  _.uniq = _.unique = function(array, isSorted, iterator, context) {
448
+    if (_.isFunction(isSorted)) {
449
+      context = iterator;
450
+      iterator = isSorted;
451
+      isSorted = false;
452
+    }
453
+    var initial = iterator ? _.map(array, iterator, context) : array;
454
+    var results = [];
455
+    var seen = [];
456
+    each(initial, function(value, index) {
457
+      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
458
+        seen.push(value);
459
+        results.push(array[index]);
460
+      }
461
+    });
462
+    return results;
463
+  };
464
+
465
+  // Produce an array that contains the union: each distinct element from all of
466
+  // the passed-in arrays.
467
+  _.union = function() {
468
+    return _.uniq(concat.apply(ArrayProto, arguments));
469
+  };
470
+
471
+  // Produce an array that contains every item shared between all the
472
+  // passed-in arrays.
473
+  _.intersection = function(array) {
474
+    var rest = slice.call(arguments, 1);
475
+    return _.filter(_.uniq(array), function(item) {
476
+      return _.every(rest, function(other) {
477
+        return _.indexOf(other, item) >= 0;
478
+      });
479
+    });
480
+  };
481
+
482
+  // Take the difference between one array and a number of other arrays.
483
+  // Only the elements present in just the first array will remain.
484
+  _.difference = function(array) {
485
+    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
486
+    return _.filter(array, function(value){ return !_.contains(rest, value); });
487
+  };
488
+
489
+  // Zip together multiple lists into a single array -- elements that share
490
+  // an index go together.
491
+  _.zip = function() {
492
+    var args = slice.call(arguments);
493
+    var length = _.max(_.pluck(args, 'length'));
494
+    var results = new Array(length);
495
+    for (var i = 0; i < length; i++) {
496
+      results[i] = _.pluck(args, "" + i);
497
+    }
498
+    return results;
499
+  };
500
+
501
+  // Converts lists into objects. Pass either a single array of `[key, value]`
502
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
503
+  // the corresponding values.
504
+  _.object = function(list, values) {
505
+    if (list == null) return {};
506
+    var result = {};
507
+    for (var i = 0, l = list.length; i < l; i++) {
508
+      if (values) {
509
+        result[list[i]] = values[i];
510
+      } else {
511
+        result[list[i][0]] = list[i][1];
512
+      }
513
+    }
514
+    return result;
515
+  };
516
+
517
+  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
518
+  // we need this function. Return the position of the first occurrence of an
519
+  // item in an array, or -1 if the item is not included in the array.
520
+  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
521
+  // If the array is large and already in sort order, pass `true`
522
+  // for **isSorted** to use binary search.
523
+  _.indexOf = function(array, item, isSorted) {
524
+    if (array == null) return -1;
525
+    var i = 0, l = array.length;
526
+    if (isSorted) {
527
+      if (typeof isSorted == 'number') {
528
+        i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
529
+      } else {
530
+        i = _.sortedIndex(array, item);
531
+        return array[i] === item ? i : -1;
532
+      }
533
+    }
534
+    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
535
+    for (; i < l; i++) if (array[i] === item) return i;
536
+    return -1;
537
+  };
538
+
539
+  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
540
+  _.lastIndexOf = function(array, item, from) {
541
+    if (array == null) return -1;
542
+    var hasIndex = from != null;
543
+    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
544
+      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
545
+    }
546
+    var i = (hasIndex ? from : array.length);
547
+    while (i--) if (array[i] === item) return i;
548
+    return -1;
549
+  };
550
+
551
+  // Generate an integer Array containing an arithmetic progression. A port of
552
+  // the native Python `range()` function. See
553
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
554
+  _.range = function(start, stop, step) {
555
+    if (arguments.length <= 1) {
556
+      stop = start || 0;
557
+      start = 0;
558
+    }
559
+    step = arguments[2] || 1;
560
+
561
+    var len = Math.max(Math.ceil((stop - start) / step), 0);
562
+    var idx = 0;
563
+    var range = new Array(len);
564
+
565
+    while(idx < len) {
566
+      range[idx++] = start;
567
+      start += step;
568
+    }
569
+
570
+    return range;
571
+  };
572
+
573
+  // Function (ahem) Functions
574
+  // ------------------
575
+
576
+  // Create a function bound to a given object (assigning `this`, and arguments,
577
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
578
+  // available.
579
+  _.bind = function(func, context) {
580
+    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
581
+    var args = slice.call(arguments, 2);
582
+    return function() {
583
+      return func.apply(context, args.concat(slice.call(arguments)));
584
+    };
585
+  };
586
+
587
+  // Partially apply a function by creating a version that has had some of its
588
+  // arguments pre-filled, without changing its dynamic `this` context.
589
+  _.partial = function(func) {
590
+    var args = slice.call(arguments, 1);
591
+    return function() {
592
+      return func.apply(this, args.concat(slice.call(arguments)));
593
+    };
594
+  };
595
+
596
+  // Bind all of an object's methods to that object. Useful for ensuring that
597
+  // all callbacks defined on an object belong to it.
598
+  _.bindAll = function(obj) {
599
+    var funcs = slice.call(arguments, 1);
600
+    if (funcs.length === 0) funcs = _.functions(obj);
601
+    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
602
+    return obj;
603
+  };
604
+
605
+  // Memoize an expensive function by storing its results.
606
+  _.memoize = function(func, hasher) {
607
+    var memo = {};
608
+    hasher || (hasher = _.identity);
609
+    return function() {
610
+      var key = hasher.apply(this, arguments);
611
+      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
612
+    };
613
+  };
614
+
615
+  // Delays a function for the given number of milliseconds, and then calls
616
+  // it with the arguments supplied.
617
+  _.delay = function(func, wait) {
618
+    var args = slice.call(arguments, 2);
619
+    return setTimeout(function(){ return func.apply(null, args); }, wait);
620
+  };
621
+
622
+  // Defers a function, scheduling it to run after the current call stack has
623
+  // cleared.
624
+  _.defer = function(func) {
625
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
626
+  };
627
+
628
+  // Returns a function, that, when invoked, will only be triggered at most once
629
+  // during a given window of time.
630
+  _.throttle = function(func, wait) {
631
+    var context, args, timeout, result;
632
+    var previous = 0;
633
+    var later = function() {
634
+      previous = new Date;
635
+      timeout = null;
636
+      result = func.apply(context, args);
637
+    };
638
+    return function() {
639
+      var now = new Date;
640
+      var remaining = wait - (now - previous);
641
+      context = this;
642
+      args = arguments;
643
+      if (remaining <= 0) {
644
+        clearTimeout(timeout);
645
+        timeout = null;
646
+        previous = now;
647
+        result = func.apply(context, args);
648
+      } else if (!timeout) {
649
+        timeout = setTimeout(later, remaining);
650
+      }
651
+      return result;
652
+    };
653
+  };
654
+
655
+  // Returns a function, that, as long as it continues to be invoked, will not
656
+  // be triggered. The function will be called after it stops being called for
657
+  // N milliseconds. If `immediate` is passed, trigger the function on the
658
+  // leading edge, instead of the trailing.
659
+  _.debounce = function(func, wait, immediate) {
660
+    var timeout, result;
661
+    return function() {
662
+      var context = this, args = arguments;
663
+      var later = function() {
664
+        timeout = null;
665
+        if (!immediate) result = func.apply(context, args);
666
+      };
667
+      var callNow = immediate && !timeout;
668
+      clearTimeout(timeout);
669
+      timeout = setTimeout(later, wait);
670
+      if (callNow) result = func.apply(context, args);
671
+      return result;
672
+    };
673
+  };
674
+
675
+  // Returns a function that will be executed at most one time, no matter how
676
+  // often you call it. Useful for lazy initialization.
677
+  _.once = function(func) {
678
+    var ran = false, memo;
679
+    return function() {
680
+      if (ran) return memo;
681
+      ran = true;
682
+      memo = func.apply(this, arguments);
683
+      func = null;
684
+      return memo;
685
+    };
686
+  };
687
+
688
+  // Returns the first function passed as an argument to the second,
689
+  // allowing you to adjust arguments, run code before and after, and
690
+  // conditionally execute the original function.
691
+  _.wrap = function(func, wrapper) {
692
+    return function() {
693
+      var args = [func];
694
+      push.apply(args, arguments);
695
+      return wrapper.apply(this, args);
696
+    };
697
+  };
698
+
699
+  // Returns a function that is the composition of a list of functions, each
700
+  // consuming the return value of the function that follows.
701
+  _.compose = function() {
702
+    var funcs = arguments;
703
+    return function() {
704
+      var args = arguments;
705
+      for (var i = funcs.length - 1; i >= 0; i--) {
706
+        args = [funcs[i].apply(this, args)];
707
+      }
708
+      return args[0];
709
+    };
710
+  };
711
+
712
+  // Returns a function that will only be executed after being called N times.
713
+  _.after = function(times, func) {
714
+    if (times <= 0) return func();
715
+    return function() {
716
+      if (--times < 1) {
717
+        return func.apply(this, arguments);
718
+      }
719
+    };
720
+  };
721
+
722
+  // Object Functions
723
+  // ----------------
724
+
725
+  // Retrieve the names of an object's properties.
726
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
727
+  _.keys = nativeKeys || function(obj) {
728
+    if (obj !== Object(obj)) throw new TypeError('Invalid object');
729
+    var keys = [];
730
+    for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
731
+    return keys;
732
+  };
733
+
734
+  // Retrieve the values of an object's properties.
735
+  _.values = function(obj) {
736
+    var values = [];
737
+    for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
738
+    return values;
739
+  };
740
+
741
+  // Convert an object into a list of `[key, value]` pairs.
742
+  _.pairs = function(obj) {
743
+    var pairs = [];
744
+    for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
745
+    return pairs;
746
+  };
747
+
748
+  // Invert the keys and values of an object. The values must be serializable.
749
+  _.invert = function(obj) {
750
+    var result = {};
751
+    for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
752
+    return result;
753
+  };
754
+
755
+  // Return a sorted list of the function names available on the object.
756
+  // Aliased as `methods`
757
+  _.functions = _.methods = function(obj) {
758
+    var names = [];
759
+    for (var key in obj) {
760
+      if (_.isFunction(obj[key])) names.push(key);
761
+    }
762
+    return names.sort();
763
+  };
764
+
765
+  // Extend a given object with all the properties in passed-in object(s).
766
+  _.extend = function(obj) {
767
+    each(slice.call(arguments, 1), function(source) {
768
+      if (source) {
769
+        for (var prop in source) {
770
+          obj[prop] = source[prop];
771
+        }
772
+      }
773
+    });
774
+    return obj;
775
+  };
776
+
777
+  // Return a copy of the object only containing the whitelisted properties.
778
+  _.pick = function(obj) {
779
+    var copy = {};
780
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
781
+    each(keys, function(key) {
782
+      if (key in obj) copy[key] = obj[key];
783
+    });
784
+    return copy;
785
+  };
786
+
787
+   // Return a copy of the object without the blacklisted properties.
788
+  _.omit = function(obj) {
789
+    var copy = {};
790
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
791
+    for (var key in obj) {
792
+      if (!_.contains(keys, key)) copy[key] = obj[key];
793
+    }
794
+    return copy;
795
+  };
796
+
797
+  // Fill in a given object with default properties.
798
+  _.defaults = function(obj) {
799
+    each(slice.call(arguments, 1), function(source) {
800
+      if (source) {
801
+        for (var prop in source) {
802
+          if (obj[prop] == null) obj[prop] = source[prop];
803
+        }
804
+      }
805
+    });
806
+    return obj;
807
+  };
808
+
809
+  // Create a (shallow-cloned) duplicate of an object.
810
+  _.clone = function(obj) {
811
+    if (!_.isObject(obj)) return obj;
812
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
813
+  };
814
+
815
+  // Invokes interceptor with the obj, and then returns obj.
816
+  // The primary purpose of this method is to "tap into" a method chain, in
817
+  // order to perform operations on intermediate results within the chain.
818
+  _.tap = function(obj, interceptor) {
819
+    interceptor(obj);
820
+    return obj;
821
+  };
822
+
823
+  // Internal recursive comparison function for `isEqual`.
824
+  var eq = function(a, b, aStack, bStack) {
825
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
826
+    // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
827
+    if (a === b) return a !== 0 || 1 / a == 1 / b;
828
+    // A strict comparison is necessary because `null == undefined`.
829
+    if (a == null || b == null) return a === b;
830
+    // Unwrap any wrapped objects.
831
+    if (a instanceof _) a = a._wrapped;
832
+    if (b instanceof _) b = b._wrapped;
833
+    // Compare `[[Class]]` names.
834
+    var className = toString.call(a);
835
+    if (className != toString.call(b)) return false;
836
+    switch (className) {
837
+      // Strings, numbers, dates, and booleans are compared by value.
838
+      case '[object String]':
839
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
840
+        // equivalent to `new String("5")`.
841
+        return a == String(b);
842
+      case '[object Number]':
843
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
844
+        // other numeric values.
845
+        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
846
+      case '[object Date]':
847
+      case '[object Boolean]':
848
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
849
+        // millisecond representations. Note that invalid dates with millisecond representations
850
+        // of `NaN` are not equivalent.
851
+        return +a == +b;
852
+      // RegExps are compared by their source patterns and flags.
853
+      case '[object RegExp]':
854
+        return a.source == b.source &&
855
+               a.global == b.global &&
856
+               a.multiline == b.multiline &&
857
+               a.ignoreCase == b.ignoreCase;
858
+    }
859
+    if (typeof a != 'object' || typeof b != 'object') return false;
860
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
861
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
862
+    var length = aStack.length;
863
+    while (length--) {
864
+      // Linear search. Performance is inversely proportional to the number of
865
+      // unique nested structures.
866
+      if (aStack[length] == a) return bStack[length] == b;
867
+    }
868
+    // Add the first object to the stack of traversed objects.
869
+    aStack.push(a);
870
+    bStack.push(b);
871
+    var size = 0, result = true;
872
+    // Recursively compare objects and arrays.
873
+    if (className == '[object Array]') {
874
+      // Compare array lengths to determine if a deep comparison is necessary.
875
+      size = a.length;
876
+      result = size == b.length;
877
+      if (result) {
878
+        // Deep compare the contents, ignoring non-numeric properties.
879
+        while (size--) {
880
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
881
+        }
882
+      }
883
+    } else {
884
+      // Objects with different constructors are not equivalent, but `Object`s
885
+      // from different frames are.
886
+      var aCtor = a.constructor, bCtor = b.constructor;
887
+      if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
888
+                               _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
889
+        return false;
890
+      }
891
+      // Deep compare objects.
892
+      for (var key in a) {
893
+        if (_.has(a, key)) {
894
+          // Count the expected number of properties.
895
+          size++;
896
+          // Deep compare each member.
897
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
898
+        }
899
+      }
900
+      // Ensure that both objects contain the same number of properties.
901
+      if (result) {
902
+        for (key in b) {
903
+          if (_.has(b, key) && !(size--)) break;
904
+        }
905
+        result = !size;
906
+      }
907
+    }
908
+    // Remove the first object from the stack of traversed objects.
909
+    aStack.pop();
910
+    bStack.pop();
911
+    return result;
912
+  };
913
+
914
+  // Perform a deep comparison to check if two objects are equal.
915
+  _.isEqual = function(a, b) {
916
+    return eq(a, b, [], []);
917
+  };
918
+
919
+  // Is a given array, string, or object empty?
920
+  // An "empty" object has no enumerable own-properties.
921
+  _.isEmpty = function(obj) {
922
+    if (obj == null) return true;
923
+    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
924
+    for (var key in obj) if (_.has(obj, key)) return false;
925
+    return true;
926
+  };
927
+
928
+  // Is a given value a DOM element?
929
+  _.isElement = function(obj) {
930
+    return !!(obj && obj.nodeType === 1);
931
+  };
932
+
933
+  // Is a given value an array?
934
+  // Delegates to ECMA5's native Array.isArray
935
+  _.isArray = nativeIsArray || function(obj) {
936
+    return toString.call(obj) == '[object Array]';
937
+  };
938
+
939
+  // Is a given variable an object?
940
+  _.isObject = function(obj) {
941
+    return obj === Object(obj);
942
+  };
943
+
944
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
945
+  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
946
+    _['is' + name] = function(obj) {
947
+      return toString.call(obj) == '[object ' + name + ']';
948
+    };
949
+  });
950
+
951
+  // Define a fallback version of the method in browsers (ahem, IE), where
952
+  // there isn't any inspectable "Arguments" type.
953
+  if (!_.isArguments(arguments)) {
954
+    _.isArguments = function(obj) {
955
+      return !!(obj && _.has(obj, 'callee'));
956
+    };
957
+  }
958
+
959
+  // Optimize `isFunction` if appropriate.
960
+  if (typeof (/./) !== 'function') {
961
+    _.isFunction = function(obj) {
962
+      return typeof obj === 'function';
963
+    };
964
+  }
965
+
966
+  // Is a given object a finite number?
967
+  _.isFinite = function(obj) {
968
+    return isFinite(obj) && !isNaN(parseFloat(obj));
969
+  };
970
+
971
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
972
+  _.isNaN = function(obj) {
973
+    return _.isNumber(obj) && obj != +obj;
974
+  };
975
+
976
+  // Is a given value a boolean?
977
+  _.isBoolean = function(obj) {
978
+    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
979
+  };
980
+
981
+  // Is a given value equal to null?
982
+  _.isNull = function(obj) {
983
+    return obj === null;
984
+  };
985
+
986
+  // Is a given variable undefined?
987
+  _.isUndefined = function(obj) {
988
+    return obj === void 0;
989
+  };
990
+
991
+  // Shortcut function for checking if an object has a given property directly
992
+  // on itself (in other words, not on a prototype).
993
+  _.has = function(obj, key) {
994
+    return hasOwnProperty.call(obj, key);
995
+  };
996
+
997
+  // Utility Functions
998
+  // -----------------
999
+
1000
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1001
+  // previous owner. Returns a reference to the Underscore object.
1002
+  _.noConflict = function() {
1003
+    root._ = previousUnderscore;
1004
+    return this;
1005
+  };
1006
+
1007
+  // Keep the identity function around for default iterators.
1008
+  _.identity = function(value) {
1009
+    return value;
1010
+  };
1011
+
1012
+  // Run a function **n** times.
1013
+  _.times = function(n, iterator, context) {
1014
+    var accum = Array(n);
1015
+    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
1016
+    return accum;
1017
+  };
1018
+
1019
+  // Return a random integer between min and max (inclusive).
1020
+  _.random = function(min, max) {
1021
+    if (max == null) {
1022
+      max = min;
1023
+      min = 0;
1024
+    }
1025
+    return min + Math.floor(Math.random() * (max - min + 1));
1026
+  };
1027
+
1028
+  // List of HTML entities for escaping.
1029
+  var entityMap = {
1030
+    escape: {
1031
+      '&': '&amp;',
1032
+      '<': '&lt;',
1033
+      '>': '&gt;',
1034
+      '"': '&quot;',
1035
+      "'": '&#x27;',
1036
+      '/': '&#x2F;'
1037
+    }
1038
+  };
1039
+  entityMap.unescape = _.invert(entityMap.escape);
1040
+
1041
+  // Regexes containing the keys and values listed immediately above.
1042
+  var entityRegexes = {
1043
+    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
1044
+    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
1045
+  };
1046
+
1047
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
1048
+  _.each(['escape', 'unescape'], function(method) {
1049
+    _[method] = function(string) {
1050
+      if (string == null) return '';
1051
+      return ('' + string).replace(entityRegexes[method], function(match) {
1052
+        return entityMap[method][match];
1053
+      });
1054
+    };
1055
+  });
1056
+
1057
+  // If the value of the named property is a function then invoke it;
1058
+  // otherwise, return it.
1059
+  _.result = function(object, property) {
1060
+    if (object == null) return null;
1061
+    var value = object[property];
1062
+    return _.isFunction(value) ? value.call(object) : value;
1063
+  };
1064
+
1065
+  // Add your own custom functions to the Underscore object.
1066
+  _.mixin = function(obj) {
1067
+    each(_.functions(obj), function(name){
1068
+      var func = _[name] = obj[name];
1069
+      _.prototype[name] = function() {
1070
+        var args = [this._wrapped];
1071
+        push.apply(args, arguments);
1072
+        return result.call(this, func.apply(_, args));
1073
+      };
1074
+    });
1075
+  };
1076
+
1077
+  // Generate a unique integer id (unique within the entire client session).
1078
+  // Useful for temporary DOM ids.
1079
+  var idCounter = 0;
1080
+  _.uniqueId = function(prefix) {
1081
+    var id = ++idCounter + '';
1082
+    return prefix ? prefix + id : id;
1083
+  };
1084
+
1085
+  // By default, Underscore uses ERB-style template delimiters, change the
1086
+  // following template settings to use alternative delimiters.
1087
+  _.templateSettings = {
1088
+    evaluate    : /<%([\s\S]+?)%>/g,
1089
+    interpolate : /<%=([\s\S]+?)%>/g,
1090
+    escape      : /<%-([\s\S]+?)%>/g
1091
+  };
1092
+
1093
+  // When customizing `templateSettings`, if you don't want to define an
1094
+  // interpolation, evaluation or escaping regex, we need one that is
1095
+  // guaranteed not to match.
1096
+  var noMatch = /(.)^/;
1097
+
1098
+  // Certain characters need to be escaped so that they can be put into a
1099
+  // string literal.
1100
+  var escapes = {
1101
+    "'":      "'",
1102
+    '\\':     '\\',
1103
+    '\r':     'r',
1104
+    '\n':     'n',
1105
+    '\t':     't',
1106
+    '\u2028': 'u2028',
1107
+    '\u2029': 'u2029'
1108
+  };
1109
+
1110
+  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
1111
+
1112
+  // JavaScript micro-templating, similar to John Resig's implementation.
1113
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
1114
+  // and correctly escapes quotes within interpolated code.
1115
+  _.template = function(text, data, settings) {
1116
+    var render;
1117
+    settings = _.defaults({}, settings, _.templateSettings);
1118
+
1119
+    // Combine delimiters into one regular expression via alternation.
1120
+    var matcher = new RegExp([
1121
+      (settings.escape || noMatch).source,
1122
+      (settings.interpolate || noMatch).source,
1123
+      (settings.evaluate || noMatch).source
1124
+    ].join('|') + '|$', 'g');
1125
+
1126
+    // Compile the template source, escaping string literals appropriately.
1127
+    var index = 0;
1128
+    var source = "__p+='";
1129
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1130
+      source += text.slice(index, offset)
1131
+        .replace(escaper, function(match) { return '\\' + escapes[match]; });
1132
+
1133
+      if (escape) {
1134
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1135
+      }
1136
+      if (interpolate) {
1137
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1138
+      }
1139
+      if (evaluate) {
1140
+        source += "';\n" + evaluate + "\n__p+='";
1141
+      }
1142
+      index = offset + match.length;
1143
+      return match;
1144
+    });
1145
+    source += "';\n";
1146
+
1147
+    // If a variable is not specified, place data values in local scope.
1148
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1149
+
1150
+    source = "var __t,__p='',__j=Array.prototype.join," +
1151
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
1152
+      source + "return __p;\n";
1153
+
1154
+    try {
1155
+      render = new Function(settings.variable || 'obj', '_', source);
1156
+    } catch (e) {
1157
+      e.source = source;
1158
+      throw e;
1159
+    }
1160
+
1161
+    if (data) return render(data, _);
1162
+    var template = function(data) {
1163
+      return render.call(this, data, _);
1164
+    };
1165
+
1166
+    // Provide the compiled function source as a convenience for precompilation.
1167
+    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
1168
+
1169
+    return template;
1170
+  };
1171
+
1172
+  // Add a "chain" function, which will delegate to the wrapper.
1173
+  _.chain = function(obj) {
1174
+    return _(obj).chain();
1175
+  };
1176
+
1177
+  // OOP
1178
+  // ---------------
1179
+  // If Underscore is called as a function, it returns a wrapped object that
1180
+  // can be used OO-style. This wrapper holds altered versions of all the
1181
+  // underscore functions. Wrapped objects may be chained.
1182
+
1183
+  // Helper function to continue chaining intermediate results.
1184
+  var result = function(obj) {
1185
+    return this._chain ? _(obj).chain() : obj;
1186
+  };
1187
+
1188
+  // Add all of the Underscore functions to the wrapper object.
1189
+  _.mixin(_);
1190
+
1191
+  // Add all mutator Array functions to the wrapper.
1192
+  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1193
+    var method = ArrayProto[name];
1194
+    _.prototype[name] = function() {
1195
+      var obj = this._wrapped;
1196
+      method.apply(obj, arguments);
1197
+      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
1198
+      return result.call(this, obj);
1199
+    };
1200
+  });
1201
+
1202
+  // Add all accessor Array functions to the wrapper.
1203
+  each(['concat', 'join', 'slice'], function(name) {
1204
+    var method = ArrayProto[name];
1205
+    _.prototype[name] = function() {
1206
+      return result.call(this, method.apply(this._wrapped, arguments));
1207
+    };
1208
+  });
1209
+
1210
+  _.extend(_.prototype, {
1211
+
1212
+    // Start chaining a wrapped Underscore object.
1213
+    chain: function() {
1214
+      this._chain = true;
1215
+      return this;
1216
+    },
1217
+
1218
+    // Extracts the result from a wrapped and chained object.
1219
+    value: function() {
1220
+      return this._wrapped;
1221
+    }
1222
+
1223
+  });
1224
+
1225
+}).call(this);