Event Handling
Interactivity in rfw v2 is driven by events. Templates register listeners with directives, and Go code defines the handlers. composition.New auto-discovers them by method signature.
Auto-Discovered Handlers
Exported no-argument methods on a struct are automatically registered as event handlers by composition.New:
type Form struct {
composition.Component
Count *t.Int
}
func (f *Form) Save() {
f.Count.Set(f.Count.Get() + 1)
}
The method name (Save) becomes the handler name. Wire it in your template:
<button @on:click:Save>Save</button>
Any exported method with no arguments and no return value is auto-discovered and registered as a handler. No tags or registration needed — composition.New detects them by convention.
Method Registration for Events
For explicit control over which events a handler responds to, define the method on the struct. The method name must match the handler name used in the template:
type Form struct {
composition.Component
}
func (f *Form) HandleForm() {
// handle form submission
}
<form @on:submit.prevent:HandleForm>...</form>
All event handlers are discovered automatically from exported no-arg methods. No additional wiring or tags required.
@on: Directive
The primary template syntax for event binding is @on:event:Handler:
<button @on:click:Save>Save</button>
<form @on:submit.prevent:HandleForm>...</form>
composition.New reads the handler name, finds the corresponding method on the struct, and wires the DOM event.
Event Modifiers
Modifiers adjust how listeners behave. Append them after the event name, separated by dots:
<form @on:submit.prevent.once:HandleForm>
| Modifier | Description |
|---|---|
.stop |
Calls event.stopPropagation(), prevents bubbling. |
.prevent |
Calls event.preventDefault(), stops browser default action. |
.once |
Removes the listener after the first invocation. |
Examples:
<button @on:click.stop.prevent:Save>Save</button>
<button @on:click.once:Load>Load once</button>
CamelCase Dataset
When rfw registers event handlers, it creates DOM event listeners that reference handlers by name via data-rfw-* attributes. Handler names use CamelCase, no kebab-case conversion:
<button @on:click:handleSubmit>Submit</button>
The handler method must be func (c *T) handleSubmit(), matching the exact name used in the template.
Full Example
package main
import (
"github.com/rfwlab/rfw/v2/composition"
t "github.com/rfwlab/rfw/v2/types"
)
type Counter struct {
composition.Component
Count *t.Int
}
//go:embed templates/counter.rtml
var templates embed.FS
func init() {
composition.RegisterFS(&templates)
}
func (c *Counter) Increment() {
c.Count.Set(c.Count.Get() + 1)
}
func (c *Counter) Decrement() {
c.Count.Set(c.Count.Get() - 1)
}
func (c *Counter) Reset() {
c.Count.Set(0)
}
<div>
<p>Count: @signal:Count</p>
<button @on:click:Increment>+</button>
<button @on:click:Decrement>-</button>
<button @on:click:Reset>Reset</button>
</div>
Each exported no-arg method (Increment, Decrement, Reset) is auto-discovered and available by name in the template.
Registering from Go
For dynamically created elements or low-level control, use the events package directly:
import "github.com/rfwlab/rfw/v2/events"
stop := events.OnClick(target, func(evt js.Value) {
// handle click
})
defer stop()
This bypasses the composition system entirely, use it only when you need programmatic control outside the template.
Summary
- Auto-discovered: exported no-arg methods on the struct are handlers.
- Type-based: signal fields (
*t.Int,*t.String, etc.) are auto-wired by their type.
- Template:
@on:event:Handlerto bind DOM events.
- Modifiers:
.stop,.prevent,.oncefor finer control.
- Go API:
events.On*for low-level programmatic listeners.