Events
React to what visitors do in your experience — clicks, hovers, state changes, and
video playback. Both a Component and a
ComponentSet expose the same method:
on(type: SdkEventType, handler: (event: ComponentEvent) => void): Unsubscribe
const off = experience.findByLocator('cta').on('component.click', (event) => {
console.log('clicked', event.target.cmlLocator)
})
off() // stop listening
on returns an unsubscribe function. Call it to detach; calling it more than
once is harmless.
Event types
| Type | Fires when |
|---|---|
'component.click' | A component is clicked. |
'component.hover' | A component is hovered. |
'component.state-changed' | A component's state changes. |
'video.play' | A video starts playing. |
'video.seek' | A video is seeked. |
'video.progress' | A video's playback position advances. |
What your handler receives
interface ComponentEvent {
type: SdkEventType // the event name you subscribed to
target: Component // the component that fired it
originalEvent: Event // the underlying DOM event
}
target is the useful one, and it's a full
Component — so it tells you what fired and lets you act on it
in the same breath:
experience.findByLocator('faq-question').on('component.click', (event) => {
event.target.states.toggle('open') // act on whatever was clicked
console.log(event.target.cmlLocator, event.target.componentType)
})
originalEvent is the raw DOM event, for anything the component surface
doesn't carry — pointer coordinates, modifier keys, or a video event's payload:
experience.findByTag('cml-video').on('video.progress', (event) => {
console.log(event.originalEvent) // the underlying event, with its detail
})
Events bubble
A listener fires for its own component and everything inside it. A listener on a leaf hears only itself; a listener on a group or section hears its whole subtree.
That makes one listener on a container the best way to handle many children — including children that come and go:
const quiz = experience.findByLocator('quiz').only()
quiz.on('component.click', (event) => {
if (event.target.cmlLocator === 'answer') {
checkAnswer(event.target)
}
})
You can also subscribe on the experience itself to hear everything:
experience.on('component.click', (event) => {
analytics.track('click', { component: event.target.cmlLocator })
})
Subscribing to many components at once
ComponentSet.on(...) attaches to every member and returns one function that
detaches them all:
const off = experience.findByLocator('cta').on('component.click', handler)
// later
off() // detaches from every member at once
The members are captured when you call on. Components that appear afterwards
aren't covered — either subscribe on a container and rely on bubbling, or run the
lookup and subscribe again.
Cleaning up
You don't need to unsubscribe before leaving a page; the listeners go away with it. Do keep the unsubscribe function when a listener is only meant to be live for part of the visit:
const off = experience
.findByLocator('modal-close')
.on('component.click', () => {
closeModal()
off() // one-shot
})
On an embedded experience that swaps pages in place, re-attach your listeners after each swap — see Delivery modes.