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",
49 TAB_INDEX = "tabIndex",
52 RENDERED = "rendered",
53 DESTROYED = "destroyed",
62 EMPTY_FN = function() {},
69 UI_ATTRS = [VISIBLE, DISABLED, HEIGHT, WIDTH, FOCUSED],
73 // Widget nodeguid-to-instance map.
77 * A base class for widgets, providing:
79 * <li>The render lifecycle method, in addition to the init and destroy
80 * lifecycle methods provide by Base</li>
81 * <li>Abstract methods to support consistent MVC structure across
82 * widgets: renderer, renderUI, bindUI, syncUI</li>
83 * <li>Support for common widget attributes, such as boundingBox, contentBox, visible,
84 * disabled, focused, strings</li>
87 * @param config {Object} Object literal specifying widget configuration properties.
93 function Widget(config) {
99 constructor = widget.constructor;
102 widget._cssPrefix = constructor.CSS_PREFIX || _getClassName(constructor.NAME.toLowerCase());
104 Widget.superclass.constructor.apply(widget, arguments);
106 render = widget.get(RENDER);
109 // Render could be a node or boolean
110 if (render !== TRUE) {
113 widget.render(parentNode);
118 * Static property provides a string to identify the class.
120 * Currently used to apply class identifiers to the bounding box
121 * and to classify events fired by the widget.
124 * @property Widget.NAME
128 Widget.NAME = "widget";
131 * Constant used to identify state changes originating from
132 * the DOM (as opposed to the JavaScript model).
134 * @property Widget.UI_SRC
139 UI = Widget.UI_SRC = "ui";
142 * Static property used to define the default attribute
143 * configuration for the Widget.
145 * @property Widget.ATTRS
149 Widget.ATTRS = ATTRS;
151 // Trying to optimize kweight by setting up attrs this way saves about 0.4K min'd
156 * @default Generated using guid()
166 * Flag indicating whether or not this Widget
167 * has been through the render lifecycle phase.
169 * @attribute rendered
180 * @attribute boundingBox
181 * @description The outermost DOM node for the Widget, used for sizing and positioning
182 * of a Widget as well as a containing element for any decorator elements used
184 * @type String | Node
187 ATTRS[BOUNDING_BOX] = {
194 * @attribute contentBox
195 * @description A DOM node that is a direct descendant of a Widget's bounding box that
196 * houses its content.
197 * @type String | Node
200 ATTRS[CONTENT_BOX] = {
201 valueFn:"_defaultCB",
207 * @attribute tabIndex
208 * @description Number (between -32767 to 32767) indicating the widget's
209 * position in the default tab flow. The value is used to set the
210 * "tabIndex" attribute on the widget's bounding box. Negative values allow
211 * the widget to receive DOM focus programmatically (by calling the focus
212 * method), while being removed from the default tab flow. A value of
213 * null removes the "tabIndex" attribute from the widget's bounding box.
219 validator: "_validTabIndex"
224 * @description Boolean indicating if the Widget, or one of its descendants,
236 * @attribute disabled
237 * @description Boolean indicating if the Widget should be disabled. The disabled implementation
238 * is left to the specific classes extending widget.
248 * @description Boolean indicating weather or not the Widget is visible.
258 * @description String with units, or number, representing the height of the Widget. If a number is provided,
259 * the default unit, defined by the Widgets DEF_UNIT, property is used.
261 * @type {String | Number}
269 * @description String with units, or number, representing the width of the Widget. If a number is provided,
270 * the default unit, defined by the Widgets DEF_UNIT, property is used.
272 * @type {String | Number}
280 * @description Collection of strings used to label elements of the Widget's UI.
286 setter: "_strSetter",
291 * Whether or not to render the widget automatically after init, and optionally, to which parent node.
294 * @type boolean | Node
303 * The css prefix which the static Widget.getClassName method should use when constructing class names
305 * @property Widget.CSS_PREFIX
307 * @default Widget.NAME.toLowerCase()
311 Widget.CSS_PREFIX = _getClassName(Widget.NAME.toLowerCase());
314 * Generate a standard prefixed classname for the Widget, prefixed by the default prefix defined
315 * by the <code>Y.config.classNamePrefix</code> attribute used by <code>ClassNameManager</code> and
316 * <code>Widget.NAME.toLowerCase()</code> (e.g. "yui-widget-xxxxx-yyyyy", based on default values for
317 * the prefix and widget class name).
319 * The instance based version of this method can be used to generate standard prefixed classnames,
320 * based on the instances NAME, as opposed to Widget.NAME. This method should be used when you
321 * need to use a constant class name across different types instances.
323 * @method getClassName
324 * @param {String*} args* 0..n strings which should be concatenated, using the default separator defined by ClassNameManager, to create the class name
326 Widget.getClassName = function() {
327 // arguments needs to be array'fied to concat
328 return _getClassName.apply(ClassNameManager, [Widget.CSS_PREFIX].concat(Y.Array(arguments), true));
331 _getWidgetClassName = Widget.getClassName;
334 * Returns the widget instance whose bounding box contains, or is, the given node.
336 * In the case of nested widgets, the nearest bounding box ancestor is used to
337 * return the widget instance.
339 * @method Widget.getByNode
341 * @param node {Node | String} The node for which to return a Widget instance. If a selector
342 * string is passed in, which selects more than one node, the first node found is used.
343 * @return {Widget} Widget instance, or null if not found.
345 Widget.getByNode = function(node) {
347 widgetMarker = _getWidgetClassName();
349 node = Node.one(node);
351 node = node.ancestor("." + widgetMarker, true);
353 widget = _instances[Y.stamp(node, TRUE)];
357 return widget || null;
360 Y.extend(Widget, Y.Base, {
363 * Returns a class name prefixed with the the value of the
364 * <code>YUI.config.classNamePrefix</code> attribute + the instances <code>NAME</code> property.
365 * Uses <code>YUI.config.classNameDelimiter</code> attribute to delimit the provided strings.
369 * // returns "yui-slider-foo-bar", for a slider instance
370 * var scn = slider.getClassName('foo','bar');
372 * // returns "yui-overlay-foo-bar", for an overlay instance
373 * var ocn = overlay.getClassName('foo','bar');
377 * @method getClassName
378 * @param {String}+ One or more classname bits to be joined and prefixed
380 getClassName: function () {
381 return _getClassName.apply(ClassNameManager, [this._cssPrefix].concat(Y.Array(arguments), true));
385 * Initializer lifecycle implementation for the Widget class. Registers the
386 * widget instance, and runs through the Widget's HTML_PARSER definition.
388 * @method initializer
390 * @param config {Object} Configuration object literal for the widget
392 initializer: function(config) {
394 _instances[Y.stamp(this.get(BOUNDING_BOX))] = this;
397 * Notification event, which widget implementations can fire, when
398 * they change the content of the widget. This event has no default
399 * behavior and cannot be prevented, so the "on" or "after"
400 * moments are effectively equivalent (with on listeners being invoked before
403 * @event widget:contentUpdate
405 * @param {EventFacade} e The Event Facade
408 if (this._applyParser) {
409 this._applyParser(config);
414 * Destructor lifecycle implementation for the Widget class. Purges events attached
415 * to the bounding box (and all child nodes) and removes the Widget from the
416 * list of registered widgets.
421 destructor: function() {
423 var boundingBox = this.get(BOUNDING_BOX),
424 contentBox = this.get(CONTENT_BOX),
425 bbGuid = Y.stamp(boundingBox, TRUE);
427 if (bbGuid in _instances) {
428 delete _instances[bbGuid];
431 if (this.UI_EVENTS) {
432 this._destroyUIEvents();
435 this._unbindUI(boundingBox);
437 if (contentBox) { // Just to be safe because it's a last minute change. Really shouldn't be required.
438 contentBox.remove(TRUE);
440 boundingBox.remove(TRUE);
444 * Establishes the initial DOM for the widget. Invoking this
445 * method will lead to the creating of all DOM elements for
446 * the widget (or the manipulation of existing DOM elements
447 * for the progressive enhancement use case).
449 * This method should only be invoked once for an initialized
453 * It delegates to the widget specific renderer method to do
460 * @param parentNode {Object | String} Optional. The Node under which the
461 * Widget is to be rendered. This can be a Node instance or a CSS selector string.
463 * If the selector string returns more than one Node, the first node will be used
464 * as the parentNode. NOTE: This argument is required if both the boundingBox and contentBox
465 * are not currently in the document. If it's not provided, the Widget will be rendered
466 * to the body of the current document in this case.
469 render: function(parentNode) {
471 if (!this.get(DESTROYED) && !this.get(RENDERED)) {
473 * Lifecycle event for the render phase, fired prior to rendering the UI
474 * for the widget (prior to invoking the widget's renderer method).
476 * Subscribers to the "on" moment of this event, will be notified
477 * before the widget is rendered.
480 * Subscribers to the "after" moment of this event, will be notified
481 * after rendering is complete.
484 * @event widget:render
485 * @preventable _defRenderFn
486 * @param {EventFacade} e The Event Facade
488 this.publish(RENDER, {
491 defaultTargetOnly:TRUE,
492 defaultFn: this._defRenderFn
495 this.fire(RENDER, {parentNode: (parentNode) ? Node.one(parentNode) : null});
501 * Default render handler
503 * @method _defRenderFn
505 * @param {EventFacade} e The Event object
506 * @param {Node} parentNode The parent node to render to, if passed in to the <code>render</code> method
508 _defRenderFn : function(e) {
509 this._parentNode = e.parentNode;
512 this._set(RENDERED, TRUE);
514 this._removeLoadingClassNames();
518 * Creates DOM (or manipulates DOM for progressive enhancement)
519 * This method is invoked by render() and is not chained
520 * automatically for the class hierarchy (unlike initializer, destructor)
521 * so it should be chained manually for subclasses if required.
526 renderer: function() {
541 * Configures/Sets up listeners to bind Widget State to UI/DOM
543 * This method is not called by framework and is not chained
544 * automatically for the class hierarchy.
552 * Adds nodes to the DOM
554 * This method is not called by framework and is not chained
555 * automatically for the class hierarchy.
563 * Refreshes the rendered UI, based on Widget State
565 * This method is not called by framework and is not chained
566 * automatically for the class hierarchy.
576 * @description Hides the Widget by setting the "visible" attribute to "false".
580 return this.set(VISIBLE, FALSE);
585 * @description Shows the Widget by setting the "visible" attribute to "true".
589 return this.set(VISIBLE, TRUE);
594 * @description Causes the Widget to receive the focus by setting the "focused"
595 * attribute to "true".
599 return this._set(FOCUSED, TRUE);
604 * @description Causes the Widget to lose focus by setting the "focused" attribute
609 return this._set(FOCUSED, FALSE);
614 * @description Set the Widget's "disabled" attribute to "false".
618 return this.set(DISABLED, FALSE);
623 * @description Set the Widget's "disabled" attribute to "true".
626 disable: function() {
627 return this.set(DISABLED, TRUE);
633 * @param {boolean} expand
635 _uiSizeCB : function(expand) {
636 this.get(CONTENT_BOX).toggleClass(_getWidgetClassName(CONTENT, "expanded"), expand);
640 * Helper method to collect the boundingBox and contentBox, set styles and append to the provided parentNode, if not
641 * already a child. The owner document of the boundingBox, or the owner document of the contentBox will be used
642 * as the document into which the Widget is rendered if a parentNode is node is not provided. If both the boundingBox and
643 * the contentBox are not currently in the document, and no parentNode is provided, the widget will be rendered
644 * to the current document's body.
648 * @param {Node} parentNode The parentNode to render the widget to. If not provided, and both the boundingBox and
649 * the contentBox are not currently in the document, the widget will be rendered to the current document's body.
651 _renderBox: function(parentNode) {
653 // TODO: Performance Optimization [ More effective algo to reduce Node refs, compares, replaces? ]
655 var widget = this, // kweight
656 contentBox = widget.get(CONTENT_BOX),
657 boundingBox = widget.get(BOUNDING_BOX),
658 srcNode = widget.get(SRC_NODE),
659 defParentNode = widget.DEF_PARENT_NODE,
661 doc = (srcNode && srcNode.get(OWNER_DOCUMENT)) || boundingBox.get(OWNER_DOCUMENT) || contentBox.get(OWNER_DOCUMENT);
663 // If srcNode (assume it's always in doc), have contentBox take its place (widget render responsible for re-use of srcNode contents)
664 if (srcNode && !srcNode.compareTo(contentBox) && !contentBox.inDoc(doc)) {
665 srcNode.replace(contentBox);
668 if (!boundingBox.compareTo(contentBox.get(PARENT_NODE)) && !boundingBox.compareTo(contentBox)) {
669 // If contentBox box is already in the document, have boundingBox box take it's place
670 if (contentBox.inDoc(doc)) {
671 contentBox.replace(boundingBox);
673 boundingBox.appendChild(contentBox);
676 parentNode = parentNode || (defParentNode && Node.one(defParentNode));
679 parentNode.appendChild(boundingBox);
680 } else if (!boundingBox.inDoc(doc)) {
681 Node.one(BODY).insert(boundingBox, 0);
686 * Setter for the boundingBox attribute
693 _setBB: function(node) {
694 return this._setBox(this.get(ID), node, this.BOUNDING_TEMPLATE);
698 * Setter for the contentBox attribute
702 * @param {Node|String} node
705 _setCB: function(node) {
706 return (this.CONTENT_TEMPLATE === null) ? this.get(BOUNDING_BOX) : this._setBox(null, node, this.CONTENT_TEMPLATE);
710 * Returns the default value for the contentBox attribute.
712 * For the Widget class, this will be the srcNode if provided, otherwise null (resulting in
713 * a new contentBox node instance being created)
718 _defaultCB : function(node) {
719 return this.get(SRC_NODE) || null;
723 * Helper method to set the bounding/content box, or create it from
724 * the provided template if not found.
729 * @param {String} id The node's id attribute
730 * @param {Node|String} node The node reference
731 * @param {String} template HTML string template for the node
732 * @return {Node} The node
734 _setBox : function(id, node, template) {
735 node = Node.one(node) || Node.create(template);
737 node.set(ID, id || Y.guid());
743 * Initializes the UI state for the Widget's bounding/content boxes.
748 _renderUI: function() {
749 this._renderBoxClassNames();
750 this._renderBox(this._parentNode);
754 * Applies standard class names to the boundingBox and contentBox
756 * @method _renderBoxClassNames
759 _renderBoxClassNames : function() {
760 var classes = this._getClasses(),
762 boundingBox = this.get(BOUNDING_BOX),
765 boundingBox.addClass(_getWidgetClassName());
767 // Start from Widget Sub Class
768 for (i = classes.length-3; i >= 0; i--) {
770 boundingBox.addClass(cl.CSS_PREFIX || _getClassName(cl.NAME.toLowerCase()));
773 // Use instance based name for content box
774 this.get(CONTENT_BOX).addClass(this.getClassName(CONTENT));
778 * Removes class names representative of the widget's loading state from
781 * @method _removeLoadingClassNames
784 _removeLoadingClassNames: function () {
786 var boundingBox = this.get(BOUNDING_BOX),
787 contentBox = this.get(CONTENT_BOX),
788 instClass = this.getClassName(LOADING),
789 widgetClass = _getWidgetClassName(LOADING);
791 boundingBox.removeClass(widgetClass)
792 .removeClass(instClass);
794 contentBox.removeClass(widgetClass)
795 .removeClass(instClass);
799 * Sets up DOM and CustomEvent listeners for the widget.
804 _bindUI: function() {
805 this._bindAttrUI(this._UI_ATTRS.BIND);
813 _unbindUI : function(boundingBox) {
814 this._unbindDOM(boundingBox);
818 * Sets up DOM listeners, on elements rendered by the widget.
823 _bindDOM : function() {
824 var oDocument = this.get(BOUNDING_BOX).get(OWNER_DOCUMENT);
826 // TODO: Perf Optimization: Use Widget.getByNode delegation, to get by
827 // with just one _onDocFocus subscription per sandbox, instead of one per widget
828 this._hDocFocus = oDocument.on("focus", this._onDocFocus, this);
831 // Document doesn't receive focus in Webkit when the user mouses
832 // down on it, so the "focused" attribute won't get set to the
835 this._hDocMouseDown = oDocument.on("mousedown", this._onDocMouseDown, this);
843 _unbindDOM : function(boundingBox) {
844 if (this._hDocFocus) {
845 this._hDocFocus.detach();
848 if (WEBKIT && this._hDocMouseDown) {
849 this._hDocMouseDown.detach();
854 * Updates the widget UI to reflect the attribute state.
859 _syncUI: function() {
860 this._syncAttrUI(this._UI_ATTRS.SYNC);
864 * Sets the height on the widget's bounding box element
866 * @method _uiSetHeight
868 * @param {String | Number} val
870 _uiSetHeight: function(val) {
871 this._uiSetDim(HEIGHT, val);
872 this._uiSizeCB((val !== EMPTY_STR && val !== AUTO));
876 * Sets the width on the widget's bounding box element
878 * @method _uiSetWidth
880 * @param {String | Number} val
882 _uiSetWidth: function(val) {
883 this._uiSetDim(WIDTH, val);
889 * @param {String} dim The dimension - "width" or "height"
890 * @param {Number | String} val The value to set
892 _uiSetDim: function(dimension, val) {
893 this.get(BOUNDING_BOX).setStyle(dimension, L.isNumber(val) ? val + this.DEF_UNIT : val);
897 * Sets the visible state for the UI
899 * @method _uiSetVisible
901 * @param {boolean} val
903 _uiSetVisible: function(val) {
904 this.get(BOUNDING_BOX).toggleClass(this.getClassName(HIDDEN), !val);
908 * Sets the disabled state for the UI
911 * @param {boolean} val
913 _uiSetDisabled: function(val) {
914 this.get(BOUNDING_BOX).toggleClass(this.getClassName(DISABLED), val);
918 * Sets the focused state for the UI
921 * @param {boolean} val
922 * @param {string} src String representing the source that triggered an update to
925 _uiSetFocused: function(val, src) {
926 var boundingBox = this.get(BOUNDING_BOX);
927 boundingBox.toggleClass(this.getClassName(FOCUSED), val);
939 * Set the tabIndex on the widget's rendered UI
941 * @method _uiSetTabIndex
945 _uiSetTabIndex: function(index) {
946 var boundingBox = this.get(BOUNDING_BOX);
948 if (L.isNumber(index)) {
949 boundingBox.set(TAB_INDEX, index);
951 boundingBox.removeAttribute(TAB_INDEX);
956 * @method _onDocMouseDown
957 * @description "mousedown" event handler for the owner document of the
958 * widget's bounding box.
960 * @param {EventFacade} evt The event facade for the DOM focus event
962 _onDocMouseDown: function (evt) {
963 if (this._domFocus) {
964 this._onDocFocus(evt);
969 * DOM focus event handler, used to sync the state of the Widget with the DOM
971 * @method _onDocFocus
973 * @param {EventFacade} evt The event facade for the DOM focus event
975 _onDocFocus: function (evt) {
976 this._domFocus = this.get(BOUNDING_BOX).contains(evt.target); // contains() checks invoking node also
977 this._set(FOCUSED, this._domFocus, { src: UI });
981 * Generic toString implementation for all widgets.
984 * @return {String} The default string value for the widget [ displays the NAME of the instance, and the unique id ]
986 toString: function() {
987 // Using deprecated name prop for kweight squeeze.
988 return this.name + "[" + this.get(ID) + "]";
992 * Default unit to use for dimension values
1000 * Default node to render the bounding box to. If not set,
1001 * will default to the current document body.
1003 * @property DEF_PARENT_NODE
1004 * @type String | Node
1006 DEF_PARENT_NODE : null,
1009 * Property defining the markup template for content box. If your Widget doesn't
1010 * need the dual boundingBox/contentBox structure, set CONTENT_TEMPLATE to null,
1011 * and contentBox and boundingBox will both point to the same Node.
1013 * @property CONTENT_TEMPLATE
1016 CONTENT_TEMPLATE : DIV,
1019 * Property defining the markup template for bounding box.
1021 * @property BOUNDING_TEMPLATE
1024 BOUNDING_TEMPLATE : DIV,
1030 _guid : function() {
1035 * @method _validTabIndex
1037 * @param {Number} tabIndex
1039 _validTabIndex : function (tabIndex) {
1040 return (L.isNumber(tabIndex) || L.isNull(tabIndex));
1044 * Binds after listeners for the list of attributes provided
1046 * @method _bindAttrUI
1048 * @param {Array} attrs
1050 _bindAttrUI : function(attrs) {
1054 for (i = 0; i < l; i++) {
1055 this.after(attrs[i] + CHANGE, this._setAttrUI);
1060 * Invokes the _uiSet=ATTR NAME> method for the list of attributes provided
1062 * @method _syncAttrUI
1064 * @param {Array} attrs
1066 _syncAttrUI : function(attrs) {
1067 var i, l = attrs.length, attr;
1068 for (i = 0; i < l; i++) {
1070 this[_UISET + _toInitialCap(attr)](this.get(attr));
1075 * @method _setAttrUI
1077 * @param {EventFacade} e
1079 _setAttrUI : function(e) {
1080 this[_UISET + _toInitialCap(e.attrName)](e.newVal, e.src);
1084 * The default setter for the strings attribute. Merges partial sets
1085 * into the full string set, to allow users to partial sets of strings
1087 * @method _strSetter
1089 * @param {Object} strings
1090 * @return {String} The full set of strings to set
1092 _strSetter : function(strings) {
1093 return Y.merge(this.get(STRINGS), strings);
1097 * Helper method to get a specific string value
1099 * @deprecated Used by deprecated WidgetLocale implementations.
1101 * @param {String} key
1102 * @return {String} The string
1104 getString : function(key) {
1105 return this.get(STRINGS)[key];
1109 * Helper method to get the complete set of strings for the widget
1111 * @deprecated Used by deprecated WidgetLocale implementations.
1113 * @param {String} key
1114 * @return {String} The string
1116 getStrings : function() {
1117 return this.get(STRINGS);
1121 * The lists of UI attributes to bind and sync for widget's _bindUI and _syncUI implementations
1123 * @property _UI_ATTRS
1129 SYNC: UI_ATTRS.concat(TAB_INDEX)
1136 }, '3.3.0' ,{requires:['attribute', 'event-focus', 'base-base', 'base-pluginhost', 'node-base', 'node-style', 'node-event-delegate', 'classnamemanager']});
1137 YUI.add('widget-uievents', function(Y) {
1140 * Support for Widget UI Events (Custom Events fired by the widget, which wrap the underlying DOM events - e.g. widget:click, widget:mousedown)
1143 * @submodule widget-uievents
1146 var BOUNDING_BOX = "boundingBox",
1150 EVENT_PREFIX_DELIMITER = ":",
1152 // Map of Node instances serving as a delegation containers for a specific
1153 // event type to Widget instances using that delegation container.
1154 _uievts = Y.Widget._uievts = Y.Widget._uievts || {};
1156 Y.mix(Widget.prototype, {
1159 * Destructor logic for UI event infrastructure,
1160 * invoked during Widget destruction.
1162 * @method _destroyUIEvents
1166 _destroyUIEvents: function() {
1168 var widgetGuid = Y.stamp(this, true);
1170 Y.each(_uievts, function (info, key) {
1171 if (info.instances[widgetGuid]) {
1172 // Unregister this Widget instance as needing this delegated
1174 delete info.instances[widgetGuid];
1176 // There are no more Widget instances using this delegated
1177 // event listener, so detach it.
1179 if (Y.Object.isEmpty(info.instances)) {
1180 info.handle.detach();
1183 delete _uievts[key];
1191 * Map of DOM events that should be fired as Custom Events by the
1194 * @property UI_EVENTS
1198 UI_EVENTS: Y.Node.DOM_EVENTS,
1201 * Returns the node on which to bind delegate listeners.
1203 * @method _getUIEventNode
1207 _getUIEventNode: function () {
1208 return this.get(BOUNDING_BOX);
1212 * Binds a delegated DOM event listener of the specified type to the
1213 * Widget's outtermost DOM element to facilitate the firing of a Custom
1214 * Event of the same type for the Widget instance.
1218 * @method _createUIEvent
1219 * @param type {String} String representing the name of the event
1221 _createUIEvent: function (type) {
1223 var uiEvtNode = this._getUIEventNode(),
1224 key = (Y.stamp(uiEvtNode) + type),
1225 info = _uievts[key],
1228 // For each Node instance: Ensure that there is only one delegated
1229 // event listener used to fire Widget UI events.
1233 handle = uiEvtNode.delegate(type, function (evt) {
1235 var widget = Widget.getByNode(this);
1236 // Make the DOM event a property of the custom event
1237 // so that developers still have access to it.
1238 widget.fire(evt.type, { domEvent: evt });
1240 }, "." + Y.Widget.getClassName());
1242 _uievts[key] = info = { instances: {}, handle: handle };
1245 // Register this Widget as using this Node as a delegation container.
1246 info.instances[Y.stamp(this)] = 1;
1250 * Determines if the specified event is a UI event.
1253 * @method _isUIEvent
1255 * @param type {String} String representing the name of the event
1256 * @return {String} Event Returns the name of the UI Event, otherwise
1259 _getUIEvent: function (type) {
1261 if (L.isString(type)) {
1262 var sType = this.parseType(type)[1],
1267 // TODO: Get delimiter from ET, or have ET support this.
1268 iDelim = sType.indexOf(EVENT_PREFIX_DELIMITER);
1270 sType = sType.substring(iDelim + EVENT_PREFIX_DELIMITER.length);
1273 if (this.UI_EVENTS[sType]) {
1283 * Sets up infrastructure required to fire a UI event.
1286 * @method _initUIEvent
1288 * @param type {String} String representing the name of the event
1291 _initUIEvent: function (type) {
1292 var sType = this._getUIEvent(type),
1293 queue = this._uiEvtsInitQueue || {};
1295 if (sType && !queue[sType]) {
1297 this._uiEvtsInitQueue = queue[sType] = 1;
1299 this.after(RENDER, function() {
1300 this._createUIEvent(sType);
1301 delete this._uiEvtsInitQueue[sType];
1306 // Override of "on" from Base to facilitate the firing of Widget events
1307 // based on DOM events of the same name/type (e.g. "click", "mouseover").
1308 // Temporary solution until we have the ability to listen to when
1309 // someone adds an event listener (bug 2528230)
1310 on: function (type) {
1311 this._initUIEvent(type);
1312 return Widget.superclass.on.apply(this, arguments);
1315 // Override of "publish" from Base to facilitate the firing of Widget events
1316 // based on DOM events of the same name/type (e.g. "click", "mouseover").
1317 // Temporary solution until we have the ability to listen to when
1318 // someone publishes an event (bug 2528230)
1319 publish: function (type, config) {
1320 var sType = this._getUIEvent(type);
1321 if (sType && config && config.defaultFn) {
1322 this._initUIEvent(sType);
1324 return Widget.superclass.publish.apply(this, arguments);
1327 }, true); // overwrite existing EventTarget methods
1330 }, '3.3.0' ,{requires:['widget-base', 'node-event-delegate']});
1331 YUI.add('widget-htmlparser', function(Y) {
1334 * Adds HTML Parser support to the base Widget class
1337 * @submodule widget-htmlparser
1342 var Widget = Y.Widget,
1346 SRC_NODE = "srcNode",
1347 CONTENT_BOX = "contentBox";
1350 * Object hash, defining how attribute values are to be parsed from
1351 * markup contained in the widget's content box. e.g.:
1354 * // Set single Node references using selector syntax
1355 * // (selector is run through node.one)
1356 * titleNode: "span.yui-title",
1357 * // Set NodeList references using selector syntax
1358 * // (array indicates selector is to be run through node.all)
1359 * listNodes: ["li.yui-item"],
1360 * // Set other attribute types, using a parse function.
1361 * // Context is set to the widget instance.
1362 * label: function(contentBox) {
1363 * return contentBox.one("span.title").get("innerHTML");
1368 * @property Widget.HTML_PARSER
1372 Widget.HTML_PARSER = {};
1375 * The build configuration for the Widget class.
1377 * Defines the static fields which need to be aggregated,
1378 * when this class is used as the main class passed to
1379 * the <a href="Base.html#method_build">Base.build</a> method.
1381 * @property _buildCfg
1387 Widget._buildCfg = {
1388 aggregates : ["HTML_PARSER"]
1392 * The DOM node to parse for configuration values, passed to the Widget's HTML_PARSER definition
1394 * @attribute srcNode
1395 * @type String | Node
1398 Widget.ATTRS[SRC_NODE] = {
1401 getter: "_getSrcNode",
1405 Y.mix(Widget.prototype, {
1408 * @method _getSrcNode
1410 * @return {Node} The Node to apply HTML_PARSER to
1412 _getSrcNode : function(val) {
1413 return val || this.get(CONTENT_BOX);
1417 * @method _applyParsedConfig
1419 * @return {Object} The merged configuration literal
1421 _applyParsedConfig : function(node, cfg, parsedCfg) {
1422 return (parsedCfg) ? Y.mix(cfg, parsedCfg, false) : cfg;
1426 * Utilitity method used to apply the <code>HTML_PARSER</code> configuration for the
1427 * instance, to retrieve config data values.
1429 * @method _applyParser
1431 * @param config {Object} User configuration object (will be populated with values from Node)
1433 _applyParser : function(config) {
1436 srcNode = widget.get(SRC_NODE),
1437 schema = widget._getHtmlParser(),
1441 if (schema && srcNode) {
1442 Y.Object.each(schema, function(v, k, o) {
1445 if (Lang.isFunction(v)) {
1446 val = v.call(widget, srcNode);
1448 if (Lang.isArray(v)) {
1449 val = srcNode.all(v[0]);
1450 if (val.isEmpty()) {
1454 val = srcNode.one(v);
1458 if (val !== null && val !== undefined) {
1459 parsedConfig = parsedConfig || {};
1460 parsedConfig[k] = val;
1464 config = widget._applyParsedConfig(srcNode, config, parsedConfig);
1468 * Gets the HTML_PARSER definition for this instance, by merging HTML_PARSER
1469 * definitions across the class hierarchy.
1472 * @method _getHtmlParser
1473 * @return {Object} HTML_PARSER definition for this instance
1475 _getHtmlParser : function() {
1476 // Removed caching for kweight. This is a private method
1477 // and only called once so don't need to cache HTML_PARSER
1478 var classes = this._getClasses(),
1482 for (i = classes.length - 1; i >= 0; i--) {
1483 p = classes[i].HTML_PARSER;
1485 Y.mix(parser, p, true);
1493 }, '3.3.0' ,{requires:['widget-base']});
1494 YUI.add('widget-skin', function(Y) {
1497 * Provides skin related utlility methods.
1500 * @submodule widget-skin
1503 var BOUNDING_BOX = "boundingBox",
1504 CONTENT_BOX = "contentBox",
1506 _getClassName = Y.ClassNameManager.getClassName;
1509 * Returns the name of the skin that's currently applied to the widget.
1510 * This is only really useful after the widget's DOM structure is in the
1511 * document, either by render or by progressive enhancement. Searches up
1512 * the Widget's ancestor axis for a class yui3-skin-(name), and returns the
1513 * (name) portion. Otherwise, returns null.
1515 * @method getSkinName
1517 * @return {String} the name of the skin, or null (yui3-skin-sam => sam)
1520 Y.Widget.prototype.getSkinName = function () {
1521 var root = this.get( CONTENT_BOX ) || this.get( BOUNDING_BOX ),
1522 search = new RegExp( '\\b' + _getClassName( SKIN ) + '-(\\S+)' ),
1526 root.ancestor( function ( node ) {
1527 match = node.get( 'className' ).match( search );
1532 return ( match ) ? match[1] : null;
1536 }, '3.3.0' ,{requires:['widget-base']});
1539 YUI.add('widget', function(Y){}, '3.3.0' ,{use:['widget-base', 'widget-uievents', 'widget-htmlparser', 'widget-skin']});