Disabling a button in vanilla JavaScript and in jQuery

15,579

Solution 1

To achieve expected result, you can utilize .isTrigger within jQuery triggered click handler to determine if event is triggered by javascript, and not user action.

Define attribute event listener as a named function, where this can be passed to check disabled property at if condition if alert() is called, or not called.

Use .attr("disabled", "disabled") to set disabled at element, .removeAttr("disabled") to remove attribute; .attr("onclick", null) to remove event attribute onclick handler; .attr("onclick", "handleClick(true)") to reset event attribute.

<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

<input type="button" id="myButton" value="button" onclick="handleClick(this)" />
<script>
  function handleClick(el) {
    if (el.disabled !== "disabled")
      alert("Hello")
  }
  var button = $("#myButton");

  button.on("click", function(e) {
    console.log(e);
    if (e.isTrigger !== 3 && !e.target.disabled)
      alert("world");
  });

  button.attr("disabled", "disabled");
  button.attr("onclick", null);
  button.click(); // no output
  
  setTimeout(function() {
    button.removeAttr("disabled");
    button.attr("onclick", "handleClick(button[0])");
    button.click(); // Output : "world" and "Hello"
    // click button during 9000 between `setTimeout` calls
    // to call both jQuery event and event attribute
  }, 1000);

  setTimeout(function() {
    button.attr("disabled", "disabled");
    button.attr("onclick", null);
    button.click(); // no output
  }, 10000);
  
</script>

Solution 2

If you take a look to jquery-1.12.4.js at these lines:

handlers: function( event, handlers ) {
    var i, matches, sel, handleObj,
        handlerQueue = [],
        delegateCount = handlers.delegateCount,
        cur = event.target;

    // Support (at least): Chrome, IE9
    // Find delegate handlers
    // Black-hole SVG <use> instance trees (#13180)
    //
    // Support: Firefox<=42+
    // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
    if ( delegateCount && cur.nodeType &&
        ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {

        /* jshint eqeqeq: false */
        for ( ; cur != this; cur = cur.parentNode || this ) {
            /* jshint eqeqeq: true */

            // Don't check non-elements (#13208)
            // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
            if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {

You will you see a different handling of events according to the delegation type:

$(document).on("click", '#btn', function() {
  console.log("world");
});


$(function () {
  $('#btnToggle').on('click', function(e) {
    $('#btn').prop('disabled', !$('#btn').prop('disabled'));
  });
  
  
  $('#btnTestClick').on('click', function(e) {
    $('#btn').click();
  });
});
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>

<button id="btn">Click  Me</button>
<button id="btnToggle">Enable/Disable button</button>
<button id="btnTestClick">Test Click</button>

Of course, if you attach the event like in:

$('#btn').on("click", function() {
    alert("world");
});

The behaviour is different and seems strange.

Share:
15,579
John Slegers
Author by

John Slegers

I'm John Slegers, full stack web developer from Belgium. Prefered tech Stats Where to find me

Updated on June 05, 2022

Comments

  • John Slegers
    John Slegers almost 2 years

    Vanilla JavaScript

    In vanilla JavaScript, one can easily enable and disable a button using the following statement:

    button.disabled = state;
    

    This works both when humans try to click a button and when buttons are clicked programmatically:

    var button = document.getElementById('myButton');
    
    button.addEventListener('click', function() {
        alert('world');
    });
    
    button.disabled = true;
    button.click(); // No output
    button.disabled = false;
    button.click(); // Output : "Hello" and "world
    button.disabled = true;
    button.click(); // No output
    <input type="button" id="myButton" value="button" onClick="alert('Hello')"/>

    This also works when using the MouseEvent interface:

    var button = document.getElementById('myButton');
    
    var click = new MouseEvent("click", {
        "view": window
    });
    
    button.addEventListener('click', function() {
        alert('world');
    });
    
    button.disabled = true;
    button.dispatchEvent(click); // No output
    button.disabled = false;
    button.dispatchEvent(click); // Output : "Hello" and "world
    button.disabled = true;
    button.dispatchEvent(click); // No output
    <input type="button" id="myButton" value="button" onClick="alert('Hello')"/>


    jQuery

    I can't seem to be able to do the same with jQuery, though :

    var button = $("#myButton");
    
    button.on("click", function() {
        alert("world");
    });
    
    button.prop("disabled", true);
    button.click(); // Output : "world" and "Hello"
    button.prop("disabled", false);
    button.click(); // Output : "world" and "Hello"
    button.prop("disabled", true);
    button.click(); // Output : "world" and "Hello"
    <script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
    <input type="button" id="myButton" value="button" onClick="alert('Hello')"/>

    Both button.prop("disabled", true); and button.attr("disabled", true); simply change the disabled property of the button element, but neither disables the actual click event. This means that events are triggered whenever button.click(); is called, even if the button is disabled!

    Additionally, "world" and "Hello" are output in the wrong order.

    The simplest code I could come up with to emulate the behavior of the vanilla JavaScript versions, is this :

    var button = $("#myButton");
    
    button.on("click", function() {
        alert("world");
    });
    
    button.disable = (function() {
        var onclick = null;
        var click = [];
        return function(state) {
            if(state) {
                this.prop('disabled', true);
                if(this.prop('onclick') !== null) {
                    onclick = this.prop('onclick');
                    this.prop('onclick', null);
                }
                var listeners = $._data(this.get()[0], "events");
                listeners = typeof listeners === 'undefined' ? [] : listeners['click'];
                if(listeners && listeners.length > 0) {
                    for(var i = 0; i < listeners.length; i++) {
                        click.push(listeners[i].handler);
                    }
                    this.off('click');
                }
            } else {
                this.removeProp('disabled');
                if(onclick !== null) {
                    this.prop('onclick', onclick);
                    onclick = null;
                }
                if(click.length > 0) {
                    this.off('click');
                    for(var i = 0; i < click.length; i++) {
                        this.on("click", click[i]);
                    }
                    click = [];
                }
            }
        }
    })();
    
    button.disable(true);
    button.click(); // No output
    button.disable(false);
    button.click(); // Output : "Hello" and "world
    button.disable(true);
    button.click(); // No output
    <script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
    <input type="button" id="myButton" value="button" onClick="alert('Hello')"/>

    That is, of course, ridiculously convoluted and "hacky" code to achieve something as simple as disabling a button.


    My questions

    • Why is it that jQuery - unlike vanilla JS - doesn't disable the events when disabling a button?
    • Is this to be considered a bug or a feature in jQuery?
    • Is there something I'm overlooking?
    • Is there a simpler way to get the expected behavior in jQuery?