views:

509

answers:

2

I have a series of buttons that fire the list function when they are clicked. The list function itself contains an AJAX request and a bunch of other stuff before and after which loads in a separate section of the page.

var list = function() { }
$$('.buttons').addEvent('click', list);

Everything works fine if I wait for list to complete before clicking on another button. However, if I click on two buttons quickly, the page will start to return incorrect responses. In fact, it appears as though the responses get out of sync by 1. So if I quickly click on button A then button B, it will load response A. Then if I click (much later) on button C, it will load response B.

There are two ways I can see to solve this:

1) Remove the click event from other buttons when any button is clicked and then restore it when list is complete. Unfortunately, I have tried placing $$('.buttons').removeEvents() at the top of the list function and then $$('.buttons').addEvent('click', list); at the bottom but this has no effect on the problem.

2) Chain the click events so that list is only ever executed when the preceding list has finished.

So can anybody tell me how to get the second solution working? Additionally, if anybody knows why the first solution doesn't work and why I get the weird delayed AJAX response behaviour, that would be great!

+2  A: 

The first solution doesn't work because events on an element are fired in order, but are executed asynchronously. You'll need to setup a queue of callbacks that you can process when the event is triggered.

Here's the basic idea:

addQueuedEvent = function(node, event, callback) {
    if ( typeof callback != "function" ) {
        throw "Callback must be a function";
    }

    event = event.toLowerCase();
    var eventQueue = "_" + event;
    if ( !node.hasOwnProperty(eventQueue) ) {
        node[eventQueue] = [];
    }

    node[eventQueue].push(callback)
};

processEventQueue = function(node, event) {
    var eventQueue = "_" + event;
    if ( node.hasOwnProperty(eventQueue) ) {
        for ( var i=0, l=node[eventQueue].length; i<l; ++i ) {
            node[eventQueue][i]();
        }
    }
};

And the usage:

var someElement = $("#some-element");
addQueuedEvent(someElement, "click", callback1);
addQueuedEvent(someElement, "click", callback2);
addQueuedEvent(someElement, "click", callback3);

someElement.addEvent("click", function() {
    processEventQueue(this, "click");
});

The syntax checks out, but this is not tested. Hope that helps.

Justin Johnson
A: 

i would personally just set a global / scoped variable in your class or whatever - something like 'isClicked = false'.

then simply check at the the the click event function, something like:

var isClicked = false, click = function() {
    if (isClicked)
        return false;

    isClicked = true;
    // ... do stuff, chained or otherwise...

    // when done, make click function work again:
    isClicked = false; // you can do this onComplete on the fx class also if you use it
};

i would go against chaining events with effects - if you have an animation going on, simply wait for it to end--otherwise it can get messy for any trigger happy user that thinks double clicking is the way to go. an alternative is to stop / cancel any effects that are taking place on a new click. for instance, you can stop any tweens etc through FX by something like:

if (isClicked === true) fxinstance.cancel(); 

http://mootools.net/docs/core/Fx/Fx

the other thing you can do is look at the mootools .chain class

http://mootools.net/docs/core/Class/Class.Extras#Chain

and also, on any fx instances, you can pass on link: "chain" and simply queue them up.

good luck

Dimitar Christoff