Event Handling
WildflowerJS binds DOM events to component methods through data-action attributes, providing a clean separation between markup and behavior.
data-action attribute to bind DOM events to component methods, providing a clean separation between markup and behavior.
Basic Event Handling
Use data-action to bind events to component methods:
<div data-component="interactive-events-demo">
<div class="row">
<div class="col-md-4">
<h5>Click Events</h5>
<button data-action="handleClick" class="btn btn-primary mb-2">
Click Me
</button>
<p><strong>Clicked:</strong> <span data-bind="clickCount" class="badge bg-primary"></span> times</p>
<button data-action="dblclick:doubleClick" class="btn btn-success btn-sm">
Double-Click Me
</button>
</div>
<div class="col-md-4">
<h5>Input Events</h5>
<input type="text"
data-action="input:handleInputChange keyup:handleKeyup"
data-model="inputValue"
placeholder="Type something..."
class="form-control mb-2">
<p><strong>Value:</strong> <em data-bind="inputValue"></em></p>
<p><strong>Length:</strong> <span data-bind="inputLength"></span> chars</p>
<p><strong>Last Key:</strong> <code data-bind="lastKey"></code></p>
</div>
<div class="col-md-4">
<h5>Mouse Events</h5>
<button data-action="mouseenter:handleHover mouseleave:handleHoverOut"
class="btn btn-secondary mb-2">
Hover Me
</button>
<p data-bind="hoverStatus"></p>
<div data-action="mousedown:handleMouseDown mouseup:handleMouseUp"
class="border rounded p-2 bg-light text-center"
style="height: 60px; cursor: pointer;">
Mouse Area
<div class="small"><span data-bind="mouseStatus"></span></div>
</div>
</div>
</div>
<div class="mt-4">
<h5>Event Log</h5>
<div class="d-flex justify-content-between mb-2">
<span>Recent events (<span data-bind="eventCount"></span>):</span>
<button data-action="clearEventLog" class="btn btn-sm btn-secondary">
Clear Log
</button>
</div>
<div class="border rounded p-2" style="height: 120px; overflow-y: auto; font-family: monospace; font-size: 0.85em;">
<div data-list="eventLog">
<template>
<div class="small d-flex justify-content-between"
data-bind-class="type === 'click' ? 'text-primary' : type === 'input' ? 'text-success' : 'text-info'">
<span><span data-bind="timestamp"></span> - <span data-bind="description"></span></span>
<span class="badge badge-sm" data-bind-class="badgeClass">
<span data-bind="type"></span>
</span>
</div>
</template>
</div>
</div>
</div>
</div>
wildflower.component('interactive-events-demo', {
state: {
clickCount: 0,
inputValue: '',
hoverStatus: 'Not hovering',
mouseStatus: 'Ready',
lastKey: 'none',
eventLog: []
},
computed: {
inputLength() {
return this.inputValue.length
},
eventCount() {
return this.eventLog.length
},
// Item-level computed for event log badges
badgeClass() {
const classes = {
'click': 'bg-primary',
'input': 'bg-success',
'mouse': 'bg-info',
'keyboard': 'bg-warning'
}
return classes[this.type] || 'bg-secondary'
}
},
// Click event handlers
handleClick(event, element) {
this.clickCount++
this.logEvent('Button clicked', 'click')
console.log('Button clicked!', { event, element })
},
doubleClick(event, element) {
this.clickCount += 2
this.logEvent('Double click performed', 'click')
},
// Input event handlers
handleInputChange(event, element) {
this.logEvent(`Input changed: "${event.target.value}"`, 'input')
console.log('Input changed:', event.target.value)
},
handleKeyup(event, element) {
this.lastKey = event.key
this.logEvent(`Key pressed: ${event.key}`, 'keyboard')
},
// Mouse event handlers
handleHover(event, element) {
this.hoverStatus = 'Currently hovering! 🙂'
this.logEvent('Mouse hover started', 'mouse')
},
handleHoverOut(event, element) {
this.hoverStatus = 'Not hovering'
this.logEvent('Mouse hover ended', 'mouse')
},
handleMouseDown(event, element) {
this.mouseStatus = 'Mouse Down 🔄'
this.logEvent('Mouse down in area', 'mouse')
},
handleMouseUp(event, element) {
this.mouseStatus = 'Mouse Up ⬆️'
this.logEvent('Mouse up in area', 'mouse')
// Reset after a short delay
setTimeout(() => {
this.mouseStatus = 'Ready'
}, 1000)
},
// Utility methods
logEvent(description, type) {
this.eventLog.unshift({
timestamp: new Date().toLocaleTimeString(),
description: description,
type: type
})
// Keep only last 15 events
if (this.eventLog.length > 15) {
this.eventLog = this.eventLog.slice(0, 15)
}
},
clearEventLog() {
this.eventLog = []
this.logEvent('Event log cleared', 'click')
}
})
Event Types and Syntax
WildflowerJS supports multiple event binding syntaxes:
| Syntax | Description | Example |
|---|---|---|
data-action="method" |
Default click event | <button data-action="save"> |
data-action="event:method" |
Specific event type | <input data-action="change:validate"> |
data-action="event1:method1 event2:method2" |
Multiple events | <div data-action="mouseenter:show mouseout:hide"> |
data-action="method('arg1', 'arg2')" |
Method with arguments | <button data-action="setStatus('active')"> |
<div data-component="event-types-demo">
<div class="row">
<div class="col-md-6">
<h5>Form Events</h5>
<form data-action="submit:handleSubmit">
<div class="mb-2">
<input type="text"
data-action="focus:handleFocus blur:handleBlur"
placeholder="Focus/blur events"
class="form-control">
</div>
<div class="mb-2">
<select data-action="change:handleSelectChange" class="form-select">
<option value="">Select an option...</option>
<option value="option1">Form Option 1</option>
<option value="option2">Form Option 2</option>
<option value="option3">Form Option 3</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Submit Form</button>
</form>
</div>
<div class="col-md-6">
<h5>Mouse Events</h5>
<div data-action="mousedown:handleMouseDown mouseup:handleMouseUp mousemove:handleMouseMove"
class="border p-3 text-center"
style="height: 120px; cursor: crosshair; background-color: var(--card-bg);">
<div>Mouse interaction area</div>
<div class="small mt-2 text-muted">
Position: (<span data-bind="mouseX">0</span>, <span data-bind="mouseY">0</span>)
</div>
<div class="small text-muted">
Status: <span data-bind="mouseStatus">Ready</span>
</div>
</div>
</div>
</div>
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5>Event Log (<span data-bind="eventCount"></span> events):</h5>
<button data-action="clearEventLog" class="btn btn-sm btn-secondary">
Clear Log
</button>
</div>
<div class="border rounded p-2"
style="height: 120px; overflow-y: auto; font-family: monospace; font-size: 0.8em; background-color: var(--card-bg);">
<div data-list="eventLog">
<template>
<div class="d-flex justify-content-between py-1 border-bottom">
<span><span data-bind="timestamp"></span> - <span data-bind="event"></span></span>
<small class="badge bg-secondary"><span data-bind="type"></span></small>
</div>
</template>
</div>
<div data-show="isEmpty" class="text-center text-muted py-3">
No events logged yet. Interact with the form and mouse area above.
</div>
</div>
</div>
</div>
wildflower.component('event-types-demo', {
state: {
mouseX: 0,
mouseY: 0,
mouseStatus: 'Ready',
eventLog: []
},
computed: {
eventCount() {
return this.eventLog.length
},
isEmpty() {
return this.eventLog.length === 0
}
},
// Form event handlers
handleSubmit(event, element) {
event.preventDefault()
this.logEvent('Form submitted', 'form')
// Get form data
const formData = new FormData(element)
const data = Object.fromEntries(formData.entries())
console.log('Form submitted with data:', data)
},
handleFocus(event, element) {
this.logEvent(`Input focused: ${element.placeholder}`, 'focus')
},
handleBlur(event, element) {
const value = event.target.value
this.logEvent(`Input blurred${value ? ': ' + value : ' (empty)'}`, 'blur')
},
handleSelectChange(event, element) {
const value = event.target.value
this.logEvent(`Select changed to: ${value || '(none)'}`, 'change')
},
// Mouse event handlers
handleMouseDown(event, element) {
this.mouseStatus = 'Mouse Down'
this.logEvent('Mouse button pressed', 'mouse')
},
handleMouseUp(event, element) {
this.mouseStatus = 'Mouse Up'
this.logEvent('Mouse button released', 'mouse')
// Reset status after delay
setTimeout(() => {
this.mouseStatus = 'Ready'
}, 1000)
},
handleMouseMove(event, element) {
const rect = element.getBoundingClientRect()
this.mouseX = Math.round(event.clientX - rect.left)
this.mouseY = Math.round(event.clientY - rect.top)
// Throttle mouse move events in log
if (!this._mouseMoveThrottle) {
this._mouseMoveThrottle = true
this.logEvent(`Mouse moved to (${this.mouseX}, ${this.mouseY})`, 'mouse')
setTimeout(() => {
this._mouseMoveThrottle = false
}, 500)
}
},
// Utility methods
logEvent(eventDescription, type = 'general') {
this.eventLog.unshift({
timestamp: new Date().toLocaleTimeString(),
event: eventDescription,
type: type
})
// Keep only last 20 events
if (this.eventLog.length > 20) {
this.eventLog = this.eventLog.slice(0, 20)
}
},
clearEventLog() {
this.eventLog = []
this.logEvent('Event log cleared', 'system')
}
})
data-event-prevent, data-event-debounce, keyboard filters, click-outside detection), custom events for component communication, and form event handling are covered on the Advanced Events page.
Event Handler Arguments
Event handlers receive three arguments:
| Argument | Type | Description |
|---|---|---|
event |
Event | Native DOM event object |
element |
HTMLElement | Element that triggered the event |
details |
Object | Additional context (list item data, etc.) |
<div data-component="arguments-demo">
<div class="mb-4">
<h5>Event Analysis</h5>
<button data-action="analyzeEvent"
class="btn btn-info me-2"
data-custom="primary-button">
Analyze This Click
</button>
<button data-action="analyzeEvent"
class="btn btn-secondary"
data-custom="secondary-button">
Analyze This Click
</button>
</div>
<div class="mb-4">
<h5>List Items with Context (<span data-bind="itemCount"></span> items):</h5>
<div class="mb-2">
<button data-action="addRandomItem" class="btn btn-sm btn-success me-2">
Add Random Item
</button>
<button data-action="resetItems" class="btn btn-sm btn-secondary">
Reset Items
</button>
</div>
<div data-list="items">
<template>
<div class="card mb-2">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<strong data-bind="name"></strong>
<small class="text-muted d-block">
Category: <span data-bind="category"></span> |
ID: <span data-bind="id"></span>
</small>
</div>
<div>
<button data-action="editItem"
class="btn btn-sm btn-primary me-2"
data-item-action="edit">
Edit
</button>
<button data-action="deleteItem"
class="btn btn-sm btn-danger"
data-item-action="delete">
Delete
</button>
</div>
</div>
</div>
</template>
</div>
<div data-show="isEmpty" class="text-center text-muted py-3">
No items to display. Add some items to see context handling.
</div>
</div>
<div class="mt-4">
<h5>Last Event Details:</h5>
<div style="background-color: var(--card-bg);">
<pre data-bind="lastEventDetails" style="margin: 0; white-space: pre-wrap; font-size: 0.85em;"></pre>
</div>
</div>
</div>
wildflower.component('arguments-demo', {
state: {
items: [
{ id: 1, name: 'Task Alpha', category: 'work' },
{ id: 2, name: 'Task Beta', category: 'personal' },
{ id: 3, name: 'Task Gamma', category: 'work' }
],
lastEventDetails: 'No events analyzed yet. Click a button or interact with list items to see event details.'
},
computed: {
itemCount() {
return this.items.length
},
isEmpty() {
return this.items.length === 0
}
},
analyzeEvent(event, element, details) {
// Comprehensive event analysis
const eventInfo = {
// Event object properties
event: {
type: event.type,
timestamp: new Date(event.timeStamp).toISOString(),
bubbles: event.bubbles,
cancelable: event.cancelable,
target: {
tagName: event.target.tagName,
className: event.target.className,
textContent: event.target.textContent.substring(0, 50)
}
},
// Element object properties
element: {
tagName: element.tagName,
className: element.className,
id: element.id || 'no-id',
dataset: element.dataset,
textContent: element.textContent.substring(0, 50)
},
// Details object (context from framework)
details: details || 'No additional context provided',
// Analysis metadata
analysis: {
eventAndElementSame: event.target === element,
hasCustomData: Object.keys(element.dataset).length > 0,
componentContext: this.constructor.name || 'unknown'
}
}
this.lastEventDetails = JSON.stringify(eventInfo, null, 2)
console.log('Event Analysis:', eventInfo)
},
editItem(event, element, details) {
// Use details.index from the framework's action context
const itemIndex = details.index
const item = details.item
const eventInfo = {
action: 'Edit item',
itemData: item,
listContext: {
itemIndex: itemIndex,
totalItems: this.items.length,
detailsProvided: true
},
elementInfo: {
tag: element.tagName,
classes: element.className.split(' '),
customAttributes: element.dataset
},
eventDetails: {
type: event.type,
button: event.button || 'n/a',
ctrlKey: event.ctrlKey,
altKey: event.altKey
}
}
this.lastEventDetails = JSON.stringify(eventInfo, null, 2)
},
deleteItem(event, element, details) {
// Use details.index and details.item from the framework's action context
const itemIndex = details.index
const item = details.item
if (confirm(`Delete "${item.name}"?`)) {
this.items.splice(itemIndex, 1)
const eventInfo = {
action: 'Deleted item',
deletedItem: item,
listContext: {
itemWasAtIndex: itemIndex,
remainingItems: this.items.length,
listLength: this.items.length
},
eventContext: {
type: event.type,
elementClicked: element.textContent.trim()
}
}
this.lastEventDetails = JSON.stringify(eventInfo, null, 2)
}
},
addRandomItem() {
const names = ['Task Delta', 'Task Epsilon', 'Task Zeta', 'Task Theta', 'Task Lambda']
const categories = ['work', 'personal', 'urgent', 'research']
const name = names[Math.floor(Math.random() * names.length)]
const category = categories[Math.floor(Math.random() * categories.length)]
const id = Date.now() + Math.floor(Math.random() * 1000)
this.items.push({ id, name, category })
this.lastEventDetails = `Added new item: ${JSON.stringify({ id, name, category }, null, 2)}`
},
resetItems() {
this.items = [
{ id: 1, name: 'Task Alpha', category: 'work' },
{ id: 2, name: 'Task Beta', category: 'personal' },
{ id: 3, name: 'Task Gamma', category: 'work' }
]
this.lastEventDetails = 'Items reset to original state.'
}
})
Passing Arguments to Actions
You can pass literal arguments directly in data-action attributes. This eliminates the need for separate methods when the only difference is a parameter value.
Supported Literal Types
| Type | Examples | Notes |
|---|---|---|
| String | 'hello', "hello" |
Single or double quoted |
| Number | 42, 3.14, -1, 0 |
Integer, decimal, negative |
| Boolean | true, false |
Exact match |
| Null | null |
Exact match |
Syntax
| Pattern | Description | Example |
|---|---|---|
method('arg') |
Click handler with argument | <button data-action="setPriority('high')"> |
method('a', 'b', 3) |
Multiple arguments | <button data-action="configure('dark', 2, true)"> |
event:method('arg') |
Specific event with argument | <input data-action="input:search('users')"> |
Accessing Arguments
Arguments are available in two ways: via details.args (an array), and as extra parameters appended after the standard (event, element, details) signature.
<div data-component="action-args-demo">
<div class="mb-3">
<h5>Priority: <span data-bind="priority"></span></h5>
<button data-action="setPriority('high')" class="btn btn-sm btn-danger me-1">High</button>
<button data-action="setPriority('medium')" class="btn btn-sm btn-warning me-1">Medium</button>
<button data-action="setPriority('low')" class="btn btn-sm btn-success">Low</button>
</div>
<div class="mb-3">
<h5>Opacity: <span data-bind="opacity"></span></h5>
<button data-action="setOpacity(1)" class="btn btn-sm btn-secondary me-1">100%</button>
<button data-action="setOpacity(0.5)" class="btn btn-sm btn-secondary me-1">50%</button>
<button data-action="setOpacity(0)" class="btn btn-sm btn-secondary">0%</button>
</div>
<div>
<h5>Last Action:</h5>
<pre data-bind="lastAction"></pre>
</div>
</div>
wildflower.component('action-args-demo', {
state: {
priority: 'medium',
opacity: 1,
lastAction: ''
},
setPriority(event, element, details) {
// details.args = ['high'], ['medium'], or ['low']
this.priority = details.args[0]
this.lastAction = 'setPriority called with: ' + details.args[0]
},
setOpacity(event, element, details) {
// details.args = [1], [0.5], or [0]
this.opacity = details.args[0]
this.lastAction = 'setOpacity called with: ' + details.args[0]
}
})
In list contexts, details.args coexists with the standard list context properties (details.item, details.index, etc.):
<div data-list="tasks">
<template>
<div>
<span data-bind="title"></span>
<button data-action="setStatus('done')">Complete</button>
<button data-action="setStatus('archived')">Archive</button>
</div>
</template>
</div>
setStatus(event, element, details) {
const task = details.item // list item for this row
const status = details.args[0] // 'done' or 'archived'
task.status = status
}
Event Best Practices
✅ Do
- Use descriptive handler method names
- Prevent default behavior when needed
- Validate form inputs on appropriate events
- Clean up custom event listeners in destroy()
- Use event delegation for dynamic content
❌ Don't
- Modify state excessively in mouse move handlers
- Use
onclick="..."attributes; usedata-actioninstead - Skip
event.preventDefault()on form submissions - Add
addEventListenerin init() without removing in destroy() - Create separate methods when
data-action="method('arg')"suffices