~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.


7ea7a4f1 Peter John

4 years ago
implement new api
example/components/counter.go ADDED
@@ -0,0 +1,93 @@
1
+ package components
2
+
3
+ import (
4
+ "fmt"
5
+ "strconv"
6
+
7
+ . "github.com/pyros2097/wapp"
8
+ . "github.com/pyros2097/wapp/example/context"
9
+ )
10
+
11
+ // data := map[string]interface{}{
12
+ // "count": 1,
13
+ // "names": []string{"123", "123"},
14
+ // "increment": func() string {
15
+ // return "this.count += 1;"
16
+ // },
17
+ // "decrement": func() string {
18
+ // return "this.count -= 1;"
19
+ // },
20
+ // }
21
+
22
+ func Counter(ctx *ReqContext, start int) *Element {
23
+ UseData(ctx, "counter", fmt.Sprintf(`{
24
+ count: %d,
25
+ increment() {
26
+ this.count += 1;
27
+ },
28
+ decrement() {
29
+ this.count -= 1;
30
+ },
31
+ }
32
+ `, start))
33
+ return Col(Css("text-3xl text-gray-700"),
34
+ Row(
35
+ Row(Css("underline"),
36
+ Text("Counter"),
37
+ ),
38
+ ),
39
+ Row(XData("counter"),
40
+ Button(Css("btn m-20"), OnClick("decrement"),
41
+ Text("-"),
42
+ ),
43
+ Row(Css("m-20"), XText("count"),
44
+ Text(strconv.Itoa(start)),
45
+ ),
46
+ Button(Css("btn m-20"), OnClick("increment"),
47
+ Text("+"),
48
+ ),
49
+ ),
50
+ )
51
+ }
52
+
53
+ // func HTML(s string, m map[string]interface{}) string {
54
+ // return s
55
+ // }
56
+
57
+ // func Counter2(start int) string {
58
+ // return HTML(`
59
+ // <div class="flex flex-col justify-center items-center text-3xl text-gray-700">
60
+ // <div class="flex flex-row justify-center items-center">
61
+ // <div class="flex flex-row justify-center items-center underline">
62
+ // Counter
63
+ // </div>
64
+ // </div>
65
+ // <div class="flex flex-row justify-center items-center" x-data="counter">
66
+ // <button @click="decrement" class="btn m-20">-</button>
67
+ // <div class="flex flex-row justify-center items-center m-20">{{ count }}</div>
68
+ // <button @click="increment" class="btn m-20">+</button>
69
+ // </div>
70
+ // <div>
71
+ // {{#if true }}
72
+ // render this
73
+ // {{/if}}
74
+ // </div>
75
+ // <div>
76
+ // {{#each names}}
77
+ // <li>{{ @index }} - {{ @value }}</li>
78
+ // {{/each}}
79
+ // </div>
80
+ // </div>
81
+ // `, data)
82
+ // }
83
+
84
+ // if becomes
85
+ // <template x-if="open">
86
+ // </template>
87
+
88
+ // each becomes
89
+ // <ul x-data="{ names: ['Red', 'Orange', 'Yellow'] }">
90
+ // <template x-for="name in names">
91
+ // <li x-text="names"></li>
92
+ // </template>
93
+ // </ul>
example/components/page.go CHANGED
@@ -1,10 +1,12 @@
1
1
  package components
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  . "github.com/pyros2097/wapp"
6
+ . "github.com/pyros2097/wapp/example/context"
5
7
  )
6
8
 
