-
-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathhandler.go
307 lines (255 loc) · 8.83 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
package htmx
import (
"context"
"encoding/json"
"html/template"
"net/http"
)
type (
Handler struct {
log Logger
w http.ResponseWriter
r *http.Request
request HxRequestHeader
response *HxResponseHeader
}
)
const (
// StatusStopPolling is the status code that will stop htmx from polling
StatusStopPolling = 286
)
// IsHxRequest returns true if the request is a htmx request.
func (h *Handler) IsHxRequest() bool {
return h.request.HxRequest
}
// IsHxBoosted returns true if the request is a htmx request and the request is boosted
func (h *Handler) IsHxBoosted() bool {
return h.request.HxBoosted
}
// IsHxHistoryRestoreRequest returns true if the request is a htmx request and the request is a history restore request
func (h *Handler) IsHxHistoryRestoreRequest() bool {
return h.request.HxHistoryRestoreRequest
}
// RenderPartial returns true if the request is an HTMX request that is either boosted or a standard request,
// provided it is not a history restore request.
func (h *Handler) RenderPartial() bool {
return (h.request.HxRequest || h.request.HxBoosted) && !h.request.HxHistoryRestoreRequest
}
// Write writes the data to the connection as part of an HTTP reply.
func (h *Handler) Write(data []byte) (n int, err error) {
return h.w.Write(data)
}
// WriteHTML is a helper that writes HTML data to the connection.
func (h *Handler) WriteHTML(html template.HTML) (n int, err error) {
return h.Write([]byte(html))
}
// WriteString is a helper that writes string data to the connection.
func (h *Handler) WriteString(s string) (n int, err error) {
return h.Write([]byte(s))
}
// WriteJSON is a helper that writes json data to the connection.
func (h *Handler) WriteJSON(data any) (n int, err error) {
payload, err := json.Marshal(data)
if err != nil {
return 0, err
}
return h.Write(payload)
}
// JustWrite writes the data to the connection as part of an HTTP reply.
func (h *Handler) JustWrite(data []byte) {
_, err := h.Write(data)
if err != nil {
h.log.Warn(err.Error())
}
}
// JustWriteHTML is a helper that writes HTML data to the connection.
func (h *Handler) JustWriteHTML(html template.HTML) {
_, err := h.WriteHTML(html)
if err != nil {
h.log.Warn(err.Error())
}
}
// JustWriteString is a helper that writes string data to the connection.
func (h *Handler) JustWriteString(s string) {
_, err := h.WriteString(s)
if err != nil {
h.log.Warn(err.Error())
}
}
// JustWriteJSON is a helper that writes json data to the connection.
func (h *Handler) JustWriteJSON(data any) {
_, err := h.WriteJSON(data)
if err != nil {
h.log.Warn(err.Error())
}
}
// MustWrite writes the data to the connection as part of an HTTP reply.
func (h *Handler) MustWrite(data []byte) {
_, err := h.Write(data)
if err != nil {
panic(err)
}
}
// MustWriteHTML is a helper that writes HTML data to the connection.
func (h *Handler) MustWriteHTML(html template.HTML) {
_, err := h.WriteHTML(html)
if err != nil {
panic(err)
}
}
// MustWriteString is a helper that writes string data to the connection.
func (h *Handler) MustWriteString(s string) {
_, err := h.WriteString(s)
if err != nil {
panic(err)
}
}
// MustWriteJSON is a helper that writes json data to the connection.
func (h *Handler) MustWriteJSON(data any) {
_, err := h.WriteJSON(data)
if err != nil {
panic(err)
}
}
// WriteHeader sets the HTTP response header with the provided status code.
func (h *Handler) WriteHeader(code int) {
h.w.WriteHeader(code)
}
// StopPolling sets the response status to 286, which will stop htmx from polling
func (h *Handler) StopPolling() {
h.WriteHeader(StatusStopPolling)
}
// Header returns the header map that will be sent by WriteHeader
func (h *Handler) Header() http.Header {
return h.w.Header()
}
type LocationInput struct {
Source string `json:"source"` // source - the source element of the request
Event string `json:"event"` //event - an event that "triggered" the request
Handler string `json:"handler"` //handler - a callback that will handle the response HTML
Target string `json:"target"` //target - the target to swap the response into
Swap string `json:"swap"` //swap - how the response will be swapped in relative to the target
Values map[string]interface{} `json:"values"` //values - values to submit with the request
Header map[string]interface{} `json:"headers"` //headers - headers to submit with the request
}
// Location can be used to trigger a client side redirection without reloading the whole page
// https://htmx.org/headers/hx-location/
func (h *Handler) Location(li *LocationInput) error {
payload, err := json.Marshal(li)
if err != nil {
return err
}
h.response.Set(HXLocation, string(payload))
return nil
}
// PushURL pushes a new url into the history stack.
// https://htmx.org/headers/hx-push-url/
func (h *Handler) PushURL(val string) {
h.response.Set(HXPushUrl, val)
}
// Redirect can be used to do a client-side redirect to a new location
func (h *Handler) Redirect(val string) {
h.response.Set(HXRedirect, val)
}
// Refresh if set to true the client side will do a full refresh of the page
func (h *Handler) Refresh(val bool) {
h.response.Set(HXRefresh, HxBoolToStr(val))
}
// ReplaceURL allows you to replace the current URL in the browser location history.
// https://htmx.org/headers/hx-replace-url/
func (h *Handler) ReplaceURL(val string) {
h.response.Set(HXReplaceUrl, val)
}
// ReSwap allows you to specify how the response will be swapped. See hx-swap for possible values
// https://htmx.org/attributes/hx-swap/
func (h *Handler) ReSwap(val string) {
h.response.Set(HXReswap, val)
}
// ReSwapWithObject allows you to specify how the response will be swapped. See hx-swap for possible values
// https://htmx.org/attributes/hx-swap/
func (h *Handler) ReSwapWithObject(s *Swap) {
h.ReSwap(s.String())
}
// ReTarget a CSS selector that updates the target of the content update to a different element on the page
func (h *Handler) ReTarget(val string) {
h.response.Set(HXRetarget, val)
}
// ReSelect a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing hx-select on the triggering element
func (h *Handler) ReSelect(val string) {
h.response.Set(HXReselect, val)
}
// Trigger triggers events as soon as the response is received.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) Trigger(val string) {
h.response.Set(HXTrigger, val)
}
// TriggerWithObject triggers events as soon as the response is received.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) TriggerWithObject(t *Trigger) {
h.Trigger(t.String())
}
// TriggerAfterSettle trigger events after the settling step.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) TriggerAfterSettle(val string) {
h.response.Set(HXTriggerAfterSettle, val)
}
// TriggerAfterSettleWithObject trigger events after the settling step.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) TriggerAfterSettleWithObject(t *Trigger) {
h.TriggerAfterSettle(t.String())
}
// TriggerAfterSwap trigger events after the swap step.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) TriggerAfterSwap(val string) {
h.response.Set(HXTriggerAfterSwap, val)
}
// TriggerAfterSwapWithObject trigger events after the swap step.
// https://htmx.org/headers/hx-trigger/
func (h *Handler) TriggerAfterSwapWithObject(t *Trigger) {
h.TriggerAfterSwap(t.String())
}
// Request returns the HxHeaders from the request
func (h *Handler) Request() HxRequestHeader {
return h.request
}
// ResponseHeader returns the value of the response header
func (h *Handler) ResponseHeader(header HxResponseKey) string {
return h.response.Get(header)
}
// Render renders the given renderer with the given context and writes the output to the response writer
func (h *Handler) Render(ctx context.Context, r RenderableComponent) (int, error) {
r.SetURL(h.r.URL)
output, err := r.Render(ctx)
if err != nil {
return 0, err
}
// If it's a partial render, return the output directly
if h.RenderPartial() {
return h.WriteHTML(output)
}
// Recursively wrap the output if the component is wrapped
output, err = h.wrapOutput(ctx, r, output)
if err != nil {
return 0, err
}
// Write the final output
return h.WriteHTML(output)
}
// wrapOutput recursively wraps the output in its parent components
func (h *Handler) wrapOutput(ctx context.Context, r RenderableComponent, output template.HTML) (template.HTML, error) {
if !r.isWrapped() {
// Base case: no more wrapping
return output, nil
}
parent := r.wrapper()
parent.SetURL(h.r.URL)
parent.injectData(r.data())
parent.addPartial(r.target(), output)
// Render the parent component
parentOutput, err := parent.Render(ctx)
if err != nil {
return "", err
}
// Recursively wrap the parent output if the parent is also wrapped
return h.wrapOutput(ctx, parent, parentOutput)
}