import {controllerFactory} from '@utils/createController' export default class ToggleController extends controllerFactory()({ targets: { toggle: HTMLInputElement, }, values: { formMethod: String, formUrl: String, csrfToken: String, }, }) { connect() { this.toggling = false } requiresFormSubmit(): boolean { return this.formUrlValue != null } async toggle() { if (this.toggling) return this.toggling = true if (this.isDisabled()) { return } if (!this.requiresFormSubmit()) { this.performToggle() this.toggling = false return } // toggle immediately to tell screen readers the switch was clicked this.performToggle() try { await this.submitForm() } catch (error) { if (error instanceof Error) { // because we toggle immediately when the switch is clicked, toggle back to the // old state on failure // this.setErrorState(error.message || 'An error occurred, please try again.') this.performToggle() } return } finally { this.toggling = false } // this.setSuccessState() } performToggle(): void { if (this.isOn()) { this.turnOff() } else { this.turnOn() } } turnOn(): void { if (this.isDisabled()) { return } this.toggleTarget.setAttribute('aria-pressed', 'true') } turnOff(): void { if (this.isDisabled()) { return } this.toggleTarget.setAttribute('aria-pressed', 'false') } isOn(): boolean { return this.toggleTarget.getAttribute('aria-pressed') === 'true' } isOff(): boolean { return !this.isOn() } isDisabled(): boolean { return this.toggleTarget.getAttribute('disabled') != null } private async submitForm() { const body = new FormData() const csrfToken = this.documentCsrfToken() || this.csrfTokenValue if (csrfToken) { body.append(this.csrfField(), csrfToken) } body.append(this.toggleTarget.name, this.isOn() ? 'true' : 'false') if (!this.formUrlValue) throw new Error('invalid src') let response try { response = await fetch(this.formUrlValue, { credentials: 'same-origin', method: this.formMethodValue, headers: { 'Requested-With': 'XMLHttpRequest', }, body, }) } catch (error) { throw new Error('A network error occurred, please try again.') } if (!response.ok) { throw new Error(await response.text()) } } // the authenticity token is passed into the element and is not generated in js land private csrfField(): string { return this.toggleTarget.getAttribute('csrf-field') || 'authenticity_token' } private documentCsrfToken(): string | null { const meta = document.querySelector('meta[name=csrf-token]') return meta && meta.getAttribute('content') } }