7
- func Page(elem *Element) *Element {
9
+ func Page(ctx *ReqContext, elem *Element) *Element {
8
10
  return Html(
9
11
  Head(
10
12
  Title("123"),
@@ -17,6 +19,10 @@ func Page(elem *Element) *Element {
17
19
  Link("stylesheet", "/assets/styles.css"),
18
20
  Script(Src("/assets/alpine.js"), Defer()),
19
21
  ),
20
- Body(elem),
22
+ Body(elem, Script(Text(fmt.Sprintf(`
23
+ document.addEventListener('alpine:init', () => {
24
+ %s
25
+ });
26
+ `, ctx.JS.String())))),
21
27
  )
22
28
  }
example/context/context.go ADDED
@@ -0,0 +1,31 @@
1
+ package context
2
+
3
+ import (
4
+ "bytes"
5
+ c "context"
6
+ "fmt"
7
+ "net/http"
8
+ )
9
+
10
+ type ReqContext struct {
11
+ c.Context
12
+ UserID string
13
+ JS *bytes.Buffer
14
+ CSS *bytes.Buffer
15
+ }
16
+
17
+ func NewReqContext(w http.ResponseWriter, r *http.Request) *ReqContext {
18
+ return &ReqContext{r.Context(), "123", bytes.NewBuffer(nil), bytes.NewBuffer(nil)}
19
+ }
20
+
21
+ func UseData(ctx *ReqContext, name, data string) {
22
+ ctx.JS.WriteString(fmt.Sprintf(`
23
+ Alpine.data('%s', () => {
24
+ return %s;
25
+ });
26
+ `, name, data))
27
+ }
28
+
29
+ // func HTML(html string, data map[string]interface{}) string {
30
+ // return fmt.Sprintf("")
31
+ // }
example/main.go CHANGED
@@ -2,6 +2,7 @@ package main
2
2
 
3
3
  import (
4
4
  "embed"
5
+ "encoding/json"
5
6
  "log"
6
7
  "net/http"
7
8
  "os"
@@ -10,16 +11,37 @@ import (
10
11
  "github.com/apex/gateway/v2"
11
12
  "github.com/gorilla/mux"
12
13
  . "github.com/pyros2097/wapp"
14
+
15
+ "github.com/pyros2097/wapp/example/context"
13
16
  "github.com/pyros2097/wapp/example/pages"
14
17
  )
15
18
 
16
19
  //go:embed assets/*
17
20
  var assetsFS embed.FS
18
21
 
22
+ type Handler func(c *context.ReqContext) (interface{}, int, error)
23
+
19
- func wrap(f func(http.ResponseWriter, *http.Request) *Element) func(http.ResponseWriter, *http.Request) {
24
+ func wrap(h Handler) func(http.ResponseWriter, *http.Request) {
20
25
  return func(w http.ResponseWriter, r *http.Request) {
26
+ ctx := context.NewReqContext(w, r)
27
+ value, status, err := h(ctx)
28
+ w.WriteHeader(status)
29
+ if err != nil {
30
+ w.Header().Set("Content-Type", "application/json")
31
+ data, _ := json.Marshal(M{
32
+ "error": err.Error(),
33
+ })
34
+ w.Write(data)
35
+ return
36
+ }
37
+ if v, ok := value.(*Element); ok {
21
- w.Header().Set("Content-Type", "text/html")
38
+ w.Header().Set("Content-Type", "text/html")
22
- f(w, r).WriteHtml(w)
39
+ v.WriteHtml(w)
40
+ return
41
+ }
42
+ w.Header().Set("Content-Type", "application/json")
43
+ data, _ := json.Marshal(value)
44
+ w.Write(data)
23
45
  }
24
46
  }
25
47
 
@@ -28,12 +50,12 @@ func main() {
28
50
  r := mux.NewRouter()
29
51
  r.PathPrefix("/assets/").Handler(http.FileServer(http.FS(assetsFS)))
30
52
  r.HandleFunc("/", wrap(pages.Index))
31
- r.HandleFunc("/about", wrap(pages.About))
53
+ // r.HandleFunc("/about", wrap(pages.About))
32
54
  if !isLambda {
33
- println("http listening on http://localhost:1234")
55
+ println("http server listening on http://localhost:3000")
34
56
  srv := &http.Server{
35
57
  Handler: r,
36
- Addr: "127.0.0.1:1234",
58
+ Addr: "127.0.0.1:3000",
37
59
  WriteTimeout: 30 * time.Second,
38
60
  ReadTimeout: 30 * time.Second,
39
61
  }
example/pages/about.go CHANGED
@@ -1,19 +1,20 @@
1
1
  package pages
2
2
 
3
- import (
3
+ // import (
4
- "net/http"
4
+ // "net/http"
5
5
 
6
- . "github.com/pyros2097/wapp"
6
+ // . "github.com/pyros2097/wapp"
7
- . "github.com/pyros2097/wapp/example/components"
7
+ // . "github.com/pyros2097/wapp/example/components"
8
- )
8
+ // )
9
9
 
10
+ // //wapp:page method=GET path=/about
10
- func About(w http.ResponseWriter, r *http.Request) *Element {
11
+ // func About(w http.ResponseWriter, r *http.Request) *Element {
11
- return Page(
12
+ // return Page(
12
- Col(
13
+ // Col(
13
- Header(),
14
+ // Header(),
14
- Row(Css("text-5xl text-gray-700"),
15
+ // Row(Css("text-5xl text-gray-700"),
15
- Text("About Me"),
16
+ // Text("About Me"),
17
+ // ),
16
- ),
18
+ // ),
17
- ),
18
- )
19
+ // )
19
- }
20
+ // }
example/pages/api/todo.go ADDED
@@ -0,0 +1,43 @@
1
+ package pages
2
+
3
+ import (
4
+ "context"
5
+ )
6
+
7
+ type Todo struct {
8
+ ID string `json:"id"`
9
+ Text string `json:"text"`
10
+ Completed bool `json:"completed"`
11
+ }
12
+
13
+ func GetParam(c context.Context, k string) string {
14
+ return ""
15
+ }
16
+
17
+ //wapp:api method=GET path=/api/todos
18
+ func GetAllTodos(c context.Context) (interface{}, error) {
19
+ return []Todo{}, nil
20
+ }
21
+
22
+ // GetTodoById(c context.Context, id string)
23
+
24
+ //wapp:api method=GET path=/api/todos/:id
25
+ func GetTodoById(c context.Context) (interface{}, error) {
26
+ id := GetParam(c, ":id")
27
+ return Todo{ID: id}, nil
28
+ }
29
+
30
+ // UpdateTodo(c context.Context, id string, body *Todo)
31
+
32
+ //wapp:api method=POST path=/api/todos
33
+ func CreateTodo(c context.Context) (interface{}, error) {
34
+ var todo Todo
35
+ // GetBody(c, &todo)
36
+ return todo, nil
37
+ }
38
+
39
+ //wapp:api method=PUT path=/api/todos/:id
40
+ func UpdateTodo(c context.Context) (interface{}, error) {
41
+ id := GetParam(c, ":id")
42
+ return Todo{ID: id}, nil
43
+ }
example/pages/index.go CHANGED
@@ -1,38 +1,40 @@
1
1
  package pages
2
2
 
3
3
  import (
4
- "net/http"
5
- "strconv"
6
-
7
4
  . "github.com/pyros2097/wapp"
8
5
  . "github.com/pyros2097/wapp/example/components"
6
+ "github.com/pyros2097/wapp/example/context"
9
7
  )
10
8
 
9
+ //wapp:page method=GET path=/
11
- func Index(w http.ResponseWriter, r *http.Request) *Element {
10
+ func Index(c *context.ReqContext) (interface{}, int, error) {
12
- return Page(
11
+ return Page(c,
13
12
  Col(
14
13
  Header(),
14
+ H1(Text("Hello "+c.UserID)),
15
15
  H1(Text("Hello this is a h1")),
16
16
  H2(Text("Hello this is a h2")),
17
- H2(XData("{ message: 'I ❤️ Alpine' }"), XText("message"), Text("")),
17
+ H3(XData("{ message: 'I ❤️ Alpine' }"), XText("message"), Text("")),
18
- Col(Css("text-3xl text-gray-700"),
19
- Row(
20
- Row(Css("underline"),
21
- Text("Counter"),
18
+ Counter(c, 4),
22
- ),
23
- ),
24
- Row(
25
- Button(Css("btn m-20"),
26
- Text("-"),
27
- ),
28
- Row(Css("m-20"),
29
- Text(strconv.Itoa(1)),
30
- ),
31
- Button(Css("btn m-20"),
32
- Text("+"),
33
- ),
34
- ),
35
- ),
36
19
  ),
37
- )
20
+ ), 200, nil
38
21
  }
22
+
23
+ // func Index2(c *context.ReqContext) (interface{}, int, error) {
24
+ // data := M{
25
+ // "userID": c.UserID,
26
+ // "message": "I ❤️ Alpine",
27
+ // }
28
+ // return Html(`
29
+ // <page x-data="pageData">
30
+ // <div class="flex flex-col items-center justify-center">
31
+ // <header></header>
32
+ // <h1>Hello <template x-text="userID"></template></h1>
33
+ // <h2>Hello this is a h1</h1>
34
+ // <h2>Hello this is a h2</h1>
35
+ // <h3 x-text="message"></h3>
36
+ // <counter></counter>
37
+ // </div>
38
+ // </page>
39
+ // `, data), 200, nil
40
+ // }
html.go CHANGED
@@ -6,6 +6,8 @@ import (
6
6
  "strconv"
7
7
  )
8
8
 
9
+ type M map[string]interface{}
10
+
9
11
  type Element struct {
10
12
  tag string
11
13
  attrs map[string]string
@@ -225,11 +227,11 @@ func Li(uis ...interface{}) *Element {
225
227
  }
226
228
 
227
229
  func Row(uis ...interface{}) *Element {
228
- return Div(append([]interface{}{Css("flex flex-row justify-center items-center")}, uis...)...)
230
+ return NewElement("div", false, append([]interface{}{Css("flex flex-row justify-center items-center")}, uis...)...)
229
231
  }
230
232
 
231
233
  func Col(uis ...interface{}) *Element {
232
- return Div(append([]interface{}{Css("flex flex-col justify-center items-center")}, uis...)...)
234
+ return NewElement("div", false, append([]interface{}{Css("flex flex-col justify-center items-center")}, uis...)...)
233
235
  }
234
236
 
235
237
  func If(expr bool, a *Element) *Element {
@@ -275,6 +277,14 @@ type Attribute struct {
275
277
  Value string
276
278
  }
277
279
 
280
+ func Attr(k, v string) Attribute {
281
+ return Attribute{k, v}
282
+ }
283
+
284
+ func OnClick(v string) Attribute {
285
+ return Attribute{"@click", v}
286
+ }
287
+
278
288
  func ID(v string) Attribute {
279
289
  return Attribute{"id", v}
280
290
  }
@@ -379,3 +389,54 @@ func MergeAttributes(parent *Element, uis ...interface{}) *Element {
379
389
  }
380
390
  return parent
381
391
  }
392
+
393
+ // func UseData(name, data string) *Element {
394
+ // return Script(Text(fmt.Sprintf(`
395
+ // document.addEventListener('alpine:init', () => {
396
+ // Alpine.data('%s', () => {
397
+ // return %s;
398
+ // });
399
+ // });
400
+ // `, name, data)))
401
+ // }
402
+
403
+ // func HTML(html string, data map[string]interface{}) string {
404
+ // return fmt.Sprintf("")
405
+ // }
406
+
407
+ // func Counter2(start int) string {
408
+ // data := map[string]interface{}{
409
+ // "count": 1,
410
+ // "names": []string{"123", "123"},
411
+ // "increment": func() string {
412
+ // return "this.count += 1;"
413
+ // },
414
+ // "decrement": func() string {
415
+ // return "this.count -= 1;"
416
+ // },
417
+ // }
418
+ // return HTML(`
419
+ // <div class="flex flex-col justify-center items-center text-3xl text-gray-700">
420
+ // <div class="flex flex-row justify-center items-center">
421
+ // <div class="flex flex-row justify-center items-center underline">
422
+ // Counter
423
+ // </div>
424
+ // </div>
425
+ // <div class="flex flex-row justify-center items-center" x-data="counter">
426
+ // <button @click="decrement" class="btn m-20">-</button>
427
+ // <div class="flex flex-row justify-center items-center m-20">{{ count }}</div>
428
+ // <button @click="increment" class="btn m-20">+</button>
429
+ // </div>
430
+ // <div>
431
+ // {{#if true }}
432
+ // render this
433
+ // {{/if}}
434
+ // </div>
435
+ // <div>
436
+ // {{#each names}}
437
+ // <li>{{ @index }} - {{ @value }}</li>
438
+ // {{/each}}
439
+ // </div>
440
+ // </div>
441
+ // `, data)
442
+ // }