1 // Backbone.View
2 // -------------
3
4 // Backbone Views are almost more convention than they are actual code. A View
5 // is simply a JavaScript object that represents a logical chunk of UI in the
6 // DOM. This might be a single item, an entire list, a sidebar or panel, or
7 // even the surrounding frame which wraps your whole app. Defining a chunk of
8 // UI as a **View** allows you to define your DOM events declaratively, without
9 // having to worry about render order ... and makes it easy for the view to
10 // react to specific changes in the state of your models.
11
12 // Creating a Backbone.View creates its initial element outside of the DOM,
13 // if an existing element is not provided...
14 //option中可以传递的参数包括
15 //{model:模型名称,
16 // collection:集合名称,
17 // el:要将html模板添加到的元素,
18 // id:el的id属性,若没有el则默认创建id为这个的div,其属性为下面的attributes,tagName,className,
19 // attributes:
20 // tagName:
21 // className:
22 // events:该元素绑定的事件},
23 // 在执行自定义的initialize时会将这些属性,events绑定到元素上
24 var View = Backbone.View = function(options) {
25 this.cid = _.uniqueId('view');
26 this.preinitialize.apply(this, arguments);
27 _.extend(this, _.pick(options, viewOptions));
28 this._ensureElement();
29 this.initialize.apply(this, arguments);
30 };
31
32 // Cached regex to split keys for `delegate`.
33 var delegateEventSplitter = /^(S+)s*(.*)$/;
34
35 // List of view options to be set as properties.
36 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
37
38 // Set up all inheritable **Backbone.View** properties and methods.
39 _.extend(View.prototype, Events, {
40
41 // The default `tagName` of a View's element is `"div"`.
42 tagName: 'div',
43
44 // jQuery delegate for element lookup, scoped to DOM elements within the
45 // current view. This should be preferred to global lookups where possible.
46 $: function(selector) {
47 return this.$el.find(selector);
48 },
49
50 // preinitialize is an empty function by default. You can override it with a function
51 // or object. preinitialize will run before any instantiation logic is run in the View
52 preinitialize: function(){},
53
54 // Initialize is an empty function by default. Override it with your own
55 // initialization logic.
56 initialize: function(){},
57
58 // **render** is the core function that your view should override, in order
59 // to populate its element (`this.el`), with the appropriate HTML. The
60 // convention is for **render** to always return `this`.
61 render: function() {
62 return this;
63 },
64
65 // Remove this view by taking the element out of the DOM, and removing any
66 // applicable Backbone.Events listeners.
67 remove: function() {
68 this._removeElement();
69 this.stopListening();
70 return this;
71 },
72
73 // Remove this view's element from the document and all event listeners
74 // attached to it. Exposed for subclasses using an alternative DOM
75 // manipulation API.
76 _removeElement: function() {
77 this.$el.remove();
78 },
79
80 // Change the view's element (`this.el` property) and re-delegate the
81 // view's events on the new element.
82 setElement: function(element) {
83 this.undelegateEvents();
84 this._setElement(element);
85 this.delegateEvents();
86 return this;
87 },
88
89 // Creates the `this.el` and `this.$el` references for this view using the
90 // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
91 // context or an element. Subclasses can override this to utilize an
92 // alternative DOM manipulation API and are only required to set the
93 // `this.el` property.
94 _setElement: function(el) {
95 this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
96 this.el = this.$el[0];
97 },
98
99 // Set callbacks, where `this.events` is a hash of
100 //
101 // *{"event selector": "callback"}*
102 //
103 // {
104 // 'mousedown .title': 'edit',
105 // 'click .button': 'save',
106 // 'click .open': function(e) { ... }
107 // }
108 //
109 // pairs. Callbacks will be bound to the view, with `this` set properly.
110 // Uses event delegation for efficiency.
111 // Omitting the selector binds the event to `this.el`.
112 delegateEvents: function(events) {
113 events || (events = _.result(this, 'events'));
114 if (!events) return this;
115 this.undelegateEvents();
116 for (var key in events) {
117 var method = events[key];
118 if (!_.isFunction(method)) method = this[method];
119 if (!method) continue;
120 var match = key.match(delegateEventSplitter);
121 this.delegate(match[1], match[2], _.bind(method, this));
122 }
123 return this;
124 },
125
126 // Add a single event listener to the view's element (or a child element
127 // using `selector`). This only works for delegate-able events: not `focus`,
128 // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
129 delegate: function(eventName, selector, listener) {
130 this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
131 return this;
132 },
133
134 // Clears all callbacks previously bound to the view by `delegateEvents`.
135 // You usually don't need to use this, but may wish to if you have multiple
136 // Backbone views attached to the same DOM element.
137 undelegateEvents: function() {
138 if (this.$el) this.$el.off('.delegateEvents' + this.cid);
139 return this;
140 },
141
142 // A finer-grained `undelegateEvents` for removing a single delegated event.
143 // `selector` and `listener` are both optional.
144 undelegate: function(eventName, selector, listener) {
145 this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
146 return this;
147 },
148
149 // Produces a DOM element to be assigned to your view. Exposed for
150 // subclasses using an alternative DOM manipulation API.
151 _createElement: function(tagName) {
152 return document.createElement(tagName);
153 },
154
155 // Ensure that the View has a DOM element to render into.
156 // If `this.el` is a string, pass it through `$()`, take the first
157 // matching element, and re-assign it to `el`. Otherwise, create
158 // an element from the `id`, `className` and `tagName` properties.
159 _ensureElement: function() {
160 if (!this.el) {
161 var attrs = _.extend({}, _.result(this, 'attributes'));
162 if (this.id) attrs.id = _.result(this, 'id');
163 if (this.className) attrs['class'] = _.result(this, 'className');
164 this.setElement(this._createElement(_.result(this, 'tagName')));
165 this._setAttributes(attrs);
166 } else {
167 this.setElement(_.result(this, 'el'));
168 }
169 },
170
171 // Set attributes from a hash on this view's element. Exposed for
172 // subclasses using an alternative DOM manipulation API.
173 _setAttributes: function(attributes) {
174 this.$el.attr(attributes);
175 }
176
177 });