1 |
efrain |
1 |
YUI.add('view', function (Y, NAME) {
|
|
|
2 |
|
|
|
3 |
/**
|
|
|
4 |
Represents a logical piece of an application's user interface, and provides a
|
|
|
5 |
lightweight, overridable API for rendering content and handling delegated DOM
|
|
|
6 |
events on a container element.
|
|
|
7 |
|
|
|
8 |
@module app
|
|
|
9 |
@submodule view
|
|
|
10 |
@since 3.4.0
|
|
|
11 |
**/
|
|
|
12 |
|
|
|
13 |
/**
|
|
|
14 |
Represents a logical piece of an application's user interface, and provides a
|
|
|
15 |
lightweight, overridable API for rendering content and handling delegated DOM
|
|
|
16 |
events on a container element.
|
|
|
17 |
|
|
|
18 |
The View class imposes little structure and provides only minimal functionality
|
|
|
19 |
of its own: it's basically just an overridable API interface that helps you
|
|
|
20 |
implement custom views.
|
|
|
21 |
|
|
|
22 |
As of YUI 3.5.0, View allows ad-hoc attributes to be specified at instantiation
|
|
|
23 |
time, so you don't need to subclass `Y.View` to add custom attributes. Just pass
|
|
|
24 |
them to the constructor:
|
|
|
25 |
|
|
|
26 |
var view = new Y.View({foo: 'bar'});
|
|
|
27 |
view.get('foo'); // => "bar"
|
|
|
28 |
|
|
|
29 |
@class View
|
|
|
30 |
@constructor
|
|
|
31 |
@extends Base
|
|
|
32 |
@since 3.4.0
|
|
|
33 |
**/
|
|
|
34 |
|
|
|
35 |
function View() {
|
|
|
36 |
View.superclass.constructor.apply(this, arguments);
|
|
|
37 |
}
|
|
|
38 |
|
|
|
39 |
Y.View = Y.extend(View, Y.Base, {
|
|
|
40 |
// -- Public Properties ----------------------------------------------------
|
|
|
41 |
|
|
|
42 |
/**
|
|
|
43 |
Template for this view's container.
|
|
|
44 |
|
|
|
45 |
@property containerTemplate
|
|
|
46 |
@type String
|
|
|
47 |
@default "<div/>"
|
|
|
48 |
@since 3.5.0
|
|
|
49 |
**/
|
|
|
50 |
containerTemplate: '<div/>',
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
Hash of CSS selectors mapped to events to delegate to elements matching
|
|
|
54 |
those selectors.
|
|
|
55 |
|
|
|
56 |
CSS selectors are relative to the `container` element. Events are attached
|
|
|
57 |
to the container, and delegation is used so that subscribers are only
|
|
|
58 |
notified of events that occur on elements inside the container that match
|
|
|
59 |
the specified selectors. This allows the container's contents to be re-
|
|
|
60 |
rendered as needed without losing event subscriptions.
|
|
|
61 |
|
|
|
62 |
Event handlers can be specified either as functions or as strings that map
|
|
|
63 |
to function names on this view instance or its prototype.
|
|
|
64 |
|
|
|
65 |
The `this` object in event handlers will refer to this view instance. If
|
|
|
66 |
you'd prefer `this` to be something else, use `Y.bind()` to bind a custom
|
|
|
67 |
`this` object.
|
|
|
68 |
|
|
|
69 |
@example
|
|
|
70 |
|
|
|
71 |
var view = new Y.View({
|
|
|
72 |
events: {
|
|
|
73 |
// Call `this.toggle()` whenever the element with the id
|
|
|
74 |
// "toggle-button" is clicked.
|
|
|
75 |
'#toggle-button': {click: 'toggle'},
|
|
|
76 |
|
|
|
77 |
// Call `this.hoverOn()` when the mouse moves over any element
|
|
|
78 |
// with the "hoverable" class, and `this.hoverOff()` when the
|
|
|
79 |
// mouse moves out of any element with the "hoverable" class.
|
|
|
80 |
'.hoverable': {
|
|
|
81 |
mouseover: 'hoverOn',
|
|
|
82 |
mouseout : 'hoverOff'
|
|
|
83 |
}
|
|
|
84 |
}
|
|
|
85 |
});
|
|
|
86 |
|
|
|
87 |
@property events
|
|
|
88 |
@type Object
|
|
|
89 |
@default {}
|
|
|
90 |
**/
|
|
|
91 |
events: {},
|
|
|
92 |
|
|
|
93 |
/**
|
|
|
94 |
Template for this view's contents.
|
|
|
95 |
|
|
|
96 |
This is a convenience property that has no default behavior of its own.
|
|
|
97 |
It's only provided as a convention to allow you to store whatever you
|
|
|
98 |
consider to be a template, whether that's an HTML string, a `Y.Node`
|
|
|
99 |
instance, a Mustache template, or anything else your little heart
|
|
|
100 |
desires.
|
|
|
101 |
|
|
|
102 |
How this template gets used is entirely up to you and your custom
|
|
|
103 |
`render()` method.
|
|
|
104 |
|
|
|
105 |
@property template
|
|
|
106 |
@type mixed
|
|
|
107 |
@default ''
|
|
|
108 |
**/
|
|
|
109 |
template: '',
|
|
|
110 |
|
|
|
111 |
// -- Protected Properties -------------------------------------------------
|
|
|
112 |
|
|
|
113 |
/**
|
|
|
114 |
This tells `Y.Base` that it should create ad-hoc attributes for config
|
|
|
115 |
properties passed to View's constructor. This makes it possible to
|
|
|
116 |
instantiate a view and set a bunch of attributes without having to subclass
|
|
|
117 |
`Y.View` and declare all those attributes first.
|
|
|
118 |
|
|
|
119 |
@property _allowAdHocAttrs
|
|
|
120 |
@type Boolean
|
|
|
121 |
@default true
|
|
|
122 |
@protected
|
|
|
123 |
@since 3.5.0
|
|
|
124 |
**/
|
|
|
125 |
_allowAdHocAttrs: true,
|
|
|
126 |
|
|
|
127 |
// -- Lifecycle Methods ----------------------------------------------------
|
|
|
128 |
initializer: function (config) {
|
|
|
129 |
config || (config = {});
|
|
|
130 |
|
|
|
131 |
// Set instance properties specified in the config.
|
|
|
132 |
config.containerTemplate &&
|
|
|
133 |
(this.containerTemplate = config.containerTemplate);
|
|
|
134 |
|
|
|
135 |
config.template && (this.template = config.template);
|
|
|
136 |
|
|
|
137 |
// Merge events from the config into events in `this.events`.
|
|
|
138 |
this.events = config.events ? Y.merge(this.events, config.events) :
|
|
|
139 |
this.events;
|
|
|
140 |
|
|
|
141 |
// When the container node changes (or when it's set for the first
|
|
|
142 |
// time), we'll attach events to it, but not until then. This allows the
|
|
|
143 |
// container to be created lazily the first time it's accessed rather
|
|
|
144 |
// than always on init.
|
|
|
145 |
this.after('containerChange', this._afterContainerChange);
|
|
|
146 |
},
|
|
|
147 |
|
|
|
148 |
/**
|
|
|
149 |
Destroys this View, detaching any DOM events and optionally also destroying
|
|
|
150 |
its container node.
|
|
|
151 |
|
|
|
152 |
By default, the container node will not be destroyed. Pass an _options_
|
|
|
153 |
object with a truthy `remove` property to destroy the container as well.
|
|
|
154 |
|
|
|
155 |
@method destroy
|
|
|
156 |
@param {Object} [options] Options.
|
|
|
157 |
@param {Boolean} [options.remove=false] If `true`, this View's container
|
|
|
158 |
will be removed from the DOM and destroyed as well.
|
|
|
159 |
@chainable
|
|
|
160 |
*/
|
|
|
161 |
destroy: function (options) {
|
|
|
162 |
// We also accept `delete` as a synonym for `remove`.
|
|
|
163 |
if (options && (options.remove || options['delete'])) {
|
|
|
164 |
// Attaching an event handler here because the `destroy` event is
|
|
|
165 |
// preventable. If we destroyed the container before calling the
|
|
|
166 |
// superclass's `destroy()` method and the event was prevented, the
|
|
|
167 |
// class would end up in a broken state.
|
|
|
168 |
this.onceAfter('destroy', function () {
|
|
|
169 |
this._destroyContainer();
|
|
|
170 |
});
|
|
|
171 |
}
|
|
|
172 |
|
|
|
173 |
return View.superclass.destroy.call(this);
|
|
|
174 |
},
|
|
|
175 |
|
|
|
176 |
destructor: function () {
|
|
|
177 |
this.detachEvents();
|
|
|
178 |
delete this._container;
|
|
|
179 |
},
|
|
|
180 |
|
|
|
181 |
// -- Public Methods -------------------------------------------------------
|
|
|
182 |
|
|
|
183 |
/**
|
|
|
184 |
Attaches delegated event handlers to this view's container element. This
|
|
|
185 |
method is called internally to subscribe to events configured in the
|
|
|
186 |
`events` attribute when the view is initialized.
|
|
|
187 |
|
|
|
188 |
You may override this method to customize the event attaching logic.
|
|
|
189 |
|
|
|
190 |
@method attachEvents
|
|
|
191 |
@param {Object} [events] Hash of events to attach. See the docs for the
|
|
|
192 |
`events` attribute for details on the format. If not specified, this
|
|
|
193 |
view's `events` property will be used.
|
|
|
194 |
@chainable
|
|
|
195 |
@see detachEvents
|
|
|
196 |
**/
|
|
|
197 |
attachEvents: function (events) {
|
|
|
198 |
var container = this.get('container'),
|
|
|
199 |
owns = Y.Object.owns,
|
|
|
200 |
handler, handlers, name, selector;
|
|
|
201 |
|
|
|
202 |
this.detachEvents();
|
|
|
203 |
|
|
|
204 |
events || (events = this.events);
|
|
|
205 |
|
|
|
206 |
for (selector in events) {
|
|
|
207 |
if (!owns(events, selector)) { continue; }
|
|
|
208 |
|
|
|
209 |
handlers = events[selector];
|
|
|
210 |
|
|
|
211 |
for (name in handlers) {
|
|
|
212 |
if (!owns(handlers, name)) { continue; }
|
|
|
213 |
|
|
|
214 |
handler = handlers[name];
|
|
|
215 |
|
|
|
216 |
// TODO: Make this more robust by using lazy-binding:
|
|
|
217 |
// `handler = Y.bind(handler, this);`
|
|
|
218 |
if (typeof handler === 'string') {
|
|
|
219 |
handler = this[handler];
|
|
|
220 |
}
|
|
|
221 |
|
|
|
222 |
if (!handler) {
|
|
|
223 |
Y.log('Missing handler for ' + selector + ' ' + name + ' event.', 'warn', 'View');
|
|
|
224 |
continue;
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
this._attachedViewEvents.push(
|
|
|
228 |
container.delegate(name, handler, selector, this));
|
|
|
229 |
}
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
return this;
|
|
|
233 |
},
|
|
|
234 |
|
|
|
235 |
/**
|
|
|
236 |
Creates and returns a container node for this view.
|
|
|
237 |
|
|
|
238 |
By default, the container is created from the HTML template specified in the
|
|
|
239 |
`containerTemplate` property, and is _not_ added to the DOM automatically.
|
|
|
240 |
|
|
|
241 |
You may override this method to customize how the container node is created
|
|
|
242 |
(such as by rendering it from a custom template format). Your method must
|
|
|
243 |
return a `Y.Node` instance.
|
|
|
244 |
|
|
|
245 |
@method create
|
|
|
246 |
@param {HTMLElement|Node|String} [container] Selector string, `Y.Node`
|
|
|
247 |
instance, or DOM element to use at the container node.
|
|
|
248 |
@return {Node} Node instance of the created container node.
|
|
|
249 |
**/
|
|
|
250 |
create: function (container) {
|
|
|
251 |
return container ? Y.one(container) :
|
|
|
252 |
Y.Node.create(this.containerTemplate);
|
|
|
253 |
},
|
|
|
254 |
|
|
|
255 |
/**
|
|
|
256 |
Detaches DOM events that have previously been attached to the container by
|
|
|
257 |
`attachEvents()`.
|
|
|
258 |
|
|
|
259 |
@method detachEvents
|
|
|
260 |
@chainable
|
|
|
261 |
@see attachEvents
|
|
|
262 |
**/
|
|
|
263 |
detachEvents: function () {
|
|
|
264 |
Y.Array.each(this._attachedViewEvents, function (handle) {
|
|
|
265 |
if (handle) {
|
|
|
266 |
handle.detach();
|
|
|
267 |
}
|
|
|
268 |
});
|
|
|
269 |
|
|
|
270 |
this._attachedViewEvents = [];
|
|
|
271 |
return this;
|
|
|
272 |
},
|
|
|
273 |
|
|
|
274 |
/**
|
|
|
275 |
Removes this view's container element from the DOM (if it's in the DOM),
|
|
|
276 |
but doesn't destroy it or any event listeners attached to it.
|
|
|
277 |
|
|
|
278 |
@method remove
|
|
|
279 |
@chainable
|
|
|
280 |
**/
|
|
|
281 |
remove: function () {
|
|
|
282 |
var container = this.get('container');
|
|
|
283 |
container && container.remove();
|
|
|
284 |
return this;
|
|
|
285 |
},
|
|
|
286 |
|
|
|
287 |
/**
|
|
|
288 |
Renders this view.
|
|
|
289 |
|
|
|
290 |
This method is a noop by default. Override it to provide a custom
|
|
|
291 |
implementation that renders this view's content and appends it to the
|
|
|
292 |
container element. Ideally your `render` method should also return `this` as
|
|
|
293 |
the end to allow chaining, but that's up to you.
|
|
|
294 |
|
|
|
295 |
Since there's no default renderer, you're free to render your view however
|
|
|
296 |
you see fit, whether that means manipulating the DOM directly, dumping
|
|
|
297 |
strings into `innerHTML`, or using a template language of some kind.
|
|
|
298 |
|
|
|
299 |
For basic templating needs, `Y.Node.create()` and `Y.Lang.sub()` may
|
|
|
300 |
suffice, but there are no restrictions on what tools or techniques you can
|
|
|
301 |
use to render your view. All you need to do is append something to the
|
|
|
302 |
container element at some point, and optionally append the container
|
|
|
303 |
to the DOM if it's not there already.
|
|
|
304 |
|
|
|
305 |
@method render
|
|
|
306 |
@chainable
|
|
|
307 |
**/
|
|
|
308 |
render: function () {
|
|
|
309 |
return this;
|
|
|
310 |
},
|
|
|
311 |
|
|
|
312 |
// -- Protected Methods ----------------------------------------------------
|
|
|
313 |
|
|
|
314 |
/**
|
|
|
315 |
Removes the `container` from the DOM and purges all its event listeners.
|
|
|
316 |
|
|
|
317 |
@method _destroyContainer
|
|
|
318 |
@protected
|
|
|
319 |
**/
|
|
|
320 |
_destroyContainer: function () {
|
|
|
321 |
var container = this.get('container');
|
|
|
322 |
container && container.remove(true);
|
|
|
323 |
},
|
|
|
324 |
|
|
|
325 |
/**
|
|
|
326 |
Getter for the `container` attribute.
|
|
|
327 |
|
|
|
328 |
@method _getContainer
|
|
|
329 |
@param {Node|null} value Current attribute value.
|
|
|
330 |
@return {Node} Container node.
|
|
|
331 |
@protected
|
|
|
332 |
@since 3.5.0
|
|
|
333 |
**/
|
|
|
334 |
_getContainer: function (value) {
|
|
|
335 |
// This wackiness is necessary to enable fully lazy creation of the
|
|
|
336 |
// container node both when no container is specified and when one is
|
|
|
337 |
// specified via a valueFn.
|
|
|
338 |
|
|
|
339 |
if (!this._container) {
|
|
|
340 |
if (value) {
|
|
|
341 |
// Attach events to the container when it's specified via a
|
|
|
342 |
// valueFn, which won't fire the containerChange event.
|
|
|
343 |
this._container = value;
|
|
|
344 |
this.attachEvents();
|
|
|
345 |
} else {
|
|
|
346 |
// Create a default container and set that as the new attribute
|
|
|
347 |
// value. The `this._container` property prevents infinite
|
|
|
348 |
// recursion.
|
|
|
349 |
value = this._container = this.create();
|
|
|
350 |
this._set('container', value);
|
|
|
351 |
}
|
|
|
352 |
}
|
|
|
353 |
|
|
|
354 |
return value;
|
|
|
355 |
},
|
|
|
356 |
|
|
|
357 |
// -- Protected Event Handlers ---------------------------------------------
|
|
|
358 |
|
|
|
359 |
/**
|
|
|
360 |
Handles `containerChange` events. Detaches event handlers from the old
|
|
|
361 |
container (if any) and attaches them to the new container.
|
|
|
362 |
|
|
|
363 |
Right now the `container` attr is initOnly so this event should only ever
|
|
|
364 |
fire the first time the container is created, but in the future (once Y.App
|
|
|
365 |
can handle it) we may allow runtime container changes.
|
|
|
366 |
|
|
|
367 |
@method _afterContainerChange
|
|
|
368 |
@protected
|
|
|
369 |
@since 3.5.0
|
|
|
370 |
**/
|
|
|
371 |
_afterContainerChange: function () {
|
|
|
372 |
this.attachEvents(this.events);
|
|
|
373 |
}
|
|
|
374 |
}, {
|
|
|
375 |
NAME: 'view',
|
|
|
376 |
|
|
|
377 |
ATTRS: {
|
|
|
378 |
/**
|
|
|
379 |
Container node into which this view's content will be rendered.
|
|
|
380 |
|
|
|
381 |
The container node serves as the host for all DOM events attached by the
|
|
|
382 |
view. Delegation is used to handle events on children of the container,
|
|
|
383 |
allowing the container's contents to be re-rendered at any time without
|
|
|
384 |
losing event subscriptions.
|
|
|
385 |
|
|
|
386 |
The default container is a `<div>` Node, but you can override this in
|
|
|
387 |
a subclass, or by passing in a custom `container` config value at
|
|
|
388 |
instantiation time. If you override the default container in a subclass
|
|
|
389 |
using `ATTRS`, you must use the `valueFn` property. The view's constructor
|
|
|
390 |
will ignore any assignments using `value`.
|
|
|
391 |
|
|
|
392 |
When `container` is overridden by a subclass or passed as a config
|
|
|
393 |
option at instantiation time, you can provide it as a selector string, a
|
|
|
394 |
DOM element, a `Y.Node` instance, or (if you are subclassing and modifying
|
|
|
395 |
the attribute), a `valueFn` function that returns a `Y.Node` instance.
|
|
|
396 |
The value will be converted into a `Y.Node` instance if it isn't one
|
|
|
397 |
already.
|
|
|
398 |
|
|
|
399 |
The container is not added to the page automatically. This allows you to
|
|
|
400 |
have full control over how and when your view is actually rendered to
|
|
|
401 |
the page.
|
|
|
402 |
|
|
|
403 |
@attribute container
|
|
|
404 |
@type HTMLElement|Node|String
|
|
|
405 |
@default Y.Node.create(this.containerTemplate)
|
|
|
406 |
@writeOnce
|
|
|
407 |
**/
|
|
|
408 |
container: {
|
|
|
409 |
getter : '_getContainer',
|
|
|
410 |
setter : Y.one,
|
|
|
411 |
writeOnce: true
|
|
|
412 |
}
|
|
|
413 |
},
|
|
|
414 |
|
|
|
415 |
/**
|
|
|
416 |
Properties that shouldn't be turned into ad-hoc attributes when passed to
|
|
|
417 |
View's constructor.
|
|
|
418 |
|
|
|
419 |
@property _NON_ATTRS_CFG
|
|
|
420 |
@type Array
|
|
|
421 |
@static
|
|
|
422 |
@protected
|
|
|
423 |
@since 3.5.0
|
|
|
424 |
**/
|
|
|
425 |
_NON_ATTRS_CFG: [
|
|
|
426 |
'containerTemplate',
|
|
|
427 |
'events',
|
|
|
428 |
'template'
|
|
|
429 |
]
|
|
|
430 |
});
|
|
|
431 |
|
|
|
432 |
|
|
|
433 |
|
|
|
434 |
}, '3.18.1', {"requires": ["base-build", "node-event-delegate"]});
|