├── .gitignore
├── .gitmodules
├── package.json
├── readme.md
├── server.js
└── webroot
├── index.html
└── js
├── index.js
└── lib
├── backbone.js
├── jquery.js
└── underscore.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/bionic-dj"]
2 | path = lib/bionic-dj
3 | url = git@github.com:gattis/bionic-dj.git
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.1",
3 | "name": "node-hands",
4 | "dependencies": {
5 | "connect": "1.8.5",
6 | "socket.io": "0.9.0",
7 | "socket.osc": "0.0.2"
8 | }
9 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | node-hands
2 | ===
3 |
4 | intention
5 | ---
6 | node-hands is a straight forward nodejs library which provides live x, y, and z hand coordinates from an xbox kinect to the browser via socket.io. if you're so inclined, [click here for a demo video](http://vimeo.com/user6080011/node-hands).
7 |
8 | installation
9 | ---
10 | 1. install [libfreenect](openkinect.org/wiki/Getting_Started)
11 |
12 | 2. install [liblo](http://liblo.sourceforge.net/)
13 |
14 | 3. download and run node-hands
15 |
16 | git clone git@github.com:catshirt/node-hands.git
17 | cd node-hands
18 | npm install
19 | node server.js
20 |
21 | if you access [localhost:8080](http://localhost:8080) you'll have an open socket waiting for OSC messages.
22 |
23 | 4. compile and run bionic-dj
24 |
25 | git submodule init
26 | git submodule update
27 | cd lib/bionic-dj
28 | gcc -lfreenect -llo -framework OpenGL -framework GLUT -I ~/Code/libfreenect/include bionic_dj.c
29 | ./a.out
30 |
31 | where `~/Code/libfreenect/include` is the path to your libfreenect header files
32 |
33 | bionic-dj should now be sending OSC messages which nodejs forwards to the browser.
34 |
35 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var Connect = require('connect'),
2 | http_serv = Connect.createServer(),
3 | io = require('socket.io').listen(http_serv),
4 | osc_serv = require(__dirname + '/node_modules/socket.osc');
5 |
6 | http_serv.use(Connect.static(__dirname + '/webroot'));
7 | http_serv.listen(8080);
8 | osc_serv.listen(io, 8000);
--------------------------------------------------------------------------------
/webroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | node-hands
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/webroot/js/index.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var socket = io.connect('http://localhost'),
4 | $body = $('body'),
5 | $win = $(window),
6 | win_height = $win.height(),
7 | win_width = $win.width();
8 |
9 | function normalizePoint(value) {
10 | value *= 100;
11 | var num = Math.round(value);
12 | value = num / 100;
13 | return value;
14 | }
15 |
16 | var HandView = Backbone.View.extend({
17 |
18 | initialize: function(options) {
19 |
20 | var self = this,
21 | model = new Backbone.Model(),
22 | path = '\/kinect\/' + options.path + '_(.*)?';
23 |
24 | model.bind('change:x', _.bind(self.setX, self));
25 | model.bind('change:y', _.bind(self.setY, self));
26 | model.bind('change:z', _.bind(self.setZ, self));
27 |
28 | socket.on('osc', function(msg) {
29 | var match = msg.path.match(path),
30 | opts = {};
31 | if (match) {
32 | opts[match[1]] = msg.params[0];
33 | model.set(opts);
34 | }
35 | });
36 |
37 | },
38 |
39 | setX: function(m, x) {
40 | var val = normalizePoint(x);
41 | this.$el.css({
42 | left: Math.round(win_width * val)
43 | });
44 | },
45 |
46 | setY: function(m, y) {
47 | var val = normalizePoint(y);
48 | this.$el.css({
49 | bottom: Math.round(win_height * val)
50 | });
51 | },
52 |
53 | setZ: function(m, z) {
54 | var val = normalizePoint(z) + 1;
55 | this.$el.css({
56 | zoom: val * 100 + '%'
57 | });
58 | },
59 |
60 | render: function() {
61 | return this.$el.css({
62 | background: 'black',
63 | width: 60,
64 | height: 60,
65 | position: 'absolute',
66 | borderRadius: 30
67 | });
68 | }
69 |
70 | });
71 |
72 | $(function() {
73 | var $body = $('body');
74 | new HandView({ path: 'left' }).render().appendTo($body);
75 | new HandView({ path: 'right' }).render().appendTo($body);
76 | });
77 |
78 | })();
--------------------------------------------------------------------------------
/webroot/js/lib/backbone.js:
--------------------------------------------------------------------------------
1 | // Backbone.js 0.9.1
2 |
3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4 | // Backbone may be freely distributed under the MIT license.
5 | // For all details and documentation:
6 | // http://backbonejs.org
7 |
8 | (function(){
9 |
10 | // Initial Setup
11 | // -------------
12 |
13 | // Save a reference to the global object (`window` in the browser, `global`
14 | // on the server).
15 | var root = this;
16 |
17 | // Save the previous value of the `Backbone` variable, so that it can be
18 | // restored later on, if `noConflict` is used.
19 | var previousBackbone = root.Backbone;
20 |
21 | // Create a local reference to slice/splice.
22 | var slice = Array.prototype.slice;
23 | var splice = Array.prototype.splice;
24 |
25 | // The top-level namespace. All public Backbone classes and modules will
26 | // be attached to this. Exported for both CommonJS and the browser.
27 | var Backbone;
28 | if (typeof exports !== 'undefined') {
29 | Backbone = exports;
30 | } else {
31 | Backbone = root.Backbone = {};
32 | }
33 |
34 | // Current version of the library. Keep in sync with `package.json`.
35 | Backbone.VERSION = '0.9.1';
36 |
37 | // Require Underscore, if we're on the server, and it's not already present.
38 | var _ = root._;
39 | if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
40 |
41 | // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
42 | var $ = root.jQuery || root.Zepto || root.ender;
43 |
44 | // Set the JavaScript library that will be used for DOM manipulation and
45 | // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
46 | // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
47 | // alternate JavaScript library (or a mock library for testing your views
48 | // outside of a browser).
49 | Backbone.setDomLibrary = function(lib) {
50 | $ = lib;
51 | };
52 |
53 | // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
54 | // to its previous owner. Returns a reference to this Backbone object.
55 | Backbone.noConflict = function() {
56 | root.Backbone = previousBackbone;
57 | return this;
58 | };
59 |
60 | // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
61 | // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
62 | // set a `X-Http-Method-Override` header.
63 | Backbone.emulateHTTP = false;
64 |
65 | // Turn on `emulateJSON` to support legacy servers that can't deal with direct
66 | // `application/json` requests ... will encode the body as
67 | // `application/x-www-form-urlencoded` instead and will send the model in a
68 | // form param named `model`.
69 | Backbone.emulateJSON = false;
70 |
71 | // Backbone.Events
72 | // -----------------
73 |
74 | // A module that can be mixed in to *any object* in order to provide it with
75 | // custom events. You may bind with `on` or remove with `off` callback functions
76 | // to an event; trigger`-ing an event fires all callbacks in succession.
77 | //
78 | // var object = {};
79 | // _.extend(object, Backbone.Events);
80 | // object.on('expand', function(){ alert('expanded'); });
81 | // object.trigger('expand');
82 | //
83 | Backbone.Events = {
84 |
85 | // Bind an event, specified by a string name, `ev`, to a `callback`
86 | // function. Passing `"all"` will bind the callback to all events fired.
87 | on: function(events, callback, context) {
88 | var ev;
89 | events = events.split(/\s+/);
90 | var calls = this._callbacks || (this._callbacks = {});
91 | while (ev = events.shift()) {
92 | // Create an immutable callback list, allowing traversal during
93 | // modification. The tail is an empty object that will always be used
94 | // as the next node.
95 | var list = calls[ev] || (calls[ev] = {});
96 | var tail = list.tail || (list.tail = list.next = {});
97 | tail.callback = callback;
98 | tail.context = context;
99 | list.tail = tail.next = {};
100 | }
101 | return this;
102 | },
103 |
104 | // Remove one or many callbacks. If `context` is null, removes all callbacks
105 | // with that function. If `callback` is null, removes all callbacks for the
106 | // event. If `ev` is null, removes all bound callbacks for all events.
107 | off: function(events, callback, context) {
108 | var ev, calls, node;
109 | if (!events) {
110 | delete this._callbacks;
111 | } else if (calls = this._callbacks) {
112 | events = events.split(/\s+/);
113 | while (ev = events.shift()) {
114 | node = calls[ev];
115 | delete calls[ev];
116 | if (!callback || !node) continue;
117 | // Create a new list, omitting the indicated event/context pairs.
118 | while ((node = node.next) && node.next) {
119 | if (node.callback === callback &&
120 | (!context || node.context === context)) continue;
121 | this.on(ev, node.callback, node.context);
122 | }
123 | }
124 | }
125 | return this;
126 | },
127 |
128 | // Trigger an event, firing all bound callbacks. Callbacks are passed the
129 | // same arguments as `trigger` is, apart from the event name.
130 | // Listening for `"all"` passes the true event name as the first argument.
131 | trigger: function(events) {
132 | var event, node, calls, tail, args, all, rest;
133 | if (!(calls = this._callbacks)) return this;
134 | all = calls['all'];
135 | (events = events.split(/\s+/)).push(null);
136 | // Save references to the current heads & tails.
137 | while (event = events.shift()) {
138 | if (all) events.push({next: all.next, tail: all.tail, event: event});
139 | if (!(node = calls[event])) continue;
140 | events.push({next: node.next, tail: node.tail});
141 | }
142 | // Traverse each list, stopping when the saved tail is reached.
143 | rest = slice.call(arguments, 1);
144 | while (node = events.pop()) {
145 | tail = node.tail;
146 | args = node.event ? [node.event].concat(rest) : rest;
147 | while ((node = node.next) !== tail) {
148 | node.callback.apply(node.context || this, args);
149 | }
150 | }
151 | return this;
152 | }
153 |
154 | };
155 |
156 | // Aliases for backwards compatibility.
157 | Backbone.Events.bind = Backbone.Events.on;
158 | Backbone.Events.unbind = Backbone.Events.off;
159 |
160 | // Backbone.Model
161 | // --------------
162 |
163 | // Create a new model, with defined attributes. A client id (`cid`)
164 | // is automatically generated and assigned for you.
165 | Backbone.Model = function(attributes, options) {
166 | var defaults;
167 | attributes || (attributes = {});
168 | if (options && options.parse) attributes = this.parse(attributes);
169 | if (defaults = getValue(this, 'defaults')) {
170 | attributes = _.extend({}, defaults, attributes);
171 | }
172 | if (options && options.collection) this.collection = options.collection;
173 | this.attributes = {};
174 | this._escapedAttributes = {};
175 | this.cid = _.uniqueId('c');
176 | if (!this.set(attributes, {silent: true})) {
177 | throw new Error("Can't create an invalid model");
178 | }
179 | delete this._changed;
180 | this._previousAttributes = _.clone(this.attributes);
181 | this.initialize.apply(this, arguments);
182 | };
183 |
184 | // Attach all inheritable methods to the Model prototype.
185 | _.extend(Backbone.Model.prototype, Backbone.Events, {
186 |
187 | // The default name for the JSON `id` attribute is `"id"`. MongoDB and
188 | // CouchDB users may want to set this to `"_id"`.
189 | idAttribute: 'id',
190 |
191 | // Initialize is an empty function by default. Override it with your own
192 | // initialization logic.
193 | initialize: function(){},
194 |
195 | // Return a copy of the model's `attributes` object.
196 | toJSON: function() {
197 | return _.clone(this.attributes);
198 | },
199 |
200 | // Get the value of an attribute.
201 | get: function(attr) {
202 | return this.attributes[attr];
203 | },
204 |
205 | // Get the HTML-escaped value of an attribute.
206 | escape: function(attr) {
207 | var html;
208 | if (html = this._escapedAttributes[attr]) return html;
209 | var val = this.attributes[attr];
210 | return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
211 | },
212 |
213 | // Returns `true` if the attribute contains a value that is not null
214 | // or undefined.
215 | has: function(attr) {
216 | return this.attributes[attr] != null;
217 | },
218 |
219 | // Set a hash of model attributes on the object, firing `"change"` unless
220 | // you choose to silence it.
221 | set: function(key, value, options) {
222 | var attrs, attr, val;
223 | if (_.isObject(key) || key == null) {
224 | attrs = key;
225 | options = value;
226 | } else {
227 | attrs = {};
228 | attrs[key] = value;
229 | }
230 |
231 | // Extract attributes and options.
232 | options || (options = {});
233 | if (!attrs) return this;
234 | if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
235 | if (options.unset) for (attr in attrs) attrs[attr] = void 0;
236 |
237 | // Run validation.
238 | if (!this._validate(attrs, options)) return false;
239 |
240 | // Check for changes of `id`.
241 | if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
242 |
243 | var now = this.attributes;
244 | var escaped = this._escapedAttributes;
245 | var prev = this._previousAttributes || {};
246 | var alreadySetting = this._setting;
247 | this._changed || (this._changed = {});
248 | this._setting = true;
249 |
250 | // Update attributes.
251 | for (attr in attrs) {
252 | val = attrs[attr];
253 | if (!_.isEqual(now[attr], val)) delete escaped[attr];
254 | options.unset ? delete now[attr] : now[attr] = val;
255 | if (this._changing && !_.isEqual(this._changed[attr], val)) {
256 | this.trigger('change:' + attr, this, val, options);
257 | this._moreChanges = true;
258 | }
259 | delete this._changed[attr];
260 | if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
261 | this._changed[attr] = val;
262 | }
263 | }
264 |
265 | // Fire the `"change"` events, if the model has been changed.
266 | if (!alreadySetting) {
267 | if (!options.silent && this.hasChanged()) this.change(options);
268 | this._setting = false;
269 | }
270 | return this;
271 | },
272 |
273 | // Remove an attribute from the model, firing `"change"` unless you choose
274 | // to silence it. `unset` is a noop if the attribute doesn't exist.
275 | unset: function(attr, options) {
276 | (options || (options = {})).unset = true;
277 | return this.set(attr, null, options);
278 | },
279 |
280 | // Clear all attributes on the model, firing `"change"` unless you choose
281 | // to silence it.
282 | clear: function(options) {
283 | (options || (options = {})).unset = true;
284 | return this.set(_.clone(this.attributes), options);
285 | },
286 |
287 | // Fetch the model from the server. If the server's representation of the
288 | // model differs from its current attributes, they will be overriden,
289 | // triggering a `"change"` event.
290 | fetch: function(options) {
291 | options = options ? _.clone(options) : {};
292 | var model = this;
293 | var success = options.success;
294 | options.success = function(resp, status, xhr) {
295 | if (!model.set(model.parse(resp, xhr), options)) return false;
296 | if (success) success(model, resp);
297 | };
298 | options.error = Backbone.wrapError(options.error, model, options);
299 | return (this.sync || Backbone.sync).call(this, 'read', this, options);
300 | },
301 |
302 | // Set a hash of model attributes, and sync the model to the server.
303 | // If the server returns an attributes hash that differs, the model's
304 | // state will be `set` again.
305 | save: function(key, value, options) {
306 | var attrs, current;
307 | if (_.isObject(key) || key == null) {
308 | attrs = key;
309 | options = value;
310 | } else {
311 | attrs = {};
312 | attrs[key] = value;
313 | }
314 |
315 | options = options ? _.clone(options) : {};
316 | if (options.wait) current = _.clone(this.attributes);
317 | var silentOptions = _.extend({}, options, {silent: true});
318 | if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
319 | return false;
320 | }
321 | var model = this;
322 | var success = options.success;
323 | options.success = function(resp, status, xhr) {
324 | var serverAttrs = model.parse(resp, xhr);
325 | if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
326 | if (!model.set(serverAttrs, options)) return false;
327 | if (success) {
328 | success(model, resp);
329 | } else {
330 | model.trigger('sync', model, resp, options);
331 | }
332 | };
333 | options.error = Backbone.wrapError(options.error, model, options);
334 | var method = this.isNew() ? 'create' : 'update';
335 | var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
336 | if (options.wait) this.set(current, silentOptions);
337 | return xhr;
338 | },
339 |
340 | // Destroy this model on the server if it was already persisted.
341 | // Optimistically removes the model from its collection, if it has one.
342 | // If `wait: true` is passed, waits for the server to respond before removal.
343 | destroy: function(options) {
344 | options = options ? _.clone(options) : {};
345 | var model = this;
346 | var success = options.success;
347 |
348 | var triggerDestroy = function() {
349 | model.trigger('destroy', model, model.collection, options);
350 | };
351 |
352 | if (this.isNew()) return triggerDestroy();
353 | options.success = function(resp) {
354 | if (options.wait) triggerDestroy();
355 | if (success) {
356 | success(model, resp);
357 | } else {
358 | model.trigger('sync', model, resp, options);
359 | }
360 | };
361 | options.error = Backbone.wrapError(options.error, model, options);
362 | var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
363 | if (!options.wait) triggerDestroy();
364 | return xhr;
365 | },
366 |
367 | // Default URL for the model's representation on the server -- if you're
368 | // using Backbone's restful methods, override this to change the endpoint
369 | // that will be called.
370 | url: function() {
371 | var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
372 | if (this.isNew()) return base;
373 | return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
374 | },
375 |
376 | // **parse** converts a response into the hash of attributes to be `set` on
377 | // the model. The default implementation is just to pass the response along.
378 | parse: function(resp, xhr) {
379 | return resp;
380 | },
381 |
382 | // Create a new model with identical attributes to this one.
383 | clone: function() {
384 | return new this.constructor(this.attributes);
385 | },
386 |
387 | // A model is new if it has never been saved to the server, and lacks an id.
388 | isNew: function() {
389 | return this.id == null;
390 | },
391 |
392 | // Call this method to manually fire a `"change"` event for this model and
393 | // a `"change:attribute"` event for each changed attribute.
394 | // Calling this will cause all objects observing the model to update.
395 | change: function(options) {
396 | if (this._changing || !this.hasChanged()) return this;
397 | this._changing = true;
398 | this._moreChanges = true;
399 | for (var attr in this._changed) {
400 | this.trigger('change:' + attr, this, this._changed[attr], options);
401 | }
402 | while (this._moreChanges) {
403 | this._moreChanges = false;
404 | this.trigger('change', this, options);
405 | }
406 | this._previousAttributes = _.clone(this.attributes);
407 | delete this._changed;
408 | this._changing = false;
409 | return this;
410 | },
411 |
412 | // Determine if the model has changed since the last `"change"` event.
413 | // If you specify an attribute name, determine if that attribute has changed.
414 | hasChanged: function(attr) {
415 | if (!arguments.length) return !_.isEmpty(this._changed);
416 | return this._changed && _.has(this._changed, attr);
417 | },
418 |
419 | // Return an object containing all the attributes that have changed, or
420 | // false if there are no changed attributes. Useful for determining what
421 | // parts of a view need to be updated and/or what attributes need to be
422 | // persisted to the server. Unset attributes will be set to undefined.
423 | // You can also pass an attributes object to diff against the model,
424 | // determining if there *would be* a change.
425 | changedAttributes: function(diff) {
426 | if (!diff) return this.hasChanged() ? _.clone(this._changed) : false;
427 | var val, changed = false, old = this._previousAttributes;
428 | for (var attr in diff) {
429 | if (_.isEqual(old[attr], (val = diff[attr]))) continue;
430 | (changed || (changed = {}))[attr] = val;
431 | }
432 | return changed;
433 | },
434 |
435 | // Get the previous value of an attribute, recorded at the time the last
436 | // `"change"` event was fired.
437 | previous: function(attr) {
438 | if (!arguments.length || !this._previousAttributes) return null;
439 | return this._previousAttributes[attr];
440 | },
441 |
442 | // Get all of the attributes of the model at the time of the previous
443 | // `"change"` event.
444 | previousAttributes: function() {
445 | return _.clone(this._previousAttributes);
446 | },
447 |
448 | // Check if the model is currently in a valid state. It's only possible to
449 | // get into an *invalid* state if you're using silent changes.
450 | isValid: function() {
451 | return !this.validate(this.attributes);
452 | },
453 |
454 | // Run validation against a set of incoming attributes, returning `true`
455 | // if all is well. If a specific `error` callback has been passed,
456 | // call that instead of firing the general `"error"` event.
457 | _validate: function(attrs, options) {
458 | if (options.silent || !this.validate) return true;
459 | attrs = _.extend({}, this.attributes, attrs);
460 | var error = this.validate(attrs, options);
461 | if (!error) return true;
462 | if (options && options.error) {
463 | options.error(this, error, options);
464 | } else {
465 | this.trigger('error', this, error, options);
466 | }
467 | return false;
468 | }
469 |
470 | });
471 |
472 | // Backbone.Collection
473 | // -------------------
474 |
475 | // Provides a standard collection class for our sets of models, ordered
476 | // or unordered. If a `comparator` is specified, the Collection will maintain
477 | // its models in sort order, as they're added and removed.
478 | Backbone.Collection = function(models, options) {
479 | options || (options = {});
480 | if (options.comparator) this.comparator = options.comparator;
481 | this._reset();
482 | this.initialize.apply(this, arguments);
483 | if (models) this.reset(models, {silent: true, parse: options.parse});
484 | };
485 |
486 | // Define the Collection's inheritable methods.
487 | _.extend(Backbone.Collection.prototype, Backbone.Events, {
488 |
489 | // The default model for a collection is just a **Backbone.Model**.
490 | // This should be overridden in most cases.
491 | model: Backbone.Model,
492 |
493 | // Initialize is an empty function by default. Override it with your own
494 | // initialization logic.
495 | initialize: function(){},
496 |
497 | // The JSON representation of a Collection is an array of the
498 | // models' attributes.
499 | toJSON: function() {
500 | return this.map(function(model){ return model.toJSON(); });
501 | },
502 |
503 | // Add a model, or list of models to the set. Pass **silent** to avoid
504 | // firing the `add` event for every new model.
505 | add: function(models, options) {
506 | var i, index, length, model, cid, id, cids = {}, ids = {};
507 | options || (options = {});
508 | models = _.isArray(models) ? models.slice() : [models];
509 |
510 | // Begin by turning bare objects into model references, and preventing
511 | // invalid models or duplicate models from being added.
512 | for (i = 0, length = models.length; i < length; i++) {
513 | if (!(model = models[i] = this._prepareModel(models[i], options))) {
514 | throw new Error("Can't add an invalid model to a collection");
515 | }
516 | if (cids[cid = model.cid] || this._byCid[cid] ||
517 | (((id = model.id) != null) && (ids[id] || this._byId[id]))) {
518 | throw new Error("Can't add the same model to a collection twice");
519 | }
520 | cids[cid] = ids[id] = model;
521 | }
522 |
523 | // Listen to added models' events, and index models for lookup by
524 | // `id` and by `cid`.
525 | for (i = 0; i < length; i++) {
526 | (model = models[i]).on('all', this._onModelEvent, this);
527 | this._byCid[model.cid] = model;
528 | if (model.id != null) this._byId[model.id] = model;
529 | }
530 |
531 | // Insert models into the collection, re-sorting if needed, and triggering
532 | // `add` events unless silenced.
533 | this.length += length;
534 | index = options.at != null ? options.at : this.models.length;
535 | splice.apply(this.models, [index, 0].concat(models));
536 | if (this.comparator) this.sort({silent: true});
537 | if (options.silent) return this;
538 | for (i = 0, length = this.models.length; i < length; i++) {
539 | if (!cids[(model = this.models[i]).cid]) continue;
540 | options.index = i;
541 | model.trigger('add', model, this, options);
542 | }
543 | return this;
544 | },
545 |
546 | // Remove a model, or a list of models from the set. Pass silent to avoid
547 | // firing the `remove` event for every model removed.
548 | remove: function(models, options) {
549 | var i, l, index, model;
550 | options || (options = {});
551 | models = _.isArray(models) ? models.slice() : [models];
552 | for (i = 0, l = models.length; i < l; i++) {
553 | model = this.getByCid(models[i]) || this.get(models[i]);
554 | if (!model) continue;
555 | delete this._byId[model.id];
556 | delete this._byCid[model.cid];
557 | index = this.indexOf(model);
558 | this.models.splice(index, 1);
559 | this.length--;
560 | if (!options.silent) {
561 | options.index = index;
562 | model.trigger('remove', model, this, options);
563 | }
564 | this._removeReference(model);
565 | }
566 | return this;
567 | },
568 |
569 | // Get a model from the set by id.
570 | get: function(id) {
571 | if (id == null) return null;
572 | return this._byId[id.id != null ? id.id : id];
573 | },
574 |
575 | // Get a model from the set by client id.
576 | getByCid: function(cid) {
577 | return cid && this._byCid[cid.cid || cid];
578 | },
579 |
580 | // Get the model at the given index.
581 | at: function(index) {
582 | return this.models[index];
583 | },
584 |
585 | // Force the collection to re-sort itself. You don't need to call this under
586 | // normal circumstances, as the set will maintain sort order as each item
587 | // is added.
588 | sort: function(options) {
589 | options || (options = {});
590 | if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
591 | var boundComparator = _.bind(this.comparator, this);
592 | if (this.comparator.length == 1) {
593 | this.models = this.sortBy(boundComparator);
594 | } else {
595 | this.models.sort(boundComparator);
596 | }
597 | if (!options.silent) this.trigger('reset', this, options);
598 | return this;
599 | },
600 |
601 | // Pluck an attribute from each model in the collection.
602 | pluck: function(attr) {
603 | return _.map(this.models, function(model){ return model.get(attr); });
604 | },
605 |
606 | // When you have more items than you want to add or remove individually,
607 | // you can reset the entire set with a new list of models, without firing
608 | // any `add` or `remove` events. Fires `reset` when finished.
609 | reset: function(models, options) {
610 | models || (models = []);
611 | options || (options = {});
612 | for (var i = 0, l = this.models.length; i < l; i++) {
613 | this._removeReference(this.models[i]);
614 | }
615 | this._reset();
616 | this.add(models, {silent: true, parse: options.parse});
617 | if (!options.silent) this.trigger('reset', this, options);
618 | return this;
619 | },
620 |
621 | // Fetch the default set of models for this collection, resetting the
622 | // collection when they arrive. If `add: true` is passed, appends the
623 | // models to the collection instead of resetting.
624 | fetch: function(options) {
625 | options = options ? _.clone(options) : {};
626 | if (options.parse === undefined) options.parse = true;
627 | var collection = this;
628 | var success = options.success;
629 | options.success = function(resp, status, xhr) {
630 | collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
631 | if (success) success(collection, resp);
632 | };
633 | options.error = Backbone.wrapError(options.error, collection, options);
634 | return (this.sync || Backbone.sync).call(this, 'read', this, options);
635 | },
636 |
637 | // Create a new instance of a model in this collection. Add the model to the
638 | // collection immediately, unless `wait: true` is passed, in which case we
639 | // wait for the server to agree.
640 | create: function(model, options) {
641 | var coll = this;
642 | options = options ? _.clone(options) : {};
643 | model = this._prepareModel(model, options);
644 | if (!model) return false;
645 | if (!options.wait) coll.add(model, options);
646 | var success = options.success;
647 | options.success = function(nextModel, resp, xhr) {
648 | if (options.wait) coll.add(nextModel, options);
649 | if (success) {
650 | success(nextModel, resp);
651 | } else {
652 | nextModel.trigger('sync', model, resp, options);
653 | }
654 | };
655 | model.save(null, options);
656 | return model;
657 | },
658 |
659 | // **parse** converts a response into a list of models to be added to the
660 | // collection. The default implementation is just to pass it through.
661 | parse: function(resp, xhr) {
662 | return resp;
663 | },
664 |
665 | // Proxy to _'s chain. Can't be proxied the same way the rest of the
666 | // underscore methods are proxied because it relies on the underscore
667 | // constructor.
668 | chain: function () {
669 | return _(this.models).chain();
670 | },
671 |
672 | // Reset all internal state. Called when the collection is reset.
673 | _reset: function(options) {
674 | this.length = 0;
675 | this.models = [];
676 | this._byId = {};
677 | this._byCid = {};
678 | },
679 |
680 | // Prepare a model or hash of attributes to be added to this collection.
681 | _prepareModel: function(model, options) {
682 | if (!(model instanceof Backbone.Model)) {
683 | var attrs = model;
684 | options.collection = this;
685 | model = new this.model(attrs, options);
686 | if (!model._validate(model.attributes, options)) model = false;
687 | } else if (!model.collection) {
688 | model.collection = this;
689 | }
690 | return model;
691 | },
692 |
693 | // Internal method to remove a model's ties to a collection.
694 | _removeReference: function(model) {
695 | if (this == model.collection) {
696 | delete model.collection;
697 | }
698 | model.off('all', this._onModelEvent, this);
699 | },
700 |
701 | // Internal method called every time a model in the set fires an event.
702 | // Sets need to update their indexes when models change ids. All other
703 | // events simply proxy through. "add" and "remove" events that originate
704 | // in other collections are ignored.
705 | _onModelEvent: function(ev, model, collection, options) {
706 | if ((ev == 'add' || ev == 'remove') && collection != this) return;
707 | if (ev == 'destroy') {
708 | this.remove(model, options);
709 | }
710 | if (model && ev === 'change:' + model.idAttribute) {
711 | delete this._byId[model.previous(model.idAttribute)];
712 | this._byId[model.id] = model;
713 | }
714 | this.trigger.apply(this, arguments);
715 | }
716 |
717 | });
718 |
719 | // Underscore methods that we want to implement on the Collection.
720 | var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
721 | 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
722 | 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
723 | 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
724 | 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
725 |
726 | // Mix in each Underscore method as a proxy to `Collection#models`.
727 | _.each(methods, function(method) {
728 | Backbone.Collection.prototype[method] = function() {
729 | return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
730 | };
731 | });
732 |
733 | // Backbone.Router
734 | // -------------------
735 |
736 | // Routers map faux-URLs to actions, and fire events when routes are
737 | // matched. Creating a new one sets its `routes` hash, if not set statically.
738 | Backbone.Router = function(options) {
739 | options || (options = {});
740 | if (options.routes) this.routes = options.routes;
741 | this._bindRoutes();
742 | this.initialize.apply(this, arguments);
743 | };
744 |
745 | // Cached regular expressions for matching named param parts and splatted
746 | // parts of route strings.
747 | var namedParam = /:\w+/g;
748 | var splatParam = /\*\w+/g;
749 | var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
750 |
751 | // Set up all inheritable **Backbone.Router** properties and methods.
752 | _.extend(Backbone.Router.prototype, Backbone.Events, {
753 |
754 | // Initialize is an empty function by default. Override it with your own
755 | // initialization logic.
756 | initialize: function(){},
757 |
758 | // Manually bind a single named route to a callback. For example:
759 | //
760 | // this.route('search/:query/p:num', 'search', function(query, num) {
761 | // ...
762 | // });
763 | //
764 | route: function(route, name, callback) {
765 | Backbone.history || (Backbone.history = new Backbone.History);
766 | if (!_.isRegExp(route)) route = this._routeToRegExp(route);
767 | if (!callback) callback = this[name];
768 | Backbone.history.route(route, _.bind(function(fragment) {
769 | var args = this._extractParameters(route, fragment);
770 | callback && callback.apply(this, args);
771 | this.trigger.apply(this, ['route:' + name].concat(args));
772 | Backbone.history.trigger('route', this, name, args);
773 | }, this));
774 | return this;
775 | },
776 |
777 | // Simple proxy to `Backbone.history` to save a fragment into the history.
778 | navigate: function(fragment, options) {
779 | Backbone.history.navigate(fragment, options);
780 | },
781 |
782 | // Bind all defined routes to `Backbone.history`. We have to reverse the
783 | // order of the routes here to support behavior where the most general
784 | // routes can be defined at the bottom of the route map.
785 | _bindRoutes: function() {
786 | if (!this.routes) return;
787 | var routes = [];
788 | for (var route in this.routes) {
789 | routes.unshift([route, this.routes[route]]);
790 | }
791 | for (var i = 0, l = routes.length; i < l; i++) {
792 | this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
793 | }
794 | },
795 |
796 | // Convert a route string into a regular expression, suitable for matching
797 | // against the current location hash.
798 | _routeToRegExp: function(route) {
799 | route = route.replace(escapeRegExp, '\\$&')
800 | .replace(namedParam, '([^\/]+)')
801 | .replace(splatParam, '(.*?)');
802 | return new RegExp('^' + route + '$');
803 | },
804 |
805 | // Given a route, and a URL fragment that it matches, return the array of
806 | // extracted parameters.
807 | _extractParameters: function(route, fragment) {
808 | return route.exec(fragment).slice(1);
809 | }
810 |
811 | });
812 |
813 | // Backbone.History
814 | // ----------------
815 |
816 | // Handles cross-browser history management, based on URL fragments. If the
817 | // browser does not support `onhashchange`, falls back to polling.
818 | Backbone.History = function() {
819 | this.handlers = [];
820 | _.bindAll(this, 'checkUrl');
821 | };
822 |
823 | // Cached regex for cleaning leading hashes and slashes .
824 | var routeStripper = /^[#\/]/;
825 |
826 | // Cached regex for detecting MSIE.
827 | var isExplorer = /msie [\w.]+/;
828 |
829 | // Has the history handling already been started?
830 | var historyStarted = false;
831 |
832 | // Set up all inheritable **Backbone.History** properties and methods.
833 | _.extend(Backbone.History.prototype, Backbone.Events, {
834 |
835 | // The default interval to poll for hash changes, if necessary, is
836 | // twenty times a second.
837 | interval: 50,
838 |
839 | // Get the cross-browser normalized URL fragment, either from the URL,
840 | // the hash, or the override.
841 | getFragment: function(fragment, forcePushState) {
842 | if (fragment == null) {
843 | if (this._hasPushState || forcePushState) {
844 | fragment = window.location.pathname;
845 | var search = window.location.search;
846 | if (search) fragment += search;
847 | } else {
848 | fragment = window.location.hash;
849 | }
850 | }
851 | fragment = decodeURIComponent(fragment);
852 | if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
853 | return fragment.replace(routeStripper, '');
854 | },
855 |
856 | // Start the hash change handling, returning `true` if the current URL matches
857 | // an existing route, and `false` otherwise.
858 | start: function(options) {
859 |
860 | // Figure out the initial configuration. Do we need an iframe?
861 | // Is pushState desired ... is it available?
862 | if (historyStarted) throw new Error("Backbone.history has already been started");
863 | this.options = _.extend({}, {root: '/'}, this.options, options);
864 | this._wantsHashChange = this.options.hashChange !== false;
865 | this._wantsPushState = !!this.options.pushState;
866 | this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
867 | var fragment = this.getFragment();
868 | var docMode = document.documentMode;
869 | var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
870 | if (oldIE) {
871 | this.iframe = $('').hide().appendTo('body')[0].contentWindow;
872 | this.navigate(fragment);
873 | }
874 |
875 | // Depending on whether we're using pushState or hashes, and whether
876 | // 'onhashchange' is supported, determine how we check the URL state.
877 | if (this._hasPushState) {
878 | $(window).bind('popstate', this.checkUrl);
879 | } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
880 | $(window).bind('hashchange', this.checkUrl);
881 | } else if (this._wantsHashChange) {
882 | this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
883 | }
884 |
885 | // Determine if we need to change the base url, for a pushState link
886 | // opened by a non-pushState browser.
887 | this.fragment = fragment;
888 | historyStarted = true;
889 | var loc = window.location;
890 | var atRoot = loc.pathname == this.options.root;
891 |
892 | // If we've started off with a route from a `pushState`-enabled browser,
893 | // but we're currently in a browser that doesn't support it...
894 | if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
895 | this.fragment = this.getFragment(null, true);
896 | window.location.replace(this.options.root + '#' + this.fragment);
897 | // Return immediately as browser will do redirect to new url
898 | return true;
899 |
900 | // Or if we've started out with a hash-based route, but we're currently
901 | // in a browser where it could be `pushState`-based instead...
902 | } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
903 | this.fragment = loc.hash.replace(routeStripper, '');
904 | window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
905 | }
906 |
907 | if (!this.options.silent) {
908 | return this.loadUrl();
909 | }
910 | },
911 |
912 | // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
913 | // but possibly useful for unit testing Routers.
914 | stop: function() {
915 | $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
916 | clearInterval(this._checkUrlInterval);
917 | historyStarted = false;
918 | },
919 |
920 | // Add a route to be tested when the fragment changes. Routes added later
921 | // may override previous routes.
922 | route: function(route, callback) {
923 | this.handlers.unshift({route: route, callback: callback});
924 | },
925 |
926 | // Checks the current URL to see if it has changed, and if it has,
927 | // calls `loadUrl`, normalizing across the hidden iframe.
928 | checkUrl: function(e) {
929 | var current = this.getFragment();
930 | if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
931 | if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
932 | if (this.iframe) this.navigate(current);
933 | this.loadUrl() || this.loadUrl(window.location.hash);
934 | },
935 |
936 | // Attempt to load the current URL fragment. If a route succeeds with a
937 | // match, returns `true`. If no defined routes matches the fragment,
938 | // returns `false`.
939 | loadUrl: function(fragmentOverride) {
940 | var fragment = this.fragment = this.getFragment(fragmentOverride);
941 | var matched = _.any(this.handlers, function(handler) {
942 | if (handler.route.test(fragment)) {
943 | handler.callback(fragment);
944 | return true;
945 | }
946 | });
947 | return matched;
948 | },
949 |
950 | // Save a fragment into the hash history, or replace the URL state if the
951 | // 'replace' option is passed. You are responsible for properly URL-encoding
952 | // the fragment in advance.
953 | //
954 | // The options object can contain `trigger: true` if you wish to have the
955 | // route callback be fired (not usually desirable), or `replace: true`, if
956 | // you which to modify the current URL without adding an entry to the history.
957 | navigate: function(fragment, options) {
958 | if (!historyStarted) return false;
959 | if (!options || options === true) options = {trigger: options};
960 | var frag = (fragment || '').replace(routeStripper, '');
961 | if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
962 |
963 | // If pushState is available, we use it to set the fragment as a real URL.
964 | if (this._hasPushState) {
965 | if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
966 | this.fragment = frag;
967 | window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
968 |
969 | // If hash changes haven't been explicitly disabled, update the hash
970 | // fragment to store history.
971 | } else if (this._wantsHashChange) {
972 | this.fragment = frag;
973 | this._updateHash(window.location, frag, options.replace);
974 | if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
975 | // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
976 | // When replace is true, we don't want this.
977 | if(!options.replace) this.iframe.document.open().close();
978 | this._updateHash(this.iframe.location, frag, options.replace);
979 | }
980 |
981 | // If you've told us that you explicitly don't want fallback hashchange-
982 | // based history, then `navigate` becomes a page refresh.
983 | } else {
984 | window.location.assign(this.options.root + fragment);
985 | }
986 | if (options.trigger) this.loadUrl(fragment);
987 | },
988 |
989 | // Update the hash location, either replacing the current entry, or adding
990 | // a new one to the browser history.
991 | _updateHash: function(location, fragment, replace) {
992 | if (replace) {
993 | location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
994 | } else {
995 | location.hash = fragment;
996 | }
997 | }
998 | });
999 |
1000 | // Backbone.View
1001 | // -------------
1002 |
1003 | // Creating a Backbone.View creates its initial element outside of the DOM,
1004 | // if an existing element is not provided...
1005 | Backbone.View = function(options) {
1006 | this.cid = _.uniqueId('view');
1007 | this._configure(options || {});
1008 | this._ensureElement();
1009 | this.initialize.apply(this, arguments);
1010 | this.delegateEvents();
1011 | };
1012 |
1013 | // Cached regex to split keys for `delegate`.
1014 | var eventSplitter = /^(\S+)\s*(.*)$/;
1015 |
1016 | // List of view options to be merged as properties.
1017 | var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1018 |
1019 | // Set up all inheritable **Backbone.View** properties and methods.
1020 | _.extend(Backbone.View.prototype, Backbone.Events, {
1021 |
1022 | // The default `tagName` of a View's element is `"div"`.
1023 | tagName: 'div',
1024 |
1025 | // jQuery delegate for element lookup, scoped to DOM elements within the
1026 | // current view. This should be prefered to global lookups where possible.
1027 | $: function(selector) {
1028 | return this.$el.find(selector);
1029 | },
1030 |
1031 | // Initialize is an empty function by default. Override it with your own
1032 | // initialization logic.
1033 | initialize: function(){},
1034 |
1035 | // **render** is the core function that your view should override, in order
1036 | // to populate its element (`this.el`), with the appropriate HTML. The
1037 | // convention is for **render** to always return `this`.
1038 | render: function() {
1039 | return this;
1040 | },
1041 |
1042 | // Remove this view from the DOM. Note that the view isn't present in the
1043 | // DOM by default, so calling this method may be a no-op.
1044 | remove: function() {
1045 | this.$el.remove();
1046 | return this;
1047 | },
1048 |
1049 | // For small amounts of DOM Elements, where a full-blown template isn't
1050 | // needed, use **make** to manufacture elements, one at a time.
1051 | //
1052 | // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1053 | //
1054 | make: function(tagName, attributes, content) {
1055 | var el = document.createElement(tagName);
1056 | if (attributes) $(el).attr(attributes);
1057 | if (content) $(el).html(content);
1058 | return el;
1059 | },
1060 |
1061 | // Change the view's element (`this.el` property), including event
1062 | // re-delegation.
1063 | setElement: function(element, delegate) {
1064 | this.$el = $(element);
1065 | this.el = this.$el[0];
1066 | if (delegate !== false) this.delegateEvents();
1067 | return this;
1068 | },
1069 |
1070 | // Set callbacks, where `this.events` is a hash of
1071 | //
1072 | // *{"event selector": "callback"}*
1073 | //
1074 | // {
1075 | // 'mousedown .title': 'edit',
1076 | // 'click .button': 'save'
1077 | // 'click .open': function(e) { ... }
1078 | // }
1079 | //
1080 | // pairs. Callbacks will be bound to the view, with `this` set properly.
1081 | // Uses event delegation for efficiency.
1082 | // Omitting the selector binds the event to `this.el`.
1083 | // This only works for delegate-able events: not `focus`, `blur`, and
1084 | // not `change`, `submit`, and `reset` in Internet Explorer.
1085 | delegateEvents: function(events) {
1086 | if (!(events || (events = getValue(this, 'events')))) return;
1087 | this.undelegateEvents();
1088 | for (var key in events) {
1089 | var method = events[key];
1090 | if (!_.isFunction(method)) method = this[events[key]];
1091 | if (!method) throw new Error('Event "' + events[key] + '" does not exist');
1092 | var match = key.match(eventSplitter);
1093 | var eventName = match[1], selector = match[2];
1094 | method = _.bind(method, this);
1095 | eventName += '.delegateEvents' + this.cid;
1096 | if (selector === '') {
1097 | this.$el.bind(eventName, method);
1098 | } else {
1099 | this.$el.delegate(selector, eventName, method);
1100 | }
1101 | }
1102 | },
1103 |
1104 | // Clears all callbacks previously bound to the view with `delegateEvents`.
1105 | // You usually don't need to use this, but may wish to if you have multiple
1106 | // Backbone views attached to the same DOM element.
1107 | undelegateEvents: function() {
1108 | this.$el.unbind('.delegateEvents' + this.cid);
1109 | },
1110 |
1111 | // Performs the initial configuration of a View with a set of options.
1112 | // Keys with special meaning *(model, collection, id, className)*, are
1113 | // attached directly to the view.
1114 | _configure: function(options) {
1115 | if (this.options) options = _.extend({}, this.options, options);
1116 | for (var i = 0, l = viewOptions.length; i < l; i++) {
1117 | var attr = viewOptions[i];
1118 | if (options[attr]) this[attr] = options[attr];
1119 | }
1120 | this.options = options;
1121 | },
1122 |
1123 | // Ensure that the View has a DOM element to render into.
1124 | // If `this.el` is a string, pass it through `$()`, take the first
1125 | // matching element, and re-assign it to `el`. Otherwise, create
1126 | // an element from the `id`, `className` and `tagName` properties.
1127 | _ensureElement: function() {
1128 | if (!this.el) {
1129 | var attrs = getValue(this, 'attributes') || {};
1130 | if (this.id) attrs.id = this.id;
1131 | if (this.className) attrs['class'] = this.className;
1132 | this.setElement(this.make(this.tagName, attrs), false);
1133 | } else {
1134 | this.setElement(this.el, false);
1135 | }
1136 | }
1137 |
1138 | });
1139 |
1140 | // The self-propagating extend function that Backbone classes use.
1141 | var extend = function (protoProps, classProps) {
1142 | var child = inherits(this, protoProps, classProps);
1143 | child.extend = this.extend;
1144 | return child;
1145 | };
1146 |
1147 | // Set up inheritance for the model, collection, and view.
1148 | Backbone.Model.extend = Backbone.Collection.extend =
1149 | Backbone.Router.extend = Backbone.View.extend = extend;
1150 |
1151 | // Backbone.sync
1152 | // -------------
1153 |
1154 | // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1155 | var methodMap = {
1156 | 'create': 'POST',
1157 | 'update': 'PUT',
1158 | 'delete': 'DELETE',
1159 | 'read': 'GET'
1160 | };
1161 |
1162 | // Override this function to change the manner in which Backbone persists
1163 | // models to the server. You will be passed the type of request, and the
1164 | // model in question. By default, makes a RESTful Ajax request
1165 | // to the model's `url()`. Some possible customizations could be:
1166 | //
1167 | // * Use `setTimeout` to batch rapid-fire updates into a single request.
1168 | // * Send up the models as XML instead of JSON.
1169 | // * Persist models via WebSockets instead of Ajax.
1170 | //
1171 | // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1172 | // as `POST`, with a `_method` parameter containing the true HTTP method,
1173 | // as well as all requests with the body as `application/x-www-form-urlencoded`
1174 | // instead of `application/json` with the model in a param named `model`.
1175 | // Useful when interfacing with server-side languages like **PHP** that make
1176 | // it difficult to read the body of `PUT` requests.
1177 | Backbone.sync = function(method, model, options) {
1178 | var type = methodMap[method];
1179 |
1180 | // Default JSON-request options.
1181 | var params = {type: type, dataType: 'json'};
1182 |
1183 | // Ensure that we have a URL.
1184 | if (!options.url) {
1185 | params.url = getValue(model, 'url') || urlError();
1186 | }
1187 |
1188 | // Ensure that we have the appropriate request data.
1189 | if (!options.data && model && (method == 'create' || method == 'update')) {
1190 | params.contentType = 'application/json';
1191 | params.data = JSON.stringify(model.toJSON());
1192 | }
1193 |
1194 | // For older servers, emulate JSON by encoding the request into an HTML-form.
1195 | if (Backbone.emulateJSON) {
1196 | params.contentType = 'application/x-www-form-urlencoded';
1197 | params.data = params.data ? {model: params.data} : {};
1198 | }
1199 |
1200 | // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1201 | // And an `X-HTTP-Method-Override` header.
1202 | if (Backbone.emulateHTTP) {
1203 | if (type === 'PUT' || type === 'DELETE') {
1204 | if (Backbone.emulateJSON) params.data._method = type;
1205 | params.type = 'POST';
1206 | params.beforeSend = function(xhr) {
1207 | xhr.setRequestHeader('X-HTTP-Method-Override', type);
1208 | };
1209 | }
1210 | }
1211 |
1212 | // Don't process data on a non-GET request.
1213 | if (params.type !== 'GET' && !Backbone.emulateJSON) {
1214 | params.processData = false;
1215 | }
1216 |
1217 | // Make the request, allowing the user to override any Ajax options.
1218 | return $.ajax(_.extend(params, options));
1219 | };
1220 |
1221 | // Wrap an optional error callback with a fallback error event.
1222 | Backbone.wrapError = function(onError, originalModel, options) {
1223 | return function(model, resp) {
1224 | resp = model === originalModel ? resp : model;
1225 | if (onError) {
1226 | onError(originalModel, resp, options);
1227 | } else {
1228 | originalModel.trigger('error', originalModel, resp, options);
1229 | }
1230 | };
1231 | };
1232 |
1233 | // Helpers
1234 | // -------
1235 |
1236 | // Shared empty constructor function to aid in prototype-chain creation.
1237 | var ctor = function(){};
1238 |
1239 | // Helper function to correctly set up the prototype chain, for subclasses.
1240 | // Similar to `goog.inherits`, but uses a hash of prototype properties and
1241 | // class properties to be extended.
1242 | var inherits = function(parent, protoProps, staticProps) {
1243 | var child;
1244 |
1245 | // The constructor function for the new subclass is either defined by you
1246 | // (the "constructor" property in your `extend` definition), or defaulted
1247 | // by us to simply call the parent's constructor.
1248 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
1249 | child = protoProps.constructor;
1250 | } else {
1251 | child = function(){ parent.apply(this, arguments); };
1252 | }
1253 |
1254 | // Inherit class (static) properties from parent.
1255 | _.extend(child, parent);
1256 |
1257 | // Set the prototype chain to inherit from `parent`, without calling
1258 | // `parent`'s constructor function.
1259 | ctor.prototype = parent.prototype;
1260 | child.prototype = new ctor();
1261 |
1262 | // Add prototype properties (instance properties) to the subclass,
1263 | // if supplied.
1264 | if (protoProps) _.extend(child.prototype, protoProps);
1265 |
1266 | // Add static properties to the constructor function, if supplied.
1267 | if (staticProps) _.extend(child, staticProps);
1268 |
1269 | // Correctly set child's `prototype.constructor`.
1270 | child.prototype.constructor = child;
1271 |
1272 | // Set a convenience property in case the parent's prototype is needed later.
1273 | child.__super__ = parent.prototype;
1274 |
1275 | return child;
1276 | };
1277 |
1278 | // Helper function to get a value from a Backbone object as a property
1279 | // or as a function.
1280 | var getValue = function(object, prop) {
1281 | if (!(object && object[prop])) return null;
1282 | return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1283 | };
1284 |
1285 | // Throw an error when a URL is needed, and none is supplied.
1286 | var urlError = function() {
1287 | throw new Error('A "url" property or function must be specified');
1288 | };
1289 |
1290 | }).call(this);
--------------------------------------------------------------------------------
/webroot/js/lib/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.3.1
2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 |
9 | (function() {
10 |
11 | // Baseline setup
12 | // --------------
13 |
14 | // Establish the root object, `window` in the browser, or `global` on the server.
15 | var root = this;
16 |
17 | // Save the previous value of the `_` variable.
18 | var previousUnderscore = root._;
19 |
20 | // Establish the object that gets returned to break out of a loop iteration.
21 | var breaker = {};
22 |
23 | // Save bytes in the minified (but not gzipped) version:
24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys,
46 | nativeBind = FuncProto.bind;
47 |
48 | // Create a safe reference to the Underscore object for use below.
49 | var _ = function(obj) { return new wrapper(obj); };
50 |
51 | // Export the Underscore object for **Node.js**, with
52 | // backwards-compatibility for the old `require()` API. If we're in
53 | // the browser, add `_` as a global object via a string identifier,
54 | // for Closure Compiler "advanced" mode.
55 | if (typeof exports !== 'undefined') {
56 | if (typeof module !== 'undefined' && module.exports) {
57 | exports = module.exports = _;
58 | }
59 | exports._ = _;
60 | } else {
61 | root['_'] = _;
62 | }
63 |
64 | // Current version.
65 | _.VERSION = '1.3.1';
66 |
67 | // Collection Functions
68 | // --------------------
69 |
70 | // The cornerstone, an `each` implementation, aka `forEach`.
71 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
72 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
73 | var each = _.each = _.forEach = function(obj, iterator, context) {
74 | if (obj == null) return;
75 | if (nativeForEach && obj.forEach === nativeForEach) {
76 | obj.forEach(iterator, context);
77 | } else if (obj.length === +obj.length) {
78 | for (var i = 0, l = obj.length; i < l; i++) {
79 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
80 | }
81 | } else {
82 | for (var key in obj) {
83 | if (_.has(obj, key)) {
84 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
85 | }
86 | }
87 | }
88 | };
89 |
90 | // Return the results of applying the iterator to each element.
91 | // Delegates to **ECMAScript 5**'s native `map` if available.
92 | _.map = _.collect = function(obj, iterator, context) {
93 | var results = [];
94 | if (obj == null) return results;
95 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
96 | each(obj, function(value, index, list) {
97 | results[results.length] = iterator.call(context, value, index, list);
98 | });
99 | if (obj.length === +obj.length) results.length = obj.length;
100 | return results;
101 | };
102 |
103 | // **Reduce** builds up a single result from a list of values, aka `inject`,
104 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
105 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
106 | var initial = arguments.length > 2;
107 | if (obj == null) obj = [];
108 | if (nativeReduce && obj.reduce === nativeReduce) {
109 | if (context) iterator = _.bind(iterator, context);
110 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
111 | }
112 | each(obj, function(value, index, list) {
113 | if (!initial) {
114 | memo = value;
115 | initial = true;
116 | } else {
117 | memo = iterator.call(context, memo, value, index, list);
118 | }
119 | });
120 | if (!initial) throw new TypeError('Reduce of empty array with no initial value');
121 | return memo;
122 | };
123 |
124 | // The right-associative version of reduce, also known as `foldr`.
125 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
126 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
127 | var initial = arguments.length > 2;
128 | if (obj == null) obj = [];
129 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
130 | if (context) iterator = _.bind(iterator, context);
131 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
132 | }
133 | var reversed = _.toArray(obj).reverse();
134 | if (context && !initial) iterator = _.bind(iterator, context);
135 | return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
136 | };
137 |
138 | // Return the first value which passes a truth test. Aliased as `detect`.
139 | _.find = _.detect = function(obj, iterator, context) {
140 | var result;
141 | any(obj, function(value, index, list) {
142 | if (iterator.call(context, value, index, list)) {
143 | result = value;
144 | return true;
145 | }
146 | });
147 | return result;
148 | };
149 |
150 | // Return all the elements that pass a truth test.
151 | // Delegates to **ECMAScript 5**'s native `filter` if available.
152 | // Aliased as `select`.
153 | _.filter = _.select = function(obj, iterator, context) {
154 | var results = [];
155 | if (obj == null) return results;
156 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
157 | each(obj, function(value, index, list) {
158 | if (iterator.call(context, value, index, list)) results[results.length] = value;
159 | });
160 | return results;
161 | };
162 |
163 | // Return all the elements for which a truth test fails.
164 | _.reject = function(obj, iterator, context) {
165 | var results = [];
166 | if (obj == null) return results;
167 | each(obj, function(value, index, list) {
168 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
169 | });
170 | return results;
171 | };
172 |
173 | // Determine whether all of the elements match a truth test.
174 | // Delegates to **ECMAScript 5**'s native `every` if available.
175 | // Aliased as `all`.
176 | _.every = _.all = function(obj, iterator, context) {
177 | var result = true;
178 | if (obj == null) return result;
179 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
180 | each(obj, function(value, index, list) {
181 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
182 | });
183 | return result;
184 | };
185 |
186 | // Determine if at least one element in the object matches a truth test.
187 | // Delegates to **ECMAScript 5**'s native `some` if available.
188 | // Aliased as `any`.
189 | var any = _.some = _.any = function(obj, iterator, context) {
190 | iterator || (iterator = _.identity);
191 | var result = false;
192 | if (obj == null) return result;
193 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
194 | each(obj, function(value, index, list) {
195 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
196 | });
197 | return !!result;
198 | };
199 |
200 | // Determine if a given value is included in the array or object using `===`.
201 | // Aliased as `contains`.
202 | _.include = _.contains = function(obj, target) {
203 | var found = false;
204 | if (obj == null) return found;
205 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
206 | found = any(obj, function(value) {
207 | return value === target;
208 | });
209 | return found;
210 | };
211 |
212 | // Invoke a method (with arguments) on every item in a collection.
213 | _.invoke = function(obj, method) {
214 | var args = slice.call(arguments, 2);
215 | return _.map(obj, function(value) {
216 | return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
217 | });
218 | };
219 |
220 | // Convenience version of a common use case of `map`: fetching a property.
221 | _.pluck = function(obj, key) {
222 | return _.map(obj, function(value){ return value[key]; });
223 | };
224 |
225 | // Return the maximum element or (element-based computation).
226 | _.max = function(obj, iterator, context) {
227 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
228 | if (!iterator && _.isEmpty(obj)) return -Infinity;
229 | var result = {computed : -Infinity};
230 | each(obj, function(value, index, list) {
231 | var computed = iterator ? iterator.call(context, value, index, list) : value;
232 | computed >= result.computed && (result = {value : value, computed : computed});
233 | });
234 | return result.value;
235 | };
236 |
237 | // Return the minimum element (or element-based computation).
238 | _.min = function(obj, iterator, context) {
239 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
240 | if (!iterator && _.isEmpty(obj)) return Infinity;
241 | var result = {computed : Infinity};
242 | each(obj, function(value, index, list) {
243 | var computed = iterator ? iterator.call(context, value, index, list) : value;
244 | computed < result.computed && (result = {value : value, computed : computed});
245 | });
246 | return result.value;
247 | };
248 |
249 | // Shuffle an array.
250 | _.shuffle = function(obj) {
251 | var shuffled = [], rand;
252 | each(obj, function(value, index, list) {
253 | if (index == 0) {
254 | shuffled[0] = value;
255 | } else {
256 | rand = Math.floor(Math.random() * (index + 1));
257 | shuffled[index] = shuffled[rand];
258 | shuffled[rand] = value;
259 | }
260 | });
261 | return shuffled;
262 | };
263 |
264 | // Sort the object's values by a criterion produced by an iterator.
265 | _.sortBy = function(obj, iterator, context) {
266 | return _.pluck(_.map(obj, function(value, index, list) {
267 | return {
268 | value : value,
269 | criteria : iterator.call(context, value, index, list)
270 | };
271 | }).sort(function(left, right) {
272 | var a = left.criteria, b = right.criteria;
273 | return a < b ? -1 : a > b ? 1 : 0;
274 | }), 'value');
275 | };
276 |
277 | // Groups the object's values by a criterion. Pass either a string attribute
278 | // to group by, or a function that returns the criterion.
279 | _.groupBy = function(obj, val) {
280 | var result = {};
281 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
282 | each(obj, function(value, index) {
283 | var key = iterator(value, index);
284 | (result[key] || (result[key] = [])).push(value);
285 | });
286 | return result;
287 | };
288 |
289 | // Use a comparator function to figure out at what index an object should
290 | // be inserted so as to maintain order. Uses binary search.
291 | _.sortedIndex = function(array, obj, iterator) {
292 | iterator || (iterator = _.identity);
293 | var low = 0, high = array.length;
294 | while (low < high) {
295 | var mid = (low + high) >> 1;
296 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
297 | }
298 | return low;
299 | };
300 |
301 | // Safely convert anything iterable into a real, live array.
302 | _.toArray = function(iterable) {
303 | if (!iterable) return [];
304 | if (iterable.toArray) return iterable.toArray();
305 | if (_.isArray(iterable)) return slice.call(iterable);
306 | if (_.isArguments(iterable)) return slice.call(iterable);
307 | return _.values(iterable);
308 | };
309 |
310 | // Return the number of elements in an object.
311 | _.size = function(obj) {
312 | return _.toArray(obj).length;
313 | };
314 |
315 | // Array Functions
316 | // ---------------
317 |
318 | // Get the first element of an array. Passing **n** will return the first N
319 | // values in the array. Aliased as `head`. The **guard** check allows it to work
320 | // with `_.map`.
321 | _.first = _.head = function(array, n, guard) {
322 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
323 | };
324 |
325 | // Returns everything but the last entry of the array. Especcialy useful on
326 | // the arguments object. Passing **n** will return all the values in
327 | // the array, excluding the last N. The **guard** check allows it to work with
328 | // `_.map`.
329 | _.initial = function(array, n, guard) {
330 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
331 | };
332 |
333 | // Get the last element of an array. Passing **n** will return the last N
334 | // values in the array. The **guard** check allows it to work with `_.map`.
335 | _.last = function(array, n, guard) {
336 | if ((n != null) && !guard) {
337 | return slice.call(array, Math.max(array.length - n, 0));
338 | } else {
339 | return array[array.length - 1];
340 | }
341 | };
342 |
343 | // Returns everything but the first entry of the array. Aliased as `tail`.
344 | // Especially useful on the arguments object. Passing an **index** will return
345 | // the rest of the values in the array from that index onward. The **guard**
346 | // check allows it to work with `_.map`.
347 | _.rest = _.tail = function(array, index, guard) {
348 | return slice.call(array, (index == null) || guard ? 1 : index);
349 | };
350 |
351 | // Trim out all falsy values from an array.
352 | _.compact = function(array) {
353 | return _.filter(array, function(value){ return !!value; });
354 | };
355 |
356 | // Return a completely flattened version of an array.
357 | _.flatten = function(array, shallow) {
358 | return _.reduce(array, function(memo, value) {
359 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
360 | memo[memo.length] = value;
361 | return memo;
362 | }, []);
363 | };
364 |
365 | // Return a version of the array that does not contain the specified value(s).
366 | _.without = function(array) {
367 | return _.difference(array, slice.call(arguments, 1));
368 | };
369 |
370 | // Produce a duplicate-free version of the array. If the array has already
371 | // been sorted, you have the option of using a faster algorithm.
372 | // Aliased as `unique`.
373 | _.uniq = _.unique = function(array, isSorted, iterator) {
374 | var initial = iterator ? _.map(array, iterator) : array;
375 | var result = [];
376 | _.reduce(initial, function(memo, el, i) {
377 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
378 | memo[memo.length] = el;
379 | result[result.length] = array[i];
380 | }
381 | return memo;
382 | }, []);
383 | return result;
384 | };
385 |
386 | // Produce an array that contains the union: each distinct element from all of
387 | // the passed-in arrays.
388 | _.union = function() {
389 | return _.uniq(_.flatten(arguments, true));
390 | };
391 |
392 | // Produce an array that contains every item shared between all the
393 | // passed-in arrays. (Aliased as "intersect" for back-compat.)
394 | _.intersection = _.intersect = function(array) {
395 | var rest = slice.call(arguments, 1);
396 | return _.filter(_.uniq(array), function(item) {
397 | return _.every(rest, function(other) {
398 | return _.indexOf(other, item) >= 0;
399 | });
400 | });
401 | };
402 |
403 | // Take the difference between one array and a number of other arrays.
404 | // Only the elements present in just the first array will remain.
405 | _.difference = function(array) {
406 | var rest = _.flatten(slice.call(arguments, 1));
407 | return _.filter(array, function(value){ return !_.include(rest, value); });
408 | };
409 |
410 | // Zip together multiple lists into a single array -- elements that share
411 | // an index go together.
412 | _.zip = function() {
413 | var args = slice.call(arguments);
414 | var length = _.max(_.pluck(args, 'length'));
415 | var results = new Array(length);
416 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
417 | return results;
418 | };
419 |
420 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
421 | // we need this function. Return the position of the first occurrence of an
422 | // item in an array, or -1 if the item is not included in the array.
423 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
424 | // If the array is large and already in sort order, pass `true`
425 | // for **isSorted** to use binary search.
426 | _.indexOf = function(array, item, isSorted) {
427 | if (array == null) return -1;
428 | var i, l;
429 | if (isSorted) {
430 | i = _.sortedIndex(array, item);
431 | return array[i] === item ? i : -1;
432 | }
433 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
434 | for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
435 | return -1;
436 | };
437 |
438 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
439 | _.lastIndexOf = function(array, item) {
440 | if (array == null) return -1;
441 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
442 | var i = array.length;
443 | while (i--) if (i in array && array[i] === item) return i;
444 | return -1;
445 | };
446 |
447 | // Generate an integer Array containing an arithmetic progression. A port of
448 | // the native Python `range()` function. See
449 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
450 | _.range = function(start, stop, step) {
451 | if (arguments.length <= 1) {
452 | stop = start || 0;
453 | start = 0;
454 | }
455 | step = arguments[2] || 1;
456 |
457 | var len = Math.max(Math.ceil((stop - start) / step), 0);
458 | var idx = 0;
459 | var range = new Array(len);
460 |
461 | while(idx < len) {
462 | range[idx++] = start;
463 | start += step;
464 | }
465 |
466 | return range;
467 | };
468 |
469 | // Function (ahem) Functions
470 | // ------------------
471 |
472 | // Reusable constructor function for prototype setting.
473 | var ctor = function(){};
474 |
475 | // Create a function bound to a given object (assigning `this`, and arguments,
476 | // optionally). Binding with arguments is also known as `curry`.
477 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
478 | // We check for `func.bind` first, to fail fast when `func` is undefined.
479 | _.bind = function bind(func, context) {
480 | var bound, args;
481 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
482 | if (!_.isFunction(func)) throw new TypeError;
483 | args = slice.call(arguments, 2);
484 | return bound = function() {
485 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
486 | ctor.prototype = func.prototype;
487 | var self = new ctor;
488 | var result = func.apply(self, args.concat(slice.call(arguments)));
489 | if (Object(result) === result) return result;
490 | return self;
491 | };
492 | };
493 |
494 | // Bind all of an object's methods to that object. Useful for ensuring that
495 | // all callbacks defined on an object belong to it.
496 | _.bindAll = function(obj) {
497 | var funcs = slice.call(arguments, 1);
498 | if (funcs.length == 0) funcs = _.functions(obj);
499 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
500 | return obj;
501 | };
502 |
503 | // Memoize an expensive function by storing its results.
504 | _.memoize = function(func, hasher) {
505 | var memo = {};
506 | hasher || (hasher = _.identity);
507 | return function() {
508 | var key = hasher.apply(this, arguments);
509 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
510 | };
511 | };
512 |
513 | // Delays a function for the given number of milliseconds, and then calls
514 | // it with the arguments supplied.
515 | _.delay = function(func, wait) {
516 | var args = slice.call(arguments, 2);
517 | return setTimeout(function(){ return func.apply(func, args); }, wait);
518 | };
519 |
520 | // Defers a function, scheduling it to run after the current call stack has
521 | // cleared.
522 | _.defer = function(func) {
523 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
524 | };
525 |
526 | // Returns a function, that, when invoked, will only be triggered at most once
527 | // during a given window of time.
528 | _.throttle = function(func, wait) {
529 | var context, args, timeout, throttling, more;
530 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
531 | return function() {
532 | context = this; args = arguments;
533 | var later = function() {
534 | timeout = null;
535 | if (more) func.apply(context, args);
536 | whenDone();
537 | };
538 | if (!timeout) timeout = setTimeout(later, wait);
539 | if (throttling) {
540 | more = true;
541 | } else {
542 | func.apply(context, args);
543 | }
544 | whenDone();
545 | throttling = true;
546 | };
547 | };
548 |
549 | // Returns a function, that, as long as it continues to be invoked, will not
550 | // be triggered. The function will be called after it stops being called for
551 | // N milliseconds.
552 | _.debounce = function(func, wait) {
553 | var timeout;
554 | return function() {
555 | var context = this, args = arguments;
556 | var later = function() {
557 | timeout = null;
558 | func.apply(context, args);
559 | };
560 | clearTimeout(timeout);
561 | timeout = setTimeout(later, wait);
562 | };
563 | };
564 |
565 | // Returns a function that will be executed at most one time, no matter how
566 | // often you call it. Useful for lazy initialization.
567 | _.once = function(func) {
568 | var ran = false, memo;
569 | return function() {
570 | if (ran) return memo;
571 | ran = true;
572 | return memo = func.apply(this, arguments);
573 | };
574 | };
575 |
576 | // Returns the first function passed as an argument to the second,
577 | // allowing you to adjust arguments, run code before and after, and
578 | // conditionally execute the original function.
579 | _.wrap = function(func, wrapper) {
580 | return function() {
581 | var args = [func].concat(slice.call(arguments, 0));
582 | return wrapper.apply(this, args);
583 | };
584 | };
585 |
586 | // Returns a function that is the composition of a list of functions, each
587 | // consuming the return value of the function that follows.
588 | _.compose = function() {
589 | var funcs = arguments;
590 | return function() {
591 | var args = arguments;
592 | for (var i = funcs.length - 1; i >= 0; i--) {
593 | args = [funcs[i].apply(this, args)];
594 | }
595 | return args[0];
596 | };
597 | };
598 |
599 | // Returns a function that will only be executed after being called N times.
600 | _.after = function(times, func) {
601 | if (times <= 0) return func();
602 | return function() {
603 | if (--times < 1) { return func.apply(this, arguments); }
604 | };
605 | };
606 |
607 | // Object Functions
608 | // ----------------
609 |
610 | // Retrieve the names of an object's properties.
611 | // Delegates to **ECMAScript 5**'s native `Object.keys`
612 | _.keys = nativeKeys || function(obj) {
613 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
614 | var keys = [];
615 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
616 | return keys;
617 | };
618 |
619 | // Retrieve the values of an object's properties.
620 | _.values = function(obj) {
621 | return _.map(obj, _.identity);
622 | };
623 |
624 | // Return a sorted list of the function names available on the object.
625 | // Aliased as `methods`
626 | _.functions = _.methods = function(obj) {
627 | var names = [];
628 | for (var key in obj) {
629 | if (_.isFunction(obj[key])) names.push(key);
630 | }
631 | return names.sort();
632 | };
633 |
634 | // Extend a given object with all the properties in passed-in object(s).
635 | _.extend = function(obj) {
636 | each(slice.call(arguments, 1), function(source) {
637 | for (var prop in source) {
638 | obj[prop] = source[prop];
639 | }
640 | });
641 | return obj;
642 | };
643 |
644 | // Fill in a given object with default properties.
645 | _.defaults = function(obj) {
646 | each(slice.call(arguments, 1), function(source) {
647 | for (var prop in source) {
648 | if (obj[prop] == null) obj[prop] = source[prop];
649 | }
650 | });
651 | return obj;
652 | };
653 |
654 | // Create a (shallow-cloned) duplicate of an object.
655 | _.clone = function(obj) {
656 | if (!_.isObject(obj)) return obj;
657 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
658 | };
659 |
660 | // Invokes interceptor with the obj, and then returns obj.
661 | // The primary purpose of this method is to "tap into" a method chain, in
662 | // order to perform operations on intermediate results within the chain.
663 | _.tap = function(obj, interceptor) {
664 | interceptor(obj);
665 | return obj;
666 | };
667 |
668 | // Internal recursive comparison function.
669 | function eq(a, b, stack) {
670 | // Identical objects are equal. `0 === -0`, but they aren't identical.
671 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
672 | if (a === b) return a !== 0 || 1 / a == 1 / b;
673 | // A strict comparison is necessary because `null == undefined`.
674 | if (a == null || b == null) return a === b;
675 | // Unwrap any wrapped objects.
676 | if (a._chain) a = a._wrapped;
677 | if (b._chain) b = b._wrapped;
678 | // Invoke a custom `isEqual` method if one is provided.
679 | if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
680 | if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
681 | // Compare `[[Class]]` names.
682 | var className = toString.call(a);
683 | if (className != toString.call(b)) return false;
684 | switch (className) {
685 | // Strings, numbers, dates, and booleans are compared by value.
686 | case '[object String]':
687 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
688 | // equivalent to `new String("5")`.
689 | return a == String(b);
690 | case '[object Number]':
691 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
692 | // other numeric values.
693 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
694 | case '[object Date]':
695 | case '[object Boolean]':
696 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
697 | // millisecond representations. Note that invalid dates with millisecond representations
698 | // of `NaN` are not equivalent.
699 | return +a == +b;
700 | // RegExps are compared by their source patterns and flags.
701 | case '[object RegExp]':
702 | return a.source == b.source &&
703 | a.global == b.global &&
704 | a.multiline == b.multiline &&
705 | a.ignoreCase == b.ignoreCase;
706 | }
707 | if (typeof a != 'object' || typeof b != 'object') return false;
708 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
709 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
710 | var length = stack.length;
711 | while (length--) {
712 | // Linear search. Performance is inversely proportional to the number of
713 | // unique nested structures.
714 | if (stack[length] == a) return true;
715 | }
716 | // Add the first object to the stack of traversed objects.
717 | stack.push(a);
718 | var size = 0, result = true;
719 | // Recursively compare objects and arrays.
720 | if (className == '[object Array]') {
721 | // Compare array lengths to determine if a deep comparison is necessary.
722 | size = a.length;
723 | result = size == b.length;
724 | if (result) {
725 | // Deep compare the contents, ignoring non-numeric properties.
726 | while (size--) {
727 | // Ensure commutative equality for sparse arrays.
728 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
729 | }
730 | }
731 | } else {
732 | // Objects with different constructors are not equivalent.
733 | if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
734 | // Deep compare objects.
735 | for (var key in a) {
736 | if (_.has(a, key)) {
737 | // Count the expected number of properties.
738 | size++;
739 | // Deep compare each member.
740 | if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
741 | }
742 | }
743 | // Ensure that both objects contain the same number of properties.
744 | if (result) {
745 | for (key in b) {
746 | if (_.has(b, key) && !(size--)) break;
747 | }
748 | result = !size;
749 | }
750 | }
751 | // Remove the first object from the stack of traversed objects.
752 | stack.pop();
753 | return result;
754 | }
755 |
756 | // Perform a deep comparison to check if two objects are equal.
757 | _.isEqual = function(a, b) {
758 | return eq(a, b, []);
759 | };
760 |
761 | // Is a given array, string, or object empty?
762 | // An "empty" object has no enumerable own-properties.
763 | _.isEmpty = function(obj) {
764 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
765 | for (var key in obj) if (_.has(obj, key)) return false;
766 | return true;
767 | };
768 |
769 | // Is a given value a DOM element?
770 | _.isElement = function(obj) {
771 | return !!(obj && obj.nodeType == 1);
772 | };
773 |
774 | // Is a given value an array?
775 | // Delegates to ECMA5's native Array.isArray
776 | _.isArray = nativeIsArray || function(obj) {
777 | return toString.call(obj) == '[object Array]';
778 | };
779 |
780 | // Is a given variable an object?
781 | _.isObject = function(obj) {
782 | return obj === Object(obj);
783 | };
784 |
785 | // Is a given variable an arguments object?
786 | _.isArguments = function(obj) {
787 | return toString.call(obj) == '[object Arguments]';
788 | };
789 | if (!_.isArguments(arguments)) {
790 | _.isArguments = function(obj) {
791 | return !!(obj && _.has(obj, 'callee'));
792 | };
793 | }
794 |
795 | // Is a given value a function?
796 | _.isFunction = function(obj) {
797 | return toString.call(obj) == '[object Function]';
798 | };
799 |
800 | // Is a given value a string?
801 | _.isString = function(obj) {
802 | return toString.call(obj) == '[object String]';
803 | };
804 |
805 | // Is a given value a number?
806 | _.isNumber = function(obj) {
807 | return toString.call(obj) == '[object Number]';
808 | };
809 |
810 | // Is the given value `NaN`?
811 | _.isNaN = function(obj) {
812 | // `NaN` is the only value for which `===` is not reflexive.
813 | return obj !== obj;
814 | };
815 |
816 | // Is a given value a boolean?
817 | _.isBoolean = function(obj) {
818 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
819 | };
820 |
821 | // Is a given value a date?
822 | _.isDate = function(obj) {
823 | return toString.call(obj) == '[object Date]';
824 | };
825 |
826 | // Is the given value a regular expression?
827 | _.isRegExp = function(obj) {
828 | return toString.call(obj) == '[object RegExp]';
829 | };
830 |
831 | // Is a given value equal to null?
832 | _.isNull = function(obj) {
833 | return obj === null;
834 | };
835 |
836 | // Is a given variable undefined?
837 | _.isUndefined = function(obj) {
838 | return obj === void 0;
839 | };
840 |
841 | // Has own property?
842 | _.has = function(obj, key) {
843 | return hasOwnProperty.call(obj, key);
844 | };
845 |
846 | // Utility Functions
847 | // -----------------
848 |
849 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
850 | // previous owner. Returns a reference to the Underscore object.
851 | _.noConflict = function() {
852 | root._ = previousUnderscore;
853 | return this;
854 | };
855 |
856 | // Keep the identity function around for default iterators.
857 | _.identity = function(value) {
858 | return value;
859 | };
860 |
861 | // Run a function **n** times.
862 | _.times = function (n, iterator, context) {
863 | for (var i = 0; i < n; i++) iterator.call(context, i);
864 | };
865 |
866 | // Escape a string for HTML interpolation.
867 | _.escape = function(string) {
868 | return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
869 | };
870 |
871 | // Add your own custom functions to the Underscore object, ensuring that
872 | // they're correctly added to the OOP wrapper as well.
873 | _.mixin = function(obj) {
874 | each(_.functions(obj), function(name){
875 | addToWrapper(name, _[name] = obj[name]);
876 | });
877 | };
878 |
879 | // Generate a unique integer id (unique within the entire client session).
880 | // Useful for temporary DOM ids.
881 | var idCounter = 0;
882 | _.uniqueId = function(prefix) {
883 | var id = idCounter++;
884 | return prefix ? prefix + id : id;
885 | };
886 |
887 | // By default, Underscore uses ERB-style template delimiters, change the
888 | // following template settings to use alternative delimiters.
889 | _.templateSettings = {
890 | evaluate : /<%([\s\S]+?)%>/g,
891 | interpolate : /<%=([\s\S]+?)%>/g,
892 | escape : /<%-([\s\S]+?)%>/g
893 | };
894 |
895 | // When customizing `templateSettings`, if you don't want to define an
896 | // interpolation, evaluation or escaping regex, we need one that is
897 | // guaranteed not to match.
898 | var noMatch = /.^/;
899 |
900 | // Within an interpolation, evaluation, or escaping, remove HTML escaping
901 | // that had been previously added.
902 | var unescape = function(code) {
903 | return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
904 | };
905 |
906 | // JavaScript micro-templating, similar to John Resig's implementation.
907 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
908 | // and correctly escapes quotes within interpolated code.
909 | _.template = function(str, data) {
910 | var c = _.templateSettings;
911 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
912 | 'with(obj||{}){__p.push(\'' +
913 | str.replace(/\\/g, '\\\\')
914 | .replace(/'/g, "\\'")
915 | .replace(c.escape || noMatch, function(match, code) {
916 | return "',_.escape(" + unescape(code) + "),'";
917 | })
918 | .replace(c.interpolate || noMatch, function(match, code) {
919 | return "'," + unescape(code) + ",'";
920 | })
921 | .replace(c.evaluate || noMatch, function(match, code) {
922 | return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
923 | })
924 | .replace(/\r/g, '\\r')
925 | .replace(/\n/g, '\\n')
926 | .replace(/\t/g, '\\t')
927 | + "');}return __p.join('');";
928 | var func = new Function('obj', '_', tmpl);
929 | if (data) return func(data, _);
930 | return function(data) {
931 | return func.call(this, data, _);
932 | };
933 | };
934 |
935 | // Add a "chain" function, which will delegate to the wrapper.
936 | _.chain = function(obj) {
937 | return _(obj).chain();
938 | };
939 |
940 | // The OOP Wrapper
941 | // ---------------
942 |
943 | // If Underscore is called as a function, it returns a wrapped object that
944 | // can be used OO-style. This wrapper holds altered versions of all the
945 | // underscore functions. Wrapped objects may be chained.
946 | var wrapper = function(obj) { this._wrapped = obj; };
947 |
948 | // Expose `wrapper.prototype` as `_.prototype`
949 | _.prototype = wrapper.prototype;
950 |
951 | // Helper function to continue chaining intermediate results.
952 | var result = function(obj, chain) {
953 | return chain ? _(obj).chain() : obj;
954 | };
955 |
956 | // A method to easily add functions to the OOP wrapper.
957 | var addToWrapper = function(name, func) {
958 | wrapper.prototype[name] = function() {
959 | var args = slice.call(arguments);
960 | unshift.call(args, this._wrapped);
961 | return result(func.apply(_, args), this._chain);
962 | };
963 | };
964 |
965 | // Add all of the Underscore functions to the wrapper object.
966 | _.mixin(_);
967 |
968 | // Add all mutator Array functions to the wrapper.
969 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
970 | var method = ArrayProto[name];
971 | wrapper.prototype[name] = function() {
972 | var wrapped = this._wrapped;
973 | method.apply(wrapped, arguments);
974 | var length = wrapped.length;
975 | if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
976 | return result(wrapped, this._chain);
977 | };
978 | });
979 |
980 | // Add all accessor Array functions to the wrapper.
981 | each(['concat', 'join', 'slice'], function(name) {
982 | var method = ArrayProto[name];
983 | wrapper.prototype[name] = function() {
984 | return result(method.apply(this._wrapped, arguments), this._chain);
985 | };
986 | });
987 |
988 | // Start chaining a wrapped Underscore object.
989 | wrapper.prototype.chain = function() {
990 | this._chain = true;
991 | return this;
992 | };
993 |
994 | // Extracts the result from a wrapped and chained object.
995 | wrapper.prototype.value = function() {
996 | return this._wrapped;
997 | };
998 |
999 | }).call(this);
--------------------------------------------------------------------------------