#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: widget/js/WidgetUIEvents.js

    /**
     * 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.
         *
         * <p>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.</p>
         *
         * @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]) {
                Y.log("Deferring creation of " + type + " delegate until render.", "info", "widget");

                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