21

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?
Community
  • 1
  • 1
John Slegers
  • 45,213
  • 22
  • 199
  • 169
  • Interesting question. My first guess would be that jQuery does exactly what you told it to - you attached a click handler to the element and it dutifully fires when clicked. It doesn't really matter if it's enabled or not, since it's just treated as a DOM element. However, it seems that the same isn't true for a plain JS. – VLAZ Aug 06 '16 at 13:59
  • http://api.jquery.com/off/ – Abhi Aug 06 '16 at 14:05
  • @Abhi : The `.off()` method just removes event handlers that were attached with `.on()` – John Slegers Aug 06 '16 at 14:08
  • @JohnSlegers My suggestion was to remove the binding while disabling and re-bind. Not sure about it's efficiency though – Abhi Aug 06 '16 at 14:12
  • What happens when you remove the inline `onClick` functions from your elements ? – DavidDomain Aug 06 '16 at 14:36
  • @Abhi : Do you see the ugly workaround at the bottom of my question? What I'm doing there, is pretty much what you're proposing: I remove all click handlers and store them somewhere when I "disable" the button. When re-enabling the button, I add them again. – John Slegers Aug 08 '16 at 13:56
  • @DavidDomain : [**This happens**](https://jsfiddle.net/wfeyjfvf/) when I remove the inline `onClick` functions from my elements – John Slegers Aug 08 '16 at 13:56
  • What I would do in such a situation is check the button state (enabled or disabled) after my click handler and do stuff accordingly. I agree that this is a deviation from the expected behavior. – Ujwal Ratra Aug 11 '16 at 11:47

4 Answers4

2

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>
guest271314
  • 1
  • 15
  • 104
  • 177
1

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.

gaetanoM
  • 41,594
  • 6
  • 42
  • 61
0

Using .prop() is the right way to do it. I think the issue is in the way that you are "testing" it. See this example where the buttons are disabled/enabled correctly using the toggle button regardless of whether the handler is attached via onclick or with jquery.

window.testFunc = function(event) {
  if (!$('#myButton2').prop('disabled')) {
     alert("hello");
     console.log("hello");
  }
}

$(document).ready(function() {
  var button = $("#myButton2");

  button.on("click", function(event) {
    if (!$(this).prop('disabled')) {
        alert("world");    
        console.log("world");
    }
  });

  $('#toggleButton').click(function() {
   $('#myButton1').prop('disabled', !$('#myButton1').prop('disabled'));
      $('#myButton2').prop('disabled', !$('#myButton2').prop('disabled'));
  });
  
  $('#tester').click(function() {
   $('#myButton1').click();
    $('#myButton2').click();
    
  });

})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="myButton1" value="vanilla button (hello)" onclick="window.testFunc(event)"/>
<input type="button" id="myButton2" value="jquery button (world)"/>
<input type="button" id="toggleButton" value="toggle disabled"/>
<input type="button" id="tester" value="test the buttons"/>

The other obvious solution is to just use vanilla javascript. Just because you are using jQuery doesn't mean that everything "must" be done using it. There are some things that are fine to do without jQuery.

EDIT: I edited the snippet showing how you could prevent jquery's .click() from actually triggering the alerts.

mcgraphix
  • 2,723
  • 1
  • 11
  • 15
  • When you `click` your button programmatically, it ignores whether or not the button is `disabled` (see [**this Fiddle**](https://jsfiddle.net/yxnhd8z9/)). This shouldn't be the case and is pretty much the issue I don't seem to be able to solve without a dirty workaround! – John Slegers Aug 08 '16 at 16:01
  • Yes. Disabling only affects user-interaction so when you "click it" programmatically the .click() handler will still function. See this SO question: http://stackoverflow.com/questions/28213070/disabled-button-still-fires-using-click – mcgraphix Aug 08 '16 at 16:04
  • 1
    I know... but that's not how enabling/disabling a button is supposed to work... or at least not how it's implemented in vanilla JavaScript (see [**this Fiddle**](https://jsfiddle.net/yxnhd8z9/1/)). – John Slegers Aug 08 '16 at 16:10
  • I agree with you that you would expect .click() handler not to get called when the button is disabled but it, for now, is one of those jQuery things we need to work around. I guess the assumption is that, if you are manually triggering click events you could just as easily manually determine if it is enabled. I would hope that the jQuery devs who implemented it that way have a good reason. – mcgraphix Aug 08 '16 at 16:17
  • Considering the expected behavior is implemented in vanilla JavaScript but not in jQuery, I suspect this is a bug in jQuery, but I've not yet been able to verify that. Hence, my question here on SO... – John Slegers Aug 08 '16 at 16:25
  • You might be right. If you look in the jQuery source code it does seem to indicate that click handlers should be processed on disabled elements: https://github.com/jquery/jquery/blob/master/src/event.js#L380 – mcgraphix Aug 08 '16 at 16:31
  • That last comment was supposed to say "seem to indicate that click handlers should NOT be processed..." – mcgraphix Aug 08 '16 at 16:47
  • best answer: ... obvious solution is to just use vanilla javascript. Just because you are using jQuery doesn't mean that everything "must" be done using it. There are some things that are fine to do without jQuery. (there are actually a ton of things better done w/ vanilla js) – MCGRAW Aug 11 '16 at 17:09
0

You're calling the click function directly 3 times ( button.click() ) which fires regardless of disabled attribute.

The disabled property only responds to click events.

See the updated example:

    var button = $("#myButton");
var button2 = $("#myButton2");

button.prop("disabled", false);

button.on("click", function() {
    alert("world");
   button2.prop("disabled", false);
});

button2.prop("disabled", true); 

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