Files
openfoodnetwork/app/webpacker/controllers/checked_controller.js
David Cook e31c16df43 Delegate events to the parent element
Ok so I wasn't as smart as I thought I was. The stimulus controller knows when its element is added/removed from the DOM (and calls connect/disconnect appropriately). But if any child elements are added, they don't automatically have my new event handlers.

So I borrowed jQuery's event delegation concept, and listen for any events that 'bubble' up to the controller element, and delegate them as needed.

Alternatively, maybe I could have used a Mutation Observer, but I think it's best to avoid where possible.

Or of course, we could just revert my change and keep the 'data-action's in the HTML. I'm curious to hear opinions on this.."
2025-02-20 12:46:27 +11:00

75 lines
1.9 KiB
JavaScript

import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["all", "checkbox", "disable"];
static values = { count: Number };
connect() {
this.toggleCheckbox();
this.element.addEventListener("change", this.#toggleChangeListener.bind(this), {passive: true});
}
toggleAll() {
this.checkboxTargets.forEach((checkbox) => {
checkbox.checked = this.allTarget.checked;
});
this.countValue = this.allTarget.checked ? this.checkboxTargets.length : 0;
this.#toggleDisabled();
}
toggleCheckbox() {
this.countValue = this.#checkedCount();
this.allTarget.checked = this.#allChecked();
this.#toggleDisabled();
}
countValueChanged() {
window.dispatchEvent(
new CustomEvent("checked:updated", { detail: { count: this.countValue } })
);
}
// private
// Delegate events for targets (this ensures we catch events from newly-added elements after an ajax action)
#toggleChangeListener(event) {
if (event.target == this.allTarget) {
this.toggleAll();
} else if (this.checkboxTargets.includes(event.target)) {
this.toggleCheckbox();
}
}
#checkedCount() {
return this.checkboxTargets.filter((checkbox) => checkbox.checked).length;
}
#allChecked() {
return this.countValue === this.checkboxTargets.length;
}
#closeDetails(elmnt) {
if (elmnt.getElementsByTagName('details').length == 0)
return;
Array.from(elmnt.getElementsByTagName('details')).forEach((element) => element.open = false);
}
#toggleDisabled() {
if (!this.hasDisableTarget) {
return;
}
if (this.#checkedCount() === 0) {
this.disableTargets.forEach((element) => element.classList.add("disabled"));
this.disableTargets.forEach(this.#closeDetails);
} else {
this.disableTargets.forEach((element) => element.classList.remove("disabled"));
}
}
}