├── .gitignore ├── helpers ├── math.go ├── math_test.go ├── utils.go └── utils_test.go ├── key.go ├── key_test.go ├── Makefile ├── attrs.go ├── h.go ├── textnode.go ├── attrs_test.go ├── textnode_test.go ├── Gopkg.toml ├── .travis.yml ├── h_test.go ├── patch.go ├── reconciler.go ├── Gopkg.lock ├── reconciler_test.go ├── dom └── dom.go ├── vnode.go ├── vnode_test.go ├── cover.out ├── README.md └── mock ├── dom.go └── node.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /helpers/math.go: -------------------------------------------------------------------------------- 1 | package vnh 2 | 3 | func Max(x int, y int) int { 4 | if x > y { 5 | return x 6 | } 7 | 8 | return y 9 | } 10 | -------------------------------------------------------------------------------- /helpers/math_test.go: -------------------------------------------------------------------------------- 1 | package vnh 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMathMax(t *testing.T) { 10 | min := 1 11 | max := 2 12 | 13 | assert.Equal(t, max, Max(min, max)) 14 | assert.Equal(t, max, Max(max, min)) 15 | } 16 | -------------------------------------------------------------------------------- /helpers/utils.go: -------------------------------------------------------------------------------- 1 | package vnh 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func IsNil(toVerify interface{}) bool { 8 | if toVerify == nil { 9 | return true 10 | } 11 | 12 | return reflect.ValueOf(toVerify).IsNil() 13 | } 14 | 15 | func NotNil(toVerify interface{}) bool { 16 | return !IsNil(toVerify) 17 | } 18 | -------------------------------------------------------------------------------- /key.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | type KeyIdentifier interface { 4 | SetValue(string) 5 | GetValue() string 6 | } 7 | 8 | type Key struct { 9 | Value string 10 | } 11 | 12 | func (Key *Key) SetValue(value string) { 13 | Key.Value = value 14 | } 15 | 16 | func (key *Key) GetValue() string { 17 | return key.Value 18 | } 19 | -------------------------------------------------------------------------------- /key_test.go: -------------------------------------------------------------------------------- 1 | package vn_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | import vn "github.com/mfrachet/go-vdom-wasm" 10 | 11 | func TestKey_Accessors(t *testing.T) { 12 | key := vn.Key{} 13 | key.SetValue("Hello") 14 | 15 | assert.Equal(t, "Hello", key.GetValue()) 16 | } 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | # deps: 3 | # $(GOPATH)/bin/dep ensure 4 | prepare-test: 5 | $(GOPATH)/bin/mockgen -source=dom/dom.go -destination=mock/dom.go -package=mock 6 | $(GOPATH)/bin/mockgen -source=vnode.go -destination=mock/node.go -package=mock 7 | test: 8 | GOOS=js GOARCH=wasm go test ./... -exec="$(shell go env GOROOT)/misc/wasm/go_js_wasm_exec" 9 | test-cover: 10 | GOOS=js GOARCH=wasm go test ./... -exec="$(shell go env GOROOT)/misc/wasm/go_js_wasm_exec" -coverprofile=cover.out 11 | go tool cover -html=cover.out -------------------------------------------------------------------------------- /attrs.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | "syscall/js" 5 | 6 | vnh "github.com/mfrachet/go-vdom-wasm/helpers" 7 | ) 8 | 9 | type Ev = map[string]func(js.Value, []js.Value) interface{} 10 | 11 | type Props = map[string]string 12 | 13 | type Attrs struct { 14 | Props *Props 15 | Events *Ev 16 | } 17 | 18 | func Sanitize(attrs *Attrs) *Attrs { 19 | if vnh.IsNil(attrs) { 20 | return &Attrs{Props: &Props{}, Events: &Ev{}} 21 | } 22 | 23 | if vnh.IsNil(attrs.Props) { 24 | attrs.Props = &Props{} 25 | } 26 | 27 | if vnh.IsNil(attrs.Events) { 28 | attrs.Events = &Ev{} 29 | } 30 | 31 | return attrs 32 | } 33 | -------------------------------------------------------------------------------- /helpers/utils_test.go: -------------------------------------------------------------------------------- 1 | package vnh 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestIsNil(t *testing.T) { 9 | var node *[]int 10 | 11 | str := "Hello world" 12 | pointer := &str 13 | 14 | assert.Equal(t, true, IsNil(nil)) 15 | assert.Equal(t, true, IsNil(node)) 16 | assert.Equal(t, false, IsNil(pointer)) 17 | } 18 | 19 | func TestNotNil(t *testing.T) { 20 | var node *[]int 21 | 22 | str := "Hello world" 23 | pointer := &str 24 | 25 | assert.Equal(t, false, NotNil(nil)) 26 | assert.Equal(t, true, NotNil(pointer)) 27 | assert.Equal(t, false, NotNil(node)) 28 | } 29 | -------------------------------------------------------------------------------- /h.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | func H(tagName string, params ...interface{}) Node { 4 | var key KeyIdentifier 5 | var children Children 6 | var textNode *TextNode 7 | attrs := &Attrs{} 8 | 9 | for _, param := range params { 10 | switch (param).(type) { 11 | case string: 12 | textNode = NewTextnode((param).(string)) 13 | case KeyIdentifier: 14 | key = param.(KeyIdentifier) 15 | case *Props: 16 | attrs.Props = param.(*Props) 17 | case *Ev: 18 | attrs.Events = param.(*Ev) 19 | default: 20 | children = param.(Children) 21 | } 22 | } 23 | 24 | sanitizedAttrs := Sanitize(attrs) 25 | 26 | return NewNode(tagName, sanitizedAttrs, children, textNode, nil, key) 27 | } 28 | -------------------------------------------------------------------------------- /textnode.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 5 | ) 6 | 7 | type TextNode struct { 8 | value string 9 | element *vnd.DomNode 10 | } 11 | 12 | func NewTextnode(value string) *TextNode { 13 | return &TextNode{value, nil} 14 | } 15 | 16 | func (textNode *TextNode) GetElement() *vnd.DomNode { 17 | return textNode.element 18 | } 19 | 20 | func (textNode *TextNode) GetValue() string { 21 | return textNode.value 22 | } 23 | 24 | func (textNode *TextNode) SetElement(element vnd.DomNode) { 25 | textNode.element = &element 26 | } 27 | 28 | func (textNode *TextNode) IsSame(other *TextNode) bool { 29 | return textNode.GetValue() == other.GetValue() 30 | 31 | } 32 | -------------------------------------------------------------------------------- /attrs_test.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAttrs_Sanitize(t *testing.T) { 10 | attrs := &Attrs{} 11 | 12 | sanitized := Sanitize(attrs) 13 | 14 | assert.Equal(t, false, sanitized.Props == nil) 15 | assert.Equal(t, false, sanitized.Events == nil) 16 | } 17 | 18 | func TestAttrs_Sanitize_Nil_Attrs(t *testing.T) { 19 | attrs := &Attrs{Props: &Props{}, Events: &Ev{}} 20 | 21 | sanitized := Sanitize(nil) 22 | 23 | assert.Equal(t, sanitized, attrs) 24 | } 25 | 26 | func TestAttrs_Sanitize_No_Changes(t *testing.T) { 27 | attrs := &Attrs{Props: &Props{}, Events: &Ev{}} 28 | 29 | sanitized := Sanitize(attrs) 30 | 31 | assert.Equal(t, attrs, sanitized) 32 | } 33 | -------------------------------------------------------------------------------- /textnode_test.go: -------------------------------------------------------------------------------- 1 | package vn_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/mock/gomock" 7 | vn "github.com/mfrachet/go-vdom-wasm" 8 | "github.com/mfrachet/go-vdom-wasm/mock" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestTextNode_IsSame(t *testing.T) { 13 | a := vn.NewTextnode("Hello world") 14 | b := vn.NewTextnode("Hello world") 15 | c := vn.NewTextnode("Hello world2") 16 | 17 | assert.Equal(t, true, a.IsSame(b)) 18 | assert.Equal(t, false, a.IsSame(c)) 19 | } 20 | 21 | func TestTextNode_Accessors(t *testing.T) { 22 | ctrl := gomock.NewController(t) 23 | defer ctrl.Finish() 24 | 25 | a := vn.NewTextnode("Hello world") 26 | 27 | mockDomNode := mock.NewMockDomNode(ctrl) 28 | a.SetElement(mockDomNode) 29 | 30 | assert.Equal(t, mockDomNode, *a.GetElement()) 31 | assert.Equal(t, "Hello world", a.GetValue()) 32 | } 33 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/golang/mock" 30 | version = "1.3.1" 31 | 32 | [[constraint]] 33 | name = "github.com/stretchr/testify" 34 | version = "1.4.0" 35 | 36 | [prune] 37 | go-tests = true 38 | unused-packages = true 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | install: 3 | # From vecty's https://github.com/gopherjs/vecty/blob/master/.travis.yml 4 | # Manually download and install Go 1.12 because the Travis / gimme version 5 | # is broken (see https://travis-ci.community/t/goos-js-goarch-wasm-go-run-fails-panic-newosproc-not-implemented/1651/6) 6 | - wget -O go.tar.gz https://dl.google.com/go/go1.12.linux-amd64.tar.gz 7 | - tar -C ~ -xzf go.tar.gz 8 | - rm go.tar.gz 9 | - export GOROOT=~/go 10 | - export GOPATH=/home/travis/go 11 | - export PATH=$GOROOT/bin:$PATH 12 | - export GOBIN=/home/travis/go/bin 13 | - go version 14 | - go env 15 | before_script: 16 | - mkdir /home/travis/go/src/mfrachet 17 | - mv /home/travis/build/mfrachet/go-vdom-wasm /home/travis/go/src/mfrachet/go-vdom-wasm 18 | - cd /home/travis/go/src/mfrachet/go-vdom-wasm 19 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 20 | - /home/travis/go/bin/dep ensure 21 | script: 22 | - make 23 | -------------------------------------------------------------------------------- /h_test.go: -------------------------------------------------------------------------------- 1 | package vn_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | import vn "github.com/mfrachet/go-vdom-wasm" 10 | 11 | func TestH_WithText(t *testing.T) { 12 | expected := vn.NewNode("div", &vn.Attrs{Props: &vn.Props{}, Events: &vn.Ev{}}, nil, vn.NewTextnode("Hello world"), nil, nil) 13 | 14 | vNode := vn.H("div", "Hello world") 15 | 16 | assert.Equal(t, expected, vNode) 17 | } 18 | 19 | func TestH_WithChildren(t *testing.T) { 20 | child := vn.H("span", "Hello world") 21 | 22 | expected := vn.NewNode("div", &vn.Attrs{Props: &vn.Props{}, Events: &vn.Ev{}}, vn.Children{child}, nil, nil, nil) 23 | 24 | vNode := vn.H("div", vn.Children{child}) 25 | 26 | assert.Equal(t, expected, vNode) 27 | } 28 | 29 | func TestH_WithAttributes(t *testing.T) { 30 | exectedWithoutAttrs := vn.NewNode("div", &vn.Attrs{Props: &vn.Props{}, Events: &vn.Ev{}}, nil, vn.NewTextnode("Hello world"), nil, nil) 31 | vNodeWithoutAttrs := vn.H("div", "Hello world") 32 | 33 | assert.Equal(t, exectedWithoutAttrs, vNodeWithoutAttrs) 34 | 35 | exectedWithAttrs := vn.NewNode("div", &vn.Attrs{Props: &vn.Props{"class": "navbar"}, Events: &vn.Ev{}}, nil, vn.NewTextnode("Hello world"), nil, nil) 36 | vNodeWithAttrs := vn.H("div", &vn.Props{"class": "navbar"}, "Hello world") 37 | 38 | assert.Equal(t, exectedWithAttrs, vNodeWithAttrs) 39 | } 40 | -------------------------------------------------------------------------------- /patch.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 5 | vnh "github.com/mfrachet/go-vdom-wasm/helpers" 6 | ) 7 | 8 | func updateElement(parent vnd.DomNode, newNode Node, oldNode Node) { 9 | if vnh.IsNil(newNode) { 10 | Remove(*oldNode.GetElement()) 11 | } else if vnh.IsNil(oldNode) { 12 | newElement := CreateIfNotExist(parent, newNode) 13 | 14 | Append(parent, newElement) 15 | } else if !newNode.IsSame(oldNode) { 16 | newElement := CreateIfNotExist(parent, newNode) 17 | 18 | oldElement := *oldNode.GetElement() 19 | oldElement.ReplaceWith(newElement) 20 | } else { 21 | newNode.SetElement(*oldNode.GetElement()) 22 | 23 | newChildrenCount := newNode.ChildrenCount() 24 | oldChildrenCount := oldNode.ChildrenCount() 25 | 26 | max := vnh.Max(newChildrenCount, oldChildrenCount) 27 | 28 | for i := 0; i < max; i++ { 29 | oldChild := oldNode.ChildAt(i) 30 | newChild := newNode.ChildAt(i) 31 | 32 | updateElement(*oldNode.GetElement(), newChild, oldChild) 33 | } 34 | } 35 | } 36 | 37 | func Patch(oldNodeRef interface{}, newVnode Node) { 38 | switch oldNodeRef.(type) { 39 | case string: 40 | rootNodeID := oldNodeRef.(string) 41 | rootNode := vnd.GetDocument().QuerySelector(rootNodeID) 42 | 43 | newElement := CreateIfNotExist(rootNode, newVnode) 44 | 45 | Append(rootNode, newElement) 46 | default: 47 | oldVnode := oldNodeRef.(Node) 48 | 49 | updateElement(*oldVnode.GetElement(), newVnode, oldVnode) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /reconciler.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 5 | vnh "github.com/mfrachet/go-vdom-wasm/helpers" 6 | ) 7 | 8 | func Append(parent vnd.DomNode, child vnd.DomNode) { 9 | parent.AppendChild(child) 10 | } 11 | 12 | func Remove(domNode vnd.DomNode) { 13 | domNode.Remove() 14 | } 15 | 16 | func CreateText(parent vnd.DomNode, virtualNode *TextNode) vnd.DomNode { 17 | return parent.CreateTextNode(virtualNode.GetValue()) 18 | } 19 | 20 | func CreateInstance(parent vnd.DomNode, vnode Node) vnd.DomNode { 21 | domNode := parent.CreateElement(vnode.GetTagName()) 22 | attrs := vnode.GetAttrs() 23 | 24 | for attr, attrValue := range *attrs.Props { 25 | domNode.SetAttribute(attr, attrValue) 26 | } 27 | 28 | for eventName, handler := range *attrs.Events { 29 | domNode.AddEventListener(eventName, handler) 30 | } 31 | 32 | return domNode 33 | } 34 | 35 | func ComputeChildren(parent vnd.DomNode, vnode Node) { 36 | textNode := vnode.GetText() 37 | if vnh.NotNil(textNode) { 38 | textElement := CreateText(parent, textNode) 39 | textNode.SetElement(textElement) 40 | 41 | Append(parent, textElement) 42 | } else { 43 | for _, el := range vnode.GetChildren() { 44 | childElement := CreateIfNotExist(parent, el) 45 | parent.AppendChild(childElement) 46 | } 47 | } 48 | } 49 | 50 | func CreateIfNotExist(parent vnd.DomNode, vnode Node) vnd.DomNode { 51 | if vnode.HasElement() { 52 | return *vnode.GetElement() 53 | } 54 | 55 | newElement := CreateInstance(parent, vnode) 56 | vnode.SetElement(newElement) 57 | 58 | ComputeChildren(newElement, vnode) 59 | 60 | return newElement 61 | } 62 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" 6 | name = "github.com/davecgh/go-spew" 7 | packages = ["spew"] 8 | pruneopts = "UT" 9 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 10 | version = "v1.1.1" 11 | 12 | [[projects]] 13 | digest = "1:be408f349cae090a7c17a279633d6e62b00068e64af66a582cae0983de8890ea" 14 | name = "github.com/golang/mock" 15 | packages = ["gomock"] 16 | pruneopts = "UT" 17 | revision = "9fa652df1129bef0e734c9cf9bf6dbae9ef3b9fa" 18 | version = "1.3.1" 19 | 20 | [[projects]] 21 | digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" 22 | name = "github.com/pmezard/go-difflib" 23 | packages = ["difflib"] 24 | pruneopts = "UT" 25 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 26 | version = "v1.0.0" 27 | 28 | [[projects]] 29 | digest = "1:8548c309c65a85933a625be5e7d52b6ac927ca30c56869fae58123b8a77a75e1" 30 | name = "github.com/stretchr/testify" 31 | packages = ["assert"] 32 | pruneopts = "UT" 33 | revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" 34 | version = "v1.4.0" 35 | 36 | [[projects]] 37 | digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737" 38 | name = "gopkg.in/yaml.v2" 39 | packages = ["."] 40 | pruneopts = "UT" 41 | revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce" 42 | version = "v2.2.7" 43 | 44 | [solve-meta] 45 | analyzer-name = "dep" 46 | analyzer-version = 1 47 | input-imports = [ 48 | "github.com/golang/mock/gomock", 49 | "github.com/stretchr/testify/assert", 50 | ] 51 | solver-name = "gps-cdcl" 52 | solver-version = 1 53 | -------------------------------------------------------------------------------- /reconciler_test.go: -------------------------------------------------------------------------------- 1 | package vn_test 2 | 3 | import ( 4 | "syscall/js" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/golang/mock/gomock" 10 | vn "github.com/mfrachet/go-vdom-wasm" 11 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 12 | "github.com/mfrachet/go-vdom-wasm/mock" 13 | ) 14 | 15 | func TestReconciler_Append(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | defer ctrl.Finish() 18 | 19 | mockParentNode := mock.NewMockDomNode(ctrl) 20 | mockChildNode := mock.NewMockDomNode(ctrl) 21 | 22 | mockParentNode.EXPECT().AppendChild(mockChildNode).Times(1) 23 | 24 | vn.Append(mockParentNode, mockChildNode) 25 | } 26 | 27 | func TestReconciler_Remove(t *testing.T) { 28 | ctrl := gomock.NewController(t) 29 | defer ctrl.Finish() 30 | 31 | mockDNode := mock.NewMockDomNode(ctrl) 32 | 33 | mockDNode.EXPECT().Remove().Times(1) 34 | 35 | vn.Remove(mockDNode) 36 | } 37 | 38 | func TestReconciler_CreateText(t *testing.T) { 39 | ctrl := gomock.NewController(t) 40 | defer ctrl.Finish() 41 | 42 | textNode := vn.NewTextnode("Hello world") 43 | mockDNode := mock.NewMockDomNode(ctrl) 44 | 45 | mockDNode.EXPECT().CreateTextNode("Hello world").Times(1) 46 | 47 | vn.CreateText(mockDNode, textNode) 48 | } 49 | 50 | func handleClick(arg []js.Value) {} 51 | func TestReconciler_CreateInstance(t *testing.T) { 52 | ctrl := gomock.NewController(t) 53 | defer ctrl.Finish() 54 | 55 | vnode := vn.H("li", &vn.Props{"class": "navbar"}, &vn.Ev{"click": nil}, "Hello world") 56 | 57 | mockParent := mock.NewMockDomNode(ctrl) 58 | mockChild := mock.NewMockDomNode(ctrl) 59 | mockParent.EXPECT().CreateElement("li").Return(mockChild).Times(1) 60 | mockChild.EXPECT().SetAttribute("class", "navbar").Times(1) 61 | mockChild.EXPECT().AddEventListener("click", nil).Times(1) 62 | 63 | domNode := vn.CreateInstance(mockParent, vnode) 64 | 65 | assert.Equal(t, domNode, mockChild) 66 | } 67 | 68 | func TestReconciler_CreateIfNotExist_WithElement(t *testing.T) { 69 | ctrl := gomock.NewController(t) 70 | defer ctrl.Finish() 71 | 72 | domElement := vnd.DomElement{} 73 | vnode := vn.H("div", vn.Children{}) 74 | vnode.SetElement(domElement) 75 | 76 | mockParent := mock.NewMockDomNode(ctrl) 77 | 78 | domNode := vn.CreateIfNotExist(mockParent, vnode) 79 | 80 | assert.Equal(t, domNode, domElement) 81 | } 82 | -------------------------------------------------------------------------------- /dom/dom.go: -------------------------------------------------------------------------------- 1 | package vnd 2 | 3 | import ( 4 | vnh "github.com/mfrachet/go-vdom-wasm/helpers" 5 | "syscall/js" 6 | ) 7 | 8 | type DomNode interface { 9 | QuerySelector(string) DomNode 10 | AppendChild(DomNode) 11 | Remove() 12 | ReplaceWith(DomNode) 13 | CreateTextNode(string) DomNode 14 | CreateElement(string) DomNode 15 | SetAttribute(string, string) 16 | AddEventListener(string, func(js.Value, []js.Value) interface{}) 17 | ChildNodes(int) DomNode 18 | GetBinding() js.Value 19 | SetBinding(js.Value) 20 | } 21 | 22 | type DomElement struct { 23 | binding js.Value 24 | } 25 | 26 | var instance *DomElement 27 | 28 | func GetDocument() DomNode { 29 | if vnh.IsNil(instance) { 30 | docNode := js.Global().Get("document") 31 | instance = &DomElement{docNode} 32 | } 33 | 34 | return *instance 35 | } 36 | 37 | func (node DomElement) GetBinding() js.Value { 38 | return node.binding 39 | } 40 | 41 | func (node DomElement) SetBinding(nextBinding js.Value) { 42 | node.binding = nextBinding 43 | } 44 | 45 | func (node DomElement) QuerySelector(element string) DomNode { 46 | bindingNode := GetDocument().GetBinding().Call("querySelector", element) 47 | 48 | return DomElement{bindingNode} 49 | } 50 | 51 | func (node DomElement) AppendChild(child DomNode) { 52 | node.GetBinding().Call("appendChild", child.GetBinding()) 53 | } 54 | 55 | func (node DomElement) Remove() { 56 | node.GetBinding().Call("remove") 57 | } 58 | 59 | func (node DomElement) ReplaceWith(next DomNode) { 60 | node.GetBinding().Call("replaceWith", next.GetBinding()) 61 | } 62 | 63 | func (node DomElement) CreateTextNode(value string) DomNode { 64 | textNode := GetDocument().GetBinding().Call("createTextNode", value) 65 | 66 | return DomElement{textNode} 67 | } 68 | 69 | func (node DomElement) CreateElement(tag string) DomNode { 70 | element := GetDocument().GetBinding().Call("createElement", tag) 71 | 72 | return DomElement{element} 73 | } 74 | 75 | func (node DomElement) SetAttribute(attr string, value string) { 76 | node.GetBinding().Call("setAttribute", attr, value) 77 | } 78 | 79 | func (node DomElement) AddEventListener(eventName string, callback func(js.Value, []js.Value) interface{}) { 80 | node.GetBinding().Call("addEventListener", eventName, js.FuncOf(callback)) 81 | } 82 | 83 | func (node DomElement) ChildNodes(index int) DomNode { 84 | element := node.GetBinding().Get("childNodes").Index(index) 85 | 86 | return DomElement{element} 87 | } 88 | -------------------------------------------------------------------------------- /vnode.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | "fmt" 5 | 6 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 7 | vnh "github.com/mfrachet/go-vdom-wasm/helpers" 8 | ) 9 | 10 | type Children []Node 11 | 12 | type Node interface { 13 | GetElement() *vnd.DomNode 14 | GetTagName() string 15 | GetAttrs() *Attrs 16 | GetKey() *string 17 | HasElement() bool 18 | SetElement(vnd.DomNode) 19 | HashCode() string 20 | ChildrenCount() int 21 | ChildAt(int) Node 22 | GetText() *TextNode 23 | GetChildren() Children 24 | IsSame(Node) bool 25 | } 26 | 27 | type Vnode struct { 28 | tagname string 29 | attrs *Attrs 30 | children Children 31 | text *TextNode 32 | element *vnd.DomNode 33 | key KeyIdentifier 34 | } 35 | 36 | func NewNode(tagname string, attrs *Attrs, children Children, text *TextNode, element *vnd.DomNode, key KeyIdentifier) Node { 37 | return &Vnode{tagname, attrs, children, text, element, key} 38 | } 39 | 40 | func (vnode *Vnode) IsSame(other Node) bool { 41 | currKey := vnode.GetKey() 42 | otherKey := other.GetKey() 43 | 44 | if currKey != nil && otherKey != nil { 45 | return *currKey == *otherKey 46 | } 47 | 48 | if vnh.IsNil(vnode.text) { 49 | if vnh.IsNil(other.GetText()) { 50 | return vnode.HashCode() == other.HashCode() 51 | } 52 | 53 | return false 54 | } 55 | 56 | if vnh.NotNil(other.GetText()) { 57 | hasSameText := other.GetText().IsSame(vnode.text) 58 | return hasSameText && vnode.HashCode() == other.HashCode() 59 | } 60 | 61 | return false 62 | } 63 | 64 | func (vnode *Vnode) ChildrenCount() int { 65 | return len(vnode.children) 66 | } 67 | 68 | func (vnode *Vnode) ChildAt(index int) Node { 69 | size := len(vnode.children) 70 | 71 | if size > index { 72 | return vnode.children[index] 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (vnode *Vnode) GetKey() *string { 79 | if vnh.IsNil(vnode.key) { 80 | return nil 81 | } 82 | 83 | key := vnode.key.GetValue() 84 | return &key 85 | } 86 | 87 | func (vnode *Vnode) GetText() *TextNode { 88 | return vnode.text 89 | } 90 | 91 | func (vnode *Vnode) GetElement() *vnd.DomNode { 92 | return vnode.element 93 | } 94 | 95 | func (vnode *Vnode) HasElement() bool { 96 | return vnh.NotNil(vnode.GetElement()) 97 | } 98 | 99 | func (vnode *Vnode) GetTagName() string { 100 | return vnode.tagname 101 | } 102 | 103 | func (vnode *Vnode) GetAttrs() *Attrs { 104 | return vnode.attrs 105 | } 106 | 107 | func (vnode *Vnode) GetChildren() Children { 108 | return vnode.children 109 | } 110 | 111 | func (vnode *Vnode) SetElement(element vnd.DomNode) { 112 | vnode.element = &element 113 | } 114 | 115 | func (vnode *Vnode) HashCode() string { 116 | return fmt.Sprintf("%s/%v", vnode.tagname, *vnode.attrs.Props) 117 | } 118 | -------------------------------------------------------------------------------- /vnode_test.go: -------------------------------------------------------------------------------- 1 | package vn 2 | 3 | import ( 4 | "testing" 5 | 6 | vnd "github.com/mfrachet/go-vdom-wasm/dom" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestVnode_IsSame(t *testing.T) { 11 | a := H("div", "Hello world") 12 | b := H("div", "Hello world") 13 | c := H("span", "Hello world") 14 | 15 | d := H("span", &Props{"hello": "world"}, "Hello world") 16 | e := H("span", &Props{"hello": "world"}, "Hello world") 17 | f := H("div", &Props{"hello": "world"}, "Hello worldx") 18 | j := H("span", &Props{"hello": "world2"}, "Hello world") 19 | 20 | k := H("li", "Hello world") 21 | l := H("li", "Hello world2") 22 | 23 | m := H("li", Children{}) 24 | 25 | assert.Equal(t, true, a.IsSame(b)) 26 | assert.Equal(t, true, d.IsSame(e)) 27 | 28 | assert.Equal(t, false, a.IsSame(c)) 29 | assert.Equal(t, false, d.IsSame(f)) 30 | assert.Equal(t, false, d.IsSame(j)) 31 | assert.Equal(t, false, k.IsSame(l)) 32 | 33 | assert.Equal(t, false, m.IsSame(a)) 34 | assert.Equal(t, false, a.IsSame(m)) 35 | } 36 | 37 | func TestVnode_IsSame_WithChildren(t *testing.T) { 38 | a := H("ul", &Props{"class": "navbar"}, Children{ 39 | H("li", "First item"), 40 | H("li", "Second item"), 41 | }) 42 | 43 | b := H("ul", &Props{"class": "navbar"}, Children{ 44 | H("li", "First item"), 45 | H("li", "Second item"), 46 | }) 47 | 48 | assert.Equal(t, true, a.IsSame(b)) 49 | } 50 | 51 | func TestVnode_IsSame_WithKey(t *testing.T) { 52 | a := H("ul", &Props{"class": "navbar"}, Children{ 53 | H("li", "First item"), 54 | H("li", "Second item"), 55 | }, &Key{"1"}) 56 | 57 | b := H("ul", &Props{"class": "navbar"}, Children{ 58 | H("li", "First item"), 59 | H("li", "Second item"), 60 | }, &Key{"2"}) 61 | 62 | assert.Equal(t, false, a.IsSame(b)) 63 | } 64 | 65 | func TestVnode_ChildrenCount(t *testing.T) { 66 | a := H("div", Children{ 67 | H("span", "Hello"), 68 | H("span", "Hello"), 69 | }) 70 | 71 | assert.Equal(t, 2, a.ChildrenCount()) 72 | } 73 | 74 | func TestVnode_ChildAt(t *testing.T) { 75 | childAtOne := H("span", "Hello World") 76 | 77 | a := H("div", Children{ 78 | H("span", "Hello"), 79 | childAtOne, 80 | }) 81 | b := H("span", "Hello") 82 | 83 | assert.Equal(t, childAtOne, a.ChildAt(1)) 84 | assert.Equal(t, nil, b.ChildAt(1)) 85 | } 86 | 87 | func TestVnode_HasElement(t *testing.T) { 88 | element := H("span", "Hello world") 89 | assert.Equal(t, false, element.HasElement()) 90 | 91 | element.SetElement(vnd.DomElement{}) 92 | assert.Equal(t, true, element.HasElement()) 93 | } 94 | 95 | func TestVnode_GetTagName(t *testing.T) { 96 | element := H("span", "Hello world") 97 | 98 | assert.Equal(t, "span", element.GetTagName()) 99 | } 100 | 101 | func TestVnode_GetAttrs(t *testing.T) { 102 | props := &Props{"class": "navbar"} 103 | ev := &Ev{} 104 | attrs := &Attrs{Props: props, Events: ev} 105 | element := H("span", props, ev, "Hello world") 106 | 107 | assert.Equal(t, attrs, element.GetAttrs()) 108 | } 109 | 110 | func TestVnode_GetChildren(t *testing.T) { 111 | children := Children{} 112 | element := H("span", children) 113 | 114 | assert.Equal(t, children, element.GetChildren()) 115 | } 116 | -------------------------------------------------------------------------------- /cover.out: -------------------------------------------------------------------------------- 1 | mode: set 2 | github.com/mfrachet/go-vdom-wasm/helpers/math.go:3.28,4.11 1 1 3 | github.com/mfrachet/go-vdom-wasm/helpers/math.go:8.2,8.10 1 1 4 | github.com/mfrachet/go-vdom-wasm/helpers/math.go:4.11,6.3 1 1 5 | github.com/mfrachet/go-vdom-wasm/helpers/utils.go:7.39,8.21 1 1 6 | github.com/mfrachet/go-vdom-wasm/helpers/utils.go:12.2,12.42 1 1 7 | github.com/mfrachet/go-vdom-wasm/helpers/utils.go:8.21,10.3 1 1 8 | github.com/mfrachet/go-vdom-wasm/helpers/utils.go:15.40,17.2 1 1 9 | github.com/mfrachet/go-vdom-wasm/vnode.go:36.125,38.2 1 1 10 | github.com/mfrachet/go-vdom-wasm/vnode.go:40.45,44.39 3 1 11 | github.com/mfrachet/go-vdom-wasm/vnode.go:48.2,48.27 1 1 12 | github.com/mfrachet/go-vdom-wasm/vnode.go:56.2,56.33 1 1 13 | github.com/mfrachet/go-vdom-wasm/vnode.go:61.2,61.14 1 1 14 | github.com/mfrachet/go-vdom-wasm/vnode.go:44.39,46.3 1 1 15 | github.com/mfrachet/go-vdom-wasm/vnode.go:48.27,49.33 1 1 16 | github.com/mfrachet/go-vdom-wasm/vnode.go:53.3,53.15 1 1 17 | github.com/mfrachet/go-vdom-wasm/vnode.go:49.33,51.4 1 1 18 | github.com/mfrachet/go-vdom-wasm/vnode.go:56.33,59.3 2 1 19 | github.com/mfrachet/go-vdom-wasm/vnode.go:64.41,66.2 1 1 20 | github.com/mfrachet/go-vdom-wasm/vnode.go:68.45,71.18 2 1 21 | github.com/mfrachet/go-vdom-wasm/vnode.go:75.2,75.12 1 1 22 | github.com/mfrachet/go-vdom-wasm/vnode.go:71.18,73.3 1 1 23 | github.com/mfrachet/go-vdom-wasm/vnode.go:78.38,79.26 1 1 24 | github.com/mfrachet/go-vdom-wasm/vnode.go:83.2,84.13 2 1 25 | github.com/mfrachet/go-vdom-wasm/vnode.go:79.26,81.3 1 1 26 | github.com/mfrachet/go-vdom-wasm/vnode.go:87.41,89.2 1 1 27 | github.com/mfrachet/go-vdom-wasm/vnode.go:91.47,93.2 1 1 28 | github.com/mfrachet/go-vdom-wasm/vnode.go:95.39,97.2 1 1 29 | github.com/mfrachet/go-vdom-wasm/vnode.go:99.41,101.2 1 1 30 | github.com/mfrachet/go-vdom-wasm/vnode.go:103.39,105.2 1 1 31 | github.com/mfrachet/go-vdom-wasm/vnode.go:107.44,109.2 1 1 32 | github.com/mfrachet/go-vdom-wasm/vnode.go:111.53,113.2 1 1 33 | github.com/mfrachet/go-vdom-wasm/vnode.go:115.39,117.2 1 1 34 | github.com/mfrachet/go-vdom-wasm/attrs.go:18.36,19.22 1 1 35 | github.com/mfrachet/go-vdom-wasm/attrs.go:23.2,23.28 1 1 36 | github.com/mfrachet/go-vdom-wasm/attrs.go:27.2,27.29 1 1 37 | github.com/mfrachet/go-vdom-wasm/attrs.go:31.2,31.14 1 1 38 | github.com/mfrachet/go-vdom-wasm/attrs.go:19.22,21.3 1 1 39 | github.com/mfrachet/go-vdom-wasm/attrs.go:23.28,25.3 1 1 40 | github.com/mfrachet/go-vdom-wasm/attrs.go:27.29,29.3 1 1 41 | github.com/mfrachet/go-vdom-wasm/h.go:3.52,9.31 5 1 42 | github.com/mfrachet/go-vdom-wasm/h.go:24.2,26.71 2 1 43 | github.com/mfrachet/go-vdom-wasm/h.go:9.31,10.25 1 1 44 | github.com/mfrachet/go-vdom-wasm/h.go:11.15,12.44 1 1 45 | github.com/mfrachet/go-vdom-wasm/h.go:13.22,14.31 1 1 46 | github.com/mfrachet/go-vdom-wasm/h.go:15.15,16.32 1 1 47 | github.com/mfrachet/go-vdom-wasm/h.go:17.12,18.30 1 1 48 | github.com/mfrachet/go-vdom-wasm/h.go:19.11,20.31 1 1 49 | github.com/mfrachet/go-vdom-wasm/key.go:12.40,14.2 1 1 50 | github.com/mfrachet/go-vdom-wasm/key.go:16.35,18.2 1 1 51 | github.com/mfrachet/go-vdom-wasm/patch.go:8.68,9.24 1 0 52 | github.com/mfrachet/go-vdom-wasm/patch.go:9.24,11.3 1 0 53 | github.com/mfrachet/go-vdom-wasm/patch.go:11.8,11.31 1 0 54 | github.com/mfrachet/go-vdom-wasm/patch.go:11.31,15.3 2 0 55 | github.com/mfrachet/go-vdom-wasm/patch.go:15.8,15.37 1 0 56 | github.com/mfrachet/go-vdom-wasm/patch.go:15.37,20.3 3 0 57 | github.com/mfrachet/go-vdom-wasm/patch.go:20.8,28.28 5 0 58 | github.com/mfrachet/go-vdom-wasm/patch.go:28.28,33.4 3 0 59 | github.com/mfrachet/go-vdom-wasm/patch.go:37.51,38.27 1 0 60 | github.com/mfrachet/go-vdom-wasm/patch.go:39.14,45.31 4 0 61 | github.com/mfrachet/go-vdom-wasm/patch.go:46.10,49.60 2 0 62 | github.com/mfrachet/go-vdom-wasm/reconciler.go:8.52,10.2 1 1 63 | github.com/mfrachet/go-vdom-wasm/reconciler.go:12.34,14.2 1 1 64 | github.com/mfrachet/go-vdom-wasm/reconciler.go:16.72,18.2 1 1 65 | github.com/mfrachet/go-vdom-wasm/reconciler.go:20.65,24.44 3 1 66 | github.com/mfrachet/go-vdom-wasm/reconciler.go:28.2,28.48 1 1 67 | github.com/mfrachet/go-vdom-wasm/reconciler.go:32.2,32.16 1 1 68 | github.com/mfrachet/go-vdom-wasm/reconciler.go:24.44,26.3 1 1 69 | github.com/mfrachet/go-vdom-wasm/reconciler.go:28.48,30.3 1 1 70 | github.com/mfrachet/go-vdom-wasm/reconciler.go:35.54,37.26 2 0 71 | github.com/mfrachet/go-vdom-wasm/reconciler.go:37.26,42.3 3 0 72 | github.com/mfrachet/go-vdom-wasm/reconciler.go:42.8,43.42 1 0 73 | github.com/mfrachet/go-vdom-wasm/reconciler.go:43.42,46.4 2 0 74 | github.com/mfrachet/go-vdom-wasm/reconciler.go:50.67,51.24 1 1 75 | github.com/mfrachet/go-vdom-wasm/reconciler.go:55.2,60.19 4 0 76 | github.com/mfrachet/go-vdom-wasm/reconciler.go:51.24,53.3 1 1 77 | github.com/mfrachet/go-vdom-wasm/textnode.go:12.42,14.2 1 1 78 | github.com/mfrachet/go-vdom-wasm/textnode.go:16.53,18.2 1 1 79 | github.com/mfrachet/go-vdom-wasm/textnode.go:20.45,22.2 1 1 80 | github.com/mfrachet/go-vdom-wasm/textnode.go:24.59,26.2 1 1 81 | github.com/mfrachet/go-vdom-wasm/textnode.go:28.56,31.2 1 1 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |