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