-2

I'm trying to solve a quite simple task but stuck with JQuery behavior.

I have a HTML button which I disable (add disabled attribute) right after it get clicked to prevent multiple clicks, do something long running (i.e. update DOM with a lot of elements) and enable the button back.

Problem is that even the button is disabled jquery queues all clicks on it and raise my click handler right after it became enabled.

According to JQuery docs it should not raise events for a disabled element.

Bellow is my code. Open JS console, click two times on the button, notice couple 'START --' messages in the console.

<div>
<button id="mybtn" type="button">Find</button>
</div>

var btnElement = $('#mybtn');
btnElement.click(doClick);

           function doClick() {
               var btnElement = $('#mybtn');
               btnElement.attr('disabled', true);

               console.log('START --' + Date());
               for (var i = 0; i < 70000; i++) {
                   var el = $('#mybtn');
                   var w = el.width();
                   w += 10;
               }
               console.log('STOP --' + Date());
               el.attr('disabled', false);
           }
user3191484
  • 23
  • 1
  • 4

1 Answers1

1

Here is my solution http://jsfiddle.net/DRyxd/8/

var btnElement = $('#mybtn');
var buttonIsBusy = false;

function doHeavyJob () {
    console.log('START --' + Date());
    for (var i = 0; i < 70000; i++) {
        var el = $('#mybtn');
        var w = el.width();
        w += 10;
    }
    var timeoutId = setTimeout (unblockTheButton, 0);
    console.log('STOP --' + Date());
}

function unblockTheButton () {
    console.log('unblockTheButton');
    btnElement.attr('disabled', false);
    buttonIsBusy = false;
}

function doClick() {
    console.log('click', buttonIsBusy);
    if (buttonIsBusy) {
        return;
    }

    btnElement.attr('disabled', true);
    buttonIsBusy = true;

    var timeoutId = setTimeout (doHeavyJob, 0);
}


btnElement.click(doClick);

The issue here is that click-handler function has not finished and browser has not refreshed the DOM. That means that block was not yet applied to the button. You can try pushing your heavy code out of the current context like this:

function someHeavyCode () {
    /* do some magic */
}
var timeoutId = setTimeout(someHeavyCode, 0);

This will push your heavy code out of the current context.Letting browser to update the DOM first and only after execute the heavy code.

While the heavy code is executed, browser (at least Chrome) kept the user input queue somewhere in other place (or most-likely other thread). And as soon as heavy code completes - it feeds the DOM with all that queued events. We need to ignore that events somehow. And I use the setTimeout with 0-time again. Letting the browser do what was queued before unblocking the button.

WARNING But be extremely careful with this technique. Browser will still be blocked and if you spawn a lot of such blocks it may hang. See also this Why is setTimeout(fn, 0) sometimes useful? and consider using webworkers.

P.S. Blocking a user input in such a way is not a good approach, try to rethink what you are going to do, probably there is a better solution for that.

Community
  • 1
  • 1
Anton Boritskiy
  • 1,539
  • 3
  • 21
  • 37
  • Thanks mate, you saved me couple days of headache. – user3191484 Jan 13 '14 at 20:20
  • While this solution is great, it doesn't seem to work with Firefox (ver. 54.0.1 on macOS). – Tadayoshi Sato Jul 10 '17 at 05:42
  • 1
    @TadayoshiSato well, as it is said in the answer - consider using webworkers https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers – Anton Boritskiy Jul 10 '17 at 11:45
  • @AntonBoritskiy Thanks, will try Web Workers. BTW, I found that you need to set 500ms instead of 0ms for Firefox https://stackoverflow.com/questions/20747591/why-doesnt-this-settimeout-based-code-work-in-firefox-with-a-small-timeout-wor – Tadayoshi Sato Jul 11 '17 at 08:25
  • 1
    @TadayoshiSato don't do 500ms, you are introducing the actual delay then and if browser takes longer to redraw - it will have no effect. Better is to try to find some browser event to listen to in order to know when the redraw is done - see https://stackoverflow.com/questions/23553328/listening-browser-reflow-event See also https://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ – Anton Boritskiy Jul 11 '17 at 10:31