Handlers
Handlers are server-side functions that process client-side events. They are a core part of Weave's reactivity model.
How Handlers Work
-
When you define a handler using the
weave/handler
macro, Weave:- Generates a unique route path based on code structure and captured variables. A hash is calculated for each handler, handlers with the same hash share the same unique route
- Registers the handler function with that route
- Returns client-side Datastar expression that will invoke this route when triggered
-
When a client-side event occurs (like a button click):
- The browser sends a request to the unique route
- Weave executes your handler function on the server
- Your handler can update the DOM, execute scripts, etc.
Handler Syntax
Variable Capture
Any variables accessed within the handler body must be explicitly captured in the first argument vector. This is required for proper caching and ensures handlers work correctly with closures:
(let [user-name "John"
counter (atom 0)]
(weave/handler [user-name counter]
(weave/push-html! [:div "Hello " user-name "! Count: " @counter])))
Handler Options
Options are provided as metadata (optional):
:auth-required?
- Whether authentication is required (defaults to the value of*secure-handlers*
):type
- Request content type (use:form
for form submissions):selector
- CSS selector for the form to submit (e.g."#myform"
)
Example
{:data-on-click
(weave/handler []
;; This code runs on the server when the button is clicked
(weave/push-html! [:div#message "Button clicked!"]))}
Example with Variables
(let [message "Hello from server!"]
{:data-on-click
(weave/handler [message]
(weave/push-html! [:div#message message]))})
When this handler is registered, Weave:
- Creates a unique route based on the handler code and captured variables.
- Sets up a POST endpoint for that route
- Returns client-side code that will POST to that route when the click event occurs
Handlers with Signals
Signals provide a powerful alternative to variable capture for
managing dynamic state. Instead of capturing variables in closures,
you can store state as signals in the browser and access them via
weave/*signals*
.
Basic Signal Example
(defn click-count-view []
[::c/view#app
[::c/center-hv
[::c/card
[:div.text-center.text-6xl.font-bold.mb-6.text-blue-600
{:data-signals-click-count "0"
:data-text "$click_count"}]
[::c/button
{:size :xl
:variant :primary
:data-on-click (weave/handler []
(let [current-count (or (:click-count weave/*signals*) 0)]
(weave/push-signal! {:click-count (inc current-count)})))}
"Increment Count"]]]])
In this example:
data-signals-click-count="0"
initializes the signal with value 0data-text="$click_count"
displays the signal value reactively- The handler reads the current value from
weave/*signals*
and updates it withpush-signal!
Why Signals Are Better Than Closures for Some Use Cases
Problem: Route Explosion with Closures
When using variable capture, each unique combination of captured variables creates a separate route. This becomes problematic with dynamic data like table rows:
;; BAD: Creates separate handler for each row × action combination
(defn user-table-bad [users]
[:table
(for [user users]
[:tr
[:td (:name user)]
[:td
[::c/button
{:data-on-click (weave/handler [user] ; Captures user - creates unique route!
(delete-user! (:id user))
(weave/push-html! (user-table-bad (get-updated-users))))}
"Delete"]
[::c/button
{:data-on-click (weave/handler [user] ; Another unique route per user!
(promote-user! (:id user))
(weave/push-html! (user-table-bad (get-updated-users))))}
"Promote"]]])])
;; With 100 users × 2 actions = 200 different routes registered!
Solution: Shared Handlers with Signals
;; GOOD: Only 2 handlers total, regardless of number of users
(defn user-table-good [users]
[:table
(for [user users]
[:tr
[:td (:name user)]
[:td
[::c/button
{:data-signals-user-id (:id user) ; Store user ID as signal
:data-on-click (weave/handler [] ; No variable capture!
(let [user-id (:user-id weave/*signals*)]
(delete-user! user-id)
(weave/push-html! (user-table-good (get-updated-users)))))}
"Delete"]
[::c/button
{:data-signals-user-id (:id user) ; Same user ID signal
:data-on-click (weave/handler [] ; Same handler pattern
(let [user-id (:user-id weave/*signals*)]
(promote-user! user-id)
(weave/push-html! (user-table-good (get-updated-users)))))}
"Promote"]]])])
;; Only 2 handlers registered total - shared across all rows!