Skip to content

Commit

Permalink
Added template nesting. Improved example.
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmiguu committed Mar 9, 2019
1 parent fa325ca commit 9a415ce
Show file tree
Hide file tree
Showing 21 changed files with 338 additions and 154 deletions.
Binary file added .DS_Store
Binary file not shown.
288 changes: 229 additions & 59 deletions coco.go
Expand Up @@ -2,86 +2,256 @@ package coco

import (
"bytes"
"errors"
"html/template"
"reflect"
"strings"
"syscall/js"
)

func getType(v interface{}) string {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
return t.Elem().Name()
var (
vdom = VDOM{
make(map[string]string),
}
return t.Name()
)

// VDOM is the virtual DOM for this package.
type VDOM struct {
cache map[string]string
}

func getFuncs(v interface{}) map[string]reflect.Value {
m := make(map[string]reflect.Value)
x := reflect.ValueOf(v)
y := reflect.TypeOf(v)
for i := 0; i < x.NumMethod(); i++ {
method := x.Method(i)
m[y.Method(i).Name] = method
// Render renders the root component as the body.
func Render(root interface{}) {
vdom.Render(root)
}

// Render renders the root component as the body.
func (v *VDOM) Render(root interface{}) {
fnMap := make(map[string]reflect.Value)
for _, comp := range append(bfsEmbedded(root), root) {
for fn, v := range getFuncs(comp) {
fnMap[fn] = v
}
}
return m

bin, err := v.compile(root)
if err != nil {
panic(err)
}

updateDOM(bin)

for fn, v := range fnMap {
cls := strings.Replace(fn, "On", "", -1)
cls = strings.Replace(cls, "Click", "", -1)
elements := js.Global().Get("document").Call("getElementsByClassName", cls)

v := v
for i := 0; i < elements.Get("length").Int(); i++ {
elements.Index(i).Call("addEventListener", "click", js.NewCallback(func(args []js.Value) {
v.Call([]reflect.Value{})
}))
}
}

select {}
}

// Set sets the diff component and patches the VDOM tree.
// Set sets the diff component and patches the * tree.
func Set(comp interface{}) {
render(comp)
vdom.Set(comp)
}

// Render renders the root component as the body.
func Render(root interface{}) {
render(root)
select {}
// Set sets the diff component and patches the * tree.
func (v *VDOM) Set(comp interface{}) {
name := getName(comp)

fnMap := make(map[string]reflect.Value)
for _, comp := range append(bfsEmbedded(comp), comp) {
for fn, v := range getFuncs(comp) {
fnMap[fn] = v
}
}

bin, err := v.compile(comp)
if err != nil {
panic(err)
}

elements := js.Global().Get("document").Call("getElementsByClassName", name)
for i := 0; i < elements.Get("length").Int(); i++ {
element := elements.Index(i)
parent := element.Get("parentElement")
parent.Call("replaceChild", htmlToElement(bin), element)
}

for fn, v := range fnMap {
cls := strings.Replace(fn, "On", "", -1)
cls = strings.Replace(cls, "Click", "", -1)
elements := js.Global().Get("document").Call("getElementsByClassName", cls)

v := v
for i := 0; i < elements.Get("length").Int(); i++ {
elements.Index(i).Call("addEventListener", "click", js.NewCallback(func(args []js.Value) {
v.Call([]reflect.Value{})
}))
}
}
}

func (v *VDOM) compile(comp interface{}) (string, error) {
bin := ""
for _, c := range bfsEmbedded(comp) {
name := getName(c)
b, err := v.compile(c)
if err != nil {
return "", err
}
bin += define(name, b)
}

name := reflect.TypeOf(comp).Name()
t := template.New(name + ".coco")

raw, ok := v.cache[name]
if !ok {
println("fetching " + name + ".html")
r, e := fetch(name + ".html")
if err := <-e; err != nil {
return "", err
}
raw = <-r
v.cache[name] = raw
}

t, err := t.Parse(bin + raw)
if err != nil {
return "", err
}

buf := new(bytes.Buffer)

err = t.Execute(buf, comp)
if err != nil {
return "", err
}

return buf.String(), nil
}

func render(root interface{}) {
html := strings.ToLower(getType(root)) + ".html"
func fetch(file string) (<-chan string, <-chan error) {
c := make(chan string, 1)
e := make(chan error, 1)

js.Global().Call("fetch", file).Call("then", js.NewCallback(func(args []js.Value) {

ok := args[0].Get("ok").Bool()
if !ok {
c <- ""
e <- errors.New(file + " not found.")
return
}

js.Global().Call("fetch", html).Call("then", js.NewCallback(func(args []js.Value) {
args[0].Call("text").Call("then", js.NewCallback(func(args []js.Value) {

t := template.New(html)

fns := getFuncs(root)
fnMap := make(template.FuncMap)
for fn, v := range fns {
fn := fn
v := v
fnMap[fn] = func(args ...interface{}) string {
v.Call([]reflect.Value{})
return "_X_"
}
}
t = t.Funcs(fnMap)

t, err := t.Parse(args[0].String())
if err != nil {
panic(err)
}

buf := new(bytes.Buffer)

t.Execute(buf, root)
document := js.Global().Get("document")
document.Get("body").Set("innerHTML", buf.String())

for fn := range fns {
fn := fn
cls := strings.Replace(fn, "On", "", -1)
cls = strings.Replace(cls, "Click", "", -1)
cls = strings.ToLower(cls)
elements := document.Call("getElementsByClassName", cls)
for i := 0; i < elements.Get("length").Int(); i++ {
elements.Index(i).Call("addEventListener", "click", js.NewCallback(func(args []js.Value) {
fnMap[fn].(func(...interface{}) string)()
}))
}
}
c <- args[0].String()
e <- nil

})).Call("catch", js.NewCallback(func(args []js.Value) {
println(args[0].Get("message").String())
}))

})).Call("catch", js.NewCallback(func(args []js.Value) {
println(args[0].Get("message").String())
}))

return c, e
}

type strErrC struct {
c <-chan string
e <-chan error
}

func newStrErrC(c <-chan string, e <-chan error) strErrC {
return strErrC{c, e}
}

func htmlToElement(html string) js.Value {
template := js.Global().Get("document").Call("createElement", "template")
html = strings.Trim(html, " ")
template.Set("innerHTML", html)
return template.Get("content").Get("firstChild")
}

func updateDOM(html string) {
js.Global().Get("document").Get("body").Set("innerHTML", html)
}

func define(name, html string) string {
return `{{define "` + name + `"}}` + `<link href="` + name + `.css" rel="stylesheet">` + html + "{{end}}"
}

func getName(v interface{}) string {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
return t.Elem().Name()
}
return t.Name()
}

func bfsEmbedded(v interface{}) []interface{} {
a := make([]interface{}, 0)
x := reflect.ValueOf(v)
y := reflect.TypeOf(v)
for i := 0; i < x.NumField(); i++ {
if y.Field(i).Name != y.Field(i).Type.Name() {
continue
}
a = append(a, x.Field(i).Interface())
}
s := []string{}
a2 := make([]interface{}, 0)
for _, emb := range a {
a2 = append(a2, bfsEmbedded(emb)...)
}
a2 = append(a2, a...)
for _, emb := range a2 {
s = append(s, reflect.TypeOf(emb).Name())
}
return a2
}

func getEmbedded(v interface{}) []interface{} {
a := make([]interface{}, 0)
x := reflect.ValueOf(v)
y := reflect.TypeOf(v)
for i := 0; i < x.NumField(); i++ {
if y.Field(i).Name != y.Field(i).Type.Name() {
continue
}
a = append(a, x.Field(i).Interface())
}
return a
}

func getFields(v interface{}) []string {
a := []string{}
x := reflect.ValueOf(v)
y := reflect.TypeOf(v)
for i := 0; i < x.NumField(); i++ {
a = append(a, y.Field(i).Type.Name())
}
return a
}

func getFuncs(v interface{}) map[string]reflect.Value {
m := make(map[string]reflect.Value)
x := reflect.ValueOf(v)
y := reflect.TypeOf(v)
for i := 0; i < x.NumMethod(); i++ {
method := x.Method(i)
m[y.Method(i).Name] = method
}
return m
}
Binary file added example/.DS_Store
Binary file not shown.
16 changes: 16 additions & 0 deletions example/Count.css
@@ -0,0 +1,16 @@
.Count {
background: lightsalmon;
border: 10px solid salmon;
}

.Cocos {
background: lightyellow;
border: 10px solid yellow;
}

.Coco {
display: inline-block;
background: lightgray;
cursor: pointer;
border: 10px solid gray;
}
31 changes: 31 additions & 0 deletions example/Count.go
@@ -0,0 +1,31 @@
package main

import "github.com/mrmiguu/coco"

type Count struct {
Up
Cocos []string
Down
}

func NewCount() Count {
return Count{
Cocos: []string{"🥥"},
}
}

func (c Count) OnUpClick() {
println("clicked Up")
c.Cocos = append(c.Cocos, "🥥")
coco.Set(c)
}

func (c Count) OnCocoClick() {
println("clicked Coco")
}

func (c Count) OnDownClick() {
println("clicked Down")
c.Cocos = c.Cocos[1:]
coco.Set(c)
}
11 changes: 11 additions & 0 deletions example/Count.html
@@ -0,0 +1,11 @@
<div class="Count">
{{template "Up"}}

<div class="Cocos">
{{range $_, $coco := .Cocos}}
<div class="Coco">{{$coco}}</div>
{{end}}
</div>

{{template "Down"}}
</div>
6 changes: 6 additions & 0 deletions example/Down.css
@@ -0,0 +1,6 @@
.Down {
display: inline-block;
background: lightblue;
cursor: pointer;
border: 10px solid blue;
}
4 changes: 4 additions & 0 deletions example/Down.go
@@ -0,0 +1,4 @@
package main

type Down struct {
}
3 changes: 3 additions & 0 deletions example/Down.html
@@ -0,0 +1,3 @@
<div class="Down">
🔽
</div>
6 changes: 6 additions & 0 deletions example/Up.css
@@ -0,0 +1,6 @@
.Up {
display: inline-block;
background: lightgreen;
cursor: pointer;
border: 10px solid green;
}

0 comments on commit 9a415ce

Please sign in to comment.