591 lines
13 KiB
Text
591 lines
13 KiB
Text
---
|
|
title: Event System
|
|
description: Communicate between components with the event system
|
|
sidebar:
|
|
order: 1
|
|
---
|
|
|
|
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
|
|
|
## Event System
|
|
|
|
Wails provides a **unified event system** for pub/sub communication. Emit events from anywhere, listen from anywhere—Go to JavaScript, JavaScript to Go, window to window—enabling decoupled architecture with typed events and lifecycle hooks.
|
|
|
|
## Quick Start
|
|
|
|
**Go (emit):**
|
|
|
|
```go
|
|
app.Event.Emit("user-logged-in", map[string]interface{}{
|
|
"userId": 123,
|
|
"name": "Alice",
|
|
})
|
|
```
|
|
|
|
**JavaScript (listen):**
|
|
|
|
```javascript
|
|
import { Events } from '@wailsio/runtime'
|
|
|
|
Events.On("user-logged-in", (event) => {
|
|
console.log(`User ${event.data.name} logged in`)
|
|
})
|
|
```
|
|
|
|
**That's it!** Cross-language pub/sub.
|
|
|
|
## Event Types
|
|
|
|
### Custom Events
|
|
|
|
Your application-specific events:
|
|
|
|
```go
|
|
// Emit from Go
|
|
app.Event.Emit("order-created", order)
|
|
app.Event.Emit("payment-processed", payment)
|
|
app.Event.Emit("notification", message)
|
|
```
|
|
|
|
```javascript
|
|
// Listen in JavaScript
|
|
Events.On("order-created", handleOrder)
|
|
Events.On("payment-processed", handlePayment)
|
|
Events.On("notification", showNotification)
|
|
```
|
|
|
|
### System Events
|
|
|
|
Built-in OS and application events:
|
|
|
|
```go
|
|
import "github.com/wailsapp/wails/v3/pkg/events"
|
|
|
|
// Theme changes
|
|
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
|
if e.Context().IsDarkMode() {
|
|
app.Logger.Info("Dark mode enabled")
|
|
}
|
|
})
|
|
|
|
// Application lifecycle
|
|
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
|
|
app.Logger.Info("Application started")
|
|
})
|
|
```
|
|
|
|
### Window Events
|
|
|
|
Window-specific events:
|
|
|
|
```go
|
|
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
|
app.Logger.Info("Window focused")
|
|
})
|
|
|
|
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
|
app.Logger.Info("Window closing")
|
|
})
|
|
```
|
|
|
|
## Emitting Events
|
|
|
|
### From Go
|
|
|
|
**Basic emit:**
|
|
|
|
```go
|
|
app.Event.Emit("event-name", data)
|
|
```
|
|
|
|
**With different data types:**
|
|
|
|
```go
|
|
// String
|
|
app.Event.Emit("message", "Hello")
|
|
|
|
// Number
|
|
app.Event.Emit("count", 42)
|
|
|
|
// Struct
|
|
app.Event.Emit("user", User{ID: 1, Name: "Alice"})
|
|
|
|
// Map
|
|
app.Event.Emit("config", map[string]interface{}{
|
|
"theme": "dark",
|
|
"fontSize": 14,
|
|
})
|
|
|
|
// Array
|
|
app.Event.Emit("items", []string{"a", "b", "c"})
|
|
```
|
|
|
|
**To specific window:**
|
|
|
|
```go
|
|
window.EmitEvent("window-specific-event", data)
|
|
```
|
|
|
|
### From JavaScript
|
|
|
|
```javascript
|
|
import { Events } from '@wailsio/runtime'
|
|
|
|
// Emit to Go
|
|
Events.Emit("button-clicked", { buttonId: "submit" })
|
|
|
|
// Emit to all windows
|
|
Events.Emit("broadcast-message", "Hello everyone")
|
|
```
|
|
|
|
## Listening to Events
|
|
|
|
### In Go
|
|
|
|
**Application events:**
|
|
|
|
```go
|
|
app.Event.On("custom-event", func(e *application.CustomEvent) {
|
|
data := e.Data
|
|
// Handle event
|
|
})
|
|
```
|
|
|
|
**With type assertion:**
|
|
|
|
```go
|
|
app.Event.On("user-updated", func(e *application.CustomEvent) {
|
|
user := e.Data.(User)
|
|
app.Logger.Info("User updated", "name", user.Name)
|
|
})
|
|
```
|
|
|
|
**Multiple handlers:**
|
|
|
|
```go
|
|
// All handlers will be called
|
|
app.Event.On("order-created", logOrder)
|
|
app.Event.On("order-created", sendEmail)
|
|
app.Event.On("order-created", updateInventory)
|
|
```
|
|
|
|
### In JavaScript
|
|
|
|
**Basic listener:**
|
|
|
|
```javascript
|
|
import { Events } from '@wailsio/runtime'
|
|
|
|
Events.On("event-name", (event) => {
|
|
console.log("Event received:", event.data)
|
|
})
|
|
```
|
|
|
|
**With cleanup:**
|
|
|
|
```javascript
|
|
const unsubscribe = Events.On("event-name", handleEvent)
|
|
|
|
// Later, stop listening
|
|
unsubscribe()
|
|
```
|
|
|
|
**Multiple handlers:**
|
|
|
|
```javascript
|
|
Events.On("data-updated", updateUI)
|
|
Events.On("data-updated", saveToCache)
|
|
Events.On("data-updated", logChange)
|
|
```
|
|
|
|
**One Time handlers:**
|
|
|
|
```javascript
|
|
Events.Once("data-updated", updateVariable)
|
|
```
|
|
|
|
## System Events
|
|
|
|
### Application Events
|
|
|
|
**Common events (cross-platform):**
|
|
|
|
```go
|
|
import "github.com/wailsapp/wails/v3/pkg/events"
|
|
|
|
// Application started
|
|
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
|
|
app.Logger.Info("App started")
|
|
})
|
|
|
|
// Theme changed
|
|
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
|
isDark := e.Context().IsDarkMode()
|
|
app.Event.Emit("theme-changed", isDark)
|
|
})
|
|
|
|
// File opened
|
|
app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) {
|
|
filePath := e.Context().OpenedFile()
|
|
openFile(filePath)
|
|
})
|
|
```
|
|
|
|
**Platform-specific events:**
|
|
|
|
<Tabs syncKey="platform">
|
|
<TabItem label="macOS" icon="apple">
|
|
```go
|
|
// Application became active
|
|
app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) {
|
|
app.Logger.Info("App became active")
|
|
})
|
|
|
|
// Application will terminate
|
|
app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) {
|
|
cleanup()
|
|
})
|
|
```
|
|
</TabItem>
|
|
|
|
<TabItem label="Windows" icon="seti:windows">
|
|
```go
|
|
// Power status changed
|
|
app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) {
|
|
app.Logger.Info("Power status changed")
|
|
})
|
|
|
|
// System suspending
|
|
app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) {
|
|
saveState()
|
|
})
|
|
```
|
|
</TabItem>
|
|
|
|
<TabItem label="Linux" icon="linux">
|
|
```go
|
|
// Application startup
|
|
app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) {
|
|
app.Logger.Info("App starting")
|
|
})
|
|
|
|
// Theme changed
|
|
app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) {
|
|
updateTheme()
|
|
})
|
|
```
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
### Window Events
|
|
|
|
**Common window events:**
|
|
|
|
```go
|
|
// Window focus
|
|
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
|
app.Logger.Info("Window focused")
|
|
})
|
|
|
|
// Window blur
|
|
window.OnWindowEvent(events.Common.WindowBlur, func(e *application.WindowEvent) {
|
|
app.Logger.Info("Window blurred")
|
|
})
|
|
|
|
// Window closing
|
|
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
|
if hasUnsavedChanges() {
|
|
e.Cancel() // Prevent close
|
|
}
|
|
})
|
|
|
|
// Window closed
|
|
window.OnWindowEvent(events.Common.WindowClosed, func(e *application.WindowEvent) {
|
|
cleanup()
|
|
})
|
|
```
|
|
|
|
## Event Hooks
|
|
|
|
Hooks run **before** standard listeners and can **cancel** events:
|
|
|
|
```go
|
|
// Hook - runs first, can cancel
|
|
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
|
if hasUnsavedChanges() {
|
|
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
|
if result != "yes" {
|
|
e.Cancel() // Prevent window close
|
|
}
|
|
}
|
|
})
|
|
|
|
// Standard listener - runs after hooks
|
|
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
|
app.Logger.Info("Window closing")
|
|
})
|
|
```
|
|
|
|
**Key differences:**
|
|
|
|
| Feature | Hooks | Standard Listeners |
|
|
|---------|-------|-------------------|
|
|
| Execution order | First, in registration order | After hooks, no guaranteed order |
|
|
| Blocking | Synchronous, blocks next hook | Asynchronous, non-blocking |
|
|
| Can cancel | Yes | No (already propagated) |
|
|
| Use case | Control flow, validation | Logging, side effects |
|
|
|
|
## Event Patterns
|
|
|
|
### Pub/Sub Pattern
|
|
|
|
```go
|
|
// Publisher (service)
|
|
type OrderService struct {
|
|
app *application.Application
|
|
}
|
|
|
|
func (o *OrderService) CreateOrder(items []Item) (*Order, error) {
|
|
order := &Order{Items: items}
|
|
|
|
if err := o.saveOrder(order); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Publish event
|
|
o.app.Event.Emit("order-created", order)
|
|
|
|
return order, nil
|
|
}
|
|
|
|
// Subscribers
|
|
app.Event.On("order-created", func(e *application.CustomEvent) {
|
|
order := e.Data.(*Order)
|
|
sendConfirmationEmail(order)
|
|
})
|
|
|
|
app.Event.On("order-created", func(e *application.CustomEvent) {
|
|
order := e.Data.(*Order)
|
|
updateInventory(order)
|
|
})
|
|
|
|
app.Event.On("order-created", func(e *application.CustomEvent) {
|
|
order := e.Data.(*Order)
|
|
logOrder(order)
|
|
})
|
|
```
|
|
|
|
### Request/Response Pattern
|
|
|
|
```go
|
|
// Frontend requests data
|
|
Emit("get-user-data", { userId: 123 })
|
|
|
|
// Backend responds
|
|
app.Event.On("get-user-data", func(e *application.CustomEvent) {
|
|
data := e.Data.(map[string]interface{})
|
|
userId := int(data["userId"].(float64))
|
|
|
|
user := getUserFromDB(userId)
|
|
|
|
// Send response
|
|
app.Event.Emit("user-data-response", user)
|
|
})
|
|
```
|
|
|
|
```javascript
|
|
// Frontend receives response
|
|
Events.On("user-data-response", (event) => {
|
|
const user = event.data
|
|
displayUser(user)
|
|
})
|
|
```
|
|
|
|
**Note:** For request/response, **bindings are better**. Use events for notifications.
|
|
|
|
### Broadcast Pattern
|
|
|
|
```go
|
|
// Broadcast to all windows
|
|
app.Event.Emit("global-notification", "System update available")
|
|
|
|
// Each window handles it
|
|
Events.On("global-notification", (event) => {
|
|
const message = event.data
|
|
showNotification(message)
|
|
})
|
|
```
|
|
|
|
### Event Aggregation
|
|
|
|
```go
|
|
type EventAggregator struct {
|
|
events []Event
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (ea *EventAggregator) Add(event Event) {
|
|
ea.mu.Lock()
|
|
defer ea.mu.Unlock()
|
|
|
|
ea.events = append(ea.events, event)
|
|
|
|
// Emit batch every 100 events
|
|
if len(ea.events) >= 100 {
|
|
app.Event.Emit("event-batch", ea.events)
|
|
ea.events = nil
|
|
}
|
|
}
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
**Go:**
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
"github.com/wailsapp/wails/v3/pkg/events"
|
|
)
|
|
|
|
type NotificationService struct {
|
|
app *application.Application
|
|
}
|
|
|
|
func (n *NotificationService) Notify(message string) {
|
|
// Emit to all windows
|
|
n.app.Event.Emit("notification", map[string]interface{}{
|
|
"message": message,
|
|
"timestamp": time.Now(),
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
app := application.New(application.Options{
|
|
Name: "Event Demo",
|
|
})
|
|
|
|
notifService := &NotificationService{app: app}
|
|
|
|
// System events
|
|
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
|
isDark := e.Context().IsDarkMode()
|
|
app.Event.Emit("theme-changed", isDark)
|
|
})
|
|
|
|
// Custom events from frontend
|
|
app.Event.On("user-action", func(e *application.CustomEvent) {
|
|
data := e.Data.(map[string]interface{})
|
|
action := data["action"].(string)
|
|
|
|
app.Logger.Info("User action", "action", action)
|
|
|
|
// Respond
|
|
notifService.Notify("Action completed: " + action)
|
|
})
|
|
|
|
// Window events
|
|
window := app.Window.New()
|
|
|
|
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
|
app.Event.Emit("window-focused", window.Name())
|
|
})
|
|
|
|
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
|
// Confirm before close
|
|
app.Event.Emit("confirm-close", nil)
|
|
e.Cancel() // Wait for confirmation
|
|
})
|
|
|
|
app.Run()
|
|
}
|
|
```
|
|
|
|
**JavaScript:**
|
|
|
|
```javascript
|
|
import { Events } from '@wailsio/runtime'
|
|
|
|
// Listen for notifications
|
|
Events.On("notification", (event) => {
|
|
showNotification(event.data.message)
|
|
})
|
|
|
|
// Listen for theme changes
|
|
Events.On("theme-changed", (event) => {
|
|
const isDark = event.data
|
|
document.body.classList.toggle('dark', isDark)
|
|
})
|
|
|
|
// Listen for window focus
|
|
Events.On("window-focused", (event) => {
|
|
const windowName = event.data
|
|
console.log(`Window ${windowName} focused`)
|
|
})
|
|
|
|
// Handle close confirmation
|
|
Events.On("confirm-close", (event) => {
|
|
if (confirm("Close window?")) {
|
|
Emit("close-confirmed", true)
|
|
}
|
|
})
|
|
|
|
// Emit user actions
|
|
document.getElementById('button').addEventListener('click', () => {
|
|
Emit("user-action", { action: "button-clicked" })
|
|
})
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### ✅ Do
|
|
|
|
- **Use events for notifications** - One-way communication
|
|
- **Use bindings for requests** - Two-way communication
|
|
- **Keep event names consistent** - Use kebab-case
|
|
- **Document event data** - What fields are included?
|
|
- **Unsubscribe when done** - Prevent memory leaks
|
|
- **Use hooks for validation** - Control event flow
|
|
|
|
### ❌ Don't
|
|
|
|
- **Don't use events for RPC** - Use bindings instead
|
|
- **Don't emit too frequently** - Batch if needed
|
|
- **Don't block in handlers** - Keep them fast
|
|
- **Don't forget to unsubscribe** - Memory leaks
|
|
- **Don't use events for large data** - Use bindings
|
|
- **Don't create event loops** - A emits B, B emits A
|
|
|
|
## Next Steps
|
|
|
|
<CardGrid>
|
|
<Card title="Custom Events" icon="star">
|
|
Create your own event types.
|
|
|
|
[Learn More →](/features/events/custom)
|
|
</Card>
|
|
|
|
<Card title="Event Patterns" icon="puzzle">
|
|
Common event patterns and best practices.
|
|
|
|
[Learn More →](/features/events/patterns)
|
|
</Card>
|
|
|
|
<Card title="Bindings" icon="rocket">
|
|
Use bindings for request/response.
|
|
|
|
[Learn More →](/features/bindings/methods)
|
|
</Card>
|
|
|
|
<Card title="Window Events" icon="laptop">
|
|
Handle window lifecycle events.
|
|
|
|
[Learn More →](/features/windows/events)
|
|
</Card>
|
|
</CardGrid>
|
|
|
|
---
|
|
|
|
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events).
|