Skip to content
DevinimJS
Phase 1 · Lesson 5

Events & Actions — User Interaction

Handle clicks, key presses, and custom events with data-on:* directives and the dv:* event system.

What you'll learn
  • Bind methods to DOM events with data-on:event="method"
  • Handle keyboard events: Enter, Escape, arrow keys
  • Emit custom dv:* events with this.emit()
  • Listen to external events with this.listen()
  • Pass event data via detail

data-on:* — the event directive

Bind component methods to DOM events directly in templates. The base class handles delegation:

template() {
  return html`
    <button data-on:click="increment">+</button>
    <button data-on:click="decrement">−</button>
    <input data-on:input="handleInput" value="${this.state.text}">
  `;
}

increment() { this.state.count += 1; }
decrement() { this.state.count -= 1; }
handleInput(event) { this.state.text = event.target.value; }

The handler receives the native DOM event. this inside the handler is the component instance.

Keyboard events

Handle key presses for accessibility and UX patterns:

template() {
  return html`
    <input data-on:keydown="handleKeydown" placeholder="Type and press Enter">
    <p>Last key: ${this.state.lastKey}</p>
  `;
}

handleKeydown(event) {
  this.state.lastKey = event.key;
  if (event.key === 'Enter') {
    this.state.submitted = this.state.text;
  }
  if (event.key === 'Escape') {
    this.reset();
  }
}

this.emit() — sending events outward

Emit custom events that parent components or page scripts can listen to. All events are prefixed with dv::

increment() {
  this.state.count += 1;
  this.emit('change', { count: this.state.count });
  // Dispatches: new CustomEvent('dv:change', { detail: { count: 5 }, bubbles: true })
}

Page scripts listen with standard addEventListener:

document.querySelector('dv-counter')
  .addEventListener('dv:change', (e) => {
    console.log('Counter changed to', e.detail.count);
  });

Bubbling by default

this.emit() creates bubbling events. Parent elements and the document can catch them. The dv: prefix makes the namespace clear.

this.listen() — safe external listeners

Listen to events on external targets (window, document, other DOM elements). The listener is automatically removed when the component disconnects:

connected() {
  this.listen(window, 'resize', this.handleResize);
  this.listen(document, 'scroll', this.handleScroll);
  this.listen(someElement, 'custom-event', this.handleCustom);
}

handleResize() {
  this.state.viewportWidth = window.innerWidth;
  this.requestUpdate();
}

No need to manually call removeEventListener in disconnected().

Live demo: Event playground

Live demo — click, type, and observe events

Event naming conventions

PatternExampleWhen
State changedv:changeCounter, toggle, any value mutation
Selectiondv:select, dv:tabUser picks an item or tab
Lifecycledv:open, dv:closeModal, dropdown, disclosure
User actiondv:confirm, dv:cancelExplicit yes/no decisions
Querydv:querySearch input changes

Recap

data-on:* for DOM events

Bind methods to click, input, keydown, and any native event.

this.emit() for outward events

Tell the page what happened. Pages own persistence.

this.listen() for external

Auto-cleaned listeners on window/document/other elements.

dv:* namespace

All component events use the dv: prefix for clarity.