/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.4.0 build: nightly */ YUI.add('widget-uievents', function(Y) { /** * Support for Widget UI Events (Custom Events fired by the widget, which wrap the underlying DOM events - e.g. widget:click, widget:mousedown) * * @module widget * @submodule widget-uievents */ var BOUNDING_BOX = "boundingBox", Widget = Y.Widget, RENDER = "render", L = Y.Lang, EVENT_PREFIX_DELIMITER = ":", // Map of Node instances serving as a delegation containers for a specific // event type to Widget instances using that delegation container. _uievts = Y.Widget._uievts = Y.Widget._uievts || {}; Y.mix(Widget.prototype, { /** * Destructor logic for UI event infrastructure, * invoked during Widget destruction. * * @method _destroyUIEvents * @for Widget * @private */ _destroyUIEvents: function() { var widgetGuid = Y.stamp(this, true); Y.each(_uievts, function (info, key) { if (info.instances[widgetGuid]) { // Unregister this Widget instance as needing this delegated // event listener. delete info.instances[widgetGuid]; // There are no more Widget instances using this delegated // event listener, so detach it. if (Y.Object.isEmpty(info.instances)) { info.handle.detach(); if (_uievts[key]) { delete _uievts[key]; } } } }); }, /** * Map of DOM events that should be fired as Custom Events by the * Widget instance. * * @property UI_EVENTS * @for Widget * @type Object */ UI_EVENTS: Y.Node.DOM_EVENTS, /** * Returns the node on which to bind delegate listeners. * * @method _getUIEventNode * @for Widget * @protected */ _getUIEventNode: function () { return this.get(BOUNDING_BOX); }, /** * Binds a delegated DOM event listener of the specified type to the * Widget's outtermost DOM element to facilitate the firing of a Custom * Event of the same type for the Widget instance. * * @method _createUIEvent * @for Widget * @param type {String} String representing the name of the event * @private */ _createUIEvent: function (type) { var uiEvtNode = this._getUIEventNode(), key = (Y.stamp(uiEvtNode) + type), info = _uievts[key], handle; // For each Node instance: Ensure that there is only one delegated // event listener used to fire Widget UI events. if (!info) { handle = uiEvtNode.delegate(type, function (evt) { var widget = Widget.getByNode(this); // Widget could be null if node instance belongs to // another Y instance. if (widget) { if (widget._filterUIEvent(evt)) { widget.fire(evt.type, { domEvent: evt }); } } }, "." + Y.Widget.getClassName()); _uievts[key] = info = { instances: {}, handle: handle }; } // Register this Widget as using this Node as a delegation container. info.instances[Y.stamp(this)] = 1; }, /** * This method is used to determine if we should fire * the UI Event or not. The default implementation makes sure * that for nested delegates (nested unrelated widgets), we don't * fire the UI event listener more than once at each level. * *

For example, without the additional filter, if you have nested * widgets, each widget will have a delegate listener. If you * click on the inner widget, the inner delegate listener's * filter will match once, but the outer will match twice * (based on delegate's design) - once for the inner widget, * and once for the outer.

* * @method _filterUIEvent * @for Widget * @param {DOMEventFacade} evt * @return {boolean} true if it's OK to fire the custom UI event, false if not. * @private * */ _filterUIEvent: function(evt) { // Either it's hitting this widget's delegate container (and not some other widget's), // or the container it's hitting is handling this widget's ui events. return (evt.currentTarget.compareTo(evt.container) || evt.container.compareTo(this._getUIEventNode())); }, /** * Determines if the specified event is a UI event. * * @private * @method _isUIEvent * @for Widget * @param type {String} String representing the name of the event * @return {String} Event Returns the name of the UI Event, otherwise * undefined. */ _getUIEvent: function (type) { if (L.isString(type)) { var sType = this.parseType(type)[1], iDelim, returnVal; if (sType) { // TODO: Get delimiter from ET, or have ET support this. iDelim = sType.indexOf(EVENT_PREFIX_DELIMITER); if (iDelim > -1) { sType = sType.substring(iDelim + EVENT_PREFIX_DELIMITER.length); } if (this.UI_EVENTS[sType]) { returnVal = sType; } } return returnVal; } }, /** * Sets up infrastructure required to fire a UI event. * * @private * @method _initUIEvent * @for Widget * @param type {String} String representing the name of the event * @return {String} */ _initUIEvent: function (type) { var sType = this._getUIEvent(type), queue = this._uiEvtsInitQueue || {}; if (sType && !queue[sType]) { this._uiEvtsInitQueue = queue[sType] = 1; this.after(RENDER, function() { this._createUIEvent(sType); delete this._uiEvtsInitQueue[sType]; }); } }, // Override of "on" from Base to facilitate the firing of Widget events // based on DOM events of the same name/type (e.g. "click", "mouseover"). // Temporary solution until we have the ability to listen to when // someone adds an event listener (bug 2528230) on: function (type) { this._initUIEvent(type); return Widget.superclass.on.apply(this, arguments); }, // Override of "publish" from Base to facilitate the firing of Widget events // based on DOM events of the same name/type (e.g. "click", "mouseover"). // Temporary solution until we have the ability to listen to when // someone publishes an event (bug 2528230) publish: function (type, config) { var sType = this._getUIEvent(type); if (sType && config && config.defaultFn) { this._initUIEvent(sType); } return Widget.superclass.publish.apply(this, arguments); } }, true); // overwrite existing EventTarget methods }, '3.4.0' ,{requires:['widget-base', 'node-event-delegate']});