diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 35298972727..e6a68419491 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -138,6 +138,7 @@ export default defineConfig({
items: [
{ label: "Method Binding", link: "/features/bindings/methods" },
{ label: "Services", link: "/features/bindings/services" },
+ { label: "Data Models", link: "/features/bindings/models" },
{ label: "Advanced Binding", link: "/features/bindings/advanced" },
{ label: "Best Practices", link: "/features/bindings/best-practices" },
],
@@ -148,7 +149,6 @@ export default defineConfig({
items: [
{ label: "Event System", link: "/features/events/system" },
{ label: "Application Events", link: "/features/events/application" },
- { label: "Window Events", link: "/features/events/window" },
{ label: "Custom Events", link: "/features/events/custom" },
],
},
@@ -230,7 +230,6 @@ export default defineConfig({
{ label: "Windows Packaging", link: "/guides/build/windows" },
{ label: "macOS Packaging", link: "/guides/build/macos" },
{ label: "Linux Packaging", link: "/guides/build/linux" },
- { label: "MSIX Packaging", link: "/guides/build/msix" },
],
},
{
diff --git a/docs/src/content/docs/features/bindings/advanced.mdx b/docs/src/content/docs/features/bindings/advanced.mdx
index 11ca5291fa6..2a2a8b8c44f 100644
--- a/docs/src/content/docs/features/bindings/advanced.mdx
+++ b/docs/src/content/docs/features/bindings/advanced.mdx
@@ -2,7 +2,7 @@
title: Advanced Binding
description: Advanced binding techniques including directives, code injection, and custom IDs
sidebar:
- order: 3
+ order: 4
---
import { FileTree } from "@astrojs/starlight/components";
diff --git a/docs/src/content/docs/features/bindings/best-practices.mdx b/docs/src/content/docs/features/bindings/best-practices.mdx
index 5b2d71d8d73..85e820117af 100644
--- a/docs/src/content/docs/features/bindings/best-practices.mdx
+++ b/docs/src/content/docs/features/bindings/best-practices.mdx
@@ -2,7 +2,7 @@
title: Bindings Best Practices
description: Design patterns and best practices for Go-JavaScript bindings
sidebar:
- order: 4
+ order: 5
---
## Bindings Best Practices
diff --git a/docs/src/content/docs/features/events/application.mdx b/docs/src/content/docs/features/events/application.mdx
new file mode 100644
index 00000000000..f611d219d5b
--- /dev/null
+++ b/docs/src/content/docs/features/events/application.mdx
@@ -0,0 +1,142 @@
+---
+title: Application Events
+description: Handle application lifecycle events
+sidebar:
+ order: 2
+---
+
+import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
+
+Application events are triggered by application-level state changes such as application startup, theme changes, and power events. You can listen for these events using the `OnApplicationEvent` method:
+
+
+```go
+app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(event *application.ApplicationEvent) {
+ app.Logger.Info("Application started!")
+})
+
+app.Event.OnApplicationEvent(events.Windows.SystemThemeChanged, func(event *application.ApplicationEvent) {
+ app.Logger.Info("System theme changed!")
+ if event.Context().IsDarkMode() {
+ app.Logger.Info("System is now using dark mode!")
+ } else {
+ app.Logger.Info("System is now using light mode!")
+ }
+ })
+```
+
+## Common Application Events
+
+Common application events are aliases for platform-specific application events. These events are triggered by application-level state
+changes such as application startup, theme changes, and power events.
+
+Here is the same example as above, but using common application events to make it work across all platforms:
+
+```go
+app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) {
+ app.Logger.Info("Application started!")
+})
+
+app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
+ if event.Context().IsDarkMode() {
+ app.Logger.Info("System is now using dark mode!")
+ } else {
+ app.Logger.Info("System is now using light mode!")
+ }
+})
+```
+### Common Application Event List
+
+| Event Name | Description |
+|----------------------------|------------------------------------------------------------------------------------------------------------------------|
+| ApplicationOpenedWithFile | Application opened with a file. See [File Associations](/guides/distribution/file-associations) for more information. |
+| ApplicationStarted | Application has started |
+| ApplicationLaunchedWithUrl | Application opened with an URL. See [Custom Protocols](/guides/distribution/custom-protocols) for more information. |
+| ThemeChanged | System theme changed |
+
+## Platform-Specific Application Events
+
+Below is a list of all platform-specific application events.
+
+
+
+
+ | Event Name | Common Event | Description |
+ |------------|--------------|-------------|
+ | ApplicationDidBecomeActive | - | Application became active |
+ | ApplicationDidChangeBackingProperties | - | Application backing properties changed |
+ | ApplicationDidChangeEffectiveAppearance | ThemeChanged | Application appearance changed |
+ | ApplicationDidChangeIcon | - | Application icon changed |
+ | ApplicationDidChangeOcclusionState | - | Application occlusion state changed |
+ | ApplicationDidChangeScreenParameters | - | Screen parameters changed |
+ | ApplicationDidChangeStatusBarFrame | - | Status bar frame changed |
+ | ApplicationDidChangeStatusBarOrientation | - | Status bar orientation changed |
+ | ApplicationDidChangeTheme | ThemeChanged | System theme changed |
+ | ApplicationDidFinishLaunching | ApplicationStarted | Application finished launching |
+ | ApplicationDidHide | - | Application hidden |
+ | ApplicationDidResignActiveNotification | - | Application resigned active state |
+ | ApplicationDidUnhide | - | Application unhidden |
+ | ApplicationDidUpdate | - | Application updated |
+ | ApplicationShouldHandleReopen | - | Application should handle reopen |
+ | ApplicationWillBecomeActive | - | Application is about to become active |
+ | ApplicationWillFinishLaunching | - | Application is about to finish launching |
+ | ApplicationWillHide | - | Application is about to hide |
+ | ApplicationWillResignActiveNotification | - | Application is about to resign active state |
+ | ApplicationWillTerminate | - | Application is about to terminate |
+ | ApplicationWillUnhide | - | Application is about to unhide |
+ | ApplicationWillUpdate | - | Application is about to update |
+
+
+
+
+
+ | Event Name | Common Event | Description |
+ |------------|--------------|-------------|
+ | APMPowerSettingChange | - | Power settings changed |
+ | APMPowerStatusChange | - | Power status changed |
+ | APMResumeAutomatic | - | System resuming automatically |
+ | APMResumeSuspend | - | System resuming from suspend |
+ | APMSuspend | - | System suspending |
+ | ApplicationStarted | ApplicationStarted | Application started |
+ | SystemThemeChanged | ThemeChanged | System theme changed |
+
+
+
+ | Event Name | Common Event | Description |
+ |------------|--------------|-------------|
+ | ApplicationStartup | ApplicationStarted | Application started |
+ | SystemThemeChanged | ThemeChanged | System theme changed |
+
+
+
+## Next Steps
+
+
+
+ Handle specific file types when users open them.
+
+ [Learn More →](/guides/distribution/file-associations)
+
+
+
+ Launch your application by clicking on a link.
+
+ [Learn More →](/guides/distribution/custom-protocols)
+
+
+
+ Create your own event types.
+
+ [Learn More →](/features/events/custom)
+
+
+
+ Handle window lifecycle events.
+
+ [Learn More →](/features/windows/events)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events).
diff --git a/docs/src/content/docs/features/events/custom.mdx b/docs/src/content/docs/features/events/custom.mdx
new file mode 100644
index 00000000000..1b99a6dd304
--- /dev/null
+++ b/docs/src/content/docs/features/events/custom.mdx
@@ -0,0 +1,601 @@
+---
+title: Custom Events
+description: Notify windows about application-specific events with optional typing and autocompletion support
+sidebar:
+ order: 3
+---
+
+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)
+```
+
+**Removing all handlers:**
+
+```javascript
+Events.Off("data-updated")
+```
+
+## 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:**
+
+
+
+ ```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()
+ })
+ ```
+
+
+
+ ```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()
+ })
+ ```
+
+
+
+ ```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()
+ })
+ ```
+
+
+
+### 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
+
+```javascript
+// Frontend requests data
+Events.Emit("get-user-data", { userId: 123 })
+```
+
+```go
+// 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")
+```
+
+```javascript
+// 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?")) {
+ Events.Emit("close-confirmed", true)
+ }
+})
+
+// Emit user actions
+document.getElementById('button').addEventListener('click', () => {
+ Events.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
+
+
+
+ Common event patterns and best practices.
+
+ [Learn More →](/features/events/system#event-patterns)
+
+
+
+ Bind complex data structures.
+
+ [Learn More →](/features/bindings/models)
+
+
+
+ Handle application lifecycle events.
+
+ [Learn More →](/features/events/application)
+
+
+
+ Handle window lifecycle events.
+
+ [Learn More →](/features/windows/events)
+
+
+
+---
+
+**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events).
diff --git a/docs/src/content/docs/features/events/system.mdx b/docs/src/content/docs/features/events/system.mdx
index 678abff2f8b..ed51fbcdf69 100644
--- a/docs/src/content/docs/features/events/system.mdx
+++ b/docs/src/content/docs/features/events/system.mdx
@@ -129,13 +129,13 @@ window.EmitEvent("window-specific-event", data)
### From JavaScript
```javascript
-import { Emit } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Emit to Go
-Emit("button-clicked", { buttonId: "submit" })
+Events.Emit("button-clicked", { buttonId: "submit" })
// Emit to all windows
-Emit("broadcast-message", "Hello everyone")
+Events.Emit("broadcast-message", "Hello everyone")
```
## Listening to Events
@@ -204,6 +204,12 @@ Events.On("data-updated", logChange)
Events.Once("data-updated", updateVariable)
```
+**Removing all handlers:**
+
+```javascript
+Events.Off("data-updated")
+```
+
## System Events
### Application Events
@@ -240,7 +246,7 @@ app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *ap
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()
@@ -254,7 +260,7 @@ app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *ap
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()
@@ -268,7 +274,7 @@ app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *ap
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()
@@ -307,7 +313,7 @@ window.OnWindowEvent(events.Common.WindowClosed, func(e *application.WindowEvent
## Event Hooks
-Hooks run **before** standard listeners and can **cancel** events:
+Hooks run **before** standard listeners and can **cancel** events, which also prevents triggering their default behaviour:
```go
// Hook - runs first, can cancel
@@ -347,14 +353,14 @@ type OrderService struct {
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
}
@@ -377,17 +383,19 @@ app.Event.On("order-created", func(e *application.CustomEvent) {
### Request/Response Pattern
-```go
+```javascript
// Frontend requests data
-Emit("get-user-data", { userId: 123 })
+Events.Emit("get-user-data", { userId: 123 })
+```
+```go
// 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)
})
@@ -408,7 +416,9 @@ Events.On("user-data-response", (event) => {
```go
// Broadcast to all windows
app.Event.Emit("global-notification", "System update available")
+```
+```javascript
// Each window handles it
Events.On("global-notification", (event) => {
const message = event.data
@@ -427,9 +437,9 @@ type EventAggregator struct {
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)
@@ -466,39 +476,39 @@ 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()
}
```
@@ -506,7 +516,7 @@ func main() {
**JavaScript:**
```javascript
-import { Events, Emit } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for notifications
Events.On("notification", (event) => {
@@ -528,13 +538,13 @@ Events.On("window-focused", (event) => {
// Handle close confirmation
Events.On("confirm-close", (event) => {
if (confirm("Close window?")) {
- Emit("close-confirmed", true)
+ Events.Emit("close-confirmed", true)
}
})
// Emit user actions
document.getElementById('button').addEventListener('click', () => {
- Emit("user-action", { action: "button-clicked" })
+ Events.Emit("user-action", { action: "button-clicked" })
})
```
@@ -563,25 +573,25 @@ document.getElementById('button').addEventListener('click', () => {
Create your own event types.
-
- [Learn More →](/features/events/custom)
-
-
- Common event patterns and best practices.
-
- [Learn More →](/features/events/patterns)
+ [Learn More →](/features/events/custom)
Use bindings for request/response.
-
+
[Learn More →](/features/bindings/methods)
+
+ Handle application lifecycle events.
+
+ [Learn More →](/features/events/application)
+
+
Handle window lifecycle events.
-
+
[Learn More →](/features/windows/events)
diff --git a/docs/src/content/docs/guides/build/windows.mdx b/docs/src/content/docs/guides/build/windows.mdx
index fe4183cb45f..6589d5f8d36 100644
--- a/docs/src/content/docs/guides/build/windows.mdx
+++ b/docs/src/content/docs/guides/build/windows.mdx
@@ -22,7 +22,7 @@ This runs `wails3 task windows:package` which:
Output: `build/windows/nsis/-installer.exe`
-### MSIX Package
+## MSIX Package
For Microsoft Store distribution or modern Windows deployment:
diff --git a/docs/src/content/docs/guides/distribution/auto-updates.mdx b/docs/src/content/docs/guides/distribution/auto-updates.mdx
index 273915653ae..2adb9868e8c 100644
--- a/docs/src/content/docs/guides/distribution/auto-updates.mdx
+++ b/docs/src/content/docs/guides/distribution/auto-updates.mdx
@@ -2,7 +2,7 @@
title: Auto-Updates
description: Implement automatic application updates with Wails v3
sidebar:
- order: 5
+ order: 1
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
diff --git a/docs/src/content/docs/guides/file-associations.mdx b/docs/src/content/docs/guides/distribution/file-associations.mdx
similarity index 99%
rename from docs/src/content/docs/guides/file-associations.mdx
rename to docs/src/content/docs/guides/distribution/file-associations.mdx
index ba37d9f35a9..8d583014e6a 100644
--- a/docs/src/content/docs/guides/file-associations.mdx
+++ b/docs/src/content/docs/guides/distribution/file-associations.mdx
@@ -1,7 +1,7 @@
---
title: File Associations
sidebar:
- order: 20
+ order: 2
---
import { Steps } from "@astrojs/starlight/components";
@@ -115,6 +115,8 @@ Let's walk through setting up file associations for a simple text editor:
- For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task.
+
+
2. ### Configure File Associations
Edit the `build/config.yml` file to add your file associations:
@@ -149,6 +151,7 @@ Let's walk through setting up file associations for a simple text editor:
})
```
+
:::tip[Why are file extensions required in both the application config and config.yml?]
On Windows, when a file is opened with a file association, the application is
@@ -175,4 +178,4 @@ Let's walk through setting up file associations for a simple text editor:
- Icons should be provided in PNG format in the build folder
- Testing file associations requires installing the packaged application
-
\ No newline at end of file
+
diff --git a/docs/src/content/docs/guides/single-instance.mdx b/docs/src/content/docs/guides/distribution/single-instance.mdx
similarity index 99%
rename from docs/src/content/docs/guides/single-instance.mdx
rename to docs/src/content/docs/guides/distribution/single-instance.mdx
index 25b7fac47ce..d1a0a7a913c 100644
--- a/docs/src/content/docs/guides/single-instance.mdx
+++ b/docs/src/content/docs/guides/distribution/single-instance.mdx
@@ -2,7 +2,7 @@
title: Single Instance
description: Limiting your app to a single running instance
sidebar:
- order: 40
+ order: 4
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx
index d6286018eac..991c51860df 100644
--- a/docs/src/content/docs/reference/events.mdx
+++ b/docs/src/content/docs/reference/events.mdx
@@ -12,9 +12,9 @@ import { Card, CardGrid } from "@astrojs/starlight/components";
The Events API provides methods to emit and listen to events, enabling communication between different parts of your application.
**Event Types:**
+- **Custom Events** - User-defined events for app-specific communication
- **Application Events** - App lifecycle events (startup, shutdown)
- **Window Events** - Window state changes (focus, blur, resize)
-- **Custom Events** - User-defined events for app-specific communication
**Communication Patterns:**
- **Go to Frontend** - Emit events from Go, listen in JavaScript
@@ -22,33 +22,79 @@ The Events API provides methods to emit and listen to events, enabling communica
- **Frontend to Frontend** - Via Go or local runtime events
- **Window to Window** - Target specific windows or broadcast to all
-## Event Methods (Go)
+## Custom Event Methods (Go)
### app.Event.Emit()
-Emits a custom event to all windows.
+Emits a custom event.
```go
-func (em *EventManager) Emit(name string, data ...interface{})
+func (em *EventManager) Emit(name string, data ...any) bool
```
**Parameters:**
- `name` - Event name
- `data` - Optional data to send with the event
+**Returns:** `true` if the event was cancelled by a hook; `false` otherwise
+
**Example:**
```go
// Emit simple event
app.Event.Emit("user-logged-in")
// Emit with data
-app.Event.Emit("data-updated", map[string]interface{}{
+app.Event.Emit("data-updated", map[string]any{
"count": 42,
"status": "success",
})
// Emit multiple values
app.Event.Emit("progress", 75, "Processing files...")
+
+// Detect cancellation
+cancelled := app.Event.Emit("document-closing", documentInfo)
+if cancelled {
+ return
+} else {
+ closeDocument()
+ app.Event.Emit("document-closed", documentInfo)
+}
+```
+
+### window.EmitEvent()
+
+Emits a custom event reporting a specific window as the sender.
+
+```go
+func (w *WebviewWindow) Emit(name string, data ...any) bool
+```
+
+**Parameters:**
+- `name` - Event name to emit
+- `...data` - Zero, one or more data items of any type
+
+**Returns:** `true` if the event was cancelled by a hook; `false` otherwise
+
+**Example:**
+```go
+// Backend
+window.EmitEvent("notification", "Hello from Go!")
+```
+
+```javascript
+// Frontend
+import { Events, Window } from '@wailsio/runtime'
+
+Events.On('notification', async (ev) => {
+ // The following comparison will be true only if window.EmitEvent was called on the webview we are running in
+ if (ev.sender === await Window.Name()) {
+ return
+ }
+
+ // Show global notifications or notifications from other windows.
+ showNotification(ev.data)
+})
```
### app.Event.On()
@@ -69,7 +115,7 @@ func (em *EventManager) On(name string, callback func(*CustomEvent)) func()
```go
// Listen for events
cleanup := app.Event.On("user-action", func(e *application.CustomEvent) {
- data := e.Data.(map[string]interface{})
+ data := e.Data.(map[string]any)
action := data["action"].(string)
app.Logger.Info("User action", "action", action)
})
@@ -78,28 +124,91 @@ cleanup := app.Event.On("user-action", func(e *application.CustomEvent) {
cleanup()
```
-### Window-Specific Events
+### app.Event.Off()
-Emit events to a specific window:
+Removes all listeners for a custom event in Go.
```go
-// Emit to specific window
-window.EmitEvent("notification", "Hello from Go!")
+func (em *EventManager) Off(name string)
+```
+
+**Parameters:**
+- `name` - Event name to stop listening for
+
+**Example:**
+```go
+app.Event.On("user-action", listener1)
+app.Event.On("user-action", listener2)
+
+// Remove both listener1 and listener2
+app.Event.Off("user-action")
+```
+
+### app.Event.Reset()
-// Emit to all windows
-app.Event.Emit("global-update", data)
+Removes all listeners for all custom events in Go.
+
+```go
+func (em *EventManager) Reset()
```
-## Event Methods (Frontend)
+**Example:**
+```go
+app.Event.On("user-action", listener1)
+app.Event.On("settings-update", listener2)
+
+// Remove both listener1 and listener2 (as well as any other registered listener)
+app.Event.Reset()
+```
-### On()
+## Custom Event Methods (Frontend)
+
+### Events.Emit()
+
+Emits a custom event with the current webview window as sender.
+
+```javascript
+import { Events } from '@wailsio/runtime'
+
+Events.Emit(name)
+Events.Emit(name, data)
+```
+
+**Parameters:**
+- `name` - Event name
+- `data` - Optional data to send with the event
+
+**Returns:** A promise that resolves to `true` if the event was cancelled by a hook; to `false` otherwise
+
+**Example:**
+```javascript
+// Emit simple event
+Events.Emit("user-logged-in")
+
+// Emit with data
+Events.Emit("data-updated", {
+ "count": 42,
+ "status": "success",
+})
+
+// Detect cancellation
+const cancelled = await Events.Emit("document-closing", documentInfo)
+if cancelled {
+ return
+} else {
+ await closeDocument()
+ app.Event.Emit("document-closed", documentInfo)
+}
+```
+
+### Events.On()
Listens for events from Go.
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-On(eventName, callback)
+Events.On(eventName, callback)
```
**Parameters:**
@@ -110,82 +219,187 @@ On(eventName, callback)
**Example:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for events
-const cleanup = On('data-updated', (data) => {
- console.log('Count:', data.count)
- console.log('Status:', data.status)
- updateUI(data)
+const cleanup = Events.On('data-updated', (ev) => {
+ console.log('Count:', ev.data.count)
+ console.log('Status:', ev.data.status)
+ updateUI(ev.data)
})
-// Later, remove listener
+// Later, stop listening
cleanup()
```
-### Once()
+### Events.Once()
Listens for a single event occurrence.
```javascript
-import { Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-Once(eventName, callback)
+Events.Once(eventName, callback)
```
+**Parameters:**
+- `eventName` - Name of the event to listen for
+- `callback` - Function called when event is received
+
+**Returns:** Cleanup function
+
**Example:**
```javascript
-import { Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for first occurrence only
-Once('initialization-complete', (data) => {
- console.log('App initialized!', data)
+Events.Once('initialization-complete', (ev) => {
+ console.log('App initialized!', ev.data)
// This will only fire once
})
```
-### Off()
+### Events.Off()
-Removes an event listener.
+Removes _all event listeners_ for a given set of events.
```javascript
-import { Off } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-Off(eventName, callback)
+Events.Off(eventName1, ..., eventNameN)
```
+**Parameters:**
+- `...eventName` - One or more event names to stop listening to
+
**Example:**
```javascript
-import { On, Off } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
-const handler = (data) => {
- console.log('Event received:', data)
+const handler1 = (ev) => {
+ console.log('Event received:', ev.name, ev.data)
}
+const handler2 = () => console.log('Additional handler triggered')
+
// Start listening
-On('my-event', handler)
+Events.On('my-event-1', handler1)
+Events.On('my-event-1', handler2)
+
+Events.On('my-event-2', handler1)
// Stop listening
-Off('my-event', handler)
+Events.Off('my-event-1', 'my-event-2')
```
-### OffAll()
+### Events.OffAll()
-Removes all listeners for an event.
+Removes _all registered listeners_ for _all events_.
```javascript
-import { OffAll } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
+
+Events.OffAll()
+```
+
+## Typed Event API
-OffAll(eventName)
+Custom events may be registered at init time, enabling strict checking of correctness on names and data types, as well as generation and runtime translation of Typescript types on the frontend side.
+
+If a registered event is emitted with the wrong data type, an error will be reported through the application error handler (configurable through application options, the default handler logs errors to the configured logger) and the event will be dropped.
+
+By default, registering an event will only enable checking of data types. If the application is compiled with the `strictevents` tag, it will also log warnings when unregistered events are emitted (but will not drop them).
+
+### application.RegisterEvent()
+
+Registers an event with a specified data type. Duplicate calls for the same event name trigger a panic.
+
+```go
+func RegisterEvent[Data any](name string)
```
+**Type Parameters:**
+- `Data` - Type of data associated to the registered event
+
+**Parameters:**
+- `name` - Event name to register
+
**Example:**
+```go
+package auth
+
+type User struct{
+ Id number `json:"id"`
+ Name string `json:"name"`
+}
+
+func init() {
+ application.RegisterEvent[User]("auth:login")
+}
+
+// Valid! Emits event
+app.Events.Emit("auth:login", User{ Id: 0, Name: "root" })
+
+// Invalid! Logs error and drops event
+app.Events.Emit("auth:login")
+app.Events.Emit("auth:login", "username")
+```
+
+```typescript
+import { Events } from '@wailsio/runtime'
+import { User } from './bindings/auth'
+
+Events.On('auth:login', ({ data }) => {
+ // data has type User, data.id has type number and data.name has type string.
+ // Without registration, their type would be any.
+})
+
+// Valid! Emits event. Go listeners will receive an instance of struct `auth.User`
+Events.Emit('auth:login', new User({ id: 0, name: 'root' }))
+
+// Invalid! Does not typecheck at compile time, and log an error at runtime
+Events.Emit('auth:login')
+Events.Emit('auth:login', "username")
+```
+
+### application.Void
+
+A placeholder type to register events that must have no associated data.
+
+```go
+type Void interface{
+ /* Private Methods */
+}
+```
+
+**Example:**
+```go
+func init() {
+ application.RegisterEvent[application.Void]("processing-complete")
+}
+
+// Valid! Emits event
+Events.Emit("processing-complete")
+
+// Invalid! Logs error and drops event
+Events.Emit("processing-complete", "100%")
+```
+
```javascript
-// Remove all listeners for this event
-OffAll('data-updated')
+import { Events } from '@wailsio/runtime'
+
+Events.On('processing-complete', ({ data }) => {
+ // data has type void
+})
+
+// Valid! Emits event
+Events.Emit('processing-complete')
+
+// Invalid! Does not typecheck at compile time, and will log an error at runtime
+Events.Emit('processing-complete', 100)
```
-## Application Events
+## Application Event Methods
### app.Event.OnApplicationEvent()
@@ -195,29 +409,58 @@ Listens for application lifecycle events.
func (em *EventManager) OnApplicationEvent(eventType ApplicationEventType, callback func(*ApplicationEvent)) func()
```
-**Event Types:**
-- `EventApplicationStarted` - Application has started
-- `EventApplicationShutdown` - Application is shutting down
-- `EventApplicationDebug` - Debug event (dev mode only)
+**Parameters:**
+- `eventType` - Type of the event to listen for (see [Application Events](#application-events))
+- `callback` - Function called when event is received
+
+**Returns:** Cleanup function
**Example:**
```go
-// Handle application startup
-app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) {
- app.Logger.Info("Application started")
- // Initialize resources
+// Handle file opening event
+app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) {
+ app.Logger.Info("File opened", "name", e.Context().Filename())
+ loadDocument(e.Context().Filename())
+})
+
+// Handle theme change
+cleanup := app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
+ app.Logger.Info("Theme changed")
})
-// Handle application shutdown
-app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) {
- app.Logger.Info("Application shutting down")
- // Cleanup resources, save state
- database.Close()
- saveSettings()
+// Stop listening to theme changes later
+cleanup()
+```
+
+### app.Event.RegisterApplicationEventHook()
+
+Registers a hook to intercept and cancel application events. For cancelable events, canceling will prevent the default behaviour. For non-cancelable events, canceling will just stop propagation to later hooks and listeners.
+
+```go
+func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func()
+```
+
+**Parameters:**
+- `eventType` - Type of the event to listen for (see [Application Events](#application-events))
+- `callback` - Function called when event is received
+
+**Returns:** Cleanup function
+
+**Example:**
+```go
+// Suppress theme change notifications conditionally
+cleanup := app.Event.RegisterApplicationEventHook(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
+ if condition {
+ // If condition is met, stop propagation
+ e.Cancel()
+ }
})
+
+// Stop filtering theme changes later
+cleanup()
```
-## Window Events
+## Window Event Methods
### OnWindowEvent()
@@ -227,25 +470,54 @@ Listens for window-specific events.
func (w *Window) OnWindowEvent(eventType WindowEventType, callback func(*WindowEvent)) func()
```
-**Event Types:**
-- `EventWindowFocus` - Window gained focus
-- `EventWindowBlur` - Window lost focus
-- `EventWindowClose` - Window is closing
-- `EventWindowResize` - Window was resized
-- `EventWindowMove` - Window was moved
+**Parameters:**
+- `eventType` - Type of the event to listen for (see [Window Events](#window-events))
+- `callback` - Function called when event is received
+
+**Returns:** Cleanup function
**Example:**
```go
// Handle window focus
-window.OnWindowEvent(application.EventWindowFocus, func(e *application.WindowEvent) {
+window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
app.Logger.Info("Window focused")
})
// Handle window resize
-window.OnWindowEvent(application.EventWindowResize, func(e *application.WindowEvent) {
+cleanup := window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) {
width, height := window.Size()
app.Logger.Info("Window resized", "width", width, "height", height)
})
+
+// Stop listening to resize events later
+cleanup()
+```
+
+### window.RegisterHook()
+
+Registers a hook to intercept and cancel window events. For cancelable events, canceling will prevent the default behaviour. For non-cancelable events, canceling will just stop propagation to later hooks and listeners.
+
+```go
+func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func()
+```
+
+**Parameters:**
+- `eventType` - Type of the event to listen for (see [Window Events](#window-events))
+- `callback` - Function called when event is received
+
+**Returns:** Cleanup function
+
+**Example:**
+```go
+// Ask for confirmation before closing window
+cleanup := window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
+ if !confirm("There is unsaved data! Do you want to quit?") {
+ e.Cancel()
+ }
+})
+
+// Stop asking for confirmation later
+cleanup()
```
## Common Patterns
@@ -268,7 +540,7 @@ func (s *DataService) FetchData(query string) ([]Item, error) {
items := fetchFromDatabase(query)
// Emit event when done
- s.app.Event.Emit("data-fetched", map[string]interface{}{
+ s.app.Event.Emit("data-fetched", map[string]any{
"query": query,
"count": len(items),
})
@@ -281,10 +553,10 @@ func (s *DataService) FetchData(query string) ([]Item, error) {
```javascript
import { FetchData } from './bindings/DataService'
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Listen for completion event
-On('data-fetched', (data) => {
+Events.On('data-fetched', ({ data }) => {
console.log(`Fetched ${data.count} items for query: ${data.query}`)
showNotification(`Found ${data.count} results`)
})
@@ -309,7 +581,7 @@ func (s *Service) ProcessFiles(files []string) error {
processFile(file)
// Emit progress event
- s.app.Event.Emit("progress", map[string]interface{}{
+ s.app.Event.Emit("progress", map[string]any{
"current": i + 1,
"total": total,
"percent": float64(i+1) / float64(total) * 100,
@@ -325,16 +597,17 @@ func (s *Service) ProcessFiles(files []string) error {
**JavaScript:**
```javascript
-import { On, Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Update progress bar
-On('progress', (data) => {
+const cleanup = Events.On('progress', ({ data }) => {
progressBar.style.width = `${data.percent}%`
statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})`
})
// Handle completion
-Once('processing-complete', () => {
+Events.Once('processing-complete', () => {
+ cleanup()
progressBar.style.width = '100%'
statusText.textContent = 'Complete!'
setTimeout(() => hideProgressBar(), 2000)
@@ -343,35 +616,43 @@ Once('processing-complete', () => {
### Multi-Window Communication
-Perfect for applications with multiple windows like settings panels, dashboards, or document viewers. Broadcast events to synchronize state across all windows (theme changes, user preferences) or send targeted events to specific windows for window-specific updates.
+Events can be sent from a specific window whose name will be reported in the `Sender` field of the event struct. Handlers will then be able to filter incoming events depending on the sending window. Perfect for applications with multiple windows like settings panels, dashboards, or document viewers.
**Go:**
```go
-// Broadcast to all windows
-app.Event.Emit("theme-changed", "dark")
-
-// Send to specific window
+// Send from specific window
preferencesWindow.EmitEvent("settings-updated", settings)
-// Window-specific listener
-window1.OnEvent("request-data", func(e *application.CustomEvent) {
- // Only this window will receive this event
- window1.EmitEvent("data-response", data)
+app.Event.On("settings-updated", func(e *application.CustomEvent) {
+ if (e.Sender === window1.EmitEvent()) {
+ // Process changes
+ } else {
+ app.Logger.Warn("settings-updated event from unexpected sender", "sender", e.Sender)
+ }
})
```
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events, Window } from '@wailsio/runtime'
-// Listen in any window
-On('theme-changed', (theme) => {
- document.body.className = theme
+// Send from current window
+Events.Emit('status-changed', localStatus)
+
+// Process only events emitted from the current window
+Events.On('status-changed', async (ev) => {
+ if (ev.sender === await Window.Name()) {
+ console.log("Status changed:", ev.data)
+ }
})
```
+:::note
+Events emitted from the frontend are always sent from the window the triggering code is running in.
+:::
+
### State Synchronization
Use when you need to keep frontend and backend state in sync, such as user sessions, application configuration, or collaborative features. When state changes on the backend, emit events to update all connected frontends, ensuring consistency across your application.
@@ -381,46 +662,52 @@ Use when you need to keep frontend and backend state in sync, such as user sessi
```go
type StateService struct {
app *application.Application
- state map[string]interface{}
+ state map[string]any
mu sync.RWMutex
}
-func (s *StateService) UpdateState(key string, value interface{}) {
+func (s *StateService) UpdateState(key string, value any) {
s.mu.Lock()
s.state[key] = value
s.mu.Unlock()
// Notify all windows
- s.app.Event.Emit("state-updated", map[string]interface{}{
+ s.app.Event.Emit("state-updated", map[string]any{
"key": key,
"value": value,
})
}
-func (s *StateService) GetState(key string) interface{} {
+func (s *StateService) GetState(key string) any {
s.mu.RLock()
defer s.mu.RUnlock()
return s.state[key]
}
+
+func (s *StateService) GetFullState() map[string]any {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return maps.Clone(s.state)
+}
```
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
-import { GetState } from './bindings/StateService'
+import { Events } from '@wailsio/runtime'
+import { GetFullState } from './bindings/StateService'
// Keep local state in sync
-let localState = {}
+const localState = {}
-On('state-updated', async (data) => {
+Events.On('state-updated', async ({ data }) => {
localState[data.key] = data.value
updateUI(data.key, data.value)
})
// Initialize state
-const initialState = await GetState("all")
-localState = initialState
+const initialState = await GetFullState()
+Object.assign(localState, initialState)
```
### Event-Driven Notifications
@@ -435,21 +722,21 @@ type NotificationService struct {
}
func (s *NotificationService) Success(message string) {
- s.app.Event.Emit("notification", map[string]interface{}{
+ s.app.Event.Emit("notification", map[string]any{
"type": "success",
"message": message,
})
}
func (s *NotificationService) Error(message string) {
- s.app.Event.Emit("notification", map[string]interface{}{
+ s.app.Event.Emit("notification", map[string]any{
"type": "error",
"message": message,
})
}
func (s *NotificationService) Info(message string) {
- s.app.Event.Emit("notification", map[string]interface{}{
+ s.app.Event.Emit("notification", map[string]any{
"type": "info",
"message": message,
})
@@ -459,10 +746,10 @@ func (s *NotificationService) Info(message string) {
**JavaScript:**
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
// Unified notification handler
-On('notification', (data) => {
+Events.On('notification', ({ data }) => {
const toast = document.createElement('div')
toast.className = `toast toast-${data.type}`
toast.textContent = data.message
@@ -486,7 +773,9 @@ package main
import (
"sync"
"time"
+
"github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/events"
)
type EventDemoService struct {
@@ -499,7 +788,7 @@ func NewEventDemoService(app *application.Application) *EventDemoService {
// Listen for custom events
app.Event.On("user-action", func(e *application.CustomEvent) {
- data := e.Data.(map[string]interface{})
+ data := e.Data.(map[string]any)
app.Logger.Info("User action received", "data", data)
})
@@ -513,14 +802,14 @@ func (s *EventDemoService) StartLongTask() {
for i := 1; i <= 10; i++ {
time.Sleep(500 * time.Millisecond)
- s.app.Event.Emit("task-progress", map[string]interface{}{
+ s.app.Event.Emit("task-progress", map[string]any{
"step": i,
"total": 10,
"percent": i * 10,
})
}
- s.app.Event.Emit("task-completed", map[string]interface{}{
+ s.app.Event.Emit("task-completed", map[string]any{
"message": "Task finished successfully!",
})
}()
@@ -536,14 +825,10 @@ func main() {
})
// Handle application lifecycle
- app.Event.OnApplicationEvent(application.EventApplicationStarted, func(e *application.ApplicationEvent) {
+ app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
app.Logger.Info("Application started!")
})
- app.Event.OnApplicationEvent(application.EventApplicationShutdown, func(e *application.ApplicationEvent) {
- app.Logger.Info("Application shutting down...")
- })
-
// Register service
service := NewEventDemoService(app)
app.RegisterService(application.NewService(service))
@@ -568,34 +853,34 @@ func main() {
**JavaScript:**
```javascript
-import { On, Once } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService'
// Task events
-On('task-started', () => {
+Events.On('task-started', () => {
console.log('Task started...')
document.getElementById('status').textContent = 'Running...'
})
-On('task-progress', (data) => {
+Events.On('task-progress', ({ data }) => {
const progressBar = document.getElementById('progress')
progressBar.style.width = `${data.percent}%`
console.log(`Step ${data.step} of ${data.total}`)
})
-Once('task-completed', (data) => {
+Events.Once('task-completed', ({ data }) => {
console.log('Task completed!', data.message)
document.getElementById('status').textContent = data.message
})
// Broadcast events
-On('broadcast', (message) => {
+Events.On('broadcast', ({ data: message }) => {
console.log('Broadcast:', message)
alert(message)
})
// Window state events
-On('window-state', (state) => {
+Events.On('window-state', ({ data: state }) => {
console.log('Window is now:', state)
document.body.dataset.windowState = state
})
@@ -761,18 +1046,18 @@ app.Event.Emit("e", value)
## Performance Considerations
-### Debouncing High-Frequency Events
+### Throttling High-Frequency Events
```go
type Service struct {
app *application.Application
lastEmit time.Time
- debounceWindow time.Duration
+ minInterval time.Duration
}
-func (s *Service) EmitWithDebounce(event string, data interface{}) {
+func (s *Service) EmitThrottled(event string, data any) {
now := time.Now()
- if now.Sub(s.lastEmit) < s.debounceWindow {
+ if now.Sub(s.lastEmit) < s.minInterval {
return // Skip this emission
}
@@ -781,15 +1066,15 @@ func (s *Service) EmitWithDebounce(event string, data interface{}) {
}
```
-### Throttling Events
+### Throttling Event Processing
```javascript
-import { On } from '@wailsio/runtime'
+import { Events } from '@wailsio/runtime'
let lastUpdate = 0
const throttleMs = 100
-On('high-frequency-event', (data) => {
+Events.On('high-frequency-event', ({ data }) => {
const now = Date.now()
if (now - lastUpdate < throttleMs) {
return // Skip this update
diff --git a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts
index cf6a2581dd7..3ff3fcd68e1 100644
--- a/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts
+++ b/v3/internal/runtime/desktop/@wailsio/runtime/src/events.ts
@@ -109,7 +109,7 @@ function dispatchWailsEvent(event: any) {
* @param maxCallbacks - The maximum number of times the callback can be called for the event. Once the maximum number is reached, the callback will no longer be called.
* @returns A function that, when called, will unregister the callback from the event.
*/
-export function OnMultiple(eventName: E, callback: WailsEventCallback, maxCallbacks: number) {
+export function OnMultiple(eventName: E, callback: WailsEventCallback, maxCallbacks: number): () => void {
let listeners = eventListeners.get(eventName) || [];
const thisListener = new Listener(eventName, callback, maxCallbacks);
listeners.push(thisListener);
@@ -164,7 +164,6 @@ export function OffAll(): void {
*/
export function Emit(name: E, data: WailsEventData): Promise
export function Emit(name: WailsEventData extends null | void ? E : never): Promise
-export function Emit(name: WailsEventData, data?: any): Promise {
- return call(EmitMethod, new WailsEvent(name, data))
+export function Emit(name: E, data?: any): Promise {
+ return call(EmitMethod, new WailsEvent(name, data))
}
-