~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
78842fba
—
Peter John 3 years ago
fix gsx
- gsx/gsx.go +82 -37
- gsx/gsx_test.go +74 -45
gsx/gsx.go
CHANGED
|
@@ -9,6 +9,7 @@ import (
|
|
|
9
9
|
"runtime"
|
|
10
10
|
"strings"
|
|
11
11
|
|
|
12
|
+
"github.com/alecthomas/repr"
|
|
12
13
|
"golang.org/x/net/html"
|
|
13
14
|
"golang.org/x/net/html/atom"
|
|
14
15
|
)
|
|
@@ -178,6 +179,29 @@ func populateChildren(n, replaceNode1 *html.Node) {
|
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
func cloneNode(n *html.Node) *html.Node {
|
|
183
|
+
attrs := []html.Attribute{}
|
|
184
|
+
for _, v := range n.Attr {
|
|
185
|
+
attrs = append(attrs, html.Attribute{
|
|
186
|
+
Key: v.Key,
|
|
187
|
+
Val: v.Val,
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
newNode := &html.Node{
|
|
191
|
+
Type: n.Type,
|
|
192
|
+
Data: n.Data,
|
|
193
|
+
DataAtom: n.DataAtom,
|
|
194
|
+
Attr: attrs,
|
|
195
|
+
}
|
|
196
|
+
if n.FirstChild != nil {
|
|
197
|
+
newNode.FirstChild = cloneNode(n.FirstChild)
|
|
198
|
+
}
|
|
199
|
+
if n.NextSibling != nil {
|
|
200
|
+
newNode.NextSibling = cloneNode(n.NextSibling)
|
|
201
|
+
}
|
|
202
|
+
return newNode
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
func populate(ctx Html, n *html.Node) {
|
|
182
206
|
if n.Type == html.TextNode {
|
|
183
207
|
if n.Data != "" && strings.Contains(n.Data, "{") && n.Data != "{children}" {
|
|
@@ -185,7 +209,33 @@ func populate(ctx Html, n *html.Node) {
|
|
|
185
209
|
}
|
|
186
210
|
} else if n.Type == html.ElementNode {
|
|
187
211
|
for i, at := range n.Attr {
|
|
212
|
+
if at.Key == "x-for" {
|
|
213
|
+
xfor := getAttribute("x-for", n.Attr)
|
|
214
|
+
arr := strings.Split(xfor, " in ")
|
|
215
|
+
ctxItemKey := arr[0]
|
|
216
|
+
ctxKey := arr[1]
|
|
217
|
+
data := ctx[ctxKey]
|
|
218
|
+
switch reflect.TypeOf(data).Kind() {
|
|
219
|
+
case reflect.Slice:
|
|
220
|
+
v := reflect.ValueOf(data)
|
|
221
|
+
firstChild := cloneNode(n.FirstChild)
|
|
222
|
+
n.RemoveChild(n.FirstChild)
|
|
223
|
+
for i := 0; i < v.Len(); i++ {
|
|
224
|
+
compCtx := map[string]interface{}{
|
|
225
|
+
ctxItemKey: v.Index(i).Interface(),
|
|
226
|
+
}
|
|
227
|
+
itemChild := cloneNode(firstChild)
|
|
228
|
+
itemChild.Parent = nil
|
|
229
|
+
if comp, ok := compMap[itemChild.Data]; ok {
|
|
230
|
+
newNode := populateComponent(compCtx, comp, itemChild, false)
|
|
231
|
+
n.AppendChild(newNode)
|
|
232
|
+
} else {
|
|
233
|
+
n.AppendChild(itemChild)
|
|
234
|
+
populate(compCtx, itemChild)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
188
|
-
if at.Val != "" && strings.Contains(at.Val, "{") {
|
|
238
|
+
} else if at.Val != "" && strings.Contains(at.Val, "{") {
|
|
189
239
|
if at.Key == "class" {
|
|
190
240
|
classes := ""
|
|
191
241
|
kvstrings := strings.Split(strings.TrimSpace(removeBrackets(at.Val)), ",")
|
|
@@ -213,26 +263,8 @@ func populate(ctx Html, n *html.Node) {
|
|
|
213
263
|
}
|
|
214
264
|
}
|
|
215
265
|
if comp, ok := compMap[n.Data]; ok {
|
|
216
|
-
h := Html(ctx)
|
|
217
|
-
args := []reflect.Value{reflect.ValueOf(h)}
|
|
218
|
-
for _, arg := range comp.Args {
|
|
219
|
-
if v, ok := ctx[arg]; ok {
|
|
220
|
-
args = append(args, reflect.ValueOf(v))
|
|
221
|
-
} else {
|
|
222
|
-
v := getAttribute(arg, n.Attr)
|
|
223
|
-
args = append(args, reflect.ValueOf(v))
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
result := reflect.ValueOf(comp.Func).Call(args)
|
|
227
|
-
|
|
266
|
+
newNode := populateComponent(ctx, comp, n, true)
|
|
228
|
-
if n.FirstChild != nil {
|
|
229
|
-
newChild := &html.Node{}
|
|
230
|
-
*newChild = *n.FirstChild
|
|
231
|
-
newChild.Parent = nil
|
|
232
|
-
n.RemoveChild(n.FirstChild)
|
|
233
|
-
populateChildren(compNode.FirstChild, newChild)
|
|
234
|
-
|
|
267
|
+
n.AppendChild(newNode)
|
|
235
|
-
}
|
|
236
268
|
}
|
|
237
269
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
238
270
|
populate(ctx, c)
|
|
@@ -240,19 +272,32 @@ func populate(ctx Html, n *html.Node) {
|
|
|
240
272
|
}
|
|
241
273
|
}
|
|
242
274
|
|
|
243
|
-
|
|
275
|
+
func renderComponent(ctx Html, comp ComponentFunc, n *html.Node) Node {
|
|
244
|
-
// if x.Name == "For" {
|
|
245
|
-
// ctxKey := getAttribute("key", x.Attributes)
|
|
246
|
-
// ctxName := getAttribute("itemKey", x.Attributes)
|
|
247
|
-
|
|
276
|
+
h := Html(ctx)
|
|
248
|
-
// switch reflect.TypeOf(data).Kind() {
|
|
249
|
-
|
|
277
|
+
args := []reflect.Value{reflect.ValueOf(h)}
|
|
278
|
+
for _, arg := range comp.Args {
|
|
279
|
+
if v, ok := ctx[arg]; ok {
|
|
250
|
-
|
|
280
|
+
args = append(args, reflect.ValueOf(v))
|
|
281
|
+
} else {
|
|
282
|
+
v := getAttribute(arg, n.Attr)
|
|
283
|
+
args = append(args, reflect.ValueOf(v))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
result := reflect.ValueOf(comp.Func).Call(args)
|
|
287
|
+
compNode := result[0].Interface().(Node)
|
|
288
|
+
return compNode
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
func populateComponent(ctx Html, comp ComponentFunc, n *html.Node, remove bool) *html.Node {
|
|
292
|
+
compNode := renderComponent(ctx, comp, n)
|
|
293
|
+
if n.FirstChild != nil {
|
|
294
|
+
newChild := cloneNode(n.FirstChild)
|
|
295
|
+
newChild.Parent = nil
|
|
251
|
-
|
|
296
|
+
if n.FirstChild != nil && remove {
|
|
252
|
-
|
|
297
|
+
n.RemoveChild(n.FirstChild)
|
|
298
|
+
}
|
|
253
|
-
|
|
299
|
+
repr.Println(compNode.String(), compNode.FirstChild.Data)
|
|
254
|
-
|
|
300
|
+
populateChildren(compNode.FirstChild, newChild)
|
|
301
|
+
}
|
|
255
|
-
|
|
302
|
+
return compNode.Node
|
|
256
|
-
// }
|
|
257
|
-
// }
|
|
258
|
-
|
|
303
|
+
}
|
gsx/gsx_test.go
CHANGED
|
@@ -17,10 +17,12 @@ func Todo(h Html, todo *TodoData) Node {
|
|
|
17
17
|
<li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
|
|
18
18
|
<div class="view">
|
|
19
19
|
<span>{todo.Text}</span>
|
|
20
|
+
<span>{todo.Text}</span>
|
|
20
21
|
</div>
|
|
21
22
|
{children}
|
|
22
23
|
<div class="count">
|
|
23
24
|
<span>{todo.Completed}</span>
|
|
25
|
+
<span>{todo.Completed}</span>
|
|
24
26
|
</div>
|
|
25
27
|
</li>
|
|
26
28
|
`)
|
|
@@ -30,64 +32,91 @@ func WebsiteName() string {
|
|
|
30
32
|
return "My Website"
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
func TestComponent(t *testing.T) {
|
|
36
|
+
r := require.New(t)
|
|
37
|
+
RegisterComponent(Todo, "todo")
|
|
38
|
+
RegisterFunc(WebsiteName)
|
|
39
|
+
h := Html(map[string]interface{}{
|
|
40
|
+
"todo": &TodoData{ID: "4", Text: "My fourth todo", Completed: false},
|
|
41
|
+
})
|
|
42
|
+
actual := h.Render(`
|
|
43
|
+
<div>
|
|
44
|
+
<Todo>
|
|
45
|
+
<div class="todo-panel">
|
|
46
|
+
<span>{todo.Text}</span>
|
|
47
|
+
<span>{todo.Completed}</span>
|
|
48
|
+
</div>
|
|
49
|
+
</Todo>
|
|
50
|
+
</div>
|
|
51
|
+
`).String()
|
|
52
|
+
expected := stripWhitespace(`
|
|
53
|
+
<div>
|
|
54
|
+
<todo>
|
|
55
|
+
<li id="todo-4" class="">
|
|
56
|
+
<div class="view"><span>My fourth todo</span><span>My fourth todo</span></div>
|
|
57
|
+
<div class="todo-panel"><span>My fourth todo</span><span>false</span></div>
|
|
58
|
+
<div class="count"><span>false</span><span>false</span></div>
|
|
59
|
+
</li>
|
|
60
|
+
</todo>
|
|
61
|
+
</div>
|
|
62
|
+
`)
|
|
63
|
+
r.Equal(expected, actual)
|
|
64
|
+
}
|
|
65
|
+
|
|
33
|
-
func
|
|
66
|
+
func TestFor(t *testing.T) {
|
|
34
67
|
r := require.New(t)
|
|
35
68
|
RegisterComponent(Todo, "todo")
|
|
36
69
|
RegisterFunc(WebsiteName)
|
|
37
70
|
h := Html(map[string]interface{}{
|
|
38
71
|
"todos": []*TodoData{
|
|
39
|
-
{ID: "
|
|
72
|
+
{ID: "1", Text: "My first todo", Completed: true},
|
|
73
|
+
{ID: "2", Text: "My second todo", Completed: false},
|
|
74
|
+
{ID: "3", Text: "My third todo", Completed: false},
|
|
40
75
|
},
|
|
41
76
|
})
|
|
42
|
-
h["todo"] = &TodoData{ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true}
|
|
43
77
|
actual := h.Render(`
|
|
44
78
|
<div>
|
|
79
|
+
<ul x-for="todo in todos" class="relative">
|
|
45
|
-
|
|
80
|
+
<li>
|
|
46
|
-
|
|
81
|
+
<span>{todo.Text}</span>
|
|
47
|
-
|
|
82
|
+
<span>{todo.Completed}</span>
|
|
83
|
+
<a>link to {todo.ID}</a>
|
|
84
|
+
</li>
|
|
85
|
+
</ul>
|
|
48
|
-
|
|
86
|
+
<ol x-for="todo in todos">
|
|
49
|
-
|
|
87
|
+
<Todo>
|
|
50
|
-
|
|
88
|
+
<div class="todo-panel">
|
|
51
|
-
|
|
89
|
+
<span>{todo.Text}</span>
|
|
52
|
-
|
|
90
|
+
<span>{todo.Completed}</span>
|
|
53
|
-
|
|
91
|
+
</div>
|
|
54
|
-
|
|
92
|
+
</Todo>
|
|
55
|
-
</template>
|
|
56
|
-
|
|
93
|
+
</ol>
|
|
57
|
-
</div>
|
|
58
|
-
<div>
|
|
59
|
-
Test
|
|
60
|
-
<button>{WebsiteName}</button>
|
|
61
|
-
</div>
|
|
62
94
|
</div>
|
|
63
95
|
`).String()
|
|
64
96
|
expected := stripWhitespace(`
|
|
65
97
|
<div>
|
|
66
|
-
<div>
|
|
67
|
-
123
|
|
68
|
-
|
|
98
|
+
<ul x-for="todo in todos" class="relative">
|
|
99
|
+
<li><span>My first todo</span><span>true</span><a>link to 1</a></li>
|
|
100
|
+
<li><span>My second todo</span><span>false</span><a>link to 2</a></li>
|
|
101
|
+
<li><span>My third todo</span><span>false</span><a>link to 3</a></li>
|
|
102
|
+
</ul>
|
|
69
|
-
|
|
103
|
+
<ol x-for="todo in todos">
|
|
70
|
-
<todo key="todo">
|
|
71
|
-
|
|
104
|
+
<li id="todo-1" class="completed">
|
|
72
|
-
<div class="view">
|
|
73
|
-
|
|
105
|
+
<div class="view"><span>My first todo</span><span>My first todo</span></div>
|
|
74
|
-
|
|
106
|
+
<div class="todo-panel"><span><nil></span><span><nil></span></div>
|
|
75
|
-
|
|
107
|
+
<div class="count"><span>true</span><span>true</span></div>
|
|
76
|
-
<h2>Title</h2>
|
|
77
|
-
<h3>Sub title</h3>
|
|
78
|
-
</div>
|
|
79
|
-
<div class="count">
|
|
80
|
-
<span>true</span>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
108
|
+
</li>
|
|
109
|
+
<li id="todo-2" class="">
|
|
110
|
+
<div class="view"><span>My second todo</span><span>My second todo</span></div>
|
|
111
|
+
<div class="todo-panel"><span><nil></span><span><nil></span></div>
|
|
112
|
+
<div class="count"><span>false</span><span>false</span></div>
|
|
83
|
-
|
|
113
|
+
</li>
|
|
114
|
+
<li id="todo-3" class="">
|
|
115
|
+
<div class="view"><span>My third todo</span><span>My third todo</span></div>
|
|
116
|
+
<div class="todo-panel"><span><nil></span><span><nil></span></div>
|
|
117
|
+
<div class="count"><span>false</span><span>false</span></div>
|
|
84
|
-
|
|
118
|
+
</li>
|
|
85
|
-
|
|
119
|
+
</ol>
|
|
86
|
-
</div>
|
|
87
|
-
<div>
|
|
88
|
-
Test
|
|
89
|
-
<button>My Website</button>
|
|
90
|
-
</div>
|
|
91
120
|
</div>
|
|
92
121
|
`)
|
|
93
122
|
r.Equal(expected, actual)
|