~repos /gromer

#golang#htmx#ssr

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
Files changed (2) hide show
  1. gsx/gsx.go +82 -37
  2. 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
- compNode := result[0].Interface().(Node)
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
- n.AppendChild(compNode.Node)
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
- // func render(x *Xml, ctx map[string]interface{}) string {
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
- // data := ctx[ctxKey]
276
+ h := Html(ctx)
248
- // switch reflect.TypeOf(data).Kind() {
249
- // case reflect.Slice:
277
+ args := []reflect.Value{reflect.ValueOf(h)}
278
+ for _, arg := range comp.Args {
279
+ if v, ok := ctx[arg]; ok {
250
- // v := reflect.ValueOf(data)
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
- // for i := 0; i < v.Len(); i++ {
296
+ if n.FirstChild != nil && remove {
252
- // ctx["_space"] = space + " "
297
+ n.RemoveChild(n.FirstChild)
298
+ }
253
- // ctx[ctxName] = v.Index(i).Interface()
299
+ repr.Println(compNode.String(), compNode.FirstChild.Data)
254
- // s += render(x.Children[0], ctx) + "\n"
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 TestHtml(t *testing.T) {
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: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true},
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
- <div>
80
+ <li>
46
- 123
81
+ <span>{todo.Text}</span>
47
- <ul id="todo-list" class="relative">
82
+ <span>{todo.Completed}</span>
83
+ <a>link to {todo.ID}</a>
84
+ </li>
85
+ </ul>
48
- <template x-for="todo in todos">
86
+ <ol x-for="todo in todos">
49
- <Todo key="todo">
87
+ <Todo>
50
- <div class="container">
88
+ <div class="todo-panel">
51
- <h2>Title</h2>
89
+ <span>{todo.Text}</span>
52
- <h3>Sub title</h3>
90
+ <span>{todo.Completed}</span>
53
- </div>
91
+ </div>
54
- </Todo>
92
+ </Todo>
55
- </template>
56
- </ul>
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
- <ul id="todo-list" class="relative">
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
- <template x-for="todo in todos">
103
+ <ol x-for="todo in todos">
70
- <todo key="todo">
71
- <li id="todo-b1a7359c-ebb4-11ec-8ea0-0242ac120002" class="completed">
104
+ <li id="todo-1" class="completed">
72
- <div class="view">
73
- <span>My first todo</span>
105
+ <div class="view"><span>My first todo</span><span>My first todo</span></div>
74
- </div>
106
+ <div class="todo-panel"><span>&lt;nil&gt;</span><span>&lt;nil&gt;</span></div>
75
- <div class="container">
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
- </li>
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>&lt;nil&gt;</span><span>&lt;nil&gt;</span></div>
112
+ <div class="count"><span>false</span><span>false</span></div>
83
- </todo>
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>&lt;nil&gt;</span><span>&lt;nil&gt;</span></div>
117
+ <div class="count"><span>false</span><span>false</span></div>
84
- </template>
118
+ </li>
85
- </ul>
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)