Skip to content

Event Handling

Peak.js provides intuitive event handling that feels familiar to developers coming from Vue.js or Alpine.js. Events are handled using the @ syntax and support both native DOM events and custom component events.

Basic Event Handling

DOM Events

Use the @ prefix followed by the event name to listen for DOM events:

html
<template>
  <div>
    <button @click="increment">Count: <span x-text="count"></span></button>
    <input @input="handleInput" x-model="message">
    <form @submit="handleSubmit">
      <input type="email" x-model="email">
      <button type="submit">Subscribe</button>
    </form>
  </div>
</template>

<script>
export default class {
  initialize() {
    this.count = 0
    this.message = ""
    this.email = ""
  }

  increment() {
    this.count++
  }

  handleInput(event) {
    console.log('Input value:', event.target.value)
  }

  submit() {
    console.log('Enter pressed:', this.message)
  }

  handleSubmit(e) {
    e.preventDefault()
    console.log('Submitting form with email:', this.email)
    // submit the form...
  }
}
</script>

The $event Object

Event handlers will receive the event as their first parameter, but you can also access the native event object using the special $event property on the component.

html
<template>
  <div>
    <input @input="handleInput($event.target.value)">
    <button @click="handleClick">Click</button>
    <div @mousemove="trackMouse">Mouse tracker</div>
  </div>
</template>

<script>
export default class {
  handleInput(value) {
    this.inputValue = value
  }

  handleClick() {
    // Access event via this.$event
    console.log('Click position:', this.$event.clientX, this.$event.clientY)

    // Stop propagation
    this.$event.stopPropagation()

    // Prevent default
    this.$event.preventDefault()
  }

  trackMouse() {
    this.mouseX = this.$event.clientX
    this.mouseY = this.$event.clientY
  }
}
</script>

Custom Component Events

Emitting Events

Components can emit custom events using $emit():

html
<!-- components/x-user-form.html -->
<template>
  <form @submit.prevent="submit">
    <input x-model="user.name" placeholder="Name" required>
    <input x-model="user.email" type="email" placeholder="Email" required>
    <button type="submit">Save User</button>
    <button type="button" @click="cancel">Cancel</button>
  </form>
</template>

<script>
export default class {
  static props = ['user']

  initialize() {
    this.user = this.user || { name: '', email: '' }
  }

  submit() {
    // Emit event with user data
    this.$emit('save', this.user)
  }

  cancel() {
    this.$emit('cancel')
  }
}
</script>

Listening to Custom Events

Listen to custom events from child components:

html
<!-- Parent component -->
<template>
  <div>
    <x-user-form
      :user="currentUser"
      @save="saveUser"
      @cancel="cancelEdit" />

    <x-notification
      @close="hideNotification"
      x-show="showNotification">
      User saved successfully! />
  </div>
</template>

<script>
export default class {
  initialize() {
    this.currentUser = { name: 'Alice', email: 'alice@example.com' }
    this.showNotification = false
  }

  saveUser(userData) {
    console.log('Saving user:', userData)
    // Save logic here
    this.showNotification = true
  }

  cancelEdit() {
    console.log('Edit cancelled')
    this.currentUser = { name: '', email: '' }
  }

  hideNotification() {
    this.showNotification = false
  }
}
</script>

Event Delegation

Using $on for Dynamic Listeners

Register event listeners dynamically:

html
<template>
  <div>
    <ul>
      <li x-for="item in items" :data-id="item.id">
        <span x-text="item.name"></span>
        <button class="delete-btn">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default class {
  initialize() {
    this.items = [
      { id: 1, name: 'Apple' },
      { id: 2, name: 'Banana' },
      { id: 3, name: 'Cherry' }
    ]

    // Listen for click events on delete buttons
    this.$on('click', (event) => {
      if (event.target.classList.contains('delete-btn')) {
        const li = event.target.closest('li')
        const itemId = parseInt(li.dataset.id)
        this.deleteItem(itemId)
      }
    })
  }

  deleteItem(id) {
    this.items = this.items.filter(item => item.id !== id)
  }
}
</script>

Global Event Bus

Create a simple event bus for component communication:

javascript
// utils/eventBus.js
class EventBus {
  constructor() {
    this.events = {}
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  }

  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data))
    }
  }

  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback)
    }
  }
}

export const eventBus = new EventBus()

Use in components:

html
<!-- components/x-notification-sender.html -->
<script>
import { eventBus } from '../utils/eventBus.js'

export default class {
  sendNotification() {
    eventBus.emit('notification', {
      type: 'success',
      message: 'Operation completed successfully!'
    })
  }
}
</script>
html
<!-- components/x-notification-receiver.html -->
<script>
import { eventBus } from '../utils/eventBus.js'

export default class {
  initialize() {
    this.notifications = []

    eventBus.on('notification', (data) => {
      this.notifications.push(data)
      setTimeout(() => this.removeNotification(data), 5000)
    })
  }

  removeNotification(notification) {
    const index = this.notifications.indexOf(notification)
    if (index > -1) {
      this.notifications.splice(index, 1)
    }
  }

  teardown() {
    eventBus.off('notification', this.handleNotification)
  }
}
</script>