pubba 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,623 @@
1
+ // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
2
+ // Backbone may be freely distributed under the MIT license.
3
+ // For all details and documentation:
4
+ // http://documentcloud.github.com/backbone
5
+
6
+ (function(){
7
+
8
+ // Initial Setup
9
+ // -------------
10
+
11
+ // The top-level namespace.
12
+ var Backbone = {};
13
+
14
+ // Keep the version here in sync with `package.json`.
15
+ Backbone.VERSION = '0.1.1';
16
+
17
+ // Export for both CommonJS and the browser.
18
+ (typeof exports !== 'undefined' ? exports : this).Backbone = Backbone;
19
+
20
+ // Require Underscore, if we're on the server, and it's not already present.
21
+ var _ = this._;
22
+ if (!_ && (typeof require !== 'undefined')) _ = require("underscore")._;
23
+
24
+ // For Backbone's purposes, jQuery owns the `$` variable.
25
+ var $ = this.$;
26
+
27
+ // Helper function to correctly set up the prototype chain, for subclasses.
28
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
29
+ // class properties to be extended.
30
+ var inherits = function(parent, protoProps, classProps) {
31
+ var child = protoProps.hasOwnProperty('constructor') ? protoProps.constructor :
32
+ function(){ return parent.apply(this, arguments); };
33
+ var ctor = function(){};
34
+ ctor.prototype = parent.prototype;
35
+ child.prototype = new ctor();
36
+ _.extend(child.prototype, protoProps);
37
+ if (classProps) _.extend(child, classProps);
38
+ child.prototype.constructor = child;
39
+ return child;
40
+ };
41
+
42
+ // Helper function to get a URL from a Model or Collection as a property
43
+ // or as a function.
44
+ var getUrl = function(object) {
45
+ return _.isFunction(object.url) ? object.url() : object.url;
46
+ };
47
+
48
+ // Backbone.Events
49
+ // -----------------
50
+
51
+ // A module that can be mixed in to *any object* in order to provide it with
52
+ // custom events. You may `bind` or `unbind` a callback function to an event;
53
+ // `trigger`-ing an event fires all callbacks in succession.
54
+ //
55
+ // var object = {};
56
+ // _.extend(object, Backbone.Events);
57
+ // object.bind('expand', function(){ alert('expanded'); });
58
+ // object.trigger('expand');
59
+ //
60
+ Backbone.Events = {
61
+
62
+ // Bind an event, specified by a string name, `ev`, to a `callback` function.
63
+ // Passing `"all"` will bind the callback to all events fired.
64
+ bind : function(ev, callback) {
65
+ var calls = this._callbacks || (this._callbacks = {});
66
+ var list = this._callbacks[ev] || (this._callbacks[ev] = []);
67
+ list.push(callback);
68
+ return this;
69
+ },
70
+
71
+ // Remove one or many callbacks. If `callback` is null, removes all
72
+ // callbacks for the event. If `ev` is null, removes all bound callbacks
73
+ // for all events.
74
+ unbind : function(ev, callback) {
75
+ var calls;
76
+ if (!ev) {
77
+ this._callbacks = {};
78
+ } else if (calls = this._callbacks) {
79
+ if (!callback) {
80
+ calls[ev] = [];
81
+ } else {
82
+ var list = calls[ev];
83
+ if (!list) return this;
84
+ for (var i = 0, l = list.length; i < l; i++) {
85
+ if (callback === list[i]) {
86
+ list.splice(i, 1);
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ return this;
93
+ },
94
+
95
+ // Trigger an event, firing all bound callbacks. Callbacks are passed the
96
+ // same arguments as `trigger` is, apart from the event name.
97
+ // Listening for `"all"` passes the true event name as the first argument.
98
+ trigger : function(ev) {
99
+ var list, calls, i, l;
100
+ var calls = this._callbacks;
101
+ if (!(calls = this._callbacks)) return this;
102
+ if (list = calls[ev]) {
103
+ for (i = 0, l = list.length; i < l; i++) {
104
+ list[i].apply(this, _.rest(arguments));
105
+ }
106
+ }
107
+ if (list = calls['all']) {
108
+ for (i = 0, l = list.length; i < l; i++) {
109
+ list[i].apply(this, arguments);
110
+ }
111
+ }
112
+ return this;
113
+ }
114
+
115
+ };
116
+
117
+ // Backbone.Model
118
+ // --------------
119
+
120
+ // Create a new model, with defined attributes. A client id (`cid`)
121
+ // is automatically generated and assigned for you.
122
+ Backbone.Model = function(attributes) {
123
+ this.attributes = {};
124
+ this.cid = _.uniqueId('c');
125
+ this.set(attributes || {}, {silent : true});
126
+ this._previousAttributes = _.clone(this.attributes);
127
+ if (this.initialize) this.initialize(attributes);
128
+ };
129
+
130
+ // Attach all inheritable methods to the Model prototype.
131
+ _.extend(Backbone.Model.prototype, Backbone.Events, {
132
+
133
+ // A snapshot of the model's previous attributes, taken immediately
134
+ // after the last `changed` event was fired.
135
+ _previousAttributes : null,
136
+
137
+ // Has the item been changed since the last `changed` event?
138
+ _changed : false,
139
+
140
+ // Return a copy of the model's `attributes` object.
141
+ toJSON : function() {
142
+ return _.clone(this.attributes);
143
+ },
144
+
145
+ // Get the value of an attribute.
146
+ get : function(attr) {
147
+ return this.attributes[attr];
148
+ },
149
+
150
+ // Set a hash of model attributes on the object, firing `changed` unless you
151
+ // choose to silence it.
152
+ set : function(attrs, options) {
153
+
154
+ // Extract attributes and options.
155
+ options || (options = {});
156
+ if (!attrs) return this;
157
+ attrs = attrs.attributes || attrs;
158
+ var now = this.attributes;
159
+
160
+ // Run validation if `validate` is defined.
161
+ if (this.validate) {
162
+ var error = this.validate(attrs);
163
+ if (error) {
164
+ this.trigger('error', this, error);
165
+ return false;
166
+ }
167
+ }
168
+
169
+ // Check for changes of `id`.
170
+ if ('id' in attrs) this.id = attrs.id;
171
+
172
+ // Update attributes.
173
+ for (var attr in attrs) {
174
+ var val = attrs[attr];
175
+ if (val === '') val = null;
176
+ if (!_.isEqual(now[attr], val)) {
177
+ now[attr] = val;
178
+ if (!options.silent) {
179
+ this._changed = true;
180
+ this.trigger('change:' + attr, this, val);
181
+ }
182
+ }
183
+ }
184
+
185
+ // Fire the `change` event, if the model has been changed.
186
+ if (!options.silent && this._changed) this.change();
187
+ return this;
188
+ },
189
+
190
+ // Remove an attribute from the model, firing `changed` unless you choose to
191
+ // silence it.
192
+ unset : function(attr, options) {
193
+ options || (options = {});
194
+ var value = this.attributes[attr];
195
+ delete this.attributes[attr];
196
+ if (!options.silent) {
197
+ this._changed = true;
198
+ this.trigger('change:' + attr, this);
199
+ this.change();
200
+ }
201
+ return value;
202
+ },
203
+
204
+ // Set a hash of model attributes, and sync the model to the server.
205
+ // If the server returns an attributes hash that differs, the model's
206
+ // state will be `set` again.
207
+ save : function(attrs, options) {
208
+ attrs || (attrs = {});
209
+ options || (options = {});
210
+ if (!this.set(attrs, options)) return false;
211
+ var model = this;
212
+ var success = function(resp) {
213
+ if (!model.set(resp.model)) return false;
214
+ if (options.success) options.success(model, resp);
215
+ };
216
+ var method = this.isNew() ? 'create' : 'update';
217
+ Backbone.sync(method, this, success, options.error);
218
+ return this;
219
+ },
220
+
221
+ // Destroy this model on the server. Upon success, the model is removed
222
+ // from its collection, if it has one.
223
+ destroy : function(options) {
224
+ options || (options = {});
225
+ var model = this;
226
+ var success = function(resp) {
227
+ if (model.collection) model.collection.remove(model);
228
+ if (options.success) options.success(model, resp);
229
+ };
230
+ Backbone.sync('delete', this, success, options.error);
231
+ return this;
232
+ },
233
+
234
+ // Default URL for the model's representation on the server -- if you're
235
+ // using Backbone's restful methods, override this to change the endpoint
236
+ // that will be called.
237
+ url : function() {
238
+ var base = getUrl(this.collection);
239
+ if (this.isNew()) return base;
240
+ return base + '/' + this.id;
241
+ },
242
+
243
+ // Create a new model with identical attributes to this one.
244
+ clone : function() {
245
+ return new this.constructor(this);
246
+ },
247
+
248
+ // A model is new if it has never been saved to the server, and has a negative
249
+ // ID.
250
+ isNew : function() {
251
+ return !this.id;
252
+ },
253
+
254
+ // Call this method to fire manually fire a `change` event for this model.
255
+ // Calling this will cause all objects observing the model to update.
256
+ change : function() {
257
+ this.trigger('change', this);
258
+ this._previousAttributes = _.clone(this.attributes);
259
+ this._changed = false;
260
+ },
261
+
262
+ // Determine if the model has changed since the last `changed` event.
263
+ // If you specify an attribute name, determine if that attribute has changed.
264
+ hasChanged : function(attr) {
265
+ if (attr) return this._previousAttributes[attr] != this.attributes[attr];
266
+ return this._changed;
267
+ },
268
+
269
+ // Return an object containing all the attributes that have changed, or false
270
+ // if there are no changed attributes. Useful for determining what parts of a
271
+ // view need to be updated and/or what attributes need to be persisted to
272
+ // the server.
273
+ changedAttributes : function(now) {
274
+ var old = this._previousAttributes, now = now || this.attributes, changed = false;
275
+ for (var attr in now) {
276
+ if (!_.isEqual(old[attr], now[attr])) {
277
+ changed = changed || {};
278
+ changed[attr] = now[attr];
279
+ }
280
+ }
281
+ return changed;
282
+ },
283
+
284
+ // Get the previous value of an attribute, recorded at the time the last
285
+ // `changed` event was fired.
286
+ previous : function(attr) {
287
+ if (!attr || !this._previousAttributes) return null;
288
+ return this._previousAttributes[attr];
289
+ },
290
+
291
+ // Get all of the attributes of the model at the time of the previous
292
+ // `changed` event.
293
+ previousAttributes : function() {
294
+ return _.clone(this._previousAttributes);
295
+ }
296
+
297
+ });
298
+
299
+ // Backbone.Collection
300
+ // -------------------
301
+
302
+ // Provides a standard collection class for our sets of models, ordered
303
+ // or unordered. If a `comparator` is specified, the Collection will maintain
304
+ // its models in sort order, as they're added and removed.
305
+ Backbone.Collection = function(models, options) {
306
+ options || (options = {});
307
+ if (options.comparator) {
308
+ this.comparator = options.comparator;
309
+ delete options.comparator;
310
+ }
311
+ this._boundOnModelEvent = _.bind(this._onModelEvent, this);
312
+ this._reset();
313
+ if (models) this.refresh(models, {silent: true});
314
+ if (this.initialize) this.initialize(models, options);
315
+ };
316
+
317
+ // Define the Collection's inheritable methods.
318
+ _.extend(Backbone.Collection.prototype, Backbone.Events, {
319
+
320
+ model : Backbone.Model,
321
+
322
+ // Add a model, or list of models to the set. Pass **silent** to avoid
323
+ // firing the `added` event for every new model.
324
+ add : function(models, options) {
325
+ if (!_.isArray(models)) return this._add(models, options);
326
+ for (var i=0; i<models.length; i++) this._add(models[i], options);
327
+ return models;
328
+ },
329
+
330
+ // Remove a model, or a list of models from the set. Pass silent to avoid
331
+ // firing the `removed` event for every model removed.
332
+ remove : function(models, options) {
333
+ if (!_.isArray(models)) return this._remove(models, options);
334
+ for (var i=0; i<models.length; i++) this._remove(models[i], options);
335
+ return models;
336
+ },
337
+
338
+ // Get a model from the set by id.
339
+ get : function(id) {
340
+ return id && this._byId[id.id != null ? id.id : id];
341
+ },
342
+
343
+ // Get a model from the set by client id.
344
+ getByCid : function(cid) {
345
+ return cid && this._byCid[cid.cid || cid];
346
+ },
347
+
348
+ // Get the model at the given index.
349
+ at: function(index) {
350
+ return this.models[index];
351
+ },
352
+
353
+ // Force the collection to re-sort itself. You don't need to call this under normal
354
+ // circumstances, as the set will maintain sort order as each item is added.
355
+ sort : function(options) {
356
+ options || (options = {});
357
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
358
+ this.models = this.sortBy(this.comparator);
359
+ if (!options.silent) this.trigger('refresh', this);
360
+ return this;
361
+ },
362
+
363
+ // Pluck an attribute from each model in the collection.
364
+ pluck : function(attr) {
365
+ return _.map(this.models, function(model){ return model.get(attr); });
366
+ },
367
+
368
+ // When you have more items than you want to add or remove individually,
369
+ // you can refresh the entire set with a new list of models, without firing
370
+ // any `added` or `removed` events. Fires `refresh` when finished.
371
+ refresh : function(models, options) {
372
+ options || (options = {});
373
+ models = models || [];
374
+ var collection = this;
375
+ if (models[0] && !(models[0] instanceof Backbone.Model)) {
376
+ models = _.map(models, function(attrs, i) {
377
+ return new collection.model(attrs);
378
+ });
379
+ }
380
+ this._reset();
381
+ this.add(models, {silent: true});
382
+ if (!options.silent) this.trigger('refresh', this);
383
+ return this;
384
+ },
385
+
386
+ // Fetch the default set of models for this collection, refreshing the
387
+ // collection when they arrive.
388
+ fetch : function(options) {
389
+ options || (options = {});
390
+ var collection = this;
391
+ var success = function(resp) {
392
+ collection.refresh(resp.models);
393
+ if (options.success) options.success(collection, resp);
394
+ };
395
+ Backbone.sync('read', this, success, options.error);
396
+ return this;
397
+ },
398
+
399
+ // Create a new instance of a model in this collection. After the model
400
+ // has been created on the server, it will be added to the collection.
401
+ create : function(model, options) {
402
+ options || (options = {});
403
+ if (!(model instanceof Backbone.Model)) model = new this.model(model);
404
+ model.collection = this;
405
+ var success = function(resp) {
406
+ if (!model.set(resp.model)) return false;
407
+ model.collection.add(model);
408
+ if (options.success) options.success(model, resp);
409
+ };
410
+ return model.save(null, {success : success, error : options.error});
411
+ },
412
+
413
+ // Reset all internal state. Called when the collection is refreshed.
414
+ _reset : function(options) {
415
+ this.length = 0;
416
+ this.models = [];
417
+ this._byId = {};
418
+ this._byCid = {};
419
+ },
420
+
421
+ // Internal implementation of adding a single model to the set, updating
422
+ // hash indexes for `id` and `cid` lookups.
423
+ _add : function(model, options) {
424
+ options || (options = {});
425
+ var already = this.get(model);
426
+ if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
427
+ this._byId[model.id] = model;
428
+ this._byCid[model.cid] = model;
429
+ model.collection = this;
430
+ var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
431
+ this.models.splice(index, 0, model);
432
+ model.bind('all', this._boundOnModelEvent);
433
+ this.length++;
434
+ if (!options.silent) this.trigger('add', model);
435
+ return model;
436
+ },
437
+
438
+ // Internal implementation of removing a single model from the set, updating
439
+ // hash indexes for `id` and `cid` lookups.
440
+ _remove : function(model, options) {
441
+ options || (options = {});
442
+ model = this.get(model);
443
+ if (!model) return null;
444
+ delete this._byId[model.id];
445
+ delete this._byCid[model.cid];
446
+ delete model.collection;
447
+ this.models.splice(this.indexOf(model), 1);
448
+ model.unbind('all', this._boundOnModelEvent);
449
+ this.length--;
450
+ if (!options.silent) this.trigger('remove', model);
451
+ return model;
452
+ },
453
+
454
+ // Internal method called every time a model in the set fires an event.
455
+ // Sets need to update their indexes when models change ids.
456
+ _onModelEvent : function(ev, model, error) {
457
+ switch (ev) {
458
+ case 'change':
459
+ if (model.hasChanged('id')) {
460
+ delete this._byId[model.previous('id')];
461
+ this._byId[model.id] = model;
462
+ }
463
+ this.trigger('change', model);
464
+ break;
465
+ case 'error':
466
+ this.trigger('error', model, error);
467
+ }
468
+ }
469
+
470
+ });
471
+
472
+ // Underscore methods that we want to implement on the Collection.
473
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
474
+ 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
475
+ 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
476
+ 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty'];
477
+
478
+ // Mix in each Underscore method as a proxy to `Collection#models`.
479
+ _.each(methods, function(method) {
480
+ Backbone.Collection.prototype[method] = function() {
481
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
482
+ };
483
+ });
484
+
485
+ // Backbone.View
486
+ // -------------
487
+
488
+ // Creating a Backbone.View creates its initial element outside of the DOM,
489
+ // if an existing element is not provided...
490
+ Backbone.View = function(options) {
491
+ this._configure(options || {});
492
+ if (this.options.el) {
493
+ this.el = this.options.el;
494
+ } else {
495
+ var attrs = {};
496
+ if (this.id) attrs.id = this.id;
497
+ if (this.className) attrs.className = this.className;
498
+ this.el = this.make(this.tagName, attrs);
499
+ }
500
+ if (this.initialize) this.initialize(options);
501
+ };
502
+
503
+ // jQuery lookup, scoped to DOM elements within the current view.
504
+ // This should be prefered to global jQuery lookups, if you're dealing with
505
+ // a specific view.
506
+ var jQueryDelegate = function(selector) {
507
+ return $(selector, this.el);
508
+ };
509
+
510
+ // Cached regex to split keys for `handleEvents`.
511
+ var eventSplitter = /^(\w+)\s*(.*)$/;
512
+
513
+ // Set up all inheritable **Backbone.View** properties and methods.
514
+ _.extend(Backbone.View.prototype, {
515
+
516
+ // The default `tagName` of a View's element is `"div"`.
517
+ tagName : 'div',
518
+
519
+ // Attach the jQuery function as the `$` and `jQuery` properties.
520
+ $ : jQueryDelegate,
521
+ jQuery : jQueryDelegate,
522
+
523
+ // **render** is the core function that your view should override, in order
524
+ // to populate its element (`this.el`), with the appropriate HTML. The
525
+ // convention is for **render** to always return `this`.
526
+ render : function() {
527
+ return this;
528
+ },
529
+
530
+ // For small amounts of DOM Elements, where a full-blown template isn't
531
+ // needed, use **make** to manufacture elements, one at a time.
532
+ //
533
+ // var el = this.make('li', {'class': 'row'}, this.model.get('title'));
534
+ //
535
+ make : function(tagName, attributes, content) {
536
+ var el = document.createElement(tagName);
537
+ if (attributes) $(el).attr(attributes);
538
+ if (content) $(el).html(content);
539
+ return el;
540
+ },
541
+
542
+ // Set callbacks, where `this.callbacks` is a hash of
543
+ //
544
+ // *{"event selector": "callback"}*
545
+ //
546
+ // {
547
+ // 'mousedown .title': 'edit',
548
+ // 'click .button': 'save'
549
+ // }
550
+ //
551
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
552
+ // Uses jQuery event delegation for efficiency.
553
+ // Omitting the selector binds the event to `this.el`.
554
+ // `"change"` events are not delegated through the view because IE does not
555
+ // bubble change events at all.
556
+ handleEvents : function(events) {
557
+ $(this.el).unbind();
558
+ if (!(events || (events = this.events))) return this;
559
+ for (key in events) {
560
+ var methodName = events[key];
561
+ var match = key.match(eventSplitter);
562
+ var eventName = match[1], selector = match[2];
563
+ var method = _.bind(this[methodName], this);
564
+ if (selector === '' || eventName == 'change') {
565
+ $(this.el).bind(eventName, method);
566
+ } else {
567
+ $(this.el).delegate(selector, eventName, method);
568
+ }
569
+ }
570
+ return this;
571
+ },
572
+
573
+ // Performs the initial configuration of a View with a set of options.
574
+ // Keys with special meaning *(model, collection, id, className)*, are
575
+ // attached directly to the view.
576
+ _configure : function(options) {
577
+ if (this.options) options = _.extend({}, this.options, options);
578
+ if (options.model) this.model = options.model;
579
+ if (options.collection) this.collection = options.collection;
580
+ if (options.id) this.id = options.id;
581
+ if (options.className) this.className = options.className;
582
+ if (options.tagName) this.tagName = options.tagName;
583
+ this.options = options;
584
+ }
585
+
586
+ });
587
+
588
+ // Set up inheritance for the model, collection, and view.
589
+ var extend = Backbone.Model.extend = Backbone.Collection.extend = Backbone.View.extend = function (protoProps, classProps) {
590
+ var child = inherits(this, protoProps, classProps);
591
+ child.extend = extend;
592
+ return child;
593
+ };
594
+
595
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
596
+ var methodMap = {
597
+ 'create': 'POST',
598
+ 'update': 'PUT',
599
+ 'delete': 'DELETE',
600
+ 'read' : 'GET'
601
+ };
602
+
603
+ // Override this function to change the manner in which Backbone persists
604
+ // models to the server. You will be passed the type of request, and the
605
+ // model in question. By default, uses jQuery to make a RESTful Ajax request
606
+ // to the model's `url()`. Some possible customizations could be:
607
+ //
608
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
609
+ // * Send up the models as XML instead of JSON.
610
+ // * Persist models via WebSockets instead of Ajax.
611
+ //
612
+ Backbone.sync = function(method, model, success, error) {
613
+ $.ajax({
614
+ url : getUrl(model),
615
+ type : methodMap[method],
616
+ data : {model : JSON.stringify(model)},
617
+ dataType : 'json',
618
+ success : success,
619
+ error : error
620
+ });
621
+ };
622
+
623
+ })();