// Backbone.View // ------------- // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... Backbone.View = function(options) { this.cid = _.uniqueId('view'); this._configure(options || {}); this._ensureElement(); this.delegateEvents(); this.initialize.apply(this, arguments); }; // Element lookup, scoped to DOM elements within the current view. // This should be prefered to global lookups, if you're dealing with // a specific view. var selectorDelegate = function(selector) { return $(selector, this.el); }; // Cached regex to split keys for `delegate`. var eventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(Backbone.View.prototype, Backbone.Events, { // The default `tagName` of a View's element is `"div"`. tagName : 'div', // Attach the `selectorDelegate` function as the `$` property. $ : selectorDelegate, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize : function(){}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render : function() { return this; }, // Remove this view from the DOM. Note that the view isn't present in the // DOM by default, so calling this method may be a no-op. remove : function() { $(this.el).remove(); return this; }, // For small amounts of DOM Elements, where a full-blown template isn't // needed, use **make** to manufacture elements, one at a time. // // var el = this.make('li', {'class': 'row'}, this.model.escape('title')); // make : function(tagName, attributes, content) { var el = document.createElement(tagName); if (attributes) $(el).attr(attributes); if (content) $(el).html(content); return el; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save' // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents : function(events) { if (!(events || (events = this.events))) return; if (_.isFunction(events)) events = events.call(this); this.undelegateEvents(); for (var key in events) { var method = this[events[key]]; if (!method) throw new Error('Event "' + events[key] + '" does not exist'); var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; if (selector === '') { $(this.el).bind(eventName, method); } else { $(this.el).delegate(selector, eventName, method); } } }, // Clears all callbacks previously bound to the view with `delegateEvents`. undelegateEvents: function() { $(this.el).unbind('.delegateEvents' + this.cid); }, // Performs the initial configuration of a View with a set of options. // Keys with special meaning *(model, collection, id, className)*, are // attached directly to the view. _configure : function(options) { if (this.options) options = _.extend({}, this.options, options); for (var i = 0, l = viewOptions.length; i < l; i++) { var attr = viewOptions[i]; if (options[attr]) this[attr] = options[attr]; } this.options = options; }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement : function() { if (!this.el) { var attrs = this.attributes || {}; if (this.id) attrs.id = this.id; if (this.className) attrs['class'] = this.className; this.el = this.make(this.tagName, attrs); } else if (_.isString(this.el)) { this.el = $(this.el).get(0); } } }); // The self-propagating extend function that Backbone classes use. var extend = function (protoProps, classProps) { var child = inherits(this, protoProps, classProps); child.extend = this.extend; return child; }; // Set up inheritance for the model, collection, and view. Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = extend; // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'delete': 'DELETE', 'read' : 'GET' };