1

I am trying to have a checkbox inside a button and both clicking on the checkbox or the button should toggle the checkbox and the boolean value bound to it from the view model.

app.html:

<template>
  <h1>${heading}</h1>
  <button type="button" click.trigger="toggleIsChecked()">
    <input type="checkbox" checked.bind="isChecked"> ${isChecked}
  </button>
</template>

app.ts:

export class App {
  isChecked: boolean;

  toggleIsChecked() {
      this.isChecked = !this.isChecked;
  }
}

What happens is that when I click the button (outside the checkbox) everything works as expected. But when I click the checkbox the boolean value in the view model changes but the checkbox is not checked or unchecked. What could be causing this?

I have tried different approaches but they all produce similar results. While debugging I noticed the checkbox gets checked but something in the Aurelia framework removes it almost instantly. Seems like the event handling is not working properly?

EDIT: I made a gist so you can try it yourself: https://gist.run/?id=4a7b2c11db33bdb37213eb4ea1b5b2b0

mattipet
  • 277
  • 2
  • 16
  • IIRC, on the checkbox you should be using one-way binding: `checked.bind="isChecked"` should be `checked.one-way="isChecked"`; also, you probably want to use a click.delegate, not a trigger. – Metro Smurf Dec 27 '16 at 17:12
  • Also, take a look at this SO question: http://stackoverflow.com/questions/35296915 – Metro Smurf Dec 27 '16 at 17:13
  • Hi, thanks for the tips, but switching to one-way binding did not fix it and neither did click.delegate. I have read the SO question you linked before and tried the different suggestions there but without success. Actually the accepted answer to the question you linked suggests using two-way binding instead of one-way. – mattipet Dec 27 '16 at 18:09
  • Use a `label` instead. This is just bad HTML. – powerbuoy Dec 29 '16 at 04:36
  • I admit it is not pretty nor is it probably very good HTML but the reason I asked was to understand better how events work in Aurelia. Again thanks for all the comments! – mattipet Dec 29 '16 at 13:42

3 Answers3

3

It's not the Aurelia framework that is "removing" the checking. What is happening is that when you click the checkbox, the isChecked is automatically set to true, then toggleIsChecked() is fired and isChecked is set to false (isChecked is set twice when you click the checkbox). To solve this you have to not set isChecked if the target is the checkbox. Something like this:

JS

toggleIsChecked(event) {
  if (event.target.tagName === 'INPUT') {
    return true; //checkbox has been clicked, do nothing!
  }
  this.isChecked = !this.isChecked;
}

HTML

<button type="button" click.trigger="toggleIsChecked($event)">
  <input type="checkbox" checked.bind="isChecked"> ${isChecked}
</button>
Fabio
  • 11,892
  • 1
  • 25
  • 41
  • That's not what is happening. If it were then the value in the view model would also not change (or change twice actually). What happens here is that the default event handler is never called and therefore the checkbox is never checked/unchecked. I will post a working gist and an explanation how this should be done. – mattipet Dec 28 '16 at 08:44
2

Same explanation with Fabio Luz & going to do the same thing, but instead of checking event target tag name, You can use self binding behavior, like this

<template>
  <require from='./self'></require>
  <h1>${heading}</h1>
  <button type="button" click.delegate="toggleIsChecked() & self">
    <input type="checkbox" checked.bind="isChecked"> ${isChecked}
  </button>
</template>

What self binding behavior does here is to ensure toggleIsChecked only fires when you click on button itself, not its descendant, same with this block of code:

toggleIsChecked(event) {
  if (event.target === this.button) {
    // Do your thing
  }
}

Note: self just got merged, but has not been released yet. I have included the code at this gist: https://gist.run/?id=5e66dfd996d852344a524010ae82a936

You can read more about the PR here: https://github.com/aurelia/templating-resources/pull/263

bigopon
  • 1,924
  • 2
  • 14
  • 23
  • Thanks for the tip. I have not heard of the self binding behavior before. Sounds useful. Although that is not what is causing my problem and actually what I want to happen is for the event to propagate to the ancestors of the checkbox so that the button click event listener will be called. – mattipet Dec 28 '16 at 08:46
  • And after properly reading through your answer I noticed it works just as well as returning true. So there are two ways to get this to work. I will mark this as the correct answer but I would like everyone to notice that returning true from the event handler also fixes it. – mattipet Dec 28 '16 at 08:56
  • First I had heard of "self", interesting but only works if the checkbox is the only/direct child element. I came up with the following which works for more cases (including table rows). `click.delegate="($event.target.type !== 'checkbox') ? checkbox.checked = !checkbox.checked : true"` (checkbox is a reference on the input field: ref="checkbox") – Matt Dec 06 '17 at 07:24
1

Kind people at the Aurelia Gitter chat provided me with an answer. What is happening in my gist is that the default event handler is not being called. Reason for this is that Aurelia automatically calls the event.preventDefault() function. In order for the default event handler to be called I must return true from my own event handler. Here's a working gist proving how it works: https://gist.run/?id=3cb545572065cffd737f98788a4105a1

Thank you all for your answers. I decided to answer this myself since I got the answer from the Gitter chat, but the kudos belongs to the awesome Aurelia community and especially @CasiOo.

mattipet
  • 277
  • 2
  • 16