Radio Selection Unselect with Stimulus.js

Radio buttons, modeled after physical buttons on old radios, allow for only one element in a list to be selected. The browser automatically unselects the other options. This simple behavior often fits your needs, but I recently found myself wanting to be alerted on an unselect action so that I could change what was displayed on the page.

Capturing radio selects to unselect content

I had a set of radio buttons, and when selected, I wanted some extra information visible. But, when another radio button was selected, that information needed to hide. There isn’t a builtin way to know that a radio button isn’t selected.

Enter a small Stimulus controller that waits for the selection event, and emits its own event that other radio button controllers will receive. Each controller then hides it’s own the content if it’s not the selected element. It shows an interesting way to use custom events to communicate between disparate controllers that aren’t in a parent-child relationship.

The HTML

<h1>Order An Ice Cream Cone</h1>
<form>
  <div class="control" data-controller="radio-selection-toggle">
    <label class="radio">
      <input type="radio" name="order[flavor]" value="chocolate" data-target="radio-selection-toggle.radioButton" data-action="radio-selection-toggle#toggle">
      Chocolate
    </label>
    <div hidden="true" data-target="radio-selection-toggle.modal">A real treat.</div>
  </div>
  <div class="control" data-controller="radio-selection-toggle">
    <label class="radio">
      <input type="radio" name="order[flavor]" value="vanilla" data-target="radio-selection-toggle.radioButton" data-action="radio-selection-toggle#toggle">
      Vanilla
    </label>
    <div hidden="true" data-target="radio-selection-toggle.modal">Subtle tropical flavors.</div>
  </div>
  <div class="control" data-controller="radio-selection-toggle">
    <label class="radio">
      <input type="radio" name="order[flavor]" value="strawberry" data-target="radio-selection-toggle.radioButton" data-action="radio-selection-toggle#toggle">
      Strawberry
    </label>
    <div hidden="true" data-target="radio-selection-toggle.modal">Sweet and fruity.</div>
  </div>
  <input type="submit" value="Finish Order">
</form>

The Controller

Passing events is a strategy to keep each controller small, and discrete. You easily add and remove options from a radio selection. It also is an example of combining small controllers into a larger, very interactive webpage. This is how you can leverage Stimulus to beat the other JS libraries in interactivity any day.

The controller listens for the toggle action, then creates and sends an event to the document. This ensures all the fellow radio-selection-toggle controllers receive the event. The event contains the name and value of the selected input field, because every controller receives the event, and the selected controller needs to show, not hide, its own information.

import {Controller} from "stimulus"

const RADIO_SELECTION_TOGGLE = 'radio-selection-toggle'

export default class extends Controller {

    static targets = ["modal", "radioButton"]

    connect() {
        document.addEventListener(RADIO_SELECTION_TOGGLE, this.handleSelection.bind(this));
    }

    toggle(event) {
        event.stopImmediatePropagation()

        const selectionEvent = new CustomEvent(RADIO_SELECTION_TOGGLE, {
            detail: {
                name: event.target.name,
                value: event.target.value
            }
        });
        document.dispatchEvent(selectionEvent);
        return false;
    }

    handleSelection(event) {
        if (event.detail.name === this.radioButtonTarget.name) {
            this.modalTarget.hidden = event.detail.value !== this.radioButtonTarget.value
        }
    }
}

Passing events isn’t a new concept, but this is the first time I’ve used it in a Stimulus controller. If you’re familiar with OS X/NextStep development, this follows the message pattern that provided a lot of those platforms developer magic.

Think of message passing as allowing you to have controllers on separate DOM hierarchies in your page that don’t have any explicit relationship. You can use message passing to communicate without needing to setup a parent controller.

Comments or Questions? Let me know how your experience went in the comments below.

Want To Learn More?

Try out some more of my Stimulus.js Tutorials.

Leave a Reply