Is there a way to listen to multiple events?

- 1 answer

Ad

For example, I'd like to trigger a callback when ALL of the events happen:

on(['aEvent', 'bEvent', 'cEvent'], callback)

where the callback function gets an array(or maybe object) of arguments from both aEvent, bEvent and cEvent.

UPDATE: About what I want to implement.

I'm building an alarm system which exposes APIs to my users, so when some events happen, users could respond to them. Sometimes the users would like to respond only if aEvent and bEvent both happen. And this requirement becomes the question.

Ad

Answer

Ad

I'm having a hard time understanding when this would actually be used in the real world which makes me unsure I understand what you really want to do, but you can write your own function to keep track of when each of a series of events have occurred:

function listenMultiple(elem, events, callback) {
    var eventList = events.split(" ");
    var cnt = 0;
    var results = {};

    eventList.forEach(function(event) {
        function localHandler(e) {
            this.removeEventListener(event, localHandler);
            results[event] = e;
            ++cnt;
            if (cnt === eventList.length) {
                callback(results);
            }
        }
        elem.addEventListener(event, localHandler);
    });
}

Usage:

listenMultiple(elem, "aEvent bEvent cEvent", function(results) {
    // all three events have happened
});

Note: To avoid counting a given event more than once, this removes the listener when the event occurs.


Responding multiple times to this situation is complicated because it depends upon exactly how you want that to work as there are numerous ways that could be configured and you have not explained how you would want that to work. The simplest way I can think of would be to just have it re-initialize itself again when it fires. This would wait for all events to occur and then when it does, recall the same function to set it up again. This would essentially restart all counters when the last event happens so it would ignore any surplus of any of the other events that had occurred before the last one of the series occurred.

function listenMultiple(elem, events, callback) {
    var eventList = events.split(" ");
    var cnt = 0;
    var results = {};

    eventList.forEach(function(event) {
        function localHandler(e) {
            this.removeEventListener(event, localHandler);
            results[event] = e;
            ++cnt;
            if (cnt === eventList.length) {
                callback(results);
                // configure for next sequence
                listenMultiple(elem, events, callback);
            }
        }
        elem.addEventListener(event, localHandler);
    });
}

EDIT

Now that you've clarified what you want when there are multiple occurrences of the various events interleaved within them, it gets a lot more complicated because you have to keep N levels of state for each event. Here's a scheme that works for DOM events (since that's the easiest way for me to test it), but it could certainly be adapted to any type of event:

// pass a list of ids and a list of events and a callback
function listenMultiple(ids, events, callback) {
    var eventList = events.split(" ");
    var idList = ids.split(" ");
    if (idList.length !== eventList.length) {
        throw new Error("Number of ids and events must be the same");
    }
    var eventCntrs = {};
    var results = [];

    eventList.forEach(function(event, index) {
        var key = event + idList[index];
        eventCntrs[key] = 0;
        
        function localHandler(e) {
            // get our current cnt for this event
            var cnt = eventCntrs[key];
            log("click on: ", this.id, ", cnt = ", cnt);
            // if we don't yet have any results for this cnt, 
            // then initialize the results object for this cnt
            if (!results[cnt]) {
                results[cnt] = {_cntr: 0};
            }
            // save this event object
            // and count it
            results[cnt][key] = {time: Date.now()};
            ++results[cnt]._cntr;
            
            // count this event
            ++eventCntrs[key];
            
            // if this fills out a result set, then fire the callback
            if (results[cnt]._cntr === eventList.length) {
                callback(results[cnt]);
                // clear item saved in results array so it can be garbage collected
                delete results[cnt];
            }
        }
        document.getElementById(idList[index]).addEventListener(event, localHandler);
    });
}

listenMultiple("test1 test2 test3", "click click click", function(result) {
    log("callback: ", result);
})


// diagnostic function for showing results
function log(args) {
    var str = "";
    for (var i = 0; i < arguments.length; i++) {
        if (typeof arguments[i] === "object") {
            str += JSON.stringify(arguments[i]);
        } else {
            str += arguments[i];
        }
    }
    var div = document.createElement("div");
    div.innerHTML = str;
    var target = log.id ? document.getElementById(log.id) : document.body;
    target.appendChild(div);
}
log.id = "output";
#output {
  margin-top: 30px;
}
Click any sequence of the three buttons.<br><br>Each time a combination of each one of the three is completed, a result will be output via the callback.<br><br>
<button id="test1">1</button><button id="test2">2</button><button id="test3">3</button>
<div id="output">

Ad
source: stackoverflow.com
Ad