@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hx-trigger
|
|
3
|
+
title: HxTrigger
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`HxTrigger` represents the `hx-trigger` attribute, declaring which event fires an HTMX request. It combines an event name with optional modifiers that refine timing (delay, throttle), state (once, changed), source (from), queue strategy, and filtering. `HxTriggerSet` composes multiple triggers in a comma-separated list for complex event handling.
|
|
7
|
+
|
|
8
|
+
Start from a predefined trigger like `HxTrigger.click` or construct one with `HxTrigger("eventName")`. Add modifiers by chaining methods. Modifiers within the same group (e.g., two `delay` calls) replace earlier values; unrelated modifiers accumulate. Here are the core patterns:
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
import zio.http.htmx._
|
|
12
|
+
import scala.concurrent.duration._
|
|
13
|
+
|
|
14
|
+
// Predefined trigger
|
|
15
|
+
HxTrigger.click
|
|
16
|
+
|
|
17
|
+
// Custom event name
|
|
18
|
+
HxTrigger("myEvent")
|
|
19
|
+
|
|
20
|
+
// With modifiers
|
|
21
|
+
HxTrigger.input.delay(500.millis).changed
|
|
22
|
+
|
|
23
|
+
// Multiple triggers
|
|
24
|
+
HxTrigger(HxTrigger.click, HxTrigger.load)
|
|
25
|
+
|
|
26
|
+
// Polling
|
|
27
|
+
HxTrigger.every(2.seconds)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Predefined Triggers
|
|
31
|
+
|
|
32
|
+
Common HTMX events are available as case objects:
|
|
33
|
+
|
|
34
|
+
- `HxTrigger.click` — Fires on element click.
|
|
35
|
+
- `HxTrigger.submit` — Fires on form submission.
|
|
36
|
+
- `HxTrigger.load` — Fires when the element is first loaded and added to the DOM.
|
|
37
|
+
- `HxTrigger.change` — Fires when an input/select/textarea value changes.
|
|
38
|
+
- `HxTrigger.input` — Fires on each keystroke in an input/textarea.
|
|
39
|
+
- `HxTrigger.revealed` — Fires when the element scrolls into view (requires Intersection Observer).
|
|
40
|
+
- `HxTrigger.intersect` — Fires when the element enters the intersection observer threshold.
|
|
41
|
+
|
|
42
|
+
All predefined triggers are ready for modifier chaining:
|
|
43
|
+
|
|
44
|
+
```scala
|
|
45
|
+
import zio.blocks.html._
|
|
46
|
+
import zio.http.htmx._
|
|
47
|
+
|
|
48
|
+
input(hxPost := "/search", hxTrigger := HxTrigger.input)
|
|
49
|
+
form(hxPost := "/submit", hxTrigger := HxTrigger.submit)
|
|
50
|
+
div(hxGet := "/status", hxTrigger := HxTrigger.load)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Custom Events
|
|
54
|
+
|
|
55
|
+
Construct a trigger from an arbitrary event name using `HxTrigger("eventName")`:
|
|
56
|
+
|
|
57
|
+
```scala
|
|
58
|
+
import zio.blocks.html._
|
|
59
|
+
import zio.http.htmx._
|
|
60
|
+
|
|
61
|
+
// Custom event (must be fired by JavaScript)
|
|
62
|
+
div(hxPost := "/api/action", hxTrigger := HxTrigger("custom-event"))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The module validates that the event name is non-empty but otherwise accepts any string. Custom events must be fired by your JavaScript code or other HTMX event sources.
|
|
66
|
+
|
|
67
|
+
## Timing Modifiers
|
|
68
|
+
|
|
69
|
+
Control when the request fires relative to the triggering event:
|
|
70
|
+
|
|
71
|
+
**`delay(duration: FiniteDuration)`** adds a `delay:` modifier, waiting before firing the request. Useful for debouncing high-frequency events like typing:
|
|
72
|
+
|
|
73
|
+
```scala
|
|
74
|
+
import zio.blocks.html._
|
|
75
|
+
import zio.http.htmx._
|
|
76
|
+
import scala.concurrent.duration._
|
|
77
|
+
|
|
78
|
+
// Wait 500ms after typing stops before firing
|
|
79
|
+
input(
|
|
80
|
+
hxPost := "/search",
|
|
81
|
+
hxTrigger := HxTrigger.input.delay(500.millis)
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**`throttle(duration: FiniteDuration)`** adds a `throttle:` modifier, firing at most once per duration. Useful for rate-limiting requests on frequent events:
|
|
86
|
+
|
|
87
|
+
```scala
|
|
88
|
+
import zio.blocks.html._
|
|
89
|
+
import zio.http.htmx._
|
|
90
|
+
import scala.concurrent.duration._
|
|
91
|
+
|
|
92
|
+
// Fire at most once per second
|
|
93
|
+
input(
|
|
94
|
+
hxPost := "/search",
|
|
95
|
+
hxTrigger := HxTrigger.input.throttle(1.second)
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Both modifiers accept any `FiniteDuration`. Calling `delay()` or `throttle()` twice replaces the earlier value:
|
|
100
|
+
|
|
101
|
+
```scala
|
|
102
|
+
import zio.http.htmx._
|
|
103
|
+
import scala.concurrent.duration._
|
|
104
|
+
|
|
105
|
+
val trigger1 = HxTrigger.input.delay(1.second)
|
|
106
|
+
val trigger2 = trigger1.delay(500.millis) // replaces 1.second
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## State Modifiers
|
|
110
|
+
|
|
111
|
+
Refine which events fire the request based on element state:
|
|
112
|
+
|
|
113
|
+
**`once: HxTrigger`** adds the `once` modifier, firing the request exactly once and then disabling the trigger:
|
|
114
|
+
|
|
115
|
+
```scala
|
|
116
|
+
import zio.blocks.html._
|
|
117
|
+
import zio.http.htmx._
|
|
118
|
+
|
|
119
|
+
// Load data only on first page visit
|
|
120
|
+
div(hxGet := "/initial-data", hxTrigger := HxTrigger.load.once)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**`changed: HxTrigger`** adds the `changed` modifier, firing only when the element's value actually changes (not on every event):
|
|
124
|
+
|
|
125
|
+
```scala
|
|
126
|
+
import zio.blocks.html._
|
|
127
|
+
import zio.http.htmx._
|
|
128
|
+
import scala.concurrent.duration._
|
|
129
|
+
|
|
130
|
+
// Fire only if the value is different from the last value
|
|
131
|
+
input(
|
|
132
|
+
hxPost := "/validate",
|
|
133
|
+
hxTrigger := HxTrigger.input.changed
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Combine these with timing modifiers for fine-grained control:
|
|
138
|
+
|
|
139
|
+
```scala
|
|
140
|
+
import zio.http.htmx._
|
|
141
|
+
import scala.concurrent.duration._
|
|
142
|
+
|
|
143
|
+
HxTrigger.input.delay(500.millis).changed
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Source & Target Control
|
|
147
|
+
|
|
148
|
+
Direct where the request originates and how it affects other elements:
|
|
149
|
+
|
|
150
|
+
**`from(selector: String)` or `from(target: HxTarget)`** adds a `from:` modifier, listening for the trigger on a different element. The request still fires on the original element, but it listens for the trigger event on the specified source:
|
|
151
|
+
|
|
152
|
+
```scala
|
|
153
|
+
import zio.blocks.html._
|
|
154
|
+
import zio.http.htmx._
|
|
155
|
+
|
|
156
|
+
// Button fires a request, but listens for clicks on the input
|
|
157
|
+
input(id := "search")
|
|
158
|
+
button(
|
|
159
|
+
"Go",
|
|
160
|
+
hxPost := "/search",
|
|
161
|
+
hxTrigger := HxTrigger.click.from("#search")
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**`target(selector: String)`** adds a `target:` modifier, restricting the trigger to events from specific descendant elements. Useful for event delegation:
|
|
166
|
+
|
|
167
|
+
```scala
|
|
168
|
+
import zio.blocks.html._
|
|
169
|
+
import zio.http.htmx._
|
|
170
|
+
|
|
171
|
+
// Only fire on clicks within .clickable items
|
|
172
|
+
div(
|
|
173
|
+
hxPost := "/item-selected",
|
|
174
|
+
hxTrigger := HxTrigger.click.target(".clickable"),
|
|
175
|
+
div(className := "clickable", "Item 1"),
|
|
176
|
+
div(className := "clickable", "Item 2")
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Queue Strategy
|
|
181
|
+
|
|
182
|
+
Control how requests queue when multiple triggers fire in quick succession:
|
|
183
|
+
|
|
184
|
+
**`queue(strategy: HxTrigger.QueueStrategy)`** adds a `queue:` modifier. Valid strategies are:
|
|
185
|
+
|
|
186
|
+
- `HxTrigger.QueueStrategy.First` — Queue the first request only; discard later ones until it completes.
|
|
187
|
+
- `HxTrigger.QueueStrategy.Last` — Keep the most recent request; discard earlier queued ones.
|
|
188
|
+
- `HxTrigger.QueueStrategy.All` — Queue all requests and fire them in order (default HTMX behavior).
|
|
189
|
+
- `HxTrigger.QueueStrategy.None` — Abort pending requests and fire the new one immediately.
|
|
190
|
+
|
|
191
|
+
Here are examples using different queue strategies:
|
|
192
|
+
|
|
193
|
+
```scala
|
|
194
|
+
import zio.blocks.html._
|
|
195
|
+
import zio.http.htmx._
|
|
196
|
+
import scala.concurrent.duration._
|
|
197
|
+
|
|
198
|
+
// Only fire the most recent request
|
|
199
|
+
input(
|
|
200
|
+
hxPost := "/search",
|
|
201
|
+
hxTrigger := HxTrigger.input.delay(500.millis).queue(HxTrigger.QueueStrategy.Last)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// Fire only the first request in a burst
|
|
205
|
+
button(
|
|
206
|
+
hxPost := "/submit",
|
|
207
|
+
hxTrigger := HxTrigger.click.queue(HxTrigger.QueueStrategy.First)
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Intersection Observer
|
|
212
|
+
|
|
213
|
+
**`threshold(value: Double)`** adds a `threshold:` modifier for Intersection Observer-based triggers (e.g., `HxTrigger.intersect`). The value is a fraction between 0.0 and 1.0:
|
|
214
|
+
|
|
215
|
+
```scala
|
|
216
|
+
import zio.blocks.html._
|
|
217
|
+
import zio.http.htmx._
|
|
218
|
+
|
|
219
|
+
// Load when 50% of the element is visible
|
|
220
|
+
div(
|
|
221
|
+
hxGet := "/load-more",
|
|
222
|
+
hxTrigger := HxTrigger.intersect.threshold(0.5)
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**`root(selector: String)`** adds a `root:` modifier, specifying the container for Intersection Observer calculations:
|
|
227
|
+
|
|
228
|
+
```scala
|
|
229
|
+
import zio.blocks.html._
|
|
230
|
+
import zio.http.htmx._
|
|
231
|
+
|
|
232
|
+
// Observe intersection within a scrollable container
|
|
233
|
+
div(
|
|
234
|
+
id := "scrollable-list",
|
|
235
|
+
div(
|
|
236
|
+
hxGet := "/item",
|
|
237
|
+
hxTrigger := HxTrigger.intersect.root(".scrollable-list")
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## JavaScript Filtering
|
|
243
|
+
|
|
244
|
+
**`filter(expression: Js)`** adds a JavaScript expression that must return true for the request to fire. The `Js` type is intentionally raw—do not build it from unsanitized user input:
|
|
245
|
+
|
|
246
|
+
```scala
|
|
247
|
+
import zio.blocks.html._
|
|
248
|
+
import zio.http.htmx._
|
|
249
|
+
|
|
250
|
+
// Only POST if input has 3+ characters
|
|
251
|
+
input(
|
|
252
|
+
hxPost := "/search",
|
|
253
|
+
hxTrigger := HxTrigger.input.filter(Js("event.target.value.length > 2"))
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The filter expression has access to the native JavaScript `event` object and the element's JavaScript context.
|
|
258
|
+
|
|
259
|
+
## Request Consumption
|
|
260
|
+
|
|
261
|
+
**`consume: HxTrigger`** adds the `consume` modifier, preventing the triggering event from bubbling to parent handlers:
|
|
262
|
+
|
|
263
|
+
```scala
|
|
264
|
+
import zio.blocks.html._
|
|
265
|
+
import zio.http.htmx._
|
|
266
|
+
|
|
267
|
+
// Click on button fires request and stops event propagation
|
|
268
|
+
button(
|
|
269
|
+
hxPost := "/action",
|
|
270
|
+
hxTrigger := HxTrigger.click.consume
|
|
271
|
+
)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Polling
|
|
275
|
+
|
|
276
|
+
**`HxTrigger.every(duration: FiniteDuration)`** creates a special polling trigger that fires every N milliseconds/seconds:
|
|
277
|
+
|
|
278
|
+
```scala
|
|
279
|
+
import zio.blocks.html._
|
|
280
|
+
import zio.http.htmx._
|
|
281
|
+
import scala.concurrent.duration._
|
|
282
|
+
|
|
283
|
+
// Poll every 3 seconds
|
|
284
|
+
div(
|
|
285
|
+
hxGet := "/status",
|
|
286
|
+
hxTrigger := HxTrigger.every(3.seconds)
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The duration can be any `FiniteDuration`. Polling triggers can be paused and resumed by HTMX via the `htmx:beforeRequest` and `htmx:afterRequest` events.
|
|
291
|
+
|
|
292
|
+
## Rendering & Parsing
|
|
293
|
+
|
|
294
|
+
**`render: String`** produces the literal `hx-trigger` attribute value string:
|
|
295
|
+
|
|
296
|
+
```scala
|
|
297
|
+
import zio.http.htmx._
|
|
298
|
+
import scala.concurrent.duration._
|
|
299
|
+
|
|
300
|
+
val trigger = HxTrigger.input.delay(500.millis).changed
|
|
301
|
+
trigger.render // "input delay:500ms changed"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Multiple Triggers (HxTriggerSet)
|
|
305
|
+
|
|
306
|
+
Use the `HxTrigger` constructor to combine multiple triggers in a comma-separated list:
|
|
307
|
+
|
|
308
|
+
```scala
|
|
309
|
+
import zio.http.htmx._
|
|
310
|
+
import scala.concurrent.duration._
|
|
311
|
+
|
|
312
|
+
// Fire on click or every 5 seconds
|
|
313
|
+
val triggers = HxTrigger(
|
|
314
|
+
HxTrigger.click,
|
|
315
|
+
HxTrigger.every(5.seconds)
|
|
316
|
+
)
|
|
317
|
+
triggers.render // "click, every 5s"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Assign `HxTriggerSet` directly to the `hxTrigger` attribute:
|
|
321
|
+
|
|
322
|
+
```scala
|
|
323
|
+
import zio.blocks.html._
|
|
324
|
+
import zio.http.htmx._
|
|
325
|
+
import scala.concurrent.duration._
|
|
326
|
+
|
|
327
|
+
div(
|
|
328
|
+
hxGet := "/update",
|
|
329
|
+
hxTrigger := HxTrigger(
|
|
330
|
+
HxTrigger.click,
|
|
331
|
+
HxTrigger.load,
|
|
332
|
+
HxTrigger.every(10.seconds)
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Common Patterns
|
|
338
|
+
|
|
339
|
+
`HxTrigger` enables sophisticated event handling through modifiers. Here are practical usage patterns:
|
|
340
|
+
|
|
341
|
+
### Debounced Search
|
|
342
|
+
|
|
343
|
+
Delay firing to let the user finish typing, and only fire if the value changed:
|
|
344
|
+
|
|
345
|
+
```scala
|
|
346
|
+
import zio.blocks.html._
|
|
347
|
+
import zio.http.htmx._
|
|
348
|
+
import scala.concurrent.duration._
|
|
349
|
+
|
|
350
|
+
input(
|
|
351
|
+
placeholder := "Search...",
|
|
352
|
+
hxPost := "/api/search",
|
|
353
|
+
hxTrigger := HxTrigger.input.delay(500.millis).changed
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Rate-Limited Live Updates
|
|
358
|
+
|
|
359
|
+
Throttle rapid events to avoid overwhelming the server:
|
|
360
|
+
|
|
361
|
+
```scala
|
|
362
|
+
import zio.blocks.html._
|
|
363
|
+
import zio.http.htmx._
|
|
364
|
+
import scala.concurrent.duration._
|
|
365
|
+
|
|
366
|
+
div(
|
|
367
|
+
hxPost := "/analytics",
|
|
368
|
+
hxTrigger := HxTrigger.input.throttle(1.second)
|
|
369
|
+
)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Load Once
|
|
373
|
+
|
|
374
|
+
Fire the request exactly once when the page loads:
|
|
375
|
+
|
|
376
|
+
```scala
|
|
377
|
+
import zio.blocks.html._
|
|
378
|
+
import zio.http.htmx._
|
|
379
|
+
|
|
380
|
+
div(
|
|
381
|
+
hxGet := "/welcome-message",
|
|
382
|
+
hxTrigger := HxTrigger.load.once
|
|
383
|
+
)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Lazy Loading with Intersection Observer
|
|
387
|
+
|
|
388
|
+
Load content when it scrolls into view:
|
|
389
|
+
|
|
390
|
+
```scala
|
|
391
|
+
import zio.blocks.html._
|
|
392
|
+
import zio.http.htmx._
|
|
393
|
+
|
|
394
|
+
// Load when 10% of the image is visible
|
|
395
|
+
img(
|
|
396
|
+
src := "/placeholder.jpg",
|
|
397
|
+
hxGet := "/lazy-image",
|
|
398
|
+
hxSwap := HxSwap.OuterHTML,
|
|
399
|
+
hxTrigger := HxTrigger.intersect.threshold(0.1)
|
|
400
|
+
)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Dual Triggers: Click or Automatic
|
|
404
|
+
|
|
405
|
+
Fire on click or every 30 seconds:
|
|
406
|
+
|
|
407
|
+
```scala
|
|
408
|
+
import zio.blocks.html._
|
|
409
|
+
import zio.http.htmx._
|
|
410
|
+
import scala.concurrent.duration._
|
|
411
|
+
|
|
412
|
+
button(
|
|
413
|
+
hxPost := "/refresh",
|
|
414
|
+
hxTrigger := HxTrigger(
|
|
415
|
+
HxTrigger.click,
|
|
416
|
+
HxTrigger.every(30.seconds)
|
|
417
|
+
),
|
|
418
|
+
"Refresh Now"
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Event Delegation
|
|
423
|
+
|
|
424
|
+
Parent element listens for clicks on child items:
|
|
425
|
+
|
|
426
|
+
```scala
|
|
427
|
+
import zio.blocks.html._
|
|
428
|
+
import zio.http.htmx._
|
|
429
|
+
|
|
430
|
+
ul(
|
|
431
|
+
hxPost := "/item-selected",
|
|
432
|
+
hxTrigger := HxTrigger.click.target("li"),
|
|
433
|
+
li("Item 1"),
|
|
434
|
+
li("Item 2"),
|
|
435
|
+
li("Item 3")
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Integration with Other Module Types
|
|
440
|
+
|
|
441
|
+
`HxTrigger` pairs with `HxTarget` (where to swap) and `HxSwap` (how to swap):
|
|
442
|
+
|
|
443
|
+
```scala
|
|
444
|
+
import zio.blocks.html._
|
|
445
|
+
import zio.http.htmx._
|
|
446
|
+
import scala.concurrent.duration._
|
|
447
|
+
|
|
448
|
+
button(
|
|
449
|
+
hxPost := "/api/action",
|
|
450
|
+
hxTrigger := HxTrigger.click.delay(100.millis),
|
|
451
|
+
hxTarget := HxTarget.closest("form"),
|
|
452
|
+
hxSwap := HxSwap.InnerHTML.settle(250.millis),
|
|
453
|
+
"Submit"
|
|
454
|
+
)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
The `ToHtmxValue` type class handles both `HxTrigger` (single trigger) and `HxTriggerSet` (multiple triggers), so you can assign either directly to the `hxTrigger` attribute key.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hx-url-update
|
|
3
|
+
title: HxUrlUpdate
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`HxUrlUpdate` represents the `hx-push-url` and `hx-replace-url` attributes, controlling whether and how the browser's URL bar updates after an HTMX request. You can enable/disable URL updates as booleans or specify a custom URL to push/replace.
|
|
7
|
+
|
|
8
|
+
Use `HxUrlUpdate.Enabled` or `HxUrlUpdate(true)` to update the URL with the response URL, or pass a custom URL string/Path/URL to update to a different address. Use `HxUrlUpdate.Disabled` or `HxUrlUpdate(false)` to prevent updates. Here are the core patterns:
|
|
9
|
+
|
|
10
|
+
```scala
|
|
11
|
+
import zio.http.htmx._
|
|
12
|
+
import zio.http.Path
|
|
13
|
+
|
|
14
|
+
// Enable URL update (default)
|
|
15
|
+
HxUrlUpdate.Enabled
|
|
16
|
+
|
|
17
|
+
// Disable URL update
|
|
18
|
+
HxUrlUpdate.Disabled
|
|
19
|
+
|
|
20
|
+
// Custom URL string
|
|
21
|
+
HxUrlUpdate("/custom-url")
|
|
22
|
+
|
|
23
|
+
// Custom URL using Path
|
|
24
|
+
HxUrlUpdate(Path("/custom-url"))
|
|
25
|
+
|
|
26
|
+
// Boolean shorthand
|
|
27
|
+
HxUrlUpdate(true)
|
|
28
|
+
HxUrlUpdate(false)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Strategies
|
|
32
|
+
|
|
33
|
+
**`HxUrlUpdate.Enabled`** updates the browser URL with the response URL. This is the default behavior when `hxPushUrl` or `hxReplaceUrl` are set:
|
|
34
|
+
|
|
35
|
+
```scala
|
|
36
|
+
import zio.blocks.html._
|
|
37
|
+
import zio.http.htmx._
|
|
38
|
+
|
|
39
|
+
a(
|
|
40
|
+
href := "/page-2",
|
|
41
|
+
hxPushUrl := HxUrlUpdate.Enabled,
|
|
42
|
+
"Page 2"
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**`HxUrlUpdate.Disabled`** prevents the URL from updating, even though the page content changes:
|
|
47
|
+
|
|
48
|
+
```scala
|
|
49
|
+
import zio.blocks.html._
|
|
50
|
+
import zio.http.htmx._
|
|
51
|
+
|
|
52
|
+
form(
|
|
53
|
+
hxPost := "/search",
|
|
54
|
+
hxPushUrl := HxUrlUpdate.Disabled,
|
|
55
|
+
"Search (URL won't change)"
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**`HxUrlUpdate(customUrl: String)`** updates the URL to a specific custom address instead of the response URL:
|
|
60
|
+
|
|
61
|
+
```scala
|
|
62
|
+
import zio.blocks.html._
|
|
63
|
+
import zio.http.htmx._
|
|
64
|
+
|
|
65
|
+
form(
|
|
66
|
+
hxPost := "/api/search-internal",
|
|
67
|
+
hxPushUrl := HxUrlUpdate("/results"),
|
|
68
|
+
"Search (URL becomes /results)"
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**`HxUrlUpdate(path: Path)` or `HxUrlUpdate(url: URL)`** accepts typed paths and URLs, which are then encoded to strings:
|
|
73
|
+
|
|
74
|
+
```scala
|
|
75
|
+
import zio.blocks.html._
|
|
76
|
+
import zio.http.htmx._
|
|
77
|
+
import zio.http.Path
|
|
78
|
+
|
|
79
|
+
form(
|
|
80
|
+
hxPost := "/api/search",
|
|
81
|
+
hxPushUrl := HxUrlUpdate(Path("/search-results")),
|
|
82
|
+
button("Search")
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Boolean Constructors
|
|
87
|
+
|
|
88
|
+
Convenient overloads let you use booleans directly:
|
|
89
|
+
|
|
90
|
+
```scala
|
|
91
|
+
import zio.blocks.html._
|
|
92
|
+
import zio.http.htmx._
|
|
93
|
+
|
|
94
|
+
form(
|
|
95
|
+
hxPost := "/search",
|
|
96
|
+
hxPushUrl := HxUrlUpdate(true), // same as HxUrlUpdate.Enabled
|
|
97
|
+
button("Search")
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
button(
|
|
101
|
+
hxPost := "/action",
|
|
102
|
+
hxPushUrl := HxUrlUpdate(false), // same as HxUrlUpdate.Disabled
|
|
103
|
+
"Action (URL unchanged)"
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Rendering & Parsing
|
|
108
|
+
|
|
109
|
+
**`render: String`** produces the literal attribute value string:
|
|
110
|
+
|
|
111
|
+
```scala
|
|
112
|
+
import zio.http.htmx._
|
|
113
|
+
|
|
114
|
+
HxUrlUpdate.Enabled.render // "true"
|
|
115
|
+
HxUrlUpdate.Disabled.render // "false"
|
|
116
|
+
HxUrlUpdate("/results").render // "/results"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**`HxUrlUpdate.parse(value: String): Either[String, HxUrlUpdate]`** parses a rendered string back into a typed `HxUrlUpdate`:
|
|
120
|
+
|
|
121
|
+
```scala
|
|
122
|
+
import zio.http.htmx._
|
|
123
|
+
|
|
124
|
+
HxUrlUpdate.parse("true") // Right(HxUrlUpdate.Enabled)
|
|
125
|
+
HxUrlUpdate.parse("false") // Right(HxUrlUpdate.Disabled)
|
|
126
|
+
HxUrlUpdate.parse("/custom") // Right(HxUrlUpdate("/custom"))
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Common Patterns
|
|
130
|
+
|
|
131
|
+
URL updates enable seamless integration with browser history and bookmarking. Here are representative usage patterns:
|
|
132
|
+
|
|
133
|
+
### Progressive Enhancement with URL Updates
|
|
134
|
+
|
|
135
|
+
Keep the URL in sync with SPA-like navigation while using server-side rendering:
|
|
136
|
+
|
|
137
|
+
```scala
|
|
138
|
+
import zio.blocks.html._
|
|
139
|
+
import zio.http.htmx._
|
|
140
|
+
|
|
141
|
+
nav(
|
|
142
|
+
a(
|
|
143
|
+
href := "/users",
|
|
144
|
+
hxPushUrl := HxUrlUpdate.Enabled,
|
|
145
|
+
hxTarget := HxTarget.css("#content"),
|
|
146
|
+
"Users"
|
|
147
|
+
),
|
|
148
|
+
a(
|
|
149
|
+
href := "/settings",
|
|
150
|
+
hxPushUrl := HxUrlUpdate.Enabled,
|
|
151
|
+
hxTarget := HxTarget.css("#content"),
|
|
152
|
+
"Settings"
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### API-Only Requests Without URL Change
|
|
158
|
+
|
|
159
|
+
Make internal API calls that don't affect the URL:
|
|
160
|
+
|
|
161
|
+
```scala
|
|
162
|
+
import zio.blocks.html._
|
|
163
|
+
import zio.http.htmx._
|
|
164
|
+
|
|
165
|
+
button(
|
|
166
|
+
hxPost := "/api/save-draft",
|
|
167
|
+
hxPushUrl := HxUrlUpdate.Disabled,
|
|
168
|
+
"Save Draft"
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Redirect to a Different URL
|
|
173
|
+
|
|
174
|
+
Use an internal API endpoint but show a different user-friendly URL:
|
|
175
|
+
|
|
176
|
+
```scala
|
|
177
|
+
import zio.blocks.html._
|
|
178
|
+
import zio.http.htmx._
|
|
179
|
+
|
|
180
|
+
form(
|
|
181
|
+
hxPost := "/api/checkout-internal",
|
|
182
|
+
hxPushUrl := HxUrlUpdate("/order-confirmation"),
|
|
183
|
+
hxTarget := HxTarget.css("#main"),
|
|
184
|
+
button("Complete Order")
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Search with Query Parameter URL
|
|
189
|
+
|
|
190
|
+
Push a URL with query parameters to reflect the search state:
|
|
191
|
+
|
|
192
|
+
```scala
|
|
193
|
+
import zio.blocks.html._
|
|
194
|
+
import zio.http.htmx._
|
|
195
|
+
|
|
196
|
+
input(
|
|
197
|
+
name := "query",
|
|
198
|
+
hxPost := "/search",
|
|
199
|
+
hxPushUrl := HxUrlUpdate("/search?query=..."), // placeholder; dynamically set by server
|
|
200
|
+
placeholder := "Search..."
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Replace vs Push
|
|
205
|
+
|
|
206
|
+
Use `hxReplaceUrl` instead of `hxPushUrl` to replace the current history entry rather than adding a new one:
|
|
207
|
+
|
|
208
|
+
```scala
|
|
209
|
+
import zio.blocks.html._
|
|
210
|
+
import zio.http.htmx._
|
|
211
|
+
|
|
212
|
+
// This request replaces the current history entry
|
|
213
|
+
form(
|
|
214
|
+
hxPost := "/search",
|
|
215
|
+
hxReplaceUrl := HxUrlUpdate("/search-results"),
|
|
216
|
+
button("Search")
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Both attributes accept the same `HxUrlUpdate` type. Use `replace` for searches, filters, and state changes that shouldn't create browser history entries. Use `push` for navigation to new pages.
|
|
221
|
+
|
|
222
|
+
## Integration with Other Attributes
|
|
223
|
+
|
|
224
|
+
Combine `HxUrlUpdate` with `hxPushUrl` and `hxReplaceUrl` to control history and URL bar updates:
|
|
225
|
+
|
|
226
|
+
```scala
|
|
227
|
+
import zio.blocks.html._
|
|
228
|
+
import zio.http.htmx._
|
|
229
|
+
|
|
230
|
+
form(
|
|
231
|
+
hxPost := "/api/search",
|
|
232
|
+
hxTarget := HxTarget.css("#results"),
|
|
233
|
+
hxSwap := HxSwap.InnerHTML,
|
|
234
|
+
hxReplaceUrl := HxUrlUpdate("/search?q=example"),
|
|
235
|
+
input(placeholder := "Search...")
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The `ToHtmxValue[HxUrlUpdate]` instance renders automatically, so `HxUrlUpdate` values work seamlessly with both attribute keys.
|