Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('widget-base', function (Y, NAME) {
2
 
3
/**
4
 * Provides the base Widget class, with HTML Parser support
5
 *
6
 * @module widget
7
 * @main widget
8
 */
9
 
10
/**
11
 * Provides the base Widget class
12
 *
13
 * @module widget
14
 * @submodule widget-base
15
 */
16
var L = Y.Lang,
17
    Node = Y.Node,
18
 
19
    ClassNameManager = Y.ClassNameManager,
20
 
21
    _getClassName = ClassNameManager.getClassName,
22
    _getWidgetClassName,
23
 
24
    _toInitialCap = Y.cached(function(str) {
25
        return str.substring(0, 1).toUpperCase() + str.substring(1);
26
    }),
27
 
28
    // K-Weight, IE GC optimizations
29
    CONTENT = "content",
30
    VISIBLE = "visible",
31
    HIDDEN = "hidden",
32
    DISABLED = "disabled",
33
    FOCUSED = "focused",
34
    WIDTH = "width",
35
    HEIGHT = "height",
36
    BOUNDING_BOX = "boundingBox",
37
    CONTENT_BOX = "contentBox",
38
    PARENT_NODE = "parentNode",
39
    OWNER_DOCUMENT = "ownerDocument",
40
    AUTO = "auto",
41
    SRC_NODE = "srcNode",
42
    BODY = "body",
43
    TAB_INDEX = "tabIndex",
44
    ID = "id",
45
    RENDER = "render",
46
    RENDERED = "rendered",
47
    DESTROYED = "destroyed",
48
    STRINGS = "strings",
49
    DIV = "<div></div>",
50
    CHANGE = "Change",
51
    LOADING = "loading",
52
 
53
    _UISET = "_uiSet",
54
 
55
    EMPTY_STR = "",
56
    EMPTY_FN = function() {},
57
 
58
    TRUE = true,
59
    FALSE = false,
60
 
61
    UI,
62
    ATTRS = {},
63
    UI_ATTRS = [VISIBLE, DISABLED, HEIGHT, WIDTH, FOCUSED, TAB_INDEX],
64
 
65
    WEBKIT = Y.UA.webkit,
66
 
67
    // Widget nodeid-to-instance map.
68
    _instances = {};
69
 
70
/**
71
 * A base class for widgets, providing:
72
 * <ul>
73
 *    <li>The render lifecycle method, in addition to the init and destroy
74
 *        lifecycle methods provide by Base</li>
75
 *    <li>Abstract methods to support consistent MVC structure across
76
 *        widgets: renderer, renderUI, bindUI, syncUI</li>
77
 *    <li>Support for common widget attributes, such as boundingBox, contentBox, visible,
78
 *        disabled, focused, strings</li>
79
 * </ul>
80
 *
81
 * @param config {Object} Object literal specifying widget configuration properties.
82
 *
83
 * @class Widget
84
 * @constructor
85
 * @extends Base
86
 */
87
function Widget(config) {
88
    Y.log('constructor called', 'life', 'widget');
89
 
90
    // kweight
91
    var widget = this,
92
        parentNode,
93
        render,
94
        constructor = widget.constructor;
95
 
96
    widget._strs = {};
97
    widget._cssPrefix = constructor.CSS_PREFIX || _getClassName(constructor.NAME.toLowerCase());
98
 
99
    // We need a config for HTML_PARSER to work.
100
    config = config || {};
101
 
102
    Widget.superclass.constructor.call(widget, config);
103
 
104
    render = widget.get(RENDER);
105
 
106
    if (render) {
107
        // Render could be a node or boolean
108
        if (render !== TRUE) {
109
            parentNode = render;
110
        }
111
        widget.render(parentNode);
112
    }
113
}
114
 
115
/**
116
 * Static property provides a string to identify the class.
117
 * <p>
118
 * Currently used to apply class identifiers to the bounding box
119
 * and to classify events fired by the widget.
120
 * </p>
121
 *
122
 * @property NAME
123
 * @type String
124
 * @static
125
 */
126
Widget.NAME = "widget";
127
 
128
/**
129
 * Constant used to identify state changes originating from
130
 * the DOM (as opposed to the JavaScript model).
131
 *
132
 * @property UI_SRC
133
 * @type String
134
 * @static
135
 * @final
136
 */
137
UI = Widget.UI_SRC = "ui";
138
 
139
/**
140
 * Static property used to define the default attribute
141
 * configuration for the Widget.
142
 *
143
 * @property ATTRS
144
 * @type Object
145
 * @static
146
 */
147
Widget.ATTRS = ATTRS;
148
 
149
// Trying to optimize kweight by setting up attrs this way saves about 0.4K min'd
150
 
151
/**
152
 * @attribute id
153
 * @writeOnce
154
 * @default Generated using guid()
155
 * @type String
156
 */
157
 
158
ATTRS[ID] = {
159
    valueFn: "_guid",
160
    writeOnce: TRUE
161
};
162
 
163
/**
164
 * Flag indicating whether or not this Widget
165
 * has been through the render lifecycle phase.
166
 *
167
 * @attribute rendered
168
 * @readOnly
169
 * @default false
170
 * @type boolean
171
 */
172
ATTRS[RENDERED] = {
173
    value:FALSE,
174
    readOnly: TRUE
175
};
176
 
177
/**
178
 * @attribute boundingBox
179
 * @description The outermost DOM node for the Widget, used for sizing and positioning
180
 * of a Widget as well as a containing element for any decorator elements used
181
 * for skinning.
182
 * @type String | Node
183
 * @writeOnce
184
 */
185
ATTRS[BOUNDING_BOX] = {
186
    valueFn:"_defaultBB",
187
    setter: "_setBB",
188
    writeOnce: TRUE
189
};
190
 
191
/**
192
 * @attribute contentBox
193
 * @description A DOM node that is a direct descendant of a Widget's bounding box that
194
 * houses its content.
195
 * @type String | Node
196
 * @writeOnce
197
 */
198
ATTRS[CONTENT_BOX] = {
199
    valueFn:"_defaultCB",
200
    setter: "_setCB",
201
    writeOnce: TRUE
202
};
203
 
204
/**
205
 * @attribute tabIndex
206
 * @description Number (between -32767 to 32767) indicating the widget's
207
 * position in the default tab flow.  The value is used to set the
208
 * "tabIndex" attribute on the widget's bounding box.  Negative values allow
209
 * the widget to receive DOM focus programmatically (by calling the focus
210
 * method), while being removed from the default tab flow.  A value of
211
 * null removes the "tabIndex" attribute from the widget's bounding box.
212
 * @type Number
213
 * @default null
214
 */
215
ATTRS[TAB_INDEX] = {
216
    value: null,
217
    validator: "_validTabIndex"
218
};
219
 
220
/**
221
 * @attribute focused
222
 * @description Boolean indicating if the Widget, or one of its descendants,
223
 * has focus.
224
 * @readOnly
225
 * @default false
226
 * @type boolean
227
 */
228
ATTRS[FOCUSED] = {
229
    value: FALSE,
230
    readOnly:TRUE
231
};
232
 
233
/**
234
 * @attribute disabled
235
 * @description Boolean indicating if the Widget should be disabled. The disabled implementation
236
 * is left to the specific classes extending widget.
237
 * @default false
238
 * @type boolean
239
 */
240
ATTRS[DISABLED] = {
241
    value: FALSE
242
};
243
 
244
/**
245
 * @attribute visible
246
 * @description Boolean indicating whether or not the Widget is visible.
247
 * @default TRUE
248
 * @type boolean
249
 */
250
ATTRS[VISIBLE] = {
251
    value: TRUE
252
};
253
 
254
/**
255
 * @attribute height
256
 * @description String with units, or number, representing the height of the Widget. If a number is provided,
257
 * the default unit, defined by the Widgets DEF_UNIT, property is used.
258
 * @default EMPTY_STR
259
 * @type {String | Number}
260
 */
261
ATTRS[HEIGHT] = {
262
    value: EMPTY_STR
263
};
264
 
265
/**
266
 * @attribute width
267
 * @description String with units, or number, representing the width of the Widget. If a number is provided,
268
 * the default unit, defined by the Widgets DEF_UNIT, property is used.
269
 * @default EMPTY_STR
270
 * @type {String | Number}
271
 */
272
ATTRS[WIDTH] = {
273
    value: EMPTY_STR
274
};
275
 
276
/**
277
 * @attribute strings
278
 * @description Collection of strings used to label elements of the Widget's UI.
279
 * @default null
280
 * @type Object
281
 */
282
ATTRS[STRINGS] = {
283
    value: {},
284
    setter: "_strSetter",
285
    getter: "_strGetter"
286
};
287
 
288
/**
289
 * Whether or not to render the widget automatically after init, and optionally, to which parent node.
290
 *
291
 * @attribute render
292
 * @type boolean | Node
293
 * @writeOnce
294
 */
295
ATTRS[RENDER] = {
296
    value:FALSE,
297
    writeOnce:TRUE
298
};
299
 
300
/**
301
 * The css prefix which the static Widget.getClassName method should use when constructing class names
302
 *
303
 * @property CSS_PREFIX
304
 * @type String
305
 * @default Widget.NAME.toLowerCase()
306
 * @private
307
 * @static
308
 */
309
Widget.CSS_PREFIX = _getClassName(Widget.NAME.toLowerCase());
310
 
311
/**
312
 * Generate a standard prefixed classname for the Widget, prefixed by the default prefix defined
313
 * by the <code>Y.config.classNamePrefix</code> attribute used by <code>ClassNameManager</code> and
314
 * <code>Widget.NAME.toLowerCase()</code> (e.g. "yui-widget-xxxxx-yyyyy", based on default values for
315
 * the prefix and widget class name).
316
 * <p>
317
 * The instance based version of this method can be used to generate standard prefixed classnames,
318
 * based on the instances NAME, as opposed to Widget.NAME. This method should be used when you
319
 * need to use a constant class name across different types instances.
320
 * </p>
321
 * @method getClassName
322
 * @param {String*} args* 0..n strings which should be concatenated, using the default separator defined by ClassNameManager, to create the class name
323
 */
324
Widget.getClassName = function() {
325
    // arguments needs to be array'fied to concat
326
    return _getClassName.apply(ClassNameManager, [Widget.CSS_PREFIX].concat(Y.Array(arguments), true));
327
};
328
 
329
_getWidgetClassName = Widget.getClassName;
330
 
331
/**
332
 * Returns the widget instance whose bounding box contains, or is, the given node.
333
 * <p>
334
 * In the case of nested widgets, the nearest bounding box ancestor is used to
335
 * return the widget instance.
336
 * </p>
337
 * @method getByNode
338
 * @static
339
 * @param node {Node | String} The node for which to return a Widget instance. If a selector
340
 * string is passed in, which selects more than one node, the first node found is used.
341
 * @return {Widget} Widget instance, or null if not found.
342
 */
343
Widget.getByNode = function(node) {
344
    var widget,
345
        widgetMarker = _getWidgetClassName();
346
 
347
    node = Node.one(node);
348
    if (node) {
349
        node = node.ancestor("." + widgetMarker, true);
350
        if (node) {
351
            widget = _instances[Y.stamp(node, true)];
352
        }
353
    }
354
 
355
    return widget || null;
356
};
357
 
358
Y.extend(Widget, Y.Base, {
359
 
360
    /**
361
     * Returns a class name prefixed with the the value of the
362
     * <code>YUI.config.classNamePrefix</code> attribute + the instances <code>NAME</code> property.
363
     * Uses <code>YUI.config.classNameDelimiter</code> attribute to delimit the provided strings.
364
     * e.g.
365
     * <code>
366
     * <pre>
367
     *    // returns "yui-slider-foo-bar", for a slider instance
368
     *    var scn = slider.getClassName('foo','bar');
369
     *
370
     *    // returns "yui-overlay-foo-bar", for an overlay instance
371
     *    var ocn = overlay.getClassName('foo','bar');
372
     * </pre>
373
     * </code>
374
     *
375
     * @method getClassName
376
     * @param {String} [classnames*] One or more classname bits to be joined and prefixed
377
     */
378
    getClassName: function () {
379
        return _getClassName.apply(ClassNameManager, [this._cssPrefix].concat(Y.Array(arguments), true));
380
    },
381
 
382
    /**
383
     * Initializer lifecycle implementation for the Widget class. Registers the
384
     * widget instance, and runs through the Widget's HTML_PARSER definition.
385
     *
386
     * @method initializer
387
     * @protected
388
     * @param  config {Object} Configuration object literal for the widget
389
     */
390
    initializer: function(config) {
391
        Y.log('initializer called', 'life', 'widget');
392
 
393
        var bb = this.get(BOUNDING_BOX);
394
 
395
        if (bb instanceof Node) {
396
            this._mapInstance(Y.stamp(bb));
397
        }
398
 
399
        /**
400
         * Notification event, which widget implementations can fire, when
401
         * they change the content of the widget. This event has no default
402
         * behavior and cannot be prevented, so the "on" or "after"
403
         * moments are effectively equivalent (with on listeners being invoked before
404
         * after listeners).
405
         *
406
         * @event widget:contentUpdate
407
         * @preventable false
408
         * @param {EventFacade} e The Event Facade
409
         */
410
    },
411
 
412
    /**
413
     * Utility method used to add an entry to the boundingBox id to instance map.
414
     *
415
     * This method can be used to populate the instance with lazily created boundingBox Node references.
416
     *
417
     * @method _mapInstance
418
     * @param {String} The boundingBox id
419
     * @protected
420
     */
421
    _mapInstance : function(id) {
422
        _instances[id] = this;
423
    },
424
 
425
    /**
426
     * Destructor lifecycle implementation for the Widget class. Purges events attached
427
     * to the bounding box and content box, removes them from the DOM and removes
428
     * the Widget from the list of registered widgets.
429
     *
430
     * @method destructor
431
     * @protected
432
     */
433
    destructor: function() {
434
        Y.log('destructor called', 'life', 'widget');
435
 
436
        var boundingBox = this.get(BOUNDING_BOX),
437
            bbGuid;
438
 
439
        if (boundingBox instanceof Node) {
440
            bbGuid = Y.stamp(boundingBox,true);
441
 
442
            if (bbGuid in _instances) {
443
                delete _instances[bbGuid];
444
            }
445
 
446
            this._destroyBox();
447
        }
448
    },
449
 
450
    /**
451
     * <p>
452
     * Destroy lifecycle method. Fires the destroy
453
     * event, prior to invoking destructors for the
454
     * class hierarchy.
455
     *
456
     * Overrides Base's implementation, to support arguments to destroy
457
     * </p>
458
     * <p>
459
     * Subscribers to the destroy
460
     * event can invoke preventDefault on the event object, to prevent destruction
461
     * from proceeding.
462
     * </p>
463
     * @method destroy
464
     * @param destroyAllNodes {Boolean} If true, all nodes contained within the Widget are
465
     * removed and destroyed. Defaults to false due to potentially high run-time cost.
466
     * @return {Widget} A reference to this object
467
     * @chainable
468
     */
469
    destroy: function(destroyAllNodes) {
470
        this._destroyAllNodes = destroyAllNodes;
471
        return Widget.superclass.destroy.apply(this);
472
    },
473
 
474
    /**
475
     * Removes and destroys the widgets rendered boundingBox, contentBox,
476
     * and detaches bound UI events.
477
     *
478
     * @method _destroyBox
479
     * @protected
480
     */
481
    _destroyBox : function() {
482
 
483
        var boundingBox = this.get(BOUNDING_BOX),
484
            contentBox = this.get(CONTENT_BOX),
485
            deep = this._destroyAllNodes,
486
            same;
487
 
488
        same = boundingBox && boundingBox.compareTo(contentBox);
489
 
490
        if (this.UI_EVENTS) {
491
            this._destroyUIEvents();
492
        }
493
 
494
        this._unbindUI(boundingBox);
495
 
496
        if (contentBox) {
497
            if (deep) {
498
                contentBox.empty();
499
            }
500
            contentBox.remove(TRUE);
501
        }
502
 
503
        if (!same) {
504
            if (deep) {
505
                boundingBox.empty();
506
            }
507
            boundingBox.remove(TRUE);
508
        }
509
    },
510
 
511
    /**
512
     * Establishes the initial DOM for the widget. Invoking this
513
     * method will lead to the creating of all DOM elements for
514
     * the widget (or the manipulation of existing DOM elements
515
     * for the progressive enhancement use case).
516
     * <p>
517
     * This method should only be invoked once for an initialized
518
     * widget.
519
     * </p>
520
     * <p>
521
     * It delegates to the widget specific renderer method to do
522
     * the actual work.
523
     * </p>
524
     *
525
     * @method render
526
     * @chainable
527
     * @final
528
     * @param  parentNode {Object | String} Optional. The Node under which the
529
     * Widget is to be rendered. This can be a Node instance or a CSS selector string.
530
     * <p>
531
     * If the selector string returns more than one Node, the first node will be used
532
     * as the parentNode. NOTE: This argument is required if both the boundingBox and contentBox
533
     * are not currently in the document. If it's not provided, the Widget will be rendered
534
     * to the body of the current document in this case.
535
     * </p>
536
     */
537
    render: function(parentNode) {
538
        if (this.get(DESTROYED)) { Y.log("Render failed; widget has been destroyed", "error", "widget"); }
539
 
540
        if (!this.get(DESTROYED) && !this.get(RENDERED)) {
541
             /**
542
              * Lifecycle event for the render phase, fired prior to rendering the UI
543
              * for the widget (prior to invoking the widget's renderer method).
544
              * <p>
545
              * Subscribers to the "on" moment of this event, will be notified
546
              * before the widget is rendered.
547
              * </p>
548
              * <p>
549
              * Subscribers to the "after" moment of this event, will be notified
550
              * after rendering is complete.
551
              * </p>
552
              *
553
              * @event render
554
              * @preventable _defRenderFn
555
              * @param {EventFacade} e The Event Facade
556
              */
557
            this.publish(RENDER, {
558
                queuable:FALSE,
559
                fireOnce:TRUE,
560
                defaultTargetOnly:TRUE,
561
                defaultFn: this._defRenderFn
562
            });
563
 
564
            this.fire(RENDER, {parentNode: (parentNode) ? Node.one(parentNode) : null});
565
        }
566
        return this;
567
    },
568
 
569
    /**
570
     * Default render handler
571
     *
572
     * @method _defRenderFn
573
     * @protected
574
     * @param {EventFacade} e The Event object
575
     * @param {Node} parentNode The parent node to render to, if passed in to the <code>render</code> method
576
     */
577
    _defRenderFn : function(e) {
578
        this._parentNode = e.parentNode;
579
 
580
        this.renderer();
581
        this._set(RENDERED, TRUE);
582
 
583
        this._removeLoadingClassNames();
584
    },
585
 
586
    /**
587
     * Creates DOM (or manipulates DOM for progressive enhancement)
588
     * This method is invoked by render() and is not chained
589
     * automatically for the class hierarchy (unlike initializer, destructor)
590
     * so it should be chained manually for subclasses if required.
591
     *
592
     * @method renderer
593
     * @protected
594
     */
595
    renderer: function() {
596
        // kweight
597
        var widget = this;
598
 
599
        widget._renderUI();
600
        widget.renderUI();
601
 
602
        widget._bindUI();
603
        widget.bindUI();
604
 
605
        widget._syncUI();
606
        widget.syncUI();
607
    },
608
 
609
    /**
610
     * Configures/Sets up listeners to bind Widget State to UI/DOM
611
     *
612
     * This method is not called by framework and is not chained
613
     * automatically for the class hierarchy.
614
     *
615
     * @method bindUI
616
     * @protected
617
     */
618
    bindUI: EMPTY_FN,
619
 
620
    /**
621
     * Adds nodes to the DOM
622
     *
623
     * This method is not called by framework and is not chained
624
     * automatically for the class hierarchy.
625
     *
626
     * @method renderUI
627
     * @protected
628
     */
629
    renderUI: EMPTY_FN,
630
 
631
    /**
632
     * Refreshes the rendered UI, based on Widget State
633
     *
634
     * This method is not called by framework and is not chained
635
     * automatically for the class hierarchy.
636
     *
637
     * @method syncUI
638
     * @protected
639
     *
640
     */
641
    syncUI: EMPTY_FN,
642
 
643
    /**
644
     * @method hide
645
     * @description Hides the Widget by setting the "visible" attribute to "false".
646
     * @chainable
647
     */
648
    hide: function() {
649
        return this.set(VISIBLE, FALSE);
650
    },
651
 
652
    /**
653
     * @method show
654
     * @description Shows the Widget by setting the "visible" attribute to "true".
655
     * @chainable
656
     */
657
    show: function() {
658
        return this.set(VISIBLE, TRUE);
659
    },
660
 
661
    /**
662
     * @method focus
663
     * @description Causes the Widget to receive the focus by setting the "focused"
664
     * attribute to "true".
665
     * @chainable
666
     */
667
    focus: function () {
668
        return this._set(FOCUSED, TRUE);
669
    },
670
 
671
    /**
672
     * @method blur
673
     * @description Causes the Widget to lose focus by setting the "focused" attribute
674
     * to "false"
675
     * @chainable
676
     */
677
    blur: function () {
678
        return this._set(FOCUSED, FALSE);
679
    },
680
 
681
    /**
682
     * @method enable
683
     * @description Set the Widget's "disabled" attribute to "false".
684
     * @chainable
685
     */
686
    enable: function() {
687
        return this.set(DISABLED, FALSE);
688
    },
689
 
690
    /**
691
     * @method disable
692
     * @description Set the Widget's "disabled" attribute to "true".
693
     * @chainable
694
     */
695
    disable: function() {
696
        return this.set(DISABLED, TRUE);
697
    },
698
 
699
    /**
700
     * @method _uiSizeCB
701
     * @protected
702
     * @param {boolean} expand
703
     */
704
    _uiSizeCB : function(expand) {
705
        this.get(CONTENT_BOX).toggleClass(_getWidgetClassName(CONTENT, "expanded"), expand);
706
    },
707
 
708
    /**
709
     * Helper method to collect the boundingBox and contentBox and append to the provided parentNode, if not
710
     * already a child. The owner document of the boundingBox, or the owner document of the contentBox will be used
711
     * as the document into which the Widget is rendered if a parentNode is node is not provided. If both the boundingBox and
712
     * the contentBox are not currently in the document, and no parentNode is provided, the widget will be rendered
713
     * to the current document's body.
714
     *
715
     * @method _renderBox
716
     * @private
717
     * @param {Node} parentNode The parentNode to render the widget to. If not provided, and both the boundingBox and
718
     * the contentBox are not currently in the document, the widget will be rendered to the current document's body.
719
     */
720
    _renderBox: function(parentNode) {
721
 
722
        // TODO: Performance Optimization [ More effective algo to reduce Node refs, compares, replaces? ]
723
 
724
        var widget = this, // kweight
725
            contentBox = widget.get(CONTENT_BOX),
726
            boundingBox = widget.get(BOUNDING_BOX),
727
            srcNode = widget.get(SRC_NODE),
728
            defParentNode = widget.DEF_PARENT_NODE,
729
 
730
            doc = (srcNode && srcNode.get(OWNER_DOCUMENT)) || boundingBox.get(OWNER_DOCUMENT) || contentBox.get(OWNER_DOCUMENT);
731
 
732
        // If srcNode (assume it's always in doc), have contentBox take its place (widget render responsible for re-use of srcNode contents)
733
        if (srcNode && !srcNode.compareTo(contentBox) && !contentBox.inDoc(doc)) {
734
            srcNode.replace(contentBox);
735
        }
736
 
737
        if (!boundingBox.compareTo(contentBox.get(PARENT_NODE)) && !boundingBox.compareTo(contentBox)) {
738
            // If contentBox box is already in the document, have boundingBox box take it's place
739
            if (contentBox.inDoc(doc)) {
740
                contentBox.replace(boundingBox);
741
            }
742
            boundingBox.appendChild(contentBox);
743
        }
744
 
745
        parentNode = parentNode || (defParentNode && Node.one(defParentNode));
746
 
747
        if (parentNode) {
748
            parentNode.appendChild(boundingBox);
749
        } else if (!boundingBox.inDoc(doc)) {
750
            Node.one(BODY).insert(boundingBox, 0);
751
        }
752
    },
753
 
754
    /**
755
     * Setter for the boundingBox attribute
756
     *
757
     * @method _setBB
758
     * @private
759
     * @param {Node|String} node
760
     * @return Node
761
     */
762
    _setBB: function(node) {
763
        return this._setBox(this.get(ID), node, this.BOUNDING_TEMPLATE, true);
764
    },
765
 
766
    /**
767
     * Setter for the contentBox attribute
768
     *
769
     * @method _setCB
770
     * @private
771
     * @param {Node|String} node
772
     * @return Node
773
     */
774
    _setCB: function(node) {
775
        return (this.CONTENT_TEMPLATE === null) ? this.get(BOUNDING_BOX) : this._setBox(null, node, this.CONTENT_TEMPLATE, false);
776
    },
777
 
778
    /**
779
     * Returns the default value for the boundingBox attribute.
780
     *
781
     * For the Widget class, this will most commonly be null (resulting in a new
782
     * boundingBox node instance being created), unless a srcNode was provided
783
     * and CONTENT_TEMPLATE is null, in which case it will be srcNode.
784
     * This behavior was introduced in 3.18.1 to accomodate single-box widgets
785
     * whose BB & CB both point to srcNode (e.g. Y.Button).
786
     *
787
     * @method _defaultBB
788
     * @protected
789
     */
790
    _defaultBB : function() {
791
        var node = this.get(SRC_NODE),
792
            nullCT = (this.CONTENT_TEMPLATE === null);
793
 
794
        return ((node && nullCT) ? node : null);
795
    },
796
 
797
    /**
798
     * Returns the default value for the contentBox attribute.
799
     *
800
     * For the Widget class, this will be the srcNode if provided, otherwise null (resulting in
801
     * a new contentBox node instance being created)
802
     *
803
     * @method _defaultCB
804
     * @protected
805
     */
806
    _defaultCB : function(node) {
807
        return this.get(SRC_NODE) || null;
808
    },
809
 
810
    /**
811
     * Helper method to set the bounding/content box, or create it from
812
     * the provided template if not found.
813
     *
814
     * @method _setBox
815
     * @private
816
     *
817
     * @param {String} id The node's id attribute
818
     * @param {Node|String} node The node reference
819
     * @param {String} template HTML string template for the node
820
     * @param {boolean} isBounding true if this is the boundingBox, false if it's the contentBox
821
     * @return {Node} The node
822
     */
823
    _setBox : function(id, node, template, isBounding) {
824
 
825
        node = Node.one(node);
826
 
827
        if (!node) {
828
            node = Node.create(template);
829
 
830
            if (isBounding) {
831
                this._bbFromTemplate = true;
832
            } else {
833
                this._cbFromTemplate = true;
834
            }
835
        }
836
 
837
        if (!node.get(ID)) {
838
            node.set(ID, id || Y.guid());
839
        }
840
 
841
        return node;
842
    },
843
 
844
    /**
845
     * Initializes the UI state for the Widget's bounding/content boxes.
846
     *
847
     * @method _renderUI
848
     * @protected
849
     */
850
    _renderUI: function() {
851
        this._renderBoxClassNames();
852
        this._renderBox(this._parentNode);
853
    },
854
 
855
    /**
856
     * Applies standard class names to the boundingBox and contentBox
857
     *
858
     * @method _renderBoxClassNames
859
     * @protected
860
     */
861
    _renderBoxClassNames : function() {
862
        var classes = this._getClasses(),
863
            cl,
864
            boundingBox = this.get(BOUNDING_BOX),
865
            i;
866
 
867
        boundingBox.addClass(_getWidgetClassName());
868
 
869
        // Start from Widget Sub Class
870
        for (i = classes.length-3; i >= 0; i--) {
871
            cl = classes[i];
872
            boundingBox.addClass(cl.CSS_PREFIX || _getClassName(cl.NAME.toLowerCase()));
873
        }
874
 
875
        // Use instance based name for content box
876
        this.get(CONTENT_BOX).addClass(this.getClassName(CONTENT));
877
    },
878
 
879
    /**
880
     * Removes class names representative of the widget's loading state from
881
     * the boundingBox.
882
     *
883
     * @method _removeLoadingClassNames
884
     * @protected
885
     */
886
    _removeLoadingClassNames: function () {
887
 
888
        var boundingBox = this.get(BOUNDING_BOX),
889
            contentBox = this.get(CONTENT_BOX),
890
            instClass = this.getClassName(LOADING),
891
            widgetClass = _getWidgetClassName(LOADING);
892
 
893
        boundingBox.removeClass(widgetClass)
894
                   .removeClass(instClass);
895
 
896
        contentBox.removeClass(widgetClass)
897
                  .removeClass(instClass);
898
    },
899
 
900
    /**
901
     * Sets up DOM and CustomEvent listeners for the widget.
902
     *
903
     * @method _bindUI
904
     * @protected
905
     */
906
    _bindUI: function() {
907
        this._bindAttrUI(this._UI_ATTRS.BIND);
908
        this._bindDOM();
909
    },
910
 
911
    /**
912
     * @method _unbindUI
913
     * @protected
914
     */
915
    _unbindUI : function(boundingBox) {
916
        this._unbindDOM(boundingBox);
917
    },
918
 
919
    /**
920
     * Sets up DOM listeners, on elements rendered by the widget.
921
     *
922
     * @method _bindDOM
923
     * @protected
924
     */
925
    _bindDOM : function() {
926
        var oDocument = this.get(BOUNDING_BOX).get(OWNER_DOCUMENT),
927
            focusHandle = Widget._hDocFocus;
928
 
929
        // Shared listener across all Widgets.
930
        if (!focusHandle) {
931
            focusHandle = Widget._hDocFocus = oDocument.on("focus", this._onDocFocus, this);
932
            focusHandle.listeners = {
933
                count: 0
934
            };
935
        }
936
 
937
        focusHandle.listeners[Y.stamp(this, true)] = true;
938
        focusHandle.listeners.count++;
939
 
940
        //	Fix for Webkit:
941
        //	Document doesn't receive focus in Webkit when the user mouses
942
        //	down on it, so the "focused" attribute won't get set to the
943
        //	correct value. Keeping this instance based for now, potential better performance.
944
        //  Otherwise we'll end up looking up widgets from the DOM on every mousedown.
945
        if (WEBKIT){
946
            this._hDocMouseDown = oDocument.on("mousedown", this._onDocMouseDown, this);
947
        }
948
    },
949
 
950
    /**
951
     * @method _unbindDOM
952
     * @protected
953
     */
954
    _unbindDOM : function(boundingBox) {
955
 
956
        var focusHandle = Widget._hDocFocus,
957
            yuid = Y.stamp(this, true),
958
            focusListeners,
959
            mouseHandle = this._hDocMouseDown;
960
 
961
        if (focusHandle) {
962
 
963
            focusListeners = focusHandle.listeners;
964
 
965
            if (focusListeners[yuid]) {
966
                delete focusListeners[yuid];
967
                focusListeners.count--;
968
            }
969
 
970
            if (focusListeners.count === 0) {
971
                focusHandle.detach();
972
                Widget._hDocFocus = null;
973
            }
974
        }
975
 
976
        if (WEBKIT && mouseHandle) {
977
            mouseHandle.detach();
978
        }
979
    },
980
 
981
    /**
982
     * Updates the widget UI to reflect the attribute state.
983
     *
984
     * @method _syncUI
985
     * @protected
986
     */
987
    _syncUI: function() {
988
        this._syncAttrUI(this._UI_ATTRS.SYNC);
989
    },
990
 
991
    /**
992
     * Sets the height on the widget's bounding box element
993
     *
994
     * @method _uiSetHeight
995
     * @protected
996
     * @param {String | Number} val
997
     */
998
    _uiSetHeight: function(val) {
999
        this._uiSetDim(HEIGHT, val);
1000
        this._uiSizeCB((val !== EMPTY_STR && val !== AUTO));
1001
    },
1002
 
1003
    /**
1004
     * Sets the width on the widget's bounding box element
1005
     *
1006
     * @method _uiSetWidth
1007
     * @protected
1008
     * @param {String | Number} val
1009
     */
1010
    _uiSetWidth: function(val) {
1011
        this._uiSetDim(WIDTH, val);
1012
    },
1013
 
1014
    /**
1015
     * @method _uiSetDim
1016
     * @private
1017
     * @param {String} dim The dimension - "width" or "height"
1018
     * @param {Number | String} val The value to set
1019
     */
1020
    _uiSetDim: function(dimension, val) {
1021
        this.get(BOUNDING_BOX).setStyle(dimension, L.isNumber(val) ? val + this.DEF_UNIT : val);
1022
    },
1023
 
1024
    /**
1025
     * Sets the visible state for the UI
1026
     *
1027
     * @method _uiSetVisible
1028
     * @protected
1029
     * @param {boolean} val
1030
     */
1031
    _uiSetVisible: function(val) {
1032
        this.get(BOUNDING_BOX).toggleClass(this.getClassName(HIDDEN), !val);
1033
    },
1034
 
1035
    /**
1036
     * Sets the disabled state for the UI
1037
     *
1038
     * @method _uiSetDisabled
1039
     * @protected
1040
     * @param {boolean} val
1041
     */
1042
    _uiSetDisabled: function(val) {
1043
        this.get(BOUNDING_BOX).toggleClass(this.getClassName(DISABLED), val);
1044
    },
1045
 
1046
    /**
1047
     * Sets the focused state for the UI
1048
     *
1049
     * @method _uiSetFocused
1050
     * @protected
1051
     * @param {boolean} val
1052
     * @param {string} src String representing the source that triggered an update to
1053
     * the UI.
1054
     */
1055
    _uiSetFocused: function(val, src) {
1056
         var boundingBox = this.get(BOUNDING_BOX);
1057
         boundingBox.toggleClass(this.getClassName(FOCUSED), val);
1058
 
1059
         if (src !== UI) {
1060
            if (val) {
1061
                boundingBox.focus();
1062
            } else {
1063
                boundingBox.blur();
1064
            }
1065
         }
1066
    },
1067
 
1068
    /**
1069
     * Set the tabIndex on the widget's rendered UI
1070
     *
1071
     * @method _uiSetTabIndex
1072
     * @protected
1073
     * @param Number
1074
     */
1075
    _uiSetTabIndex: function(index) {
1076
        var boundingBox = this.get(BOUNDING_BOX);
1077
 
1078
        if (L.isNumber(index)) {
1079
            boundingBox.set(TAB_INDEX, index);
1080
        } else {
1081
            boundingBox.removeAttribute(TAB_INDEX);
1082
        }
1083
    },
1084
 
1085
    /**
1086
     * @method _onDocMouseDown
1087
     * @description "mousedown" event handler for the owner document of the
1088
     * widget's bounding box.
1089
     * @protected
1090
     * @param {EventFacade} evt The event facade for the DOM focus event
1091
     */
1092
    _onDocMouseDown: function (evt) {
1093
        if (this._domFocus) {
1094
            this._onDocFocus(evt);
1095
        }
1096
    },
1097
 
1098
    /**
1099
     * DOM focus event handler, used to sync the state of the Widget with the DOM
1100
     *
1101
     * @method _onDocFocus
1102
     * @protected
1103
     * @param {EventFacade} evt The event facade for the DOM focus event
1104
     */
1105
    _onDocFocus: function (evt) {
1106
        var widget = Widget.getByNode(evt.target),
1107
            activeWidget = Widget._active;
1108
 
1109
        if (activeWidget && (activeWidget !== widget)) {
1110
            activeWidget._domFocus = false;
1111
            activeWidget._set(FOCUSED, false, {src:UI});
1112
 
1113
            Widget._active = null;
1114
        }
1115
 
1116
        if (widget) {
1117
            widget._domFocus = true;
1118
            widget._set(FOCUSED, true, {src:UI});
1119
 
1120
            Widget._active = widget;
1121
        }
1122
    },
1123
 
1124
    /**
1125
     * Generic toString implementation for all widgets.
1126
     *
1127
     * @method toString
1128
     * @return {String} The default string value for the widget [ displays the NAME of the instance, and the unique id ]
1129
     */
1130
    toString: function() {
1131
        // Using deprecated name prop for kweight squeeze.
1132
        return this.name + "[" + this.get(ID) + "]";
1133
    },
1134
 
1135
    /**
1136
     * Default unit to use for dimension values
1137
     *
1138
     * @property DEF_UNIT
1139
     * @type String
1140
     */
1141
    DEF_UNIT : "px",
1142
 
1143
    /**
1144
     * Default node to render the bounding box to. If not set,
1145
     * will default to the current document body.
1146
     *
1147
     * @property DEF_PARENT_NODE
1148
     * @type String | Node
1149
     */
1150
    DEF_PARENT_NODE : null,
1151
 
1152
    /**
1153
     * Property defining the markup template for content box. If your Widget doesn't
1154
     * need the dual boundingBox/contentBox structure, set CONTENT_TEMPLATE to null,
1155
     * and contentBox and boundingBox will both point to the same Node.
1156
     *
1157
     * @property CONTENT_TEMPLATE
1158
     * @type String
1159
     */
1160
    CONTENT_TEMPLATE : DIV,
1161
 
1162
    /**
1163
     * Property defining the markup template for bounding box.
1164
     *
1165
     * @property BOUNDING_TEMPLATE
1166
     * @type String
1167
     */
1168
    BOUNDING_TEMPLATE : DIV,
1169
 
1170
    /**
1171
     * @method _guid
1172
     * @protected
1173
     */
1174
    _guid : function() {
1175
        return Y.guid();
1176
    },
1177
 
1178
    /**
1179
     * @method _validTabIndex
1180
     * @protected
1181
     * @param {Number} tabIndex
1182
     */
1183
    _validTabIndex : function (tabIndex) {
1184
        return (L.isNumber(tabIndex) || L.isNull(tabIndex));
1185
    },
1186
 
1187
    /**
1188
     * Binds after listeners for the list of attributes provided
1189
     *
1190
     * @method _bindAttrUI
1191
     * @private
1192
     * @param {Array} attrs
1193
     */
1194
    _bindAttrUI : function(attrs) {
1195
        var i,
1196
            l = attrs.length;
1197
 
1198
        for (i = 0; i < l; i++) {
1199
            this.after(attrs[i] + CHANGE, this._setAttrUI);
1200
        }
1201
    },
1202
 
1203
    /**
1204
     * Invokes the _uiSet&#61;ATTR NAME&#62; method for the list of attributes provided
1205
     *
1206
     * @method _syncAttrUI
1207
     * @private
1208
     * @param {Array} attrs
1209
     */
1210
    _syncAttrUI : function(attrs) {
1211
        var i, l = attrs.length, attr;
1212
        for (i = 0; i < l; i++) {
1213
            attr = attrs[i];
1214
            this[_UISET + _toInitialCap(attr)](this.get(attr));
1215
        }
1216
    },
1217
 
1218
    /**
1219
     * @method _setAttrUI
1220
     * @private
1221
     * @param {EventFacade} e
1222
     */
1223
    _setAttrUI : function(e) {
1224
        if (e.target === this) {
1225
            this[_UISET + _toInitialCap(e.attrName)](e.newVal, e.src);
1226
        }
1227
    },
1228
 
1229
    /**
1230
     * The default setter for the strings attribute. Merges partial sets
1231
     * into the full string set, to allow users to partial sets of strings
1232
     *
1233
     * @method _strSetter
1234
     * @protected
1235
     * @param {Object} strings
1236
     * @return {String} The full set of strings to set
1237
     */
1238
    _strSetter : function(strings) {
1239
        return Y.merge(this.get(STRINGS), strings);
1240
    },
1241
 
1242
    /**
1243
     * Helper method to get a specific string value
1244
     *
1245
     * @deprecated Used by deprecated WidgetLocale implementations.
1246
     * @method getString
1247
     * @param {String} key
1248
     * @return {String} The string
1249
     */
1250
    getString : function(key) {
1251
        return this.get(STRINGS)[key];
1252
    },
1253
 
1254
    /**
1255
     * Helper method to get the complete set of strings for the widget
1256
     *
1257
     * @deprecated  Used by deprecated WidgetLocale implementations.
1258
     * @method getStrings
1259
     * @param {String} key
1260
     * @return {String} The strings
1261
     */
1262
    getStrings : function() {
1263
        return this.get(STRINGS);
1264
    },
1265
 
1266
    /**
1267
     * The lists of UI attributes to bind and sync for widget's _bindUI and _syncUI implementations
1268
     *
1269
     * @property _UI_ATTRS
1270
     * @type Object
1271
     * @private
1272
     */
1273
    _UI_ATTRS : {
1274
        BIND: UI_ATTRS,
1275
        SYNC: UI_ATTRS
1276
    }
1277
});
1278
 
1279
Y.Widget = Widget;
1280
 
1281
 
1282
}, '3.18.1', {
1283
    "requires": [
1284
        "attribute",
1285
        "base-base",
1286
        "base-pluginhost",
1287
        "classnamemanager",
1288
        "event-focus",
1289
        "node-base",
1290
        "node-style"
1291
    ],
1292
    "skinnable": true
1293
});