javascripteventsdom-eventsgoogle-closuregoogle-closure-library

Get an event when an event listener is added or removed on an object in the Google closure library


I am developing an API in which I want to be able to know when an event listener on an object is added or removed. The reason is that some of the events I am firing will require me to continually poll an object for updates, and I don't want to have to poll the object if nothing is listening for the event. I am polling the html5 media player and other players for buffering updates, so eliminating the need to poll is not an option. I also don't want the users to have to call a function to initiate the polling when adding or removing the event listener.

I have developed a solution where I intercept the adding of event listeners before passing the call on to the Closure Library, but it is a very hacky way of doing things, so I'd like to find a better way. I'll post my way below, but I'd love to find a better way to do this.


Solution

  • As I said above, I know this is a very hacky way of doing things, but it is the only way I have come up with. Feel free to remove the sometechie. namespace references and/or use any or all of this code in any project you are working on.

    /**
     * @fileoverview Adds the ability to get events when an event listener is added or removed.
     * @author Joshua Dwire
     * @requires goog.events
     */
    
    
    goog.provide('sometechie.eventhack');
    goog.provide('sometechie.eventhack.GotListenerEvent');
    goog.provide('sometechie.eventhack.LostListenerEvent');
    goog.require('goog.events');
    
    /**
     * @class Provides a way to get events when an event listener is added or removed
     */
    sometechie.eventhack={}
    /**
     * Generates the type of event that will be fired when a listener of the given type is added.
     *
     * @example goog.events.listen([src], sometechie.eventhack.generateGotListenerEventType([type]), [listener], [opt_capt], [opt_handler]) 
     *
     * @param {string} type Event Type.
     * @returns {string} The type of event that will be fired when a listener of the given type is added.
     */
    sometechie.eventhack.generateGotListenerEventType=function(type){
        return '$sometechie.eventhack.gotlistener@'+type;
    }
    
    /**
     * Generates the type of event that will be fired when the first listener of the given type is added.
     *
     * @example goog.events.listen([src], sometechie.eventhack.generateGotFirstListenerEventType([type]), [listener], [opt_capt], [opt_handler]) 
     *
     * @param {string} type Event Type.
     * @returns {string} The type of event that will be fired when the first listener of the given type is added.
     */
    sometechie.eventhack.generateGotFirstListenerEventType=function(type){
        return '$sometechie.eventhack.gotfirstlistener@'+type;
    }
    
    /**
     * Generates the type of event that will be fired when a listener of the given type is removed.
     *
     * @example goog.events.listen([src], sometechie.eventhack.generateLostListenerEventType([type]), [listener], [opt_capt], [opt_handler]) 
     *
     * @param {string} type Event Type.
     * @returns {string} The type of event that will be fired when a listener of the given type is removed.
     */
    sometechie.eventhack.generateLostListenerEventType=function(type){
        return '$sometechie.eventhack.lostlistener@'+type;
    }
    
    /**
     * Generates the type of event that will be fired when the last listener of the given type is removed.
     *
     * @example goog.events.listen([src], sometechie.eventhack.generateLostLastListenerEventType([type]), [listener], [opt_capt], [opt_handler]) 
     *
     * @param {string} type Event Type.
     * @returns {string} The type of event that will be fired when the last listener of the given type is removed.
     */
    sometechie.eventhack.generateLostLastListenerEventType=function(type){
        return '$sometechie.eventhack.lostlastlistener@'+type;
    }
    
    /**
     * Object representing a eventhack GotListener event.
     *
     * @param {string} type Event type.
     * @param {Object} target
     * @param {string} eventType listener type added.
     * @param {boolean} gotFirst If this was the first listener added (there were no listeners before).
     * @extends {goog.events.Event}
     * @constructor
     */
    sometechie.eventhack.GotListenerEvent = function(type, target, eventType, gotFirst) {
      goog.base(this, type, target);
      this.eventType = eventType;
      this.gotFirst = gotFirst;
    };
    goog.inherits(sometechie.eventhack.GotListenerEvent, goog.events.Event);
    /**
     * The type of event that was added.
     * @type {string}
     */
    sometechie.eventhack.GotListenerEvent.prototype.eventType='';
    /**
     * If this was the first listener added (there were no listeners before).
     * @type {boolean}
     */
    sometechie.eventhack.GotListenerEvent.prototype.gotFirst=false;
    
    
    /**
     * Object representing a eventhack LostListener event.
     *
     * @param {string} type Event type.
     * @param {Object} target
     * @param {string} eventType listener type lost.
     * @param {boolean} lostLast If the last listener was removed (there are no listeners anymore).
     * @extends {goog.events.Event}
     * @constructor
     */
    sometechie.eventhack.LostListenerEvent = function(type, target, eventType,lostLast) {
      goog.base(this, type, target);
      this.eventType = eventType;
      this.lostLast = lostLast;
    };
    goog.inherits(sometechie.eventhack.LostListenerEvent, goog.events.Event);
    /**
     * The type of event that was removed.
     * @type {string}
     */
    sometechie.eventhack.LostListenerEvent.prototype.eventType='';
    /**
     * If the last listener was removed (there are no listeners anymore).
     * @type {boolean}
     */
    sometechie.eventhack.LostListenerEvent.prototype.lostLast=false;
    
    //Capture adding of event listeners
    goog.events.$st_listen_overridden=goog.events.listen;
    goog.events.listen=function(src, type, listener, opt_capt, opt_handler){
        var eventSrc = /** @type {goog.events.EventTarget} */ src;
        var hadListener=false;
        try{
            if(!goog.isArray(type) && !goog.isNull(type))hadListener=goog.events.hasListener(src,type);
        }catch(e){}
        var ret = goog.events.$st_listen_overridden.apply(this,arguments);
        try{
            if(!goog.isArray(type) && !goog.isNull(type)){
                var hasListener=goog.events.hasListener(src,type);
                var gotFirst=!hadListener&&hasListener;
    
                if(gotFirst){
                    goog.events.dispatchEvent(eventSrc,
                        new sometechie.eventhack.GotListenerEvent(
                            sometechie.eventhack.generateGotFirstListenerEventType(type),
                            src,type,true));
                }
    
                goog.events.dispatchEvent(eventSrc,
                    new sometechie.eventhack.GotListenerEvent(
                        sometechie.eventhack.generateGotListenerEventType(type),
                        src,type,gotFirst));
            }
        }catch(e){}
        return ret;
    }
    
    //Capture removing of event listeners
    goog.events.$st_unlistenByKey_overridden=goog.events.unlistenByKey;
    goog.events.unlistenByKey=function(key){
        var src=null,type=null;
        try{
            if (goog.events.listeners_[key]){
                var listener = goog.events.listeners_[key];
                if (!listener.removed) {
                    src = /** @type {goog.events.EventTarget} */ listener.src;
                    type = listener.type;
                }
            }
        }catch(e){}
        var ret = goog.events.$st_unlistenByKey_overridden.apply(this,arguments);
        try{
            if(src!=null&&type!=null){
                var lostLast=!goog.events.hasListener(src,type);
    
                if(lostLast){
                    goog.events.dispatchEvent(src,
                        new sometechie.eventhack.LostListenerEvent(
                            sometechie.eventhack.generateLostLastListenerEventType(type),
                            src,type,true));
                }
    
                goog.events.dispatchEvent(src,
                    new sometechie.eventhack.LostListenerEvent(
                        sometechie.eventhack.generateLostListenerEventType(type),
                        src,type,lostLast));
            }
        }catch(e){}
        return ret;
    }