Skip to content

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.

Connecting Multiple Platforms, Empowering Innovation