2
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.com/yui/license.html
8
YUI.add('widget-base', function(Y) {
11
* Provides the base Widget class, with HTML Parser support
17
* Provides the base Widget class
20
* @submodule widget-base
25
ClassNameManager = Y.ClassNameManager,
27
_getClassName = ClassNameManager.getClassName,
30
_toInitialCap = Y.cached(function(str) {
31
return str.substring(0, 1).toUpperCase() + str.substring(1);
34
// K-Weight, IE GC optimizations
38
DISABLED = "disabled",
42
BOUNDING_BOX = "boundingBox",
43
CONTENT_BOX = "contentBox",
44
PARENT_NODE = "parentNode",
45
OWNER_DOCUMENT = "ownerDocument",
46
OFFSET_HEIGHT = "offsetHeight",
50
TAB_INDEX = "tabIndex",
53
RENDERED = "rendered",
54
DESTROYED = "destroyed",
62
EMPTY_FN = function() {},
64
UI_EVENT_REGEX = /(\w+):(\w+)/,
65
UI_EVENT_REGEX_REPLACE = "$2",
72
UI_ATTRS = [VISIBLE, DISABLED, HEIGHT, WIDTH, FOCUSED],
77
ContentUpdate = "contentUpdate",
79
// Map of Node instances serving as a delegation containers for a specific
80
// event type to Widget instances using that delegation container.
83
// Widget nodeguid-to-instance map.
87
* A base class for widgets, providing:
89
* <li>The render lifecycle method, in addition to the init and destroy
90
* lifecycle methods provide by Base</li>
91
* <li>Abstract methods to support consistent MVC structure across
92
* widgets: renderer, renderUI, bindUI, syncUI</li>
93
* <li>Support for common widget attributes, such as boundingBox, contentBox, visible,
94
* disabled, focused, strings</li>
97
* @param config {Object} Object literal specifying widget configuration properties.
103
function Widget(config) {
104
Y.log('constructor called', 'life', 'widget');
108
this._cssPrefix = this.constructor.CSS_PREFIX || _getClassName(this.constructor.NAME.toLowerCase());
110
Widget.superclass.constructor.apply(this, arguments);
112
var render = this.get(RENDER), parentNode;
114
// Render could be a node or boolean
115
if (render !== TRUE) {
118
this.render(parentNode);
123
* Static property provides a string to identify the class.
125
* Currently used to apply class identifiers to the bounding box
126
* and to classify events fired by the widget.
129
* @property Widget.NAME
133
Widget.NAME = "widget";
136
* Constant used to identify state changes originating from
137
* the DOM (as opposed to the JavaScript model).
139
* @property Widget.UI_SRC
144
UI = Widget.UI_SRC = "ui";
147
* Static property used to define the default attribute
148
* configuration for the Widget.
150
* @property Widget.ATTRS
154
Widget.ATTRS = ATTRS;
156
// Trying to optimize kweight by setting up attrs this way saves about 0.4K min'd
161
* @default Generated using guid()
171
* Flag indicating whether or not this Widget
172
* has been through the render lifecycle phase.
174
* @attribute rendered
185
* @attribute boundingBox
186
* @description The outermost DOM node for the Widget, used for sizing and positioning
187
* of a Widget as well as a containing element for any decorator elements used
189
* @type String | Node
192
ATTRS[BOUNDING_BOX] = {
199
* @attribute contentBox
200
* @description A DOM node that is a direct descendent of a Widget's bounding box that
201
* houses its content.
202
* @type String | Node
205
ATTRS[CONTENT_BOX] = {
206
valueFn:"_defaultCB",
212
* @attribute tabIndex
213
* @description Number (between -32767 to 32767) indicating the widget's
214
* position in the default tab flow. The value is used to set the
215
* "tabIndex" attribute on the widget's bounding box. Negative values allow
216
* the widget to receive DOM focus programmatically (by calling the focus
217
* method), while being removed from the default tab flow. A value of
218
* null removes the "tabIndex" attribute from the widget's bounding box.
224
validator: "_validTabIndex"
229
* @description Boolean indicating if the Widget, or one of its descendants,
241
* @attribute disabled
242
* @description Boolean indicating if the Widget should be disabled. The disabled implementation
243
* is left to the specific classes extending widget.
253
* @description Boolean indicating weather or not the Widget is visible.
263
* @description String with units, or number, representing the height of the Widget. If a number is provided,
264
* the default unit, defined by the Widgets DEF_UNIT, property is used.
266
* @type {String | Number}
274
* @description String with units, or number, representing the width of the Widget. If a number is provided,
275
* the default unit, defined by the Widgets DEF_UNIT, property is used.
277
* @type {String | Number}
285
* @description Collection of strings used to label elements of the Widget's UI.
291
setter: "_strSetter",
296
* Whether or not to render the widget automatically after init, and optionally, to which parent node.
299
* @type boolean | Node
308
* The css prefix which the static Widget.getClassName method should use when constructing class names
310
* @property Widget.CSS_PREFIX
312
* @default Widget.NAME.toLowerCase()
316
Widget.CSS_PREFIX = _getClassName(Widget.NAME.toLowerCase());
319
* Generate a standard prefixed classname for the Widget, prefixed by the default prefix defined
320
* by the <code>Y.config.classNamePrefix</code> attribute used by <code>ClassNameManager</code> and
321
* <code>Widget.NAME.toLowerCase()</code> (e.g. "yui-widget-xxxxx-yyyyy", based on default values for
322
* the prefix and widget class name).
324
* The instance based version of this method can be used to generate standard prefixed classnames,
325
* based on the instances NAME, as opposed to Widget.NAME. This method should be used when you
326
* need to use a constant class name across different types instances.
328
* @method getClassName
329
* @param {String*} args* 0..n strings which should be concatenated, using the default separator defined by ClassNameManager, to create the class name
331
Widget.getClassName = function() {
332
// arguments needs to be array'fied to concat
333
return _getClassName.apply(ClassNameManager, [Widget.CSS_PREFIX].concat(Y.Array(arguments), true));
336
_getWidgetClassName = Widget.getClassName;
339
* Returns the widget instance whose bounding box contains, or is, the given node.
341
* In the case of nested widgets, the nearest bounding box ancestor is used to
342
* return the widget instance.
344
* @method Widget.getByNode
346
* @param node {Node | String} The node for which to return a Widget instance. If a selector
347
* string is passed in, which selects more than one node, the first node found is used.
348
* @return {Widget} Widget instance, or null if not found.
350
Widget.getByNode = function(node) {
352
widgetMarker = _getWidgetClassName();
354
node = Node.one(node);
356
node = node.ancestor("." + widgetMarker, true);
358
widget = _instances[Y.stamp(node, TRUE)];
362
return widget || null;
365
Y.extend(Widget, Y.Base, {
368
* Returns a class name prefixed with the the value of the
369
* <code>YUI.config.classNamePrefix</code> attribute + the instances <code>NAME</code> property.
370
* Uses <code>YUI.config.classNameDelimiter</code> attribute to delimit the provided strings.
374
* // returns "yui-slider-foo-bar", for a slider instance
375
* var scn = slider.getClassName('foo','bar');
377
* // returns "yui-overlay-foo-bar", for an overlay instance
378
* var ocn = overlay.getClassName('foo','bar');
382
* @method getClassName
383
* @param {String}+ One or more classname bits to be joined and prefixed
385
getClassName: function () {
386
return _getClassName.apply(ClassNameManager, [this._cssPrefix].concat(Y.Array(arguments), true));
390
* Returns the name of the skin that's currently applied to the widget.
391
* This is only really useful after the widget's DOM structure is in the
392
* document, either by render or by progressive enhancement. Searches up
393
* the Widget's ancestor axis for a class yui3-skin-(name), and returns the
394
* (name) portion. Otherwise, returns null.
396
* @method getSkinName
397
* @return {String} the name of the skin, or null (yui3-skin-sam => sam)
399
getSkinName: function () {
400
var root = this.get( CONTENT_BOX ) || this.get( BOUNDING_BOX ),
401
search = new RegExp( '\\b' + _getClassName( 'skin' ) + '-(\\S+)' ),
405
root.ancestor( function ( node ) {
406
match = node.get( 'className' ).match( search );
411
return ( match ) ? match[1] : null;
415
* Initializer lifecycle implementation for the Widget class. Registers the
416
* widget instance, and runs through the Widget's HTML_PARSER definition.
418
* @method initializer
420
* @param config {Object} Configuration object literal for the widget
422
initializer: function(config) {
423
Y.log('initializer called', 'life', 'widget');
425
_instances[Y.stamp(this.get(BOUNDING_BOX))] = this;
428
* Notification event, which widget implementations can fire, when
429
* they change the content of the widget. This event has no default
430
* behavior and cannot be prevented, so the "on" or "after"
431
* moments are effectively equivalent (with on listeners being invoked before
434
* @event widget:contentUpdate
436
* @param {EventFacade} e The Event Facade
438
this.publish(ContentUpdate, { preventable:FALSE });
440
if (this._applyParser) {
441
this._applyParser(config);
446
* Destructor lifecycle implementation for the Widget class. Purges events attached
447
* to the bounding box (and all child nodes) and removes the Widget from the
448
* list of registered widgets.
453
destructor: function() {
454
Y.log('destructor called', 'life', 'widget');
456
var boundingBox = this.get(BOUNDING_BOX),
457
bbGuid = Y.stamp(boundingBox, TRUE),
458
widgetGuid = Y.stamp(this, TRUE);
460
if (bbGuid in _instances) {
461
delete _instances[bbGuid];
464
Y.each(_delegates, function (info) {
465
if (info.instances[widgetGuid]) {
466
// Unregister this Widget instance as needing this delegated
468
delete info.instances[widgetGuid];
470
// There are no more Widget instances using this delegated
471
// event listener, so detach it.
472
if (Y.Object.size(info.instances) === 0) {
473
info.handle.detach();
478
this._unbindUI(boundingBox);
479
boundingBox.remove(TRUE);
483
* Establishes the initial DOM for the widget. Invoking this
484
* method will lead to the creating of all DOM elements for
485
* the widget (or the manipulation of existing DOM elements
486
* for the progressive enhancement use case).
488
* This method should only be invoked once for an initialized
492
* It delegates to the widget specific renderer method to do
499
* @param parentNode {Object | String} Optional. The Node under which the
500
* Widget is to be rendered. This can be a Node instance or a CSS selector string.
502
* If the selector string returns more than one Node, the first node will be used
503
* as the parentNode. NOTE: This argument is required if both the boundingBox and contentBox
504
* are not currently in the document. If it's not provided, the Widget will be rendered
505
* to the body of the current document in this case.
508
render: function(parentNode) {
509
if (this.get(DESTROYED)) { Y.log("Render failed; widget has been destroyed", "error", "widget"); }
511
if (!this.get(DESTROYED) && !this.get(RENDERED)) {
513
* Lifcyle event for the render phase, fired prior to rendering the UI
514
* for the widget (prior to invoking the widget's renderer method).
516
* Subscribers to the "on" moment of this event, will be notified
517
* before the widget is rendered.
520
* Subscribers to the "after" moment of this event, will be notified
521
* after rendering is complete.
524
* @event widget:render
525
* @preventable _defRenderFn
526
* @param {EventFacade} e The Event Facade
528
this.publish(RENDER, {
531
defaultTargetOnly:TRUE,
532
defaultFn: this._defRenderFn
535
this.fire(RENDER, {parentNode: (parentNode) ? Node.one(parentNode) : null});
541
* Default render handler
543
* @method _defRenderFn
545
* @param {EventFacade} e The Event object
546
* @param {Node} parentNode The parent node to render to, if passed in to the <code>render</code> method
548
_defRenderFn : function(e) {
549
this._parentNode = e.parentNode;
552
this._set(RENDERED, TRUE);
554
this._removeLoadingClassNames();
558
* Creates DOM (or manipulates DOM for progressive enhancement)
559
* This method is invoked by render() and is not chained
560
* automatically for the class hierarchy (unlike initializer, destructor)
561
* so it should be chained manually for subclasses if required.
566
renderer: function() {
578
* Configures/Sets up listeners to bind Widget State to UI/DOM
580
* This method is not called by framework and is not chained
581
* automatically for the class hierarchy.
589
* Adds nodes to the DOM
591
* This method is not called by framework and is not chained
592
* automatically for the class hierarchy.
600
* Refreshes the rendered UI, based on Widget State
602
* This method is not called by framework and is not chained
603
* automatically for the class hierarchy.
613
* @description Hides the Widget by setting the "visible" attribute to "false".
617
return this.set(VISIBLE, FALSE);
622
* @description Shows the Widget by setting the "visible" attribute to "true".
626
return this.set(VISIBLE, TRUE);
631
* @description Causes the Widget to receive the focus by setting the "focused"
632
* attribute to "true".
636
return this._set(FOCUSED, TRUE);
641
* @description Causes the Widget to lose focus by setting the "focused" attribute
646
return this._set(FOCUSED, FALSE);
651
* @description Set the Widget's "disabled" attribute to "false".
655
return this.set(DISABLED, FALSE);
660
* @description Set the Widget's "disabled" attribute to "true".
663
disable: function() {
664
return this.set(DISABLED, TRUE);
670
* @param {boolean} expand
672
_uiSizeCB : function(expand) {
674
var bb = this.get(BOUNDING_BOX),
675
cb = this.get(CONTENT_BOX),
677
bbTempExpanding = _getWidgetClassName("tmp", "forcesize"),
679
borderBoxSupported = this._bbs,
680
heightReallyMinHeight = IE && IE < 7;
682
if (borderBoxSupported) {
683
cb.toggleClass(_getWidgetClassName(CONTENT, "expanded"), expand);
686
if (heightReallyMinHeight) {
687
bb.addClass(bbTempExpanding);
690
cb.set(OFFSET_HEIGHT, bb.get(OFFSET_HEIGHT));
692
if (heightReallyMinHeight) {
693
bb.removeClass(bbTempExpanding);
696
cb.setStyle(HEIGHT, EMPTY_STR);
702
* Helper method to collect the boundingBox and contentBox, set styles and append to the provided parentNode, if not
703
* already a child. The owner document of the boundingBox, or the owner document of the contentBox will be used
704
* as the document into which the Widget is rendered if a parentNode is node is not provided. If both the boundingBox and
705
* the contentBox are not currently in the document, and no parentNode is provided, the widget will be rendered
706
* to the current document's body.
710
* @param {Node} parentNode The parentNode to render the widget to. If not provided, and both the boundingBox and
711
* the contentBox are not currently in the document, the widget will be rendered to the current document's body.
713
_renderBox: function(parentNode) {
715
// TODO: Performance Optimization [ More effective algo to reduce Node refs, compares, replaces? ]
717
var contentBox = this.get(CONTENT_BOX),
718
boundingBox = this.get(BOUNDING_BOX),
719
srcNode = this.get(SRC_NODE),
720
defParentNode = this.DEF_PARENT_NODE,
722
doc = (srcNode && srcNode.get(OWNER_DOCUMENT)) || boundingBox.get(OWNER_DOCUMENT) || contentBox.get(OWNER_DOCUMENT);
724
// If srcNode (assume it's always in doc), have contentBox take its place (widget render responsible for re-use of srcNode contents)
725
if (srcNode && !srcNode.compareTo(contentBox) && !contentBox.inDoc(doc)) {
726
srcNode.replace(contentBox);
729
if (!boundingBox.compareTo(contentBox.get(PARENT_NODE)) && !boundingBox.compareTo(contentBox)) {
730
// If contentBox box is already in the document, have boundingBox box take it's place
731
if (contentBox.inDoc(doc)) {
732
contentBox.replace(boundingBox);
734
boundingBox.appendChild(contentBox);
737
parentNode = parentNode || (defParentNode && Node.one(defParentNode));
740
parentNode.appendChild(boundingBox);
741
} else if (!boundingBox.inDoc(doc)) {
742
Node.one(BODY).insert(boundingBox, 0);
745
this._bbs = !(IE && IE < 8 && doc.compatMode != "BackCompat");
749
* Setter for the boundingBox attribute
756
_setBB: function(node) {
757
return this._setBox(this.get(ID), node, this.BOUNDING_TEMPLATE);
761
* Setter for the contentBox attribute
765
* @param {Node|String} node
768
_setCB: function(node) {
769
return (this.CONTENT_TEMPLATE === null) ? this.get(BOUNDING_BOX) : this._setBox(null, node, this.CONTENT_TEMPLATE);
773
* Returns the default value for the contentBox attribute.
775
* For the Widget class, this will be the srcNode if provided, otherwise null (resulting in
776
* a new contentBox node instance being created)
781
_defaultCB : function(node) {
782
return this.get(SRC_NODE) || null;
786
* Helper method to set the bounding/content box, or create it from
787
* the provided template if not found.
792
* @param {String} id The node's id attribute
793
* @param {Node|String} node The node reference
794
* @param {String} template HTML string template for the node
795
* @return {Node} The node
797
_setBox : function(id, node, template) {
798
node = Node.one(node) || Node.create(template);
800
node.set(ID, id || Y.guid());
806
* Initializes the UI state for the Widget's bounding/content boxes.
811
_renderUI: function() {
812
this._renderBoxClassNames();
813
this._renderBox(this._parentNode);
817
* Applies standard class names to the boundingBox and contentBox
819
* @method _renderBoxClassNames
822
_renderBoxClassNames : function() {
823
var classes = this._getClasses(),
825
boundingBox = this.get(BOUNDING_BOX),
828
boundingBox.addClass(_getWidgetClassName());
830
// Start from Widget Sub Class
831
for (i = classes.length-3; i >= 0; i--) {
833
boundingBox.addClass(cl.CSS_PREFIX || _getClassName(cl.NAME.toLowerCase()));
836
// Use instance based name for content box
837
this.get(CONTENT_BOX).addClass(this.getClassName(CONTENT));
842
* Removes class names representative of the widget's loading state from
845
* @method _removeLoadingClassNames
848
_removeLoadingClassNames: function () {
850
var boundingBox = this.get(BOUNDING_BOX),
851
contentBox = this.get(CONTENT_BOX);
853
boundingBox.removeClass(_getWidgetClassName(LOADING));
854
boundingBox.removeClass(this.getClassName(LOADING));
856
contentBox.removeClass(_getWidgetClassName(LOADING));
857
contentBox.removeClass(this.getClassName(LOADING));
862
* Sets up DOM and CustomEvent listeners for the widget.
867
_bindUI: function() {
868
this._bindAttrUI(this._BIND_UI_ATTRS);
876
_unbindUI : function(boundingBox) {
877
this._unbindDOM(boundingBox);
881
* Sets up DOM listeners, on elements rendered by the widget.
886
_bindDOM : function() {
887
var oDocument = this.get(BOUNDING_BOX).get(OWNER_DOCUMENT);
889
// TODO: Perf Optimization: Use Widget.getByNode delegation, to get by
890
// with just one _onDocFocus subscription per sandbox, instead of one per widget
891
this._hDocFocus = oDocument.on("focus", this._onDocFocus, this);
894
// Document doesn't receive focus in Webkit when the user mouses
895
// down on it, so the "focused" attribute won't get set to the
898
this._hDocMouseDown = oDocument.on("mousedown", this._onDocMouseDown, this);
906
_unbindDOM : function(boundingBox) {
907
if (this._hDocFocus) {
908
this._hDocFocus.detach();
911
if (WEBKIT && this._hDocMouseDown) {
912
this._hDocMouseDown.detach();
917
* Updates the widget UI to reflect the attribute state.
922
_syncUI: function() {
923
this._syncAttrUI(this._SYNC_UI_ATTRS);
927
* Sets the height on the widget's bounding box element
929
* @method _uiSetHeight
931
* @param {String | Number} val
933
_uiSetHeight: function(val) {
934
this._uiSetDim(HEIGHT, val);
935
this._uiSizeCB((val !== EMPTY_STR && val !== AUTO));
939
* Sets the width on the widget's bounding box element
941
* @method _uiSetWidth
943
* @param {String | Number} val
945
_uiSetWidth: function(val) {
946
this._uiSetDim(WIDTH, val);
952
* @param {String} dim The dimension - "width" or "height"
953
* @param {Number | String} val The value to set
955
_uiSetDim: function(dimension, val) {
956
this.get(BOUNDING_BOX).setStyle(dimension, L.isNumber(val) ? val + this.DEF_UNIT : val);
960
* Sets the visible state for the UI
962
* @method _uiSetVisible
964
* @param {boolean} val
966
_uiSetVisible: function(val) {
967
this.get(BOUNDING_BOX).toggleClass(this.getClassName(HIDDEN), !val);
971
* Sets the disabled state for the UI
974
* @param {boolean} val
976
_uiSetDisabled: function(val) {
977
this.get(BOUNDING_BOX).toggleClass(this.getClassName(DISABLED), val);
981
* Sets the focused state for the UI
984
* @param {boolean} val
985
* @param {string} src String representing the source that triggered an update to
988
_uiSetFocused: function(val, src) {
989
var boundingBox = this.get(BOUNDING_BOX);
991
boundingBox.toggleClass(this.getClassName(FOCUSED), val);
1002
* Set the tabIndex on the widget's rendered UI
1004
* @method _uiSetTabIndex
1008
_uiSetTabIndex: function(index) {
1009
var boundingBox = this.get(BOUNDING_BOX);
1011
if (L.isNumber(index)) {
1012
boundingBox.set(TAB_INDEX, index);
1014
boundingBox.removeAttribute(TAB_INDEX);
1019
* @method _onDocMouseDown
1020
* @description "mousedown" event handler for the owner document of the
1021
* widget's bounding box.
1023
* @param {EventFacade} evt The event facade for the DOM focus event
1025
_onDocMouseDown: function (evt) {
1026
if (this._hasDOMFocus) {
1027
this._onDocFocus(evt);
1032
* DOM focus event handler, used to sync the state of the Widget with the DOM
1034
* @method _onDocFocus
1036
* @param {EventFacade} evt The event facade for the DOM focus event
1038
_onDocFocus: function (evt) {
1039
var bFocused = this.get(BOUNDING_BOX).contains(evt.target); // contains() checks invoking node also
1041
this._hasDOMFocus = bFocused;
1042
this._set(FOCUSED, bFocused, { src: UI });
1046
* Generic toString implementation for all widgets.
1049
* @return {String} The default string value for the widget [ displays the NAME of the instance, and the unique id ]
1051
toString: function() {
1052
return this.constructor.NAME + "[" + this.get(ID) + "]";
1056
* Default unit to use for dimension values
1058
* @property DEF_UNIT
1064
* Default node to render the bounding box to. If not set,
1065
* will default to the current document body.
1067
* @property DEF_PARENT_NODE
1068
* @type String | Node
1070
DEF_PARENT_NODE : null,
1073
* Property defining the markup template for content box. If your Widget doesn't
1074
* need the dual boundingBox/contentBox structure, set CONTENT_TEMPLATE to null,
1075
* and contentBox and boundingBox will both point to the same Node.
1077
* @property CONTENT_TEMPLATE
1080
CONTENT_TEMPLATE : DIV,
1083
* Property defining the markup template for bounding box.
1085
* @property BOUNDING_TEMPLATE
1088
BOUNDING_TEMPLATE : DIV,
1094
_guid : function() {
1099
* @method _validTabIndex
1101
* @param {Number} tabIndex
1103
_validTabIndex : function (tabIndex) {
1104
return (L.isNumber(tabIndex) || L.isNull(tabIndex));
1108
* Binds after listeners for the list of attributes provided
1110
* @method _bindAttrUI
1112
* @param {Array} attrs
1114
_bindAttrUI : function(attrs) {
1118
for (i = 0; i < l; i++) {
1119
this.after(attrs[i] + CHANGE, this._setAttrUI);
1124
* Invokes the _uiSet=ATTR NAME> method for the list of attributes provided
1126
* @method _syncAttrUI
1128
* @param {Array} attrs
1130
_syncAttrUI : function(attrs) {
1131
var i, l = attrs.length, attr;
1132
for (i = 0; i < l; i++) {
1134
this[_UISET + _toInitialCap(attr)](this.get(attr));
1139
* @method _setAttrUI
1141
* @param {EventFacade} e
1143
_setAttrUI : function(e) {
1144
this[_UISET + _toInitialCap(e.attrName)](e.newVal, e.src);
1148
* The default setter for the strings attribute. Merges partial sets
1149
* into the full string set, to allow users to partial sets of strings
1151
* @method _strSetter
1153
* @param {Object} strings
1154
* @return {String} The full set of strings to set
1156
_strSetter : function(strings) {
1157
return Y.merge(this.get(STRINGS), strings);
1161
* Helper method to get a specific string value
1163
* @deprecated Used by deprecated WidgetLocale implementations.
1165
* @param {String} key
1166
* @return {String} The string
1168
getString : function(key) {
1169
return this.get(STRINGS)[key];
1173
* Helper method to get the complete set of strings for the widget
1175
* @deprecated Used by deprecated WidgetLocale implementations.
1177
* @param {String} key
1178
* @return {String} The string
1180
getStrings : function() {
1181
return this.get(STRINGS);
1185
* The list of UI attributes to bind for Widget's _bindUI implementation
1187
* @property _BIND_UI_ATTRS
1191
_BIND_UI_ATTRS : UI_ATTRS,
1194
* The list of UI attributes to sync for Widget's _syncUI implementation
1196
* @property _SYNC_UI_ATTRS
1200
_SYNC_UI_ATTRS : UI_ATTRS.concat(TAB_INDEX),
1203
* Map of DOM events that should be fired as Custom Events by the
1206
* @property UI_EVENTS
1209
UI_EVENTS: Y.Node.DOM_EVENTS,
1212
* Returns the node on which to bind delegate listeners.
1214
* @method _getUIEventNode
1217
_getUIEventNode: function () {
1218
return this.get(BOUNDING_BOX);
1222
* Binds a delegated DOM event listener of the specified type to the
1223
* Widget's outtermost DOM element to facilitate the firing of a Custom
1224
* Event of the same type for the Widget instance.
1227
* @method _createUIEvent
1228
* @param type {String} String representing the name of the event
1230
_createUIEvent: function (type) {
1232
var uiEvtNode = this._getUIEventNode(),
1233
parentNode = uiEvtNode.get(PARENT_NODE),
1234
key = (Y.stamp(parentNode) + type),
1235
info = _delegates[key],
1238
// For each Node instance: Ensure that there is only one delegated
1239
// event listener used to fire Widget UI events.
1243
Y.log("Creating delegate for the " + type + " event.", "info", "widget");
1245
handle = parentNode.delegate(type, function (evt) {
1247
var widget = Widget.getByNode(this);
1249
// Make the DOM event a property of the custom event
1250
// so that developers still have access to it.
1251
widget.fire(evt.type, { domEvent: evt });
1253
}, "." + _getWidgetClassName());
1255
_delegates[key] = info = { instances: {}, handle: handle };
1259
// Register this Widget as using this Node as a delegation container.
1260
info.instances[Y.stamp(this)] = 1;
1265
* Determines if the specified event is a UI event.
1268
* @method _isUIEvent
1269
* @param type {String} String representing the name of the event
1270
* @return {String} Event Returns the name of the UI Event, otherwise
1273
_getUIEvent: function (type) {
1274
if (L.isString(type)) {
1275
var sType = type.replace(UI_EVENT_REGEX, UI_EVENT_REGEX_REPLACE),
1278
if (this.UI_EVENTS[sType]) {
1287
* Sets up infastructure required to fire a UI event.
1290
* @method _initUIEvent
1291
* @param type {String} String representing the name of the event
1294
_initUIEvent: function (type) {
1295
var sType = this._getUIEvent(type),
1296
queue = this._uiEvtsInitQueue || {};
1298
if (sType && !queue[sType]) {
1300
Y.log("Deferring creation of " + type + " delegate until render.", "info", "widget");
1302
this.after(RENDER, function() {
1303
this._createUIEvent(sType);
1304
delete this._uiEvtsInitQueue[sType];
1307
this._uiEvtsInitQueue = queue[sType] = 1;
1312
// Override of "on" from Base to facilitate the firing of Widget events
1313
// based on DOM events of the same name/type (e.g. "click", "mouseover").
1314
// Temporary solution until we have the ability to listen to when
1315
// someone adds an event listener (bug 2528230)
1316
on: function (type) {
1317
this._initUIEvent(type);
1318
return Widget.superclass.on.apply(this, arguments);
1321
// Override of "after" from Base to facilitate the firing of Widget events
1322
// based on DOM events of the same name/type (e.g. "click", "mouseover").
1323
// Temporary solution until we have the ability to listen to when
1324
// someone adds an event listener (bug 2528230)
1325
after: function (type) {
1326
this._initUIEvent(type);
1327
return Widget.superclass.after.apply(this, arguments);
1330
// Override of "publish" from Base to facilitate the firing of Widget events
1331
// based on DOM events of the same name/type (e.g. "click", "mouseover").
1332
// Temporary solution until we have the ability to listen to when
1333
// someone publishes an event (bug 2528230)
1334
publish: function (type, config) {
1335
var sType = this._getUIEvent(type);
1336
if (sType && config && config.defaultFn) {
1337
this._initUIEvent(sType);
1339
return Widget.superclass.publish.apply(this, arguments);
1347
}, '3.1.0' ,{requires:['attribute', 'event-focus', 'base', 'node', 'classnamemanager', 'intl']});