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
                    continue;
224
                }
225
 
226
                this._attachedViewEvents.push(
227
                    container.delegate(name, handler, selector, this));
228
            }
229
        }
230
 
231
        return this;
232
    },
233
 
234
    /**
235
    Creates and returns a container node for this view.
236
 
237
    By default, the container is created from the HTML template specified in the
238
    `containerTemplate` property, and is _not_ added to the DOM automatically.
239
 
240
    You may override this method to customize how the container node is created
241
    (such as by rendering it from a custom template format). Your method must
242
    return a `Y.Node` instance.
243
 
244
    @method create
245
    @param {HTMLElement|Node|String} [container] Selector string, `Y.Node`
246
        instance, or DOM element to use at the container node.
247
    @return {Node} Node instance of the created container node.
248
    **/
249
    create: function (container) {
250
        return container ? Y.one(container) :
251
                Y.Node.create(this.containerTemplate);
252
    },
253
 
254
    /**
255
    Detaches DOM events that have previously been attached to the container by
256
    `attachEvents()`.
257
 
258
    @method detachEvents
259
    @chainable
260
    @see attachEvents
261
    **/
262
    detachEvents: function () {
263
        Y.Array.each(this._attachedViewEvents, function (handle) {
264
            if (handle) {
265
                handle.detach();
266
            }
267
        });
268
 
269
        this._attachedViewEvents = [];
270
        return this;
271
    },
272
 
273
    /**
274
    Removes this view's container element from the DOM (if it's in the DOM),
275
    but doesn't destroy it or any event listeners attached to it.
276
 
277
    @method remove
278
    @chainable
279
    **/
280
    remove: function () {
281
        var container = this.get('container');
282
        container && container.remove();
283
        return this;
284
    },
285
 
286
    /**
287
    Renders this view.
288
 
289
    This method is a noop by default. Override it to provide a custom
290
    implementation that renders this view's content and appends it to the
291
    container element. Ideally your `render` method should also return `this` as
292
    the end to allow chaining, but that's up to you.
293
 
294
    Since there's no default renderer, you're free to render your view however
295
    you see fit, whether that means manipulating the DOM directly, dumping
296
    strings into `innerHTML`, or using a template language of some kind.
297
 
298
    For basic templating needs, `Y.Node.create()` and `Y.Lang.sub()` may
299
    suffice, but there are no restrictions on what tools or techniques you can
300
    use to render your view. All you need to do is append something to the
301
    container element at some point, and optionally append the container
302
    to the DOM if it's not there already.
303
 
304
    @method render
305
    @chainable
306
    **/
307
    render: function () {
308
        return this;
309
    },
310
 
311
    // -- Protected Methods ----------------------------------------------------
312
 
313
    /**
314
    Removes the `container` from the DOM and purges all its event listeners.
315
 
316
    @method _destroyContainer
317
    @protected
318
    **/
319
    _destroyContainer: function () {
320
        var container = this.get('container');
321
        container && container.remove(true);
322
    },
323
 
324
    /**
325
    Getter for the `container` attribute.
326
 
327
    @method _getContainer
328
    @param {Node|null} value Current attribute value.
329
    @return {Node} Container node.
330
    @protected
331
    @since 3.5.0
332
    **/
333
    _getContainer: function (value) {
334
        // This wackiness is necessary to enable fully lazy creation of the
335
        // container node both when no container is specified and when one is
336
        // specified via a valueFn.
337
 
338
        if (!this._container) {
339
            if (value) {
340
                // Attach events to the container when it's specified via a
341
                // valueFn, which won't fire the containerChange event.
342
                this._container = value;
343
                this.attachEvents();
344
            } else {
345
                // Create a default container and set that as the new attribute
346
                // value. The `this._container` property prevents infinite
347
                // recursion.
348
                value = this._container = this.create();
349
                this._set('container', value);
350
            }
351
        }
352
 
353
        return value;
354
    },
355
 
356
    // -- Protected Event Handlers ---------------------------------------------
357
 
358
    /**
359
    Handles `containerChange` events. Detaches event handlers from the old
360
    container (if any) and attaches them to the new container.
361
 
362
    Right now the `container` attr is initOnly so this event should only ever
363
    fire the first time the container is created, but in the future (once Y.App
364
    can handle it) we may allow runtime container changes.
365
 
366
    @method _afterContainerChange
367
    @protected
368
    @since 3.5.0
369
    **/
370
    _afterContainerChange: function () {
371
        this.attachEvents(this.events);
372
    }
373
}, {
374
    NAME: 'view',
375
 
376
    ATTRS: {
377
        /**
378
        Container node into which this view's content will be rendered.
379
 
380
        The container node serves as the host for all DOM events attached by the
381
        view. Delegation is used to handle events on children of the container,
382
        allowing the container's contents to be re-rendered at any time without
383
        losing event subscriptions.
384
 
385
        The default container is a `<div>` Node, but you can override this in
386
        a subclass, or by passing in a custom `container` config value at
387
        instantiation time. If you override the default container in a subclass
388
        using `ATTRS`, you must use the `valueFn` property. The view's constructor
389
        will ignore any assignments using `value`.
390
 
391
        When `container` is overridden by a subclass or passed as a config
392
        option at instantiation time, you can provide it as a selector string, a
393
        DOM element, a `Y.Node` instance, or (if you are subclassing and modifying
394
        the attribute), a `valueFn` function that returns a `Y.Node` instance.
395
        The value will be converted into a `Y.Node` instance if it isn't one
396
        already.
397
 
398
        The container is not added to the page automatically. This allows you to
399
        have full control over how and when your view is actually rendered to
400
        the page.
401
 
402
        @attribute container
403
        @type HTMLElement|Node|String
404
        @default Y.Node.create(this.containerTemplate)
405
        @writeOnce
406
        **/
407
        container: {
408
            getter   : '_getContainer',
409
            setter   : Y.one,
410
            writeOnce: true
411
        }
412
    },
413
 
414
    /**
415
    Properties that shouldn't be turned into ad-hoc attributes when passed to
416
    View's constructor.
417
 
418
    @property _NON_ATTRS_CFG
419
    @type Array
420
    @static
421
    @protected
422
    @since 3.5.0
423
    **/
424
    _NON_ATTRS_CFG: [
425
        'containerTemplate',
426
        'events',
427
        'template'
428
    ]
429
});
430
 
431
 
432
 
433
}, '3.18.1', {"requires": ["base-build", "node-event-delegate"]});