From 054e87b912d96d7b2676b1031f8f6d3f1466536c Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Sun, 16 Sep 2018 08:23:01 -0400 Subject: [PATCH] Initial progress/prototype --- cmd/dom/main.go | 22 +++ document.go | 94 +++++++++++++ element.go | 67 +++++++++ eventtarget.go | 8 ++ generate/document.json | 75 ++++++++++ generate/element.json | 17 +++ generate/eventtarget.json | 20 +++ generate/main.go | 278 ++++++++++++++++++++++++++++++++++++++ generate/node.json | 59 ++++++++ go.mod | 1 + helpers.go | 14 ++ init.go | 12 ++ node.go | 6 + 13 files changed, 673 insertions(+) create mode 100644 cmd/dom/main.go create mode 100644 document.go create mode 100644 element.go create mode 100644 eventtarget.go create mode 100644 generate/document.json create mode 100644 generate/element.json create mode 100644 generate/eventtarget.json create mode 100644 generate/main.go create mode 100644 generate/node.json create mode 100644 go.mod create mode 100644 helpers.go create mode 100644 init.go create mode 100644 node.go diff --git a/cmd/dom/main.go b/cmd/dom/main.go new file mode 100644 index 0000000..3d9d3c8 --- /dev/null +++ b/cmd/dom/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "syscall/js" + + dom "github.com/brettlangdon/go-dom/v1" +) + +func onLoad(evt js.Value) { + fmt.Println("LOADED") + fmt.Printf("%#v\r\n", evt) +} + +func main() { + dom.Document.AddEventListener("DOMContentLoaded", js.NewEventCallback(0, onLoad)) + fmt.Printf("%#v\r\n", dom.Document) + body := dom.Document.GetBody() + for i := 0; i < 50; i += 1 { + body.AppendChild(dom.Document.CreateElement(fmt.Sprintf("TEST-%d", i))) + } +} diff --git a/document.go b/document.go new file mode 100644 index 0000000..a11364b --- /dev/null +++ b/document.go @@ -0,0 +1,94 @@ +// DO NOT EDIT - generated file +package dom + +import "syscall/js" + +type document struct { + js.Value +} + +func (d *document) GetBody() *Element { + val := d.Get("body") + return &Element{Value: val} +} +func (d *document) CreateElement(tagName string) *Element { + val := d.Call("createElement", ToValue(tagName)) + return &Element{Value: val} +} +func (d *document) GetElementById(id string) *Element { + val := d.Call("getElementById", ToValue(id)) + return &Element{Value: val} +} +func (d *document) QuerySelector(selector string) *Element { + val := d.Call("querySelector", ToValue(selector)) + return &Element{Value: val} +} +func (d *document) QuerySelectorAll(selector string) []*Element { + val := d.Call("querySelectorAll", ToValue(selector)) + elms := make([]*Element, 0) + for i := 0; i < val.Length(); i += 1 { + elms = append(elms, &Element{Value: val.Index(i)}) + } + return elms +} +func (d *document) GetElementsByName(name string) []*Element { + val := d.Call("getElementsByName", ToValue(name)) + elms := make([]*Element, 0) + for i := 0; i < val.Length(); i += 1 { + elms = append(elms, &Element{Value: val.Index(i)}) + } + return elms +} +func (d *document) Write(markup string) { + d.Call("write", ToValue(markup)) +} +func (d *document) AddEventListener(t string, listener js.Callback) { + d.Call("addEventListener", ToValue(t), ToValue(listener)) +} +func (d *document) AppendChild(aChild *Element) *Element { + val := d.Call("appendChild", ToValue(aChild)) + return &Element{Value: val} +} +func (d *document) GetBaseURI() string { + val := d.Get("baseURI") + return val.String() +} +func (d *document) GetFirstChild() *Element { + val := d.Get("firstChild") + return &Element{Value: val} +} +func (d *document) GetLastChild() *Element { + val := d.Get("lastChild") + return &Element{Value: val} +} +func (d *document) GetNextSibling() *Element { + val := d.Get("nextSibling") + return &Element{Value: val} +} +func (d *document) GetPreviousSibling() *Element { + val := d.Get("previousSibling") + return &Element{Value: val} +} +func (d *document) GetParentElement() *Element { + val := d.Get("parentElement") + return &Element{Value: val} +} +func (d *document) GetRootElement() *Element { + val := d.Get("rootElement") + return &Element{Value: val} +} +func (d *document) GetPrefix() string { + val := d.Get("prefix") + return val.String() +} +func (d *document) GetNodeName() string { + val := d.Get("nodeName") + return val.String() +} +func (d *document) GetTextContent() string { + val := d.Get("textContent") + return val.String() +} +func (d *document) SetTextContent(v string) { + d.Set("textContent", v) +} diff --git a/element.go b/element.go new file mode 100644 index 0000000..e84bdea --- /dev/null +++ b/element.go @@ -0,0 +1,67 @@ +// DO NOT EDIT - generated file +package dom + +import "syscall/js" + +type Element struct { + js.Value +} + +func (e *Element) GetClassName() string { + val := e.Get("className") + return val.String() +} +func (e *Element) GetId() string { + val := e.Get("id") + return val.String() +} +func (e *Element) AddEventListener(t string, listener js.Callback) { + e.Call("addEventListener", ToValue(t), ToValue(listener)) +} +func (e *Element) AppendChild(aChild *Element) *Element { + val := e.Call("appendChild", ToValue(aChild)) + return &Element{Value: val} +} +func (e *Element) GetBaseURI() string { + val := e.Get("baseURI") + return val.String() +} +func (e *Element) GetFirstChild() *Element { + val := e.Get("firstChild") + return &Element{Value: val} +} +func (e *Element) GetLastChild() *Element { + val := e.Get("lastChild") + return &Element{Value: val} +} +func (e *Element) GetNextSibling() *Element { + val := e.Get("nextSibling") + return &Element{Value: val} +} +func (e *Element) GetPreviousSibling() *Element { + val := e.Get("previousSibling") + return &Element{Value: val} +} +func (e *Element) GetParentElement() *Element { + val := e.Get("parentElement") + return &Element{Value: val} +} +func (e *Element) GetRootElement() *Element { + val := e.Get("rootElement") + return &Element{Value: val} +} +func (e *Element) GetPrefix() string { + val := e.Get("prefix") + return val.String() +} +func (e *Element) GetNodeName() string { + val := e.Get("nodeName") + return val.String() +} +func (e *Element) GetTextContent() string { + val := e.Get("textContent") + return val.String() +} +func (e *Element) SetTextContent(v string) { + e.Set("textContent", v) +} diff --git a/eventtarget.go b/eventtarget.go new file mode 100644 index 0000000..ff3e7c3 --- /dev/null +++ b/eventtarget.go @@ -0,0 +1,8 @@ +// DO NOT EDIT - generated file +package dom + +import "syscall/js" + +type EventTarget interface { + AddEventListener(t string, listener js.Callback) +} diff --git a/generate/document.json b/generate/document.json new file mode 100644 index 0000000..43aa723 --- /dev/null +++ b/generate/document.json @@ -0,0 +1,75 @@ +{ + "Type": "document", + "Implements": [ + "EventTarget", + "Node" + ], + "Properties": [ + { + "Name": "body", + "Type": "*Element" + } + ], + "Functions": [ + { + "Name": "createElement", + "Arguments": [ + { + "Name": "tagName", + "Type": "string" + } + ], + "ReturnType": "*Element" + }, + { + "Name": "getElementById", + "Arguments": [ + { + "Name": "id", + "Type": "string" + } + ], + "ReturnType": "*Element" + }, + { + "Name": "querySelector", + "Arguments": [ + { + "Name": "selector", + "Type": "string" + } + ], + "ReturnType": "*Element" + }, + { + "Name": "querySelectorAll", + "Arguments": [ + { + "Name": "selector", + "Type": "string" + } + ], + "ReturnType": "[]*Element" + }, + { + "Name": "getElementsByName", + "Arguments": [ + { + "Name": "name", + "Type": "string" + } + ], + "ReturnType": "[]*Element" + }, + { + "Name": "write", + "Arguments": [ + { + "Name": "markup", + "Type": "string" + } + ], + "ReturnType": null + } + ] +} diff --git a/generate/element.json b/generate/element.json new file mode 100644 index 0000000..7cca74b --- /dev/null +++ b/generate/element.json @@ -0,0 +1,17 @@ +{ + "Type": "Element", + "Implements": [ + "EventTarget", + "Node" + ], + "Properties": [ + { + "Name": "className", + "Type": "string" + }, + { + "Name": "id", + "Type": "string" + } + ] +} diff --git a/generate/eventtarget.json b/generate/eventtarget.json new file mode 100644 index 0000000..67f48b1 --- /dev/null +++ b/generate/eventtarget.json @@ -0,0 +1,20 @@ +{ + "Type": "EventTarget", + "ImportJS": true, + "Interface": true, + "Functions": [ + { + "Name": "addEventListener", + "Arguments": [ + { + "Name": "t", + "Type": "string" + }, + { + "Name": "listener", + "Type": "js.Callback" + } + ] + } + ] +} diff --git a/generate/main.go b/generate/main.go new file mode 100644 index 0000000..9da3f61 --- /dev/null +++ b/generate/main.go @@ -0,0 +1,278 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/format" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +type TypeProperty struct { + Name string + Type string + Setter bool +} + +func (p TypeProperty) GetterName() string { + return "Get" + strings.Title(p.Name) +} + +func (p TypeProperty) SetterName() string { + return "Set" + strings.Title(p.Name) +} + +type FunctionArgument struct { + Name string + Type string +} + +type TypeFunction struct { + Name string + Arguments []FunctionArgument + ReturnType string +} + +func (f TypeFunction) GoName() string { + return strings.Title(f.Name) +} + +type TypeStructure struct { + Type string + ImportJS bool + Interface bool + Inherits []string + Implements []string + Properties []TypeProperty + Functions []TypeFunction +} + +func (t TypeStructure) Filename() string { + return fmt.Sprintf("%s.go", strings.ToLower(t.Type)) +} + +func (t TypeStructure) ShortName() string { + return strings.ToLower(string(t.Type[0])) +} + +func (t TypeStructure) writeArguments(out *bytes.Buffer, args []FunctionArgument) { + for i, arg := range args { + if i > 0 { + out.WriteString(",") + } + out.WriteString(fmt.Sprintf("%s %s", arg.Name, arg.Type)) + } +} + +func (t TypeStructure) writeFunctions(out *bytes.Buffer, funcs []TypeFunction) error { + for _, f := range funcs { + out.WriteString(fmt.Sprintf("func (%s *%s) %s(", t.ShortName(), t.Type, f.GoName())) + for i, arg := range f.Arguments { + if i > 0 { + out.WriteString(",") + } + out.WriteString(fmt.Sprintf("%s %s", arg.Name, arg.Type)) + } + out.WriteString(fmt.Sprintf(") %s {\r\n", f.ReturnType)) + + if f.ReturnType != "" { + out.WriteString("val := ") + } + out.WriteString(fmt.Sprintf("%s.Call(\"%s\"", t.ShortName(), f.Name)) + for _, arg := range f.Arguments { + out.WriteString(",") + out.WriteString(fmt.Sprintf("ToValue(%s)", arg.Name)) + } + out.WriteString(")\r\n") + + switch f.ReturnType { + case "string": + out.WriteString("return val.String()\r\n") + case "*Element": + out.WriteString("return &Element{ Value: val }\r\n") + case "[]*Element": + out.WriteString("elms := make([]*Element, 0)\r\n") + out.WriteString("for i := 0; i < val.Length(); i += 1 {\r\n") + out.WriteString("\telms = append(elms, &Element{Value: val.Index(i)})\r\n") + out.WriteString("}\r\n") + out.WriteString("return elms\r\n") + case "": + // skip + default: + return fmt.Errorf("Unknown function return type %s", f.ReturnType) + } + + out.WriteString("}\r\n") + } + return nil +} + +func (t TypeStructure) writeProperties(out *bytes.Buffer, props []TypeProperty) error { + for _, p := range props { + out.WriteString(fmt.Sprintf("func (%s *%s) %s() %s {\r\n", t.ShortName(), t.Type, p.GetterName(), p.Type)) + out.WriteString(fmt.Sprintf("\tval := %s.Get(\"%s\")\r\n", t.ShortName(), p.Name)) + switch p.Type { + case "string": + out.WriteString("return val.String()\r\n") + case "*Element": + out.WriteString("return &Element{ Value: val }\r\n") + case "[]*Element": + out.WriteString("elms := make([]*Element, 0)\r\n") + out.WriteString("for i := 0; i < val.Length(); i += 1 {\r\n") + out.WriteString("\telms = append(elms, &Element{Value: val.Index(i)})\r\n") + out.WriteString("}\r\n") + out.WriteString("return elms\r\n") + case "": + // skip + default: + return fmt.Errorf("Unknown property return type %s", p.Type) + } + out.WriteString(fmt.Sprintf("}\r\n")) + + if p.Setter { + out.WriteString(fmt.Sprintf("func (%s *%s) %s(v %s){\r\n", t.ShortName(), t.Type, p.SetterName(), p.Type)) + out.WriteString(fmt.Sprintf("\t%s.Set(\"%s\", v)\r\n", t.ShortName(), p.Name)) + out.WriteString("}\r\n") + } + } + return nil +} + +func (t TypeStructure) generateInterface(out *bytes.Buffer, types map[string]TypeStructure) error { + out.WriteString(fmt.Sprintf("type %s interface {\r\n", t.Type)) + + for _, f := range t.Functions { + out.WriteString(fmt.Sprintf("\t%s(", f.GoName())) + t.writeArguments(out, f.Arguments) + out.WriteString(fmt.Sprintf(") %s\r\n", f.ReturnType)) + } + + out.WriteString("}\r\n") + return nil +} + +func (t TypeStructure) generateStruct(out *bytes.Buffer, types map[string]TypeStructure) (err error) { + out.WriteString(fmt.Sprintf("type %s struct {\r\n", t.Type)) + + out.WriteString("\tjs.Value\r\n") + for _, name := range t.Inherits { + out.WriteString(fmt.Sprintf("\t%s\r\n", name)) + } + + out.WriteString("}\r\n") + + // Properties + err = t.writeProperties(out, t.Properties) + if err != nil { + return + } + + // Functions + err = t.writeFunctions(out, t.Functions) + if err != nil { + return + } + + // Interfaces + for _, it := range t.Implements { + i, ok := types[it] + if !ok { + return fmt.Errorf("%q cannot implement unknown type %q", t.Type, it) + } + + // Functions + err = t.writeFunctions(out, i.Functions) + if err != nil { + return + } + + // Properties + err = t.writeProperties(out, i.Properties) + if err != nil { + return + } + } + + return +} + +func (t TypeStructure) Generate(types map[string]TypeStructure) ([]byte, error) { + out := bytes.NewBuffer(make([]byte, 0)) + out.WriteString("// DO NOT EDIT - generated file\r\n") + out.WriteString("package dom\r\n") + + if t.Interface == false || t.ImportJS == true { + out.WriteString("import \"syscall/js\"\r\n") + } + + var err error + if t.Interface { + err = t.generateInterface(out, types) + } else { + err = t.generateStruct(out, types) + } + if err != nil { + return nil, err + } + + return format.Source(out.Bytes()) +} + +func loadFile(fname string) (t TypeStructure, err error) { + fp, err := os.Open(fname) + if err != nil { + return + } + defer fp.Close() + + val, err := ioutil.ReadAll(fp) + if err != nil { + return + } + + err = json.Unmarshal(val, &t) + return +} + +func processType(t TypeStructure, types map[string]TypeStructure) error { + contents, err := t.Generate(types) + if err != nil { + return err + } + + out, err := os.OpenFile(t.Filename(), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer out.Close() + _, err = out.Write(contents) + return err +} + +func main() { + matches, err := filepath.Glob("generate/*.json") + if err != nil { + log.Panic(err) + } + + types := make(map[string]TypeStructure) + for _, fname := range matches { + typeStructure, err := loadFile(fname) + if err != nil { + log.Panic(err) + } + types[typeStructure.Type] = typeStructure + } + + for _, t := range types { + log.Printf("Generating %s\r\n", t.Type) + err = processType(t, types) + if err != nil { + log.Panic(err) + } + } +} diff --git a/generate/node.json b/generate/node.json new file mode 100644 index 0000000..b9dedb9 --- /dev/null +++ b/generate/node.json @@ -0,0 +1,59 @@ +{ + "Type": "Node", + "Interface": true, + "Properties": [ + { + "Name": "baseURI", + "Type": "string" + }, + { + "Name": "firstChild", + "Type": "*Element" + }, + { + "Name": "lastChild", + "Type": "*Element" + }, + { + "Name": "nextSibling", + "Type": "*Element" + }, + { + "Name": "previousSibling", + "Type": "*Element" + }, + { + "Name": "parentElement", + "Type": "*Element" + }, + { + "Name": "rootElement", + "Type": "*Element" + }, + { + "Name": "prefix", + "Type": "string" + }, + { + "Name": "nodeName", + "Type": "string" + }, + { + "Name": "textContent", + "Type": "string", + "Setter": true + } + ], + "Functions": [ + { + "Name": "appendChild", + "Arguments": [ + { + "Name": "aChild", + "Type": "*Element" + } + ], + "ReturnType": "*Element" + } + ] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fb79674 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module github.com/brettlangdon/go-dom/v1 diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..4ecb95d --- /dev/null +++ b/helpers.go @@ -0,0 +1,14 @@ +package dom + +import "syscall/js" + +func ToValue(v interface{}) js.Value { + switch t := v.(type) { + case *Element: + return t.Value + case *document: + return t.Value + default: + return js.ValueOf(v) + } +} diff --git a/init.go b/init.go new file mode 100644 index 0000000..5c90010 --- /dev/null +++ b/init.go @@ -0,0 +1,12 @@ +//go:generate go run generate/main.go +package dom + +import "syscall/js" + +var ( + Document *document +) + +func init() { + Document = &document{Value: js.Global().Get("document")} +} diff --git a/node.go b/node.go new file mode 100644 index 0000000..d7e51fe --- /dev/null +++ b/node.go @@ -0,0 +1,6 @@ +// DO NOT EDIT - generated file +package dom + +type Node interface { + AppendChild(aChild *Element) *Element +}