How do i correctly use onChange event in ember-bootstrap

Hello.

I’m using ember-bootstrap 5.1.1 to construct a form. I am using the radio form.element and a checkbox form.element. The checkbox items depends on what was selected in radio form.element (basically similar to a filtered or cascading select). When an option is selected in the radio control, I want to call an event handler that filters the items displayed in the checkboxes.

I have some code that invokes the event handler as expected, the parameter passed to the event handler (an Event object) doesn’t contain the value of the selected radio button. How can i get the value of the selected radio button when the ‘change’ event handler is called?

Here’s a simplified version of the ember-bootstrap form template:

<BsForm @formLayout="vertical" @model={{this}} @onSubmit={{this.submit}} as |form|>
      <form.element @controlType="radio" @label="Category" @property="radio" @options={{@model}} @optionLabelPath="name" {{on "change" (fn this.onRadioButtonChange)}}/>
      <form.element @controlType="checkbox" @label="Tags" @property="checkbox" />
      <form.submitButton>Submit</form.submitButton>
    </BsForm>

And here is the controller:

import Controller from '@ember/controller';
import { action } from '@ember/object';

export default class ItemsCreateController extends Controller {

    @action   
    onRadioButtonChange(val) {
        debugger;
        console.log("selected category: " + val);

    }
}

you can use label value as follow: console.log("selected category: " + val.target.parentNode.innerText);

1 Like

I would recommend using another pattern. Your pattern has the disadvantage of needing to manually synchronize the state of selected item in the radio form element and the items disabled in the checkboxes. Trying to manual synchronize those states is adding technical complexity, which is not needed. Instead you could derive the items, which should be disabled in the checkboxes based on the selected element in the radio form element.

const SUPPORTED_TERRITORIES_PER_LANGUAGE = new Map([
  ['de', ['AT', 'DE']],
  ['en', ['AU', 'GB', 'US']],
]);

export default class LocalSelection extends Component {
  @tracked selectedLanguage;
  @tracked selectedTerroritory;

  supportedLanguages = ['de', 'en'];

  get possibleTerroritories() {
    return SUPPORTED_TERRITORIES_PER_LANGUAGE.get('this.selectedLanguage');
  }

  get selectedLocale() {
    return `${this.selectedLanguage}_${this.selectedTerritory}`;
  }
}
<p>Selected locale: {{this.selectedLocale}}</p>

<BsForm @model={{this}} as |form|>
  <form.element @property="selectedLanguage" @controlType="radio" @options={{this.supportedLanguages}} />
  <form.element @property"selectedTerroritory" @controlType="radio" @options={{this.possibleTerroritories}} />
</BsForm>

That example is not production ready yet. E.g. it is missing needed reset of selectedTerroritory when selectedLanguage changes. But I hope it’s enough to demonstrate the concept.

Hello @jelhan

Thank you for your suggestion. I appreciate opportunities to learn different ways of doing things. To help me be more certain about what you mean, what is the technical complexity that your approach eliminates and how does it eliminate it? I’ve included the final-ish code we’ve settled on for this form. Can you point to which lines/approaches you’d change? Thank you.

export default class ItemsCreateController extends Controller {
  @tracked selectedCategory = '';
  @tracked selectedKeywords = [];
  @service store;

  //Event handler when a new category is selected
  @action
  onRadioButtonChange(event) {
    // clear the selected keywords list
    this.selectedKeywords = [];
    //because selectedCategory is tracked, changes to that
    // field cause the 'keywordsForCategory' getter to be called as well
    this.selectedCategory = event.target.parentNode.innerText;
  }

  //returns an array of keywords that are specific to the selectedCategory.
  get keywordsForCategory() {
    if (this.selectedCategory === '') return;

    const cat = this.store
      .peekAll('category')
      .findBy('name', this.selectedCategory);

    const keysOfCategory = [];
    cat.keywords.map((keyword) => {
      keysOfCategory.push(keyword);
    });
    return keysOfCategory.sort(function (a, b) {
      const textA = a.value.toUpperCase();
      const textB = b.value.toUpperCase();
      return textA < textB ? -1 : textA > textB ? 1 : 0;
    });
  }
}
<BsForm @formLayout="vertical" @model={{this}} as |form|>      
      <form.element @controlType="radio" @label="Category" @property="radio" @options={{@model}} @optionLabelPath="name"
        {{on "change" this.onRadioButtonChange}} />
      
      <div class="d-flex flex-column mb-3">
        {{#unless this.selectedCategory}}
          <span class="placeholder-tag-content">Select a category to see available tags.</span>
        {{/unless}}
        {{#each this.keywordsForCategory as |keyword index|}}
          <label class="tag-checkbox-container d-flex align-items">
            <div class="order-2">{{keyword.value}}</div>
            <Input id="test-checkbox-{{index}}" @type="checkbox" {{on "input" (fn this.onCheckedkeyword)}} />
            <div class="checkmark order-1"></div>
          </label>
        {{/each}}
      </div>
    </BsForm>