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


7b7eb741 pyros2097

5 years ago
refac js
app.go CHANGED
@@ -1,9 +1,5 @@
1
1
  package app
2
2
 
3
- var (
4
- staticResourcesURL string
5
- )
6
-
7
3
  // Reload reloads the current page.
8
4
  func Reload() {
9
5
  dispatch(func() {
@@ -18,8 +14,3 @@ func Reload() {
18
14
  func Run(r RenderFunc) {
19
15
  run(r)
20
16
  }
21
-
22
- // Window returns the JavaScript "window" object.
23
- func Window() BrowserWindow {
24
- return window
25
- }
app_nowasm.go CHANGED
@@ -7,10 +7,6 @@ import (
7
7
  "os"
8
8
  )
9
9
 
10
- var (
11
- window *browserWindow
12
- )
13
-
14
10
  func getenv(k string) string {
15
11
  return os.Getenv(k)
16
12
  }
app_wasm.go CHANGED
@@ -2,22 +2,19 @@ package app
2
2
 
3
3
  import (
4
4
  "context"
5
- "fmt"
6
5
  "net/url"
7
- "syscall/js"
8
6
  )
9
7
 
10
8
  var (
11
9
  body *elem
12
10
  content UI
13
11
  rootPrefix string
14
- window = &browserWindow{value: value{Value: js.Global()}}
15
12
  )
16
13
 
17
14
  func run(render RenderFunc) {
18
15
  defer func() {
19
16
  err := recover()
20
- displayLoadError(err)
17
+ // show alert
21
18
  panic(err)
22
19
  }()
23
20
 
@@ -62,16 +59,6 @@ func initContent() {
62
59
  body.body = append(body.body, content)
63
60
  }
64
61
 
65
- func displayLoadError(err interface{}) {
66
- loadingLabel := Window().
67
- Get("document").
68
- Call("getElementById", "app-wasm-loader-label")
69
- if !loadingLabel.Truthy() {
70
- return
71
- }
72
- loadingLabel.Set("innerText", fmt.Sprint(err))
73
- }
74
-
75
62
  func onPopState(this Value, args []Value) interface{} {
76
63
  dispatch(func() {
77
64
  // navigate(Window().URL(), false)
@@ -86,7 +73,3 @@ func isExternalNavigation(u *url.URL) bool {
86
73
  func isFragmentNavigation(u *url.URL) bool {
87
74
  return u.Fragment != ""
88
75
  }
89
-
90
- func reload() {
91
- Window().Get("location").Call("reload")
92
- }
attributes.go CHANGED
@@ -2,6 +2,8 @@ package app
2
2
 
3
3
  import (
4
4
  "context"
5
+
6
+ "github.com/pyros2097/wapp/js"
5
7
  )
6
8
 
7
9
  type baseAttribute struct {
@@ -12,7 +14,7 @@ func (c baseAttribute) Kind() Kind {
12
14
  return Attribute
13
15
  }
14
16
 
15
- func (c baseAttribute) JSValue() Value {
17
+ func (c baseAttribute) JSValue() js.Value {
16
18
  return nil
17
19
  }
18
20
 
@@ -39,7 +41,7 @@ func (c baseAttribute) attributes() map[string]string {
39
41
  return nil
40
42
  }
41
43
 
42
- func (c baseAttribute) eventHandlers() map[string]eventHandler {
44
+ func (c baseAttribute) eventHandlers() map[string]js.EventHandler {
43
45
  return nil
44
46
  }
45
47
 
@@ -85,19 +87,19 @@ func OnClick(cb func()) UI {
85
87
 
86
88
  type OnChangeAttribute struct {
87
89
  baseAttribute
88
- cb EventHandler
90
+ cb js.EventHandlerFunc
89
91
  }
90
92
 
91
- func OnChange(cb EventHandler) UI {
93
+ func OnChange(cb js.EventHandlerFunc) UI {
92
94
  return OnChangeAttribute{cb: cb}
93
95
  }
94
96
 
95
97
  type OnInputAttribute struct {
96
98
  baseAttribute
97
- cb EventHandler
99
+ cb js.EventHandlerFunc
98
100
  }
99
101
 
100
- func OnInput(cb EventHandler) UI {
102
+ func OnInput(cb js.EventHandlerFunc) UI {
101
103
  return OnInputAttribute{cb: cb}
102
104
  }
103
105
 
@@ -113,7 +115,7 @@ func mergeAttributes(parent *elem, uis ...UI) {
113
115
  parent.setAttr("class", c.classes)
114
116
  }
115
117
  case OnClickAttribute:
116
- parent.setEventHandler("click", func(e Event) {
118
+ parent.setEventHandler("click", func(e js.Event) {
117
119
  c.cb()
118
120
  })
119
121
  case OnChangeAttribute:
component.go CHANGED
@@ -6,6 +6,7 @@ import (
6
6
  "strings"
7
7
 
8
8
  "github.com/pyros2097/wapp/errors"
9
+ "github.com/pyros2097/wapp/js"
9
10
  )
10
11
 
11
12
  var contextMap = map[int]*RenderContext{}
@@ -21,7 +22,7 @@ func (r RenderFunc) Kind() Kind {
21
22
  return FunctionalComponent
22
23
  }
23
24
 
24
- func (r RenderFunc) JSValue() Value {
25
+ func (r RenderFunc) JSValue() js.Value {
25
26
  c := getCurrentContext()
26
27
  return c.root.JSValue()
27
28
  }
@@ -85,7 +86,7 @@ func (r RenderFunc) attributes() map[string]string {
85
86
  return nil
86
87
  }
87
88
 
88
- func (r RenderFunc) eventHandlers() map[string]eventHandler {
89
+ func (r RenderFunc) eventHandlers() map[string]js.EventHandler {
89
90
  return nil
90
91
  }
91
92
 
@@ -107,18 +108,11 @@ func (r RenderFunc) children() []UI {
107
108
  func (r RenderFunc) mount() error {
108
109
  c := getCurrentContext()
109
110
  if r.Mounted() {
110
- return errors.New("mounting component failed").
111
- Tag("reason", "already mounted").
111
+ panic("mounting component failed already mounted " + r.name() + r.Kind().String())
112
- Tag("name", r.name()).
113
- Tag("kind", r.Kind())
114
112
  }
115
-
116
113
  root := r.Render()
117
114
  if err := mount(root); err != nil {
118
- return errors.New("mounting component failed").
115
+ panic("mounting component failed " + r.name() + r.Kind().String())
119
- Tag("name", r.name()).
120
- Tag("kind", r.Kind()).
121
- Wrap(err)
122
116
  }
123
117
  root.setParent(c.this)
124
118
  c.root = root
element.go CHANGED
@@ -5,13 +5,14 @@ import (
5
5
  "io"
6
6
 
7
7
  "github.com/pyros2097/wapp/errors"
8
+ "github.com/pyros2097/wapp/js"
8
9
  )
9
10
 
10
11
  type elem struct {
11
12
  attrs map[string]string
12
13
  body []UI
13
- events map[string]eventHandler
14
+ events map[string]js.EventHandler
14
- jsvalue Value
15
+ jsvalue js.Value
15
16
  parentElem UI
16
17
  selfClosing bool
17
18
  tag string
@@ -22,7 +23,7 @@ func (e *elem) Kind() Kind {
22
23
  return HTML
23
24
  }
24
25
 
25
- func (e *elem) JSValue() Value {
26
+ func (e *elem) JSValue() js.Value {
26
27
  return e.jsvalue
27
28
  }
28
29
 
@@ -47,7 +48,7 @@ func (e *elem) attributes() map[string]string {
47
48
  return e.attrs
48
49
  }
49
50
 
50
- func (e *elem) eventHandlers() map[string]eventHandler {
51
+ func (e *elem) eventHandlers() map[string]js.EventHandler {
51
52
  return e.events
52
53
  }
53
54
 
@@ -71,7 +72,7 @@ func (e *elem) mount() error {
71
72
  Tag("kind", e.Kind())
72
73
  }
73
74
 
74
- v := Window().Get("document").Call("createElement", e.tag)
75
+ v := js.Window.Get("document").Call("createElement", e.tag)
75
76
  if !v.Truthy() {
76
77
  return errors.New("mounting ui element failed").
77
78
  Tag("reason", "create javascript node returned nil").
@@ -307,7 +308,7 @@ func (e *elem) delAttr(k string) {
307
308
  delete(e.attrs, k)
308
309
  }
309
310
 
310
- func (e *elem) updateEventHandler(handlers map[string]eventHandler) {
311
+ func (e *elem) updateEventHandler(handlers map[string]js.EventHandler) {
311
312
  for k, current := range e.events {
312
313
  if _, exists := handlers[k]; !exists {
313
314
  e.delJsEventHandler(k, current)
@@ -315,11 +316,11 @@ func (e *elem) updateEventHandler(handlers map[string]eventHandler) {
315
316
  }
316
317
 
317
318
  if e.events == nil && len(handlers) != 0 {
318
- e.events = make(map[string]eventHandler, len(handlers))
319
+ e.events = make(map[string]js.EventHandler, len(handlers))
319
320
  }
320
321
 
321
322
  for k, new := range handlers {
322
- if current, exists := e.events[k]; !current.equal(new) {
323
+ if current, exists := e.events[k]; !current.Equal(new) {
323
324
  if exists {
324
325
  e.delJsEventHandler(k, current)
325
326
  }
@@ -330,27 +331,24 @@ func (e *elem) updateEventHandler(handlers map[string]eventHandler) {
330
331
  }
331
332
  }
332
333
 
333
- func (e *elem) setEventHandler(k string, h EventHandler) {
334
+ func (e *elem) setEventHandler(k string, h js.EventHandlerFunc) {
334
335
  if e.events == nil {
335
- e.events = make(map[string]eventHandler)
336
+ e.events = make(map[string]js.EventHandler)
336
337
  }
337
338
 
338
- e.events[k] = eventHandler{
339
+ e.events[k] = js.NewEventHandler(k, h)
339
- event: k,
340
- value: h,
341
- }
342
340
  }
343
341
 
344
- func (e *elem) setJsEventHandler(k string, h eventHandler) {
342
+ func (e *elem) setJsEventHandler(k string, h js.EventHandler) {
345
- jshandler := makeJsEventHandler(e.self(), h.value)
343
+ jshandler := makeJsEventHandler(e.self(), h.Value)
346
- h.jsvalue = jshandler
344
+ h.JSvalue = jshandler
347
345
  e.events[k] = h
348
346
  e.JSValue().Call("addEventListener", k, jshandler)
349
347
  }
350
348
 
351
- func (e *elem) delJsEventHandler(k string, h eventHandler) {
349
+ func (e *elem) delJsEventHandler(k string, h js.EventHandler) {
352
- e.JSValue().Call("removeEventListener", k, h.jsvalue)
350
+ e.JSValue().Call("removeEventListener", k, h.JSvalue)
353
- h.jsvalue.Release()
351
+ h.JSvalue.Release()
354
352
  delete(e.events, k)
355
353
  }
356
354
 
@@ -407,7 +405,7 @@ func (e *elem) HtmlWithIndent(w io.Writer, indent int) {
407
405
  }
408
406
 
409
407
  type text struct {
410
- jsvalue Value
408
+ jsvalue js.Value
411
409
  parentElem UI
412
410
  value string
413
411
  }
@@ -421,7 +419,7 @@ func (t *text) Kind() Kind {
421
419
  return SimpleText
422
420
  }
423
421
 
424
- func (t *text) JSValue() Value {
422
+ func (t *text) JSValue() js.Value {
425
423
  return t.jsvalue
426
424
  }
427
425
 
@@ -448,7 +446,7 @@ func (t *text) attributes() map[string]string {
448
446
  return nil
449
447
  }
450
448
 
451
- func (t *text) eventHandlers() map[string]eventHandler {
449
+ func (t *text) eventHandlers() map[string]js.EventHandler {
452
450
  return nil
453
451
  }
454
452
 
@@ -473,7 +471,7 @@ func (t *text) mount() error {
473
471
  Tag("value", t.value)
474
472
  }
475
473
 
476
- t.jsvalue = Window().
474
+ t.jsvalue = js.Window.
477
475
  Get("document").
478
476
  Call("createTextNode", t.value)
479
477
 
js.go → js/js.go RENAMED
@@ -1,6 +1,9 @@
1
- package app
1
+ package js
2
2
 
3
+ import (
4
+ "fmt"
3
- import "net/url"
5
+ "net/url"
6
+ )
4
7
 
5
8
  // Type represents the JavaScript type of a Value.
6
9
  type Type int
@@ -222,3 +225,25 @@ func CopyBytesToGo(dst []byte, src Value) int {
222
225
  func CopyBytesToJS(dst Value, src []byte) int {
223
226
  return copyBytesToJS(dst, src)
224
227
  }
228
+
229
+ // EventHandler represents a function that can handle HTML events. They are
230
+ // always called on the UI goroutine.
231
+ type EventHandlerFunc func(e Event)
232
+
233
+ type EventHandler struct {
234
+ Event string
235
+ JSvalue Func
236
+ Value EventHandlerFunc
237
+ }
238
+
239
+ func NewEventHandler(e string, v EventHandlerFunc) EventHandler {
240
+ return EventHandler{
241
+ Event: e,
242
+ Value: v,
243
+ }
244
+ }
245
+
246
+ func (h EventHandler) Equal(o EventHandler) bool {
247
+ return h.Event == o.Event &&
248
+ fmt.Sprintf("%p", h.Value) == fmt.Sprintf("%p", o.Value)
249
+ }
js_nowasm.go → js/js_nowasm.go RENAMED
@@ -1,11 +1,13 @@
1
1
  // +build !wasm
2
2
 
3
- package app
3
+ package js
4
4
 
5
5
  import (
6
6
  "net/url"
7
7
  )
8
8
 
9
+ var Window = &browserWindow{value: value{}}
10
+
9
11
  type value struct{}
10
12
 
11
13
  func (v value) Bool() bool {
@@ -116,7 +118,7 @@ func (w browserWindow) CursorPosition() (x, y int) {
116
118
  panic("wasm required")
117
119
  }
118
120
 
119
- func (w browserWindow) setCursorPosition(x, y int) {
121
+ func (w browserWindow) SetCursorPosition(x, y int) {
120
122
  panic("wasm required")
121
123
  }
122
124
 
js_wasm.go → js/js_wasm.go RENAMED
@@ -1,4 +1,4 @@
1
- package app
1
+ package js
2
2
 
3
3
  import (
4
4
  "net/url"
@@ -8,6 +8,8 @@ import (
8
8
  "github.com/pyros2097/wapp/errors"
9
9
  )
10
10
 
11
+ var Window = &browserWindow{value: value{Value: js.Global()}}
12
+
11
13
  type value struct {
12
14
  js.Value
13
15
  }
@@ -149,7 +151,7 @@ func (w *browserWindow) CursorPosition() (x, y int) {
149
151
  return w.cursorX, w.cursorY
150
152
  }
151
153
 
152
- func (w *browserWindow) setCursorPosition(x, y int) {
154
+ func (w *browserWindow) SetCursorPosition(x, y int) {
153
155
  w.cursorX = x
154
156
  w.cursorY = y
155
157
  }
@@ -174,6 +176,18 @@ func (w *browserWindow) AddEventListener(event string, h EventHandler) func() {
174
176
  }
175
177
  }
176
178
 
179
+ func (w *browserWindow) Location() *Location {
180
+ return &Location{value: Window.Get("location")}
181
+ }
182
+
183
+ type Location struct {
184
+ value js.Value
185
+ }
186
+
187
+ func (l *Location) Reload() {
188
+ l.value.Call("reload")
189
+ }
190
+
177
191
  func val(v js.Value) Value {
178
192
  return value{Value: v}
179
193
  }
node.go CHANGED
@@ -1,11 +1,11 @@
1
1
  package app
2
2
 
3
3
  import (
4
- "fmt"
5
4
  "io"
6
5
  "reflect"
7
6
 
8
7
  "github.com/pyros2097/wapp/errors"
8
+ "github.com/pyros2097/wapp/js"
9
9
  )
10
10
 
11
11
  // UI is the interface that describes a user interface element such as
@@ -15,7 +15,7 @@ type UI interface {
15
15
  Kind() Kind
16
16
 
17
17
  // JSValue returns the javascript value linked to the element.
18
- JSValue() Value
18
+ JSValue() js.Value
19
19
 
20
20
  // Reports whether the element is mounted.
21
21
  Mounted() bool
@@ -24,7 +24,7 @@ type UI interface {
24
24
  self() UI
25
25
  setSelf(UI)
26
26
  attributes() map[string]string
27
- eventHandlers() map[string]eventHandler
27
+ eventHandlers() map[string]js.EventHandler
28
28
  parent() UI
29
29
  setParent(UI)
30
30
  children() []UI
@@ -123,28 +123,13 @@ func FilterUIElems(uis ...UI) []UI {
123
123
  return elems
124
124
  }
125
125
 
126
- // EventHandler represents a function that can handle HTML events. They are
127
- // always called on the UI goroutine.
128
- type EventHandler func(e Event)
129
-
130
- type eventHandler struct {
131
- event string
132
- jsvalue Func
133
- value EventHandler
134
- }
135
-
136
- func (h eventHandler) equal(o eventHandler) bool {
137
- return h.event == o.event &&
138
- fmt.Sprintf("%p", h.value) == fmt.Sprintf("%p", o.value)
139
- }
140
-
141
- func makeJsEventHandler(src UI, h EventHandler) Func {
126
+ func makeJsEventHandler(src UI, h js.EventHandlerFunc) js.Func {
142
- return FuncOf(func(this Value, args []Value) interface{} {
127
+ return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
143
128
  dispatch(func() {
144
129
  if !src.Mounted() {
145
130
  return
146
131
  }
147
- e := Event{
132
+ e := js.Event{
148
133
  Value: args[0],
149
134
  }
150
135
  trackMousePosition(e)
@@ -155,7 +140,7 @@ func makeJsEventHandler(src UI, h EventHandler) Func {
155
140
  })
156
141
  }
157
142
 
158
- func trackMousePosition(e Event) {
143
+ func trackMousePosition(e js.Event) {
159
144
  x := e.Get("clientX")
160
145
  if !x.Truthy() {
161
146
  return
@@ -166,7 +151,7 @@ func trackMousePosition(e Event) {
166
151
  return
167
152
  }
168
153
 
169
- Window().setCursorPosition(x.Int(), y.Int())
154
+ js.Window.SetCursorPosition(x.Int(), y.Int())
170
155
  }
171
156
 
172
157
  func isErrReplace(err error) bool {
raw.go CHANGED
@@ -6,6 +6,7 @@ import (
6
6
  "strings"
7
7
 
8
8
  "github.com/pyros2097/wapp/errors"
9
+ "github.com/pyros2097/wapp/js"
9
10
  )
10
11
 
11
12
  // Raw returns a ui element from the given raw value. HTML raw value must have a
@@ -29,7 +30,7 @@ func Raw(v string) UI {
29
30
  }
30
31
 
31
32
  type raw struct {
32
- jsvalue Value
33
+ jsvalue js.Value
33
34
  parentElem UI
34
35
  tag string
35
36
  value string
@@ -39,7 +40,7 @@ func (r *raw) Kind() Kind {
39
40
  return RawHTML
40
41
  }
41
42
 
42
- func (r *raw) JSValue() Value {
43
+ func (r *raw) JSValue() js.Value {
43
44
  return r.jsvalue
44
45
  }
45
46
 
@@ -66,7 +67,7 @@ func (r *raw) attributes() map[string]string {
66
67
  return nil
67
68
  }
68
69
 
69
- func (r *raw) eventHandlers() map[string]eventHandler {
70
+ func (r *raw) eventHandlers() map[string]js.EventHandler {
70
71
  return nil
71
72
  }
72
73
 
@@ -90,7 +91,7 @@ func (r *raw) mount() error {
90
91
  Tag("kind", r.Kind())
91
92
  }
92
93
 
93
- wrapper := Window().Get("document").Call("createElement", "div")
94
+ wrapper := js.Window.Get("document").Call("createElement", "div")
94
95
  wrapper.Set("innerHTML", r.value)
95
96
 
96
97
  value := wrapper.Get("firstChild")
readme.md CHANGED
@@ -137,13 +137,13 @@ func main() {
137
137
  The directory structure is a lot lke this,
138
138
 
139
139
  ```sh
140
- ── assets
140
+ ── assets
141
- | └── icon.png
141
+ └── icon.png
142
- | └── styles.css
142
+ └── styles.css
143
143
  ├── pages
144
- | └── index
144
+ └── index
145
- | └── main.go
145
+ └── main.go
146
- | └── about
146
+ └── about
147
- | └── main.go
147
+ └── main.go
148
- |── main.go
148
+ ── main.go
149
149
  ```
selectors.go CHANGED
@@ -7,6 +7,7 @@ import (
7
7
  "sort"
8
8
 
9
9
  "github.com/pyros2097/wapp/errors"
10
+ "github.com/pyros2097/wapp/js"
10
11
  )
11
12
 
12
13
  // RangeLoop represents a control structure that iterates within a slice, an
@@ -90,7 +91,7 @@ func (r rangeLoop) Kind() Kind {
90
91
  return Selector
91
92
  }
92
93
 
93
- func (r rangeLoop) JSValue() Value {
94
+ func (r rangeLoop) JSValue() js.Value {
94
95
  return nil
95
96
  }
96
97
 
@@ -117,7 +118,7 @@ func (r rangeLoop) attributes() map[string]string {
117
118
  return nil
118
119
  }
119
120
 
120
- func (r rangeLoop) eventHandlers() map[string]eventHandler {
121
+ func (r rangeLoop) eventHandlers() map[string]js.EventHandler {
121
122
  return nil
122
123
  }
123
124
 
@@ -203,7 +204,7 @@ func (c condition) Kind() Kind {
203
204
  return Selector
204
205
  }
205
206
 
206
- func (c condition) JSValue() Value {
207
+ func (c condition) JSValue() js.Value {
207
208
  return nil
208
209
  }
209
210
 
@@ -230,7 +231,7 @@ func (c condition) attributes() map[string]string {
230
231
  return nil
231
232
  }
232
233
 
233
- func (c condition) eventHandlers() map[string]eventHandler {
234
+ func (c condition) eventHandlers() map[string]js.EventHandler {
234
235
  return nil
235
236
  }
236
237
 
utils.go CHANGED
@@ -3,6 +3,8 @@ package app
3
3
  import (
4
4
  "io"
5
5
  "unsafe"
6
+
7
+ "github.com/pyros2097/wapp/js"
6
8
  )
7
9
 
8
10
  var (
@@ -17,7 +19,7 @@ type Context struct {
17
19
  // The JavaScript value of the element tied to the context. This is a
18
20
  // shorthand for:
19
21
  // ctx.Src.JSValue()
20
- JSSrc Value
22
+ JSSrc js.Value
21
23
  }
22
24
 
23
25
  // Dispatcher is a function that executes the given function on the goroutine