Event Handling
Event handling is fundamental to creating interactive mini programs. This guide covers the essential event types, handling patterns, and best practices.
Basic Event Handling
Touch Events
html
<!-- Basic touch events -->
<view
bindtap="onTap"
bindlongpress="onLongPress"
bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
>
Touch me
</view>
javascript
Page({
onTap(e) {
console.log('Tap event:', e)
console.log('Target:', e.target.id)
console.log('Current target:', e.currentTarget.id)
console.log('Touch point:', e.detail.x, e.detail.y)
},
onLongPress(e) {
console.log('Long press detected')
// Show context menu or perform action
wx.showActionSheet({
itemList: ['Edit', 'Delete', 'Share'],
success: (res) => {
console.log('Selected:', res.tapIndex)
}
})
},
onTouchStart(e) {
console.log('Touch started at:', e.touches[0])
this.setData({
touchStartTime: Date.now(),
startX: e.touches[0].clientX,
startY: e.touches[0].clientY
})
},
onTouchMove(e) {
const { startX, startY } = this.data
const deltaX = e.touches[0].clientX - startX
const deltaY = e.touches[0].clientY - startY
// Handle swipe gestures
if (Math.abs(deltaX) > 50) {
console.log(deltaX > 0 ? 'Swipe right' : 'Swipe left')
}
},
onTouchEnd(e) {
const duration = Date.now() - this.data.touchStartTime
console.log('Touch duration:', duration + 'ms')
}
})
Input Events
html
<!-- Input field events -->
<input
type="text"
placeholder="Enter text"
bindinput="onInput"
bindfocus="onFocus"
bindblur="onBlur"
bindconfirm="onConfirm"
value="{{inputValue}}"
/>
<!-- Textarea events -->
<textarea
placeholder="Enter description"
bindinput="onTextareaInput"
bindlinechange="onLineChange"
maxlength="200"
value="{{textareaValue}}"
></textarea>
javascript
Page({
data: {
inputValue: '',
textareaValue: ''
},
onInput(e) {
const value = e.detail.value
console.log('Input value:', value)
// Real-time validation
const isValid = this.validateInput(value)
this.setData({
inputValue: value,
inputValid: isValid
})
},
onFocus(e) {
console.log('Input focused')
// Show keyboard helper or hints
},
onBlur(e) {
console.log('Input blurred')
// Validate and save data
this.saveInputData(e.detail.value)
},
onConfirm(e) {
console.log('Input confirmed:', e.detail.value)
// Submit form or perform search
this.handleSubmit(e.detail.value)
},
onTextareaInput(e) {
const value = e.detail.value
const cursor = e.detail.cursor
this.setData({
textareaValue: value,
characterCount: value.length
})
},
onLineChange(e) {
console.log('Line count:', e.detail.lineCount)
console.log('Height:', e.detail.height)
},
validateInput(value) {
// Example validation
return value.length >= 3 && value.length <= 20
},
saveInputData(value) {
// Save to storage or send to server
wx.setStorageSync('userInput', value)
},
handleSubmit(value) {
if (this.validateInput(value)) {
console.log('Submitting:', value)
// Process submission
} else {
wx.showToast({
title: 'Invalid input',
icon: 'error'
})
}
}
})
Form Events
html
<!-- Form with various inputs -->
<form bindsubmit="onFormSubmit" bindreset="onFormReset">
<input name="username" placeholder="Username" />
<input name="email" type="email" placeholder="Email" />
<radio-group name="gender" bindchange="onRadioChange">
<radio value="male">Male</radio>
<radio value="female">Female</radio>
</radio-group>
<checkbox-group name="interests" bindchange="onCheckboxChange">
<checkbox value="sports">Sports</checkbox>
<checkbox value="music">Music</checkbox>
<checkbox value="travel">Travel</checkbox>
</checkbox-group>
<picker
name="city"
range="{{cities}}"
bindchange="onPickerChange"
value="{{selectedCityIndex}}"
>
<view class="picker-display">
{{cities[selectedCityIndex] || 'Select City'}}
</view>
</picker>
<switch name="notifications" bindchange="onSwitchChange" />
<button form-type="submit">Submit</button>
<button form-type="reset">Reset</button>
</form>
javascript
Page({
data: {
cities: ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen'],
selectedCityIndex: 0,
formData: {
gender: '',
interests: [],
notifications: false
}
},
onFormSubmit(e) {
const formData = e.detail.value
console.log('Form submitted:', formData)
// Validate form data
if (this.validateForm(formData)) {
this.submitForm(formData)
} else {
wx.showToast({
title: 'Please fill all required fields',
icon: 'error'
})
}
},
onFormReset(e) {
console.log('Form reset')
this.setData({
selectedCityIndex: 0,
'formData.gender': '',
'formData.interests': [],
'formData.notifications': false
})
},
onRadioChange(e) {
const value = e.detail.value
console.log('Radio selected:', value)
this.setData({
'formData.gender': value
})
},
onCheckboxChange(e) {
const values = e.detail.value
console.log('Checkboxes selected:', values)
this.setData({
'formData.interests': values
})
},
onPickerChange(e) {
const index = e.detail.value
console.log('Picker selected:', this.data.cities[index])
this.setData({
selectedCityIndex: index
})
},
onSwitchChange(e) {
const value = e.detail.value
console.log('Switch toggled:', value)
this.setData({
'formData.notifications': value
})
},
validateForm(data) {
return data.username && data.email && data.gender
},
async submitForm(data) {
wx.showLoading({ title: 'Submitting...' })
try {
// Submit to server
const result = await this.sendFormData(data)
wx.hideLoading()
wx.showToast({
title: 'Submitted successfully',
icon: 'success'
})
} catch (error) {
wx.hideLoading()
wx.showToast({
title: 'Submission failed',
icon: 'error'
})
}
},
sendFormData(data) {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://api.example.com/submit',
method: 'POST',
data,
success: resolve,
fail: reject
})
})
}
})
Advanced Event Handling
Event Bubbling and Capturing
html
<!-- Event bubbling example -->
<view class="outer" bindtap="onOuterTap">
<view class="middle" bindtap="onMiddleTap">
<view class="inner" bindtap="onInnerTap">
Click me (bubbling)
</view>
</view>
</view>
<!-- Prevent bubbling -->
<view class="outer" bindtap="onOuterTap">
<view class="middle" catchtap="onMiddleTap">
<view class="inner" bindtap="onInnerTap">
Click me (no bubbling)
</view>
</view>
</view>
<!-- Capture phase -->
<view class="outer" capture-bind:tap="onOuterCapture">
<view class="middle" capture-bind:tap="onMiddleCapture">
<view class="inner" bindtap="onInnerTap">
Click me (capture)
</view>
</view>
</view>
javascript
Page({
onOuterTap(e) {
console.log('Outer tap (bubble phase)')
},
onMiddleTap(e) {
console.log('Middle tap (stops bubbling)')
// Event won't bubble to outer
},
onInnerTap(e) {
console.log('Inner tap')
},
onOuterCapture(e) {
console.log('Outer capture (capture phase)')
},
onMiddleCapture(e) {
console.log('Middle capture (capture phase)')
}
})
Custom Events
html
<!-- Custom component with events -->
<custom-button
bind:customtap="onCustomTap"
bind:longpress="onCustomLongPress"
data-id="{{item.id}}"
>
Custom Button
</custom-button>
javascript
// In custom component
Component({
methods: {
onTap(e) {
// Trigger custom event
this.triggerEvent('customtap', {
id: this.data.id,
timestamp: Date.now()
}, {
bubbles: true,
composed: true
})
},
onLongPress(e) {
this.triggerEvent('longpress', {
detail: 'Long press detected'
})
}
}
})
// In parent page
Page({
onCustomTap(e) {
console.log('Custom tap event:', e.detail)
console.log('Data from element:', e.currentTarget.dataset)
},
onCustomLongPress(e) {
console.log('Custom long press:', e.detail)
}
})
Gesture Recognition
javascript
// Advanced gesture handler
Page({
data: {
touchStartTime: 0,
touchStartPos: { x: 0, y: 0 },
touchEndPos: { x: 0, y: 0 },
isLongPress: false
},
onTouchStart(e) {
const touch = e.touches[0]
this.setData({
touchStartTime: Date.now(),
touchStartPos: { x: touch.clientX, y: touch.clientY },
isLongPress: false
})
// Set long press timer
this.longPressTimer = setTimeout(() => {
this.setData({ isLongPress: true })
this.handleLongPress()
}, 500)
},
onTouchMove(e) {
const touch = e.touches[0]
const { touchStartPos } = this.data
// Calculate distance moved
const deltaX = touch.clientX - touchStartPos.x
const deltaY = touch.clientY - touchStartPos.y
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
// Cancel long press if moved too much
if (distance > 10) {
clearTimeout(this.longPressTimer)
this.setData({ isLongPress: false })
}
// Handle drag
this.handleDrag(deltaX, deltaY)
},
onTouchEnd(e) {
clearTimeout(this.longPressTimer)
const touch = e.changedTouches[0]
const { touchStartTime, touchStartPos, isLongPress } = this.data
const duration = Date.now() - touchStartTime
this.setData({
touchEndPos: { x: touch.clientX, y: touch.clientY }
})
if (isLongPress) {
// Long press already handled
return
}
// Detect gesture type
const deltaX = touch.clientX - touchStartPos.x
const deltaY = touch.clientY - touchStartPos.y
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
if (distance < 10 && duration < 300) {
this.handleTap()
} else if (distance > 50) {
this.handleSwipe(deltaX, deltaY)
}
},
handleTap() {
console.log('Tap gesture detected')
},
handleLongPress() {
console.log('Long press gesture detected')
wx.vibrateShort()
},
handleSwipe(deltaX, deltaY) {
const absX = Math.abs(deltaX)
const absY = Math.abs(deltaY)
if (absX > absY) {
// Horizontal swipe
if (deltaX > 0) {
console.log('Swipe right')
this.handleSwipeRight()
} else {
console.log('Swipe left')
this.handleSwipeLeft()
}
} else {
// Vertical swipe
if (deltaY > 0) {
console.log('Swipe down')
this.handleSwipeDown()
} else {
console.log('Swipe up')
this.handleSwipeUp()
}
}
},
handleDrag(deltaX, deltaY) {
// Handle drag gesture
console.log('Dragging:', deltaX, deltaY)
},
handleSwipeLeft() {
// Navigate to next page or item
},
handleSwipeRight() {
// Navigate to previous page or item
},
handleSwipeUp() {
// Scroll up or show more content
},
handleSwipeDown() {
// Scroll down or refresh
}
})
Event Performance Optimization
Debouncing and Throttling
javascript
// Utility functions
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
function throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
Page({
onLoad() {
// Debounced search
this.debouncedSearch = debounce(this.performSearch.bind(this), 300)
// Throttled scroll handler
this.throttledScroll = throttle(this.handleScroll.bind(this), 100)
},
onSearchInput(e) {
const query = e.detail.value
// Debounce search to avoid too many requests
this.debouncedSearch(query)
},
onPageScroll(e) {
// Throttle scroll events for performance
this.throttledScroll(e.scrollTop)
},
performSearch(query) {
console.log('Searching for:', query)
// Actual search implementation
},
handleScroll(scrollTop) {
console.log('Scroll position:', scrollTop)
// Handle scroll position changes
}
})
Event Delegation
html
<!-- Event delegation for list items -->
<view class="list-container" bindtap="onListTap">
<view
class="list-item"
wx:for="{{items}}"
wx:key="id"
data-id="{{item.id}}"
data-action="select"
>
<text>{{item.name}}</text>
<button
class="delete-btn"
data-id="{{item.id}}"
data-action="delete"
size="mini"
>
Delete
</button>
</view>
</view>
javascript
Page({
data: {
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
},
onListTap(e) {
const { id, action } = e.target.dataset
if (!id || !action) return
switch (action) {
case 'select':
this.selectItem(id)
break
case 'delete':
this.deleteItem(id)
break
default:
console.log('Unknown action:', action)
}
},
selectItem(id) {
console.log('Selected item:', id)
// Handle item selection
},
deleteItem(id) {
console.log('Deleting item:', id)
wx.showModal({
title: 'Confirm Delete',
content: 'Are you sure you want to delete this item?',
success: (res) => {
if (res.confirm) {
const items = this.data.items.filter(item => item.id !== parseInt(id))
this.setData({ items })
}
}
})
}
})
Best Practices
Error Handling
javascript
Page({
onButtonTap(e) {
try {
// Event handling logic
this.processUserAction(e)
} catch (error) {
console.error('Event handling error:', error)
this.handleEventError(error)
}
},
processUserAction(e) {
const { action, data } = e.currentTarget.dataset
if (!action) {
throw new Error('No action specified')
}
// Process action
this.executeAction(action, data)
},
executeAction(action, data) {
const actions = {
save: () => this.saveData(data),
delete: () => this.deleteData(data),
share: () => this.shareContent(data)
}
const handler = actions[action]
if (!handler) {
throw new Error(`Unknown action: ${action}`)
}
handler()
},
handleEventError(error) {
wx.showToast({
title: 'Operation failed',
icon: 'error'
})
// Log error for debugging
console.error('Event error details:', {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
})
}
})
Memory Management
javascript
Page({
onLoad() {
// Set up event listeners
this.setupEventListeners()
},
onUnload() {
// Clean up event listeners and timers
this.cleanup()
},
setupEventListeners() {
// Store references for cleanup
this.resizeHandler = this.onWindowResize.bind(this)
this.networkHandler = this.onNetworkChange.bind(this)
// Add global event listeners
wx.onWindowResize(this.resizeHandler)
wx.onNetworkStatusChange(this.networkHandler)
},
cleanup() {
// Remove event listeners
if (this.resizeHandler) {
wx.offWindowResize(this.resizeHandler)
}
if (this.networkHandler) {
wx.offNetworkStatusChange(this.networkHandler)
}
// Clear timers
if (this.longPressTimer) {
clearTimeout(this.longPressTimer)
}
if (this.debounceTimer) {
clearTimeout(this.debounceTimer)
}
},
onWindowResize(res) {
console.log('Window resized:', res)
},
onNetworkChange(res) {
console.log('Network changed:', res)
}
})
Effective event handling is crucial for creating responsive and user-friendly mini programs. Always consider performance, accessibility, and user experience when implementing event handlers.