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


e6828463 Peter John

4 years ago
improv
example/components/counter.go CHANGED
@@ -1,7 +1,6 @@
1
1
  package components
2
2
 
3
3
  import (
4
- "fmt"
5
4
  "strconv"
6
5
 
7
6
  . "github.com/pyros2097/wapp"
@@ -19,17 +18,12 @@ import (
19
18
  // },
20
19
  // }
21
20
 
22
- func Counter(ctx *ReqContext, start int) *Element {
21
+ func Counter(ctx ReqContext, start int) *Element {
23
- UseData(ctx, "counter", fmt.Sprintf(`{
22
+ UseData(ctx, "counter", M{
24
- count: %d,
25
- increment() {
26
- this.count += 1;
23
+ "count": start,
24
+ "increment": func() string { return "this.count += 1;" },
25
+ "decrement": func() string { return "this.count -= 1;" },
27
- },
26
+ })
28
- decrement() {
29
- this.count -= 1;
30
- },
31
- }
32
- `, start))
33
27
  return Col(Css("text-3xl text-gray-700"),
34
28
  Row(
35
29
  Row(Css("underline"),
@@ -40,7 +34,7 @@ func Counter(ctx *ReqContext, start int) *Element {
40
34
  Button(Css("btn m-20"), OnClick("decrement"),
41
35
  Text("-"),
42
36
  ),
43
- Row(Css("m-20"), XText("count"),
37
+ Row(Css("m-20 text-8xl"), XText("count"),
44
38
  Text(strconv.Itoa(start)),
45
39
  ),
46
40
  Button(Css("btn m-20"), OnClick("increment"),
example/components/page.go CHANGED
@@ -6,7 +6,7 @@ import (
6
6
  . "github.com/pyros2097/wapp/example/context"
7
7
  )
8
8
 
9
- func Page(ctx *ReqContext, elem *Element) *Element {
9
+ func Page(ctx ReqContext, elem *Element) *Element {
10
10
  return Html(
11
11
  Head(
12
12
  Title("123"),
example/context/context.go CHANGED
@@ -3,29 +3,157 @@ package context
3
3
  import (
4
4
  "bytes"
5
5
  c "context"
6
+ "encoding/json"
6
- "fmt"
7
+ "io/ioutil"
7
8
  "net/http"
9
+ "reflect"
10
+
11
+ "github.com/gobuffalo/velvet"
12
+ "github.com/gorilla/mux"
13
+ . "github.com/pyros2097/wapp"
14
+ "golang.org/x/net/html"
8
15
  )
9
16
 
10
17
  type ReqContext struct {
11
18
  c.Context
12
- UserID string
19
+ Path string
20
+ PathParams map[string]string
21
+ QueryParams map[string]string
22
+ Body []byte
13
- JS *bytes.Buffer
23
+ JS *bytes.Buffer
14
- CSS *bytes.Buffer
24
+ CSS *bytes.Buffer
25
+ UserID string
26
+ }
27
+
28
+ func NewReqContext(r *http.Request) (ReqContext, error) {
29
+ pathParams := mux.Vars(r)
30
+ var body []byte
31
+ var err error
32
+ if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
33
+ body, err = ioutil.ReadAll(r.Body)
34
+ if err != nil {
35
+ return ReqContext{}, err
36
+ }
37
+ }
38
+ return ReqContext{
39
+ Path: r.URL.Path,
40
+ QueryParams: map[string]string{},
41
+ PathParams: pathParams,
42
+ Body: body,
43
+ JS: bytes.NewBuffer(nil),
44
+ CSS: bytes.NewBuffer(nil),
45
+ UserID: "123",
46
+ }, nil
47
+ }
48
+
49
+ type Handler func(c ReqContext) (interface{}, int, error)
50
+
51
+ func Wrap(h Handler) func(http.ResponseWriter, *http.Request) {
52
+ return func(w http.ResponseWriter, r *http.Request) {
53
+ ctx, err := NewReqContext(r)
54
+ if err != nil {
55
+ w.Header().Set("Content-Type", "application/json")
56
+ data, _ := json.Marshal(M{
57
+ "error": err.Error(),
58
+ })
59
+ w.Write(data)
60
+ return
61
+ }
62
+ value, status, err := h(ctx)
63
+ w.WriteHeader(status)
64
+ if err != nil {
65
+ w.Header().Set("Content-Type", "application/json")
66
+ data, _ := json.Marshal(M{
67
+ "error": err.Error(),
68
+ })
69
+ w.Write(data)
70
+ return
71
+ }
72
+ if v, ok := value.(*Element); ok {
73
+ w.Header().Set("Content-Type", "text/html")
74
+ v.WriteHtml(w)
75
+ return
76
+ }
77
+ w.Header().Set("Content-Type", "application/json")
78
+ data, _ := json.Marshal(value)
79
+ w.Write(data)
80
+ }
15
81
  }
16
82
 
83
+ func IsFunc(v interface{}) bool {
17
- func NewReqContext(w http.ResponseWriter, r *http.Request) *ReqContext {
84
+ return reflect.TypeOf(v).Kind() == reflect.Func
18
- return &ReqContext{r.Context(), "123", bytes.NewBuffer(nil), bytes.NewBuffer(nil)}
19
85
  }
20
86
 
21
- func UseData(ctx *ReqContext, name, data string) {
87
+ func UseData(ctx ReqContext, name string, data map[string]interface{}) {
88
+ v := velvet.NewContext()
22
- ctx.JS.WriteString(fmt.Sprintf(`
89
+ v.Set("name", name)
90
+ generatedMap := M{}
91
+ for k, v := range data {
92
+ if IsFunc(v) {
93
+ f := v.(func() string)
94
+ generatedMap[k] = "() {" + f() + "}"
95
+ } else {
96
+ generatedMap[k+":"] = v
97
+ }
98
+ }
99
+ v.Set("data", generatedMap)
100
+ s, err := velvet.Render(`
23
- Alpine.data('%s', () => {
101
+ Alpine.data('{{ name }}', () => {
24
- return %s;
102
+ return {
103
+ {{#each data}}
104
+ {{ @key }}{{ @value }},
105
+ {{/each}}
106
+ };
25
107
  });
108
+ `, v)
26
- `, name, data))
109
+ if err != nil {
110
+ panic(err)
111
+ }
112
+ ctx.JS.WriteString(s)
27
113
  }
28
114
 
115
+ type Component func(ReqContext) string
116
+
117
+ var components = map[string]Component{}
118
+
119
+ func RegisterComponent(k string, v Component) {
120
+ components[k] = v
121
+ }
122
+
29
- // func HTML(html string, data map[string]interface{}) string {
123
+ func Html2(ctx ReqContext, input string, data map[string]interface{}) string {
124
+ vctx := velvet.NewContext()
125
+ for k, v := range data {
126
+ vctx.Set(k, v)
127
+ }
128
+ doc, err := html.Parse(bytes.NewBufferString(input))
129
+ if err != nil {
130
+ panic(err)
131
+ }
132
+ var f func(*html.Node)
133
+ f = func(n *html.Node) {
134
+ if c, ok := components[n.Data]; ok {
135
+ txt := c(ctx)
136
+ println(n.Data, txt)
30
- // return fmt.Sprintf("")
137
+ // newNode, err := html.Parse(bytes.NewBufferString(txt))
138
+ // if err != nil {
139
+ // panic(err)
31
- // }
140
+ // }
141
+ println(n.NextSibling.Type)
142
+ // n.RemoveChild(n.NextSibling)
143
+ // n.RemoveChild(n.PrevSibling)
144
+ // n.AppendChild(newNode)
145
+ }
146
+ if n.Type == html.ElementNode && n.Data == "h1" {
147
+ println("h1")
148
+ }
149
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
150
+ f(c)
151
+ }
152
+ }
153
+ f(doc)
154
+ s, err := velvet.Render(input, vctx)
155
+ if err != nil {
156
+ panic(err)
157
+ }
158
+ return s
159
+ }
example/main.go CHANGED
@@ -2,7 +2,6 @@ package main
2
2
 
3
3
  import (
4
4
  "embed"
5
- "encoding/json"
6
5
  "log"
7
6
  "net/http"
8
7
  "os"
@@ -10,47 +9,19 @@ import (
10
9
 
11
10
  "github.com/apex/gateway/v2"
12
11
  "github.com/gorilla/mux"
13
- . "github.com/pyros2097/wapp"
14
-
15
- "github.com/pyros2097/wapp/example/context"
12
+ . "github.com/pyros2097/wapp/example/context"
16
13
  "github.com/pyros2097/wapp/example/pages"
17
14
  )
18
15
 
19
16
  //go:embed assets/*
20
17
  var assetsFS embed.FS
21
18
 
22
- type Handler func(c *context.ReqContext) (interface{}, int, error)
23
-
24
- func wrap(h Handler) func(http.ResponseWriter, *http.Request) {
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 {
38
- w.Header().Set("Content-Type", "text/html")
39
- v.WriteHtml(w)
40
- return
41
- }
42
- w.Header().Set("Content-Type", "application/json")
43
- data, _ := json.Marshal(value)
44
- w.Write(data)
45
- }
46
- }
47
-
48
19
  func main() {
49
20
  isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
50
21
  r := mux.NewRouter()
51
22
  r.PathPrefix("/assets/").Handler(http.FileServer(http.FS(assetsFS)))
52
- r.HandleFunc("/", wrap(pages.Index))
23
+ r.HandleFunc("/", Wrap(pages.Index)).Methods("GET")
53
- // r.HandleFunc("/about", wrap(pages.About))
24
+ r.HandleFunc("/about", Wrap(pages.About)).Methods("GET")
54
25
  if !isLambda {
55
26
  println("http server listening on http://localhost:3000")
56
27
  srv := &http.Server{
example/makefile CHANGED
@@ -1,5 +1,5 @@
1
1
  run:
2
- go run *.go
2
+ go run main.go
3
3
 
4
4
  local:
5
5
  sam local start-api
example/models/todo.go ADDED
@@ -0,0 +1,7 @@
1
+ package models
2
+
3
+ type Todo struct {
4
+ ID string `json:"id"`
5
+ Text string `json:"text"`
6
+ Completed bool `json:"completed"`
7
+ }
example/pages/about.go CHANGED
@@ -1,20 +1,19 @@
1
1
  package pages
2
2
 
3
- // import (
3
+ import (
4
- // "net/http"
4
+ . "github.com/pyros2097/wapp"
5
+ . "github.com/pyros2097/wapp/example/components"
6
+ "github.com/pyros2097/wapp/example/context"
7
+ )
5
8
 
6
- // . "github.com/pyros2097/wapp"
7
- // . "github.com/pyros2097/wapp/example/components"
8
- // )
9
-
10
- // //wapp:page method=GET path=/about
9
+ //wapp:page method=GET path=/about
11
- // func About(w http.ResponseWriter, r *http.Request) *Element {
10
+ func About(c context.ReqContext) (interface{}, int, error) {
12
- // return Page(
11
+ return Page(c,
13
- // Col(
12
+ Col(
14
- // Header(),
13
+ Header(),
15
- // Row(Css("text-5xl text-gray-700"),
14
+ Row(Css("text-5xl text-gray-700"),
16
- // Text("About Me"),
15
+ Text("About Me"),
17
- // ),
18
- // ),
16
+ ),
19
- // )
17
+ ),
20
- // }
18
+ ), 200, nil
19
+ }
example/pages/api/todos/_id/delete.go ADDED
@@ -0,0 +1,11 @@
1
+ package todo
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/wapp/example/models"
7
+ )
8
+
9
+ func DELETE(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
10
+ return Todo{}, nil
11
+ }
example/pages/api/todos/_id/get.go ADDED
@@ -0,0 +1,11 @@
1
+ package todo
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/wapp/example/models"
7
+ )
8
+
9
+ func GET(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
10
+ return Todo{}, nil
11
+ }
example/pages/api/todos/_id/put.go ADDED
@@ -0,0 +1,11 @@
1
+ package todo
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/wapp/example/models"
7
+ )
8
+
9
+ func PUT(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
10
+ return Todo{}, nil
11
+ }
example/pages/api/todos/get.go ADDED
@@ -0,0 +1,11 @@
1
+ package pages
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/wapp/example/models"
7
+ )
8
+
9
+ func GET(c context.Context) (interface{}, error) {
10
+ return []Todo{}, nil
11
+ }
example/pages/api/todos/post.go ADDED
@@ -0,0 +1,11 @@
1
+ package pages
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/wapp/example/models"
7
+ )
8
+
9
+ func POST(c context.Context) (interface{}, error) {
10
+ return Todo{}, nil
11
+ }
example/pages/index.go CHANGED
@@ -3,11 +3,35 @@ package pages
3
3
  import (
4
4
  . "github.com/pyros2097/wapp"
5
5
  . "github.com/pyros2097/wapp/example/components"
6
- "github.com/pyros2097/wapp/example/context"
6
+ . "github.com/pyros2097/wapp/example/context"
7
7
  )
8
8
 
9
+ func init() {
10
+ RegisterComponent("page", func(c ReqContext) string {
11
+ return Html2(c, `
12
+ <html>
13
+ <body>
9
- //wapp:page method=GET path=/
14
+ <slot></slot>
15
+ </body>
16
+ </html>
17
+ `, M{})
18
+ })
19
+ }
20
+
10
- func Index(c *context.ReqContext) (interface{}, int, error) {
21
+ func Index(c ReqContext) (interface{}, int, error) {
22
+ ss := Html2(c, `
23
+ <page x-data="pageData">
24
+ <div class="flex flex-col items-center justify-center">
25
+ <header></header>
26
+ <h1>Hello {{ userID }}</h1>
27
+ <h2>Hello this is a h1</h1>
28
+ <h2>Hello this is a h2</h1>
29
+ <h3 x-text="message"></h3>
30
+ <counter start={4}></counter>
31
+ </div>
32
+ </page>
33
+ `, M{"userID": c.UserID})
34
+ println("ss", ss)
11
35
  return Page(c,
12
36
  Col(
13
37
  Header(),
@@ -25,16 +49,16 @@ func Index(c *context.ReqContext) (interface{}, int, error) {
25
49
  // "userID": c.UserID,
26
50
  // "message": "I ❤️ Alpine",
27
51
  // }
28
- // return Html(`
52
+ // return Html(`
29
- // <page x-data="pageData">
53
+ // <page x-data="pageData">
30
- // <div class="flex flex-col items-center justify-center">
54
+ // <div class="flex flex-col items-center justify-center">
31
- // <header></header>
55
+ // <header></header>
32
- // <h1>Hello <template x-text="userID"></template></h1>
56
+ // <h1>Hello <template x-text="userID"></template></h1>
33
- // <h2>Hello this is a h1</h1>
57
+ // <h2>Hello this is a h1</h1>
34
- // <h2>Hello this is a h2</h1>
58
+ // <h2>Hello this is a h2</h1>
35
- // <h3 x-text="message"></h3>
59
+ // <h3 x-text="message"></h3>
36
- // <counter></counter>
60
+ // <counter start={4}></counter>
37
- // </div>
61
+ // </div>
38
- // </page>
62
+ // </page>
39
- // `, data), 200, nil
63
+ // `, data), 200, nil
40
64
  // }