diff --git a/client/src/controllers/InitController.test.js b/client/src/controllers/InitController.test.js index 88b161b97f..088abb8d72 100644 --- a/client/src/controllers/InitController.test.js +++ b/client/src/controllers/InitController.test.js @@ -103,4 +103,49 @@ describe('InitController', () => { expect(handleEvent).toHaveBeenCalledTimes(1); }); }); + + describe('when using custom event names', () => { + const handleEvent = jest.fn(); + document.addEventListener('w-init:ready', handleEvent); + document.addEventListener('custom:event', handleEvent); + document.addEventListener('other-custom:event', handleEvent); + + beforeAll(() => { + jest.clearAllMocks(); + + application?.stop(); + + // intentionally adding extra spaces in the event-value below + const events = 'custom:event other-custom:event '; + + document.body.innerHTML = ` +
+ Test body +
+ `; + + application = Application.start(); + }); + + it('should dispatch additional events', async () => { + expect(handleEvent).not.toHaveBeenCalled(); + + application.register('w-init', InitController); + + await Promise.resolve(); // no delay, just wait for the next tick + + expect(handleEvent).toHaveBeenCalledTimes(3); + + expect(handleEvent.mock.calls.map(([event]) => event.type)).toEqual([ + 'w-init:ready', + 'custom:event', + 'other-custom:event', + ]); + }); + }); }); diff --git a/client/src/controllers/InitController.ts b/client/src/controllers/InitController.ts index 090bcbb781..336de58750 100644 --- a/client/src/controllers/InitController.ts +++ b/client/src/controllers/InitController.ts @@ -5,21 +5,28 @@ import { debounce } from '../utils/debounce'; * Adds the ability for a controlled element to add or remove classes * when ready to be interacted with. * - * @example + * @example - Dynamic classes when ready *
* When the DOM is ready, this div will have the class 'loaded' added and 'hide-me' removed. *
+ * + * @example - Custom event dispatching + *
+ * When the DOM is ready, two additional custom events will be dispatched; `custom:event` and `other-custom:event`. + *
*/ export class InitController extends Controller { static classes = ['ready', 'remove']; static values = { delay: { default: -1, type: Number }, + event: { default: '', type: String }, }; declare readonly readyClasses: string[]; declare readonly removeClasses: string[]; + declare eventValue: string; declare delayValue: number; connect() { @@ -31,14 +38,19 @@ export class InitController extends Controller { * By default, the action will be immediate (negative value). * Even when immediate, allow for a microtask delay to allow for other * controllers to connect, then do any updates do classes/dispatch events. + * Support the ability to also dispatch custom event names. */ ready() { + const events = this.eventValue.split(' ').filter(Boolean); const delayValue = this.delayValue; debounce(() => true, delayValue < 0 ? null : delayValue)().then(() => { this.element.classList.add(...this.readyClasses); this.element.classList.remove(...this.removeClasses); this.dispatch('ready', { bubbles: true, cancelable: false }); + events.forEach((name) => { + this.dispatch(name, { bubbles: true, cancelable: false, prefix: '' }); + }); this.remove(); }); }