events
packageAPI reference for the events
package.
Imports
(4)Event
Event is a type-safe application event identifier.
type Event string
busHandler
busHandler pairs a handler with a stable ID so unsubscribe can remove it:
comparing function values is not valid in Go, so removal goes by ID.
type busHandler struct
Fields
| Name | Type | Description |
|---|---|---|
| id | int | |
| fn | func(any) |
eventBus
type eventBus struct
Methods
On registers a handler for an application event and returns an unsubscribe function.
Parameters
Returns
func (*eventBus) On(event Event, handler func(any)) func()
{
b.mu.Lock()
b.nextID++
id := b.nextID
b.handlers[event] = append(b.handlers[event], busHandler{id: id, fn: handler})
b.mu.Unlock()
return func() {
b.mu.Lock()
defer b.mu.Unlock()
handlers := b.handlers[event]
for i, h := range handlers {
if h.id == id {
b.handlers[event] = append(handlers[:i], handlers[i+1:]...)
return
}
}
}
}
Emit dispatches an application event to all registered handlers.
Parameters
func (*eventBus) Emit(event Event, data any)
{
b.mu.RLock()
handlers := make([]busHandler, len(b.handlers[event]))
copy(handlers, b.handlers[event])
b.mu.RUnlock()
for _, h := range handlers {
h.fn(data)
}
}
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.RWMutex | |
| handlers | map[Event][]busHandler | |
| nextID | int |
OnApp
OnApp registers a handler for an application event. Returns unsubscribe function.
Parameters
Returns
func OnApp(event Event, handler func(any)) func()
{
return bus.On(event, handler)
}
Uses
EmitApp
EmitApp dispatches an application event.
Parameters
func EmitApp(event Event, data any)
{
bus.Emit(event, data)
}
Uses
On
On attaches a handler function for the given DOM event to target.
Optional opts are forwarded to addEventListener as-is.
It returns a function that removes the listener and releases resources.
Parameters
Returns
func On(event string, target js.Value, handler func(js.Value), opts ...any) func()
{
fn := js.SafeFuncOf(func(_ js.Value, args []js.Value) any {
if len(args) > 0 {
handler(args[0])
} else {
handler(js.Null())
}
return nil
})
callArgs := []any{event, fn}
if len(opts) > 0 {
callArgs = append(callArgs, opts...)
}
target.Call("addEventListener", callArgs...)
return func() {
target.Call("removeEventListener", event, fn)
fn.Release()
}
}
Once
Once attaches a handler that fires at most one time: the listener is removed
and its callback released as soon as the event lands. One-shot browser
callbacks (FileReader load, Image load, transitionend) otherwise leak a
js.Func per call, since there is no natural moment to call the stop function.
The returned function cancels a listener that has not fired yet and is a
no-op afterwards.
Parameters
Returns
func Once(event string, target js.Value, handler func(js.Value)) func()
{
var fn js.Func
fired := false
release := func() {
if fired {
return
}
fired = true
target.Call("removeEventListener", event, fn)
fn.Release()
}
fn = js.SafeFuncOf(func(_ js.Value, args []js.Value) any {
evt := js.Null()
if len(args) > 0 {
evt = args[0]
}
release()
handler(evt)
return nil
})
target.Call("addEventListener", event, fn)
return release
}
OnClick
OnClick attaches a click handler to target.
Parameters
Returns
func OnClick(target js.Value, handler func(js.Value)) func()
{
return On("click", target, handler)
}
OnScroll
OnScroll attaches a scroll handler to target.
Parameters
Returns
func OnScroll(target js.Value, handler func(js.Value)) func()
{
return On("scroll", target, handler)
}
OnInput
OnInput attaches an input handler to target.
Parameters
Returns
func OnInput(target js.Value, handler func(js.Value)) func()
{
return On("input", target, handler)
}
OnTimeUpdate
OnTimeUpdate attaches a timeupdate handler to target.
Parameters
Returns
func OnTimeUpdate(target js.Value, handler func(js.Value)) func()
{
return On("timeupdate", target, handler)
}
OnKeyDown
OnKeyDown attaches a keydown handler to the window object.
Parameters
Returns
func OnKeyDown(handler func(js.Value)) func()
{
return On("keydown", js.Window(), handler)
}
OnKeyUp
OnKeyUp attaches a keyup handler to the window object.
Parameters
Returns
func OnKeyUp(handler func(js.Value)) func()
{
return On("keyup", js.Window(), handler)
}
Listen
Listen attaches an event listener to target and returns a channel that
receives the first argument of the event callback, plus a stop function.
The stop function removes the listener, releases the underlying js.Func and
closes the channel so range loops over it terminate; without calling it
every Listen leaks a listener and its goroutine across bind/unmount cycles.
func Listen(event string, target js.Value) (<-chan js.Value, func())
{
ch := make(chan js.Value)
fn := js.SafeFuncOf(func(_ js.Value, args []js.Value) any {
if len(args) > 0 {
ch <- args[0]
} else {
ch <- js.Null()
}
return nil
})
target.Call("addEventListener", event, fn)
stop := func() {
target.Call("removeEventListener", event, fn)
fn.Release()
close(ch)
}
return ch, stop
}
ObserveMutations
ObserveMutations observes DOM mutations on the first node matching sel.
It returns a channel receiving MutationRecord objects and a stop function
that disconnects the observer and releases resources.
Parameters
Returns
func ObserveMutations(sel string) (<-chan js.Value, func())
{
ch := make(chan js.Value)
node := js.Document().Call("querySelector", sel)
fn := js.SafeFuncOf(func(_ js.Value, args []js.Value) any {
mutations := args[0]
for i := 0; i < mutations.Length(); i++ {
m := mutations.Index(i)
t := m.Get("target")
if t.Truthy() && t.Get("closest").Type() != js.TypeUndefined {
if t.Call("closest", "[data-rfw-ignore]").Truthy() {
continue
}
}
ch <- m
}
return nil
})
observer := js.MutationObserver().New(fn)
opts := js.NewDict()
opts.Set("childList", true)
opts.Set("subtree", true)
observer.Call("observe", node, opts.Value)
stop := func() {
observer.Call("disconnect")
fn.Release()
}
return ch, stop
}
ObserveIntersections
ObserveIntersections observes intersections for elements matching sel.
opts is passed directly to the IntersectionObserver constructor.
It returns a channel receiving IntersectionObserverEntry objects and a
stop function to disconnect the observer and release resources.
func ObserveIntersections(sel string, opts js.Value) (<-chan js.Value, func())
{
ch := make(chan js.Value)
fn := js.SafeFuncOf(func(_ js.Value, args []js.Value) any {
entries := args[0]
for i := 0; i < entries.Length(); i++ {
ch <- entries.Index(i)
}
return nil
})
observer := js.IntersectionObserver().New(fn, opts)
nodes := js.Document().Call("querySelectorAll", sel)
for i := 0; i < nodes.Length(); i++ {
observer.Call("observe", nodes.Index(i))
}
stop := func() {
observer.Call("disconnect")
fn.Release()
}
return ch, stop
}
TestPlaceholder
Parameters
func TestPlaceholder(_ *testing.T)
{}
TestOnAppUnsubscribe
Unsubscribe must remove exactly the handler it was returned for; the old
implementation compared local variable addresses and never removed anything.
Parameters
func TestOnAppUnsubscribe(t *testing.T)
{
const ev Event = "test:unsub"
var first, second int
off1 := OnApp(ev, func(any) { first++ })
off2 := OnApp(ev, func(any) { second++ })
EmitApp(ev, nil)
off1()
EmitApp(ev, nil)
off2()
EmitApp(ev, nil)
if first != 1 {
t.Fatalf("first handler expected 1 call, got %d", first)
}
if second != 2 {
t.Fatalf("second handler expected 2 calls, got %d", second)
}
}
TestListenStop
Listen’s stop function removes the listener, releases the js.Func and closes
the channel so consumer goroutines terminate.
Parameters
func TestListenStop(t *testing.T)
{
doc := js.Document()
el := doc.Call("createElement", "div")
doc.Get("body").Call("appendChild", el)
defer el.Call("remove")
ch, stop := Listen("click", el)
received := 0
done := make(chan struct{})
go func() {
for range ch {
received++
}
close(done)
}()
el.Call("click")
// The dispatch is synchronous but delivery goes through the channel; give
// the consumer goroutine a beat before stopping.
time.Sleep(50 * time.Millisecond)
stop()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatalf("consumer goroutine did not terminate after stop")
}
if received != 1 {
t.Fatalf("expected 1 event before stop, got %d", received)
}
// Events after stop must not fire the released handler (would panic on a
// released js.Func if the listener were still attached).
el.Call("click")
}
dispatch
Parameters
func dispatch(target js.Value, name string)
{
evt := js.CustomEvent().New(name)
target.Call("dispatchEvent", evt)
}
TestOnceFiresOnlyOnce
Parameters
func TestOnceFiresOnlyOnce(t *testing.T)
{
target := js.Document().Call("createElement", "div")
calls := 0
Once("ping", target, func(js.Value) { calls++ })
dispatch(target, "ping")
dispatch(target, "ping")
if calls != 1 {
t.Fatalf("handler ran %d times, want 1", calls)
}
}
TestOnceCancelBeforeFiring
Parameters
func TestOnceCancelBeforeFiring(t *testing.T)
{
target := js.Document().Call("createElement", "div")
calls := 0
cancel := Once("ping", target, func(js.Value) { calls++ })
cancel()
dispatch(target, "ping")
if calls != 0 {
t.Fatalf("handler ran %d times after cancel, want 0", calls)
}
}
TestOnceCancelAfterFiringIsNoop
Parameters
func TestOnceCancelAfterFiringIsNoop(_ *testing.T)
{
target := js.Document().Call("createElement", "div")
cancel := Once("ping", target, func(js.Value) {})
dispatch(target, "ping")
cancel()
cancel()
}
TestOnRecoversHandlerPanic
A panicking handler must not take the wasm instance down with it: the
framework’s own listeners carry the recover guard, so an application that
uses events.On never has to reach for SafeFuncOf itself.
Parameters
func TestOnRecoversHandlerPanic(t *testing.T)
{
prev := js.OnFuncPanic
defer func() { js.OnFuncPanic = prev }()
var got any
js.OnFuncPanic = func(r any, _ []byte) { got = r }
target := js.Document().Call("createElement", "div")
stop := On("boom", target, func(js.Value) { panic("handler exploded") })
defer stop()
target.Call("dispatchEvent", js.CustomEvent().New("boom"))
if got == nil {
t.Fatal("panic escaped the listener")
}
if s, ok := got.(string); !ok || s != "handler exploded" {
t.Fatalf("unexpected recovered value: %v", got)
}
}
TestOnceRecoversHandlerPanic
Parameters
func TestOnceRecoversHandlerPanic(t *testing.T)
{
prev := js.OnFuncPanic
defer func() { js.OnFuncPanic = prev }()
var got any
js.OnFuncPanic = func(r any, _ []byte) { got = r }
target := js.Document().Call("createElement", "div")
Once("boom", target, func(js.Value) { panic("once exploded") })
target.Call("dispatchEvent", js.CustomEvent().New("boom"))
if got == nil {
t.Fatal("panic escaped the one-shot listener")
}
}