2

I'm creating ajax based application. Now I'm working with client side javascript. I created controller which works correctly according to application logic, but i have problems with updating views which contains checkboxes. Here is short example of what I'm doing:

<script type='text/javascript'>
    var model = { 'chk1': true, 'chk2': false, 'chk3': false };
    var Controller = {
        'processCheck': function(chk, value) {
            model[chk] = value;
            if (chk == 'chk2')
                model.chk2 = true;
            if (chk == 'chk3' && value)
                model.chk1 = false;
            if (chk == 'chk1' && model.chk3)
                model.chk1 = false;
            Controller.updateView();
        },
        'updateView': function() {
            $('#chk1').attr('checked', model.chk1);
            $('#chk2').attr('checked', model.chk2);
            $('#chk3').attr('checked', model.chk3);
        }
    }
</script>
<input id='chk1' type="checkbox" onclick="Controller.processCheck('chk1', $('#chk1').attr('checked')); return false;" />
<input id='chk2' type="checkbox" onclick="Controller.processCheck('chk2', $('#chk2').attr('checked')); return false;" />
<input id='chk3' type="checkbox" onclick="Controller.processCheck('chk3', $('#chk3').attr('checked')); return false;" />

So logic is very simplified. Problems occurs when we click on checkboxes. They are updated incorrectly. But if I call controller methods with appropriate parameters, and then check model, all seems to be correct. So problem is with click event in checkboxes. I did not have any problems with input fields and dropdowns.

  • I know how to check and uncheck checkboxes. Here is problem with something else. Actually I'm using $('#id').attr('checked', true); and $('#id').attr('checked', false); – Serhiy Prysyazhnyy Nov 10 '10 at 17:45
  • Why "return false;" in your onclick? The click was not called by anything, therefore there is no return value. – Diodeus - James MacFarlane Nov 10 '10 at 17:47
  • I return false to prevent default behavior; I don't want checkboxes to be updated by click. I want that they only call controller methods, and then controller updates some of them. – Serhiy Prysyazhnyy Nov 10 '10 at 17:48
  • Could you please confirm the behavior you are expecting when checkbox 2 is clicked for the first time? – Philar Nov 10 '10 at 18:39

2 Answers2

3

The click event on checkbox (and radio) is strange and confusing.

In principle, it is happening at the point of mouse interaction, before the click has caused the checkbox to change its state. Consequently, it should be possible to return false or event.preventDefault to cancel the default action of making that state change. In this case the the checkbox's state should never be touched.

However, this is not what actually happens in most browser. Out of some historical quirk stretching back to ancient Netscape, the checked property of the checkbox during the click event handler is in fact the new value. The click event itself already causes the checkbox to change state. Then if you prevent the default action (return false), the browser says “oops, sorry, that change never happened after all” and actively flips the checkbox back to its old value.

This means that if you happened to deliberately set the state of the checkbox from script inside the click handler, that value will be lost, overwritten by the browser's weird attempt to turn back the clock on the original click's change.

So, counter-intuitively, failing to return false and prevent the default action actually causes the browser to interfere less! Another fix is:

setTimeout(Controller.updateView, 0);

instead of calling updateView directly in the click handler. This gives the browser the opportunity to flip back its state before you overwrite it from the model. You can even put the whole of the event handler other than the return false on a 0-timeout.

The proper solution would theoretically be to use onchange instead of onclick. This event unequivocally occurs after the state change, so you don't have to worry about this weirdness and its browser compatibility at all. The problem with onchange is that IE doesn't fire it as fast as it should; it waits for focus to move out of the checkbox before firing, like on a text input. So at least on that browser, for consistency, you'd need the 0-timeout-on-click instead.

bobince
  • 528,062
  • 107
  • 651
  • 834
  • +1 interesting explanation of the problematic difference between the checkbox click event and other controls. – jball Nov 10 '10 at 18:48
  • I agree with @jball. This is very interesting. – tinifni Nov 10 '10 at 18:50
  • Thank you! setTimeout(Controller.updateView, 0); works for me! – Serhiy Prysyazhnyy Nov 10 '10 at 21:02
  • Like much of the murky, unpleasant historical baggage of the web, this is finally being documented and standardised [by HTML5](http://dev.w3.org/html5/spec/Overview.html#checkbox-state) (‘pre-click activation steps’, ‘canceled activation steps’). – bobince Nov 10 '10 at 21:05
0

Diodeus was on the right track, the return false; is causing your problems. Since your updateView function sets all the checks everytime, you don't need to worry about it being momentarily checked, since it will all happen faster than the user can see anyhow. With the return false;, the setting of the checked status in the updateView function for the checkbox that the user clicked will be ignored.

In short, remove the return false; from all the onclick functions, and your code should work the way you want.

Community
  • 1
  • 1
jball
  • 24,791
  • 9
  • 70
  • 92
  • Thank you for answer, but it will not work for me. This code is only simple example, and here we can remove 'return false', but in my project I'm working with complex structure of nested elements. There is requirement that my code should not bubble any events to it's parent controls. I can prevent default action by working with event objects, but that makes my code more complex for understanding and debug. To avoid problems now, and for future developers, we always prevent any events from controls and just call Controllers methods, and than controller updates appropriate Views if it necessary – Serhiy Prysyazhnyy Nov 11 '10 at 11:40
  • @Serhiy Prysyazhnyy - makes sense, and the solution @bobince gives with using `setTimeout` will certainly fit that structure well. – jball Nov 11 '10 at 15:57