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