-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathelement.go
More file actions
271 lines (245 loc) · 7.57 KB
/
element.go
File metadata and controls
271 lines (245 loc) · 7.57 KB
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
package jaws
import (
"bytes"
"errors"
"fmt"
"html/template"
"io"
"strings"
"sync/atomic"
"github.com/linkdata/jaws/lib/jid"
"github.com/linkdata/jaws/lib/tag"
"github.com/linkdata/jaws/lib/what"
"github.com/linkdata/jaws/lib/wire"
)
// An Element is an instance of a *Request, an UI object and a Jid.
type Element struct {
*Request // (read-only) the Request the Element belongs to
// internals
ui UI // the UI object
handlers []any // custom handlers registered, if any
jid jid.Jid // JaWS ID, unique to this Element within it's Request
deleted atomic.Bool // true if deleteElement() has been called for this Element
}
func (e *Element) String() string {
return fmt.Sprintf("Element{%T, id=%q, Tags: %v}", e.Ui(), e.Jid(), e.Request.TagsOf(e))
}
// AddHandlers adds the given handlers to the Element.
func (e *Element) AddHandlers(h ...any) {
if !e.deleted.Load() {
e.handlers = append(e.handlers, h...)
}
}
// Tag adds the given tags to the Element.
func (e *Element) Tag(tags ...any) {
if !e.deleted.Load() {
e.Request.Tag(e, tags...)
}
}
// HasTag returns true if this Element has the given tag.
func (e *Element) HasTag(tagValue any) bool {
return !e.deleted.Load() && e.Request.HasTag(e, tagValue)
}
// Jid returns the JaWS ID for this Element, unique within it's Request.
func (e *Element) Jid() jid.Jid {
return e.jid
}
// Ui returns the UI object.
func (e *Element) Ui() UI {
return e.ui
}
func (e *Element) maybeDirty(tagValue any, err error) (bool, error) {
switch err {
case nil:
e.Dirty(tagValue)
return true, nil
case ErrValueUnchanged:
return false, nil
}
return false, err
}
func (e *Element) renderDebug(w io.Writer) {
var sb strings.Builder
_, _ = fmt.Fprintf(&sb, "<!-- id=%q %T tags=[", e.Jid(), e.Ui())
if e.mu.TryRLock() {
defer e.mu.RUnlock()
for i, tagValue := range e.tagsOfLocked(e) {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(tag.TagString(tagValue))
}
} else {
sb.WriteString("n/a")
}
sb.WriteByte(']')
_, _ = w.Write([]byte(strings.ReplaceAll(sb.String(), "-->", "==>") + " -->"))
}
// JawsRender calls Ui().JawsRender() for this Element.
//
// Do not call this yourself unless it's from within another JawsRender implementation.
func (e *Element) JawsRender(w io.Writer, params []any) (err error) {
if !e.deleted.Load() {
if err = e.Ui().JawsRender(e, w, params); err == nil {
if e.Jaws.Debug {
e.renderDebug(w)
}
}
}
return
}
// JawsUpdate calls Ui().JawsUpdate() for this Element.
//
// Do not call this yourself unless it's from within another JawsUpdate implementation.
func (e *Element) JawsUpdate() {
if !e.deleted.Load() {
e.Ui().JawsUpdate(e)
}
}
func (e *Element) queue(wht what.What, data string) {
if !e.deleted.Load() {
e.Request.queue(wire.WsMsg{
Data: data,
Jid: e.jid,
What: wht,
})
}
}
// SetAttr queues sending a new attribute value
// to the browser for the Element with the given JaWS ID in this Request.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) SetAttr(attr, val string) {
e.queue(what.SAttr, attr+"\n"+val)
}
// RemoveAttr queues sending a request to remove an attribute
// to the browser for the Element with the given JaWS ID in this Request.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) RemoveAttr(attr string) {
e.queue(what.RAttr, attr)
}
// SetClass a queues sending a class
// to the browser for the Element with the given JaWS ID in this Request.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) SetClass(cls string) {
e.queue(what.SClass, cls)
}
// RemoveClass queues sending a request to remove a class
// to the browser for the Element with the given JaWS ID in this Request.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) RemoveClass(cls string) {
e.queue(what.RClass, cls)
}
// SetInner queues sending a new inner HTML content
// to the browser for the Element.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) SetInner(innerHTML template.HTML) {
e.queue(what.Inner, string(innerHTML))
}
// SetValue queues sending a new current input value in textual form
// to the browser for the Element with the given JaWS ID in this Request.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) SetValue(val string) {
e.queue(what.Value, val)
}
// Replace replaces the elements entire HTML DOM node with new HTML code.
// If the HTML code doesn't seem to contain correct HTML ID, it panics.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) Replace(htmlCode template.HTML) {
if !e.deleted.Load() {
var b []byte
b = append(b, "id="...)
b = e.Jid().AppendQuote(b)
if !bytes.Contains([]byte(htmlCode), b) {
panic(errors.New("jaws: Element.Replace(): expected HTML " + string(b)))
}
e.queue(what.Replace, string(htmlCode))
}
}
// Append appends a new HTML element as a child to the current one.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) Append(htmlCode template.HTML) {
e.queue(what.Append, string(htmlCode))
}
// Order reorders the HTML elements.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) Order(jidList []jid.Jid) {
if !e.deleted.Load() && len(jidList) > 0 {
var b []byte
for i, jid := range jidList {
if i > 0 {
b = append(b, ' ')
}
b = jid.Append(b)
}
e.queue(what.Order, string(b))
}
}
// Remove requests that the HTML child with the given HTML ID of this Element
// is removed from the Request and it's HTML element from the browser.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) Remove(htmlId string) {
e.queue(what.Remove, htmlId)
}
// ApplyParams parses the parameters passed to UI() when creating a new Element,
// adding UI tags, adding any additional event handlers found.
//
// Returns the list of HTML attributes found, if any.
func (e *Element) ApplyParams(params []any) (retv []template.HTMLAttr) {
tags, handlers, attrs := ParseParams(params)
if !e.deleted.Load() {
e.handlers = append(e.handlers, handlers...)
e.Tag(tags...)
for _, s := range attrs {
attr := template.HTMLAttr(s) // #nosec G203
retv = append(retv, attr)
}
}
return
}
// ApplyGetter examines getter, and if it's not nil, either adds it
// as a Tag, or, if it is a TagGetter, adds the result of that as a Tag.
//
// If getter is an InputHandler, ClickHandler, ContextMenuHandler or
// InitialHTMLAttrHandler, relevant values are added to the Element.
//
// Finally, if getter is an InitHandler, it's JawsInit()
// function is called.
//
// Returns the Tag(s) added (or nil if getter was nil), any initial HTML attrs
// provided by InitialHTMLAttrHandler, and any error returned from JawsInit()
// if it was called.
func (e *Element) ApplyGetter(getter any) (tagValue any, attrs []template.HTMLAttr, err error) {
if getter != nil {
tagValue = getter
if tagger, ok := getter.(tag.TagGetter); ok {
tagValue = tagger.JawsGetTag(e.Request)
}
if _, ok := getter.(InputHandler); ok {
e.handlers = append(e.handlers, getter)
} else if _, ok := getter.(ClickHandler); ok {
e.handlers = append(e.handlers, getter)
} else if _, ok := getter.(ContextMenuHandler); ok {
e.handlers = append(e.handlers, getter)
}
if ah, ok := getter.(InitialHTMLAttrHandler); ok {
if attr := ah.JawsInitialHTMLAttr(e); attr != "" {
attrs = append(attrs, attr)
}
}
e.Tag(tagValue)
if initer, ok := getter.(InitHandler); ok {
err = initer.JawsInit(e)
}
}
return
}