Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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"]});