/
jsutil.go
85 lines (78 loc) · 2.81 KB
/
jsutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//go:build js
// Package jsutil provides utility functions for interacting with
// native JavaScript APIs via syscall/js API.
// It has support for common types in honnef.co/go/js/dom/v2.
package jsutil
import (
"encoding/json"
"fmt"
"reflect"
"syscall/js"
"honnef.co/go/js/dom/v2"
)
// Wrap returns a wrapper func that handles the conversion from native JavaScript js.Value parameters
// to the following types.
//
// It supports js.Value (left unmodified), dom.Document, dom.Element, dom.Event, dom.HTMLElement, dom.Node.
// It has to be one of those types exactly; it can't be another type that implements the interface like *dom.BasicElement.
//
// For other types, the input is assumed to be a JSON string which is then unmarshalled into that type.
//
// If the number of arguments provided to the wrapped func doesn't match
// the number of arguments for original func, it panics.
//
// Here is example usage:
//
// <span onclick="Handler(event, this, {{.SomeStruct | json}});">Example</span>
//
// func Handler(event dom.Event, htmlElement dom.HTMLElement, data someStruct) {
// data.Foo = ... // Use event, htmlElement, data.
// }
//
// func main() {
// js.Global().Set("Handler", jsutil.Wrap(Handler))
// }
func Wrap(fn interface{}) js.Func {
v := reflect.ValueOf(fn)
return js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
if len(args) != v.Type().NumIn() {
panic(fmt.Errorf("wrapped %v got %v arguments, want %v", v.Type().String(), len(args), v.Type().NumIn()))
}
in := make([]reflect.Value, v.Type().NumIn())
for i := range in {
switch t := v.Type().In(i); t {
// js.Value is passed through.
case typeOf((*js.Value)(nil)):
in[i] = reflect.ValueOf(args[i])
// dom types are wrapped.
case typeOf((*dom.Document)(nil)):
in[i] = reflect.ValueOf(dom.WrapDocument(args[i]))
case typeOf((*dom.Element)(nil)):
in[i] = reflect.ValueOf(dom.WrapElement(args[i]))
case typeOf((*dom.Event)(nil)):
in[i] = reflect.ValueOf(dom.WrapEvent(args[i]))
case typeOf((*dom.HTMLElement)(nil)):
in[i] = reflect.ValueOf(dom.WrapHTMLElement(args[i]))
case typeOf((*dom.Node)(nil)):
in[i] = reflect.ValueOf(dom.WrapNode(args[i]))
// Unmarshal incoming encoded JSON into the Go type.
default:
if args[i].Type() != js.TypeString {
panic(fmt.Errorf("jsutil: incoming value type is %s; want a string with JSON content", args[i].Type()))
}
p := reflect.New(t)
err := json.Unmarshal([]byte(args[i].String()), p.Interface())
if err != nil {
panic(fmt.Errorf("jsutil: unmarshaling JSON %q into type %s failed: %v", args[i], t, err))
}
in[i] = reflect.Indirect(p)
}
}
v.Call(in)
return nil
})
}
// typeOf returns the reflect.Type of what the pointer points to.
func typeOf(pointer interface{}) reflect.Type {
return reflect.TypeOf(pointer).Elem()
}