Skip to content

Commit

Permalink
add 2e-dynamic feature and refactor to use documentFragments
Browse files Browse the repository at this point in the history
  • Loading branch information
VJftw committed Apr 20, 2024
1 parent 26ef96d commit baa7db9
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 21 deletions.
4 changes: 2 additions & 2 deletions examples/features/2a-show/test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestApp(t *testing.T) {
appHTML := appEl.MustHTML()

assert.Equal(t,
`<div id="app"><div><vigor-show><button>Log in</button></vigor-show></div></div>`,
`<div id="app"><div><button>Log in</button></div></div>`,
appHTML,
)

Expand All @@ -34,7 +34,7 @@ func TestApp(t *testing.T) {

appHTML = appEl.MustHTML()
assert.Equal(t,
`<div id="app"><div><vigor-show><button>Log out</button></vigor-show></div></div>`,
`<div id="app"><div><button>Log out</button></div></div>`,
appHTML,
)
}
2 changes: 1 addition & 1 deletion examples/features/2b-for/test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestApp(t *testing.T) {

appHTML := appEl.MustHTML()
assert.Equal(t,
`<div id="app"><ul><vigor-for><li><a target="_blank" href="https://www.youtube.com/watch?v=J---aiyznGQ">1: Keyboard Cat</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=z_AbfPXTKms">2: Maru</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=OUtn3pvWmpg">3: Henri The Existential Cat</a></li></vigor-for></ul></div>`,
`<div id="app"><ul><li><a target="_blank" href="https://www.youtube.com/watch?v=J---aiyznGQ">1: Keyboard Cat</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=z_AbfPXTKms">2: Maru</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=OUtn3pvWmpg">3: Henri The Existential Cat</a></li></ul></div>`,
appHTML,
)
}
2 changes: 1 addition & 1 deletion examples/features/2c-index/test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestApp(t *testing.T) {

appHTML := appEl.MustHTML()
assert.Equal(t,
`<div id="app"><ul><vigor-index><li><a target="_blank" href="https://www.youtube.com/watch?v=J---aiyznGQ">1: Keyboard Cat</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=z_AbfPXTKms">2: Maru</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=OUtn3pvWmpg">3: Henri The Existential Cat</a></li></vigor-index></ul></div>`,
`<div id="app"><ul><li><a target="_blank" href="https://www.youtube.com/watch?v=J---aiyznGQ">1: Keyboard Cat</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=z_AbfPXTKms">2: Maru</a></li><li><a target="_blank" href="https://www.youtube.com/watch?v=OUtn3pvWmpg">3: Henri The Existential Cat</a></li></ul></div>`,
appHTML,
)
}
2 changes: 1 addition & 1 deletion examples/features/2d-switch/test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestApp(t *testing.T) {
appHTML := appEl.MustHTML()

assert.Equal(t,
`<div id="app"><vigor-switch><p>7 is between 5 and 10</p></vigor-switch></div>`,
`<div id="app"><p>7 is between 5 and 10</p></div>`,
appHTML,
)
}
1 change: 1 addition & 0 deletions examples/features/2e-dynamic/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(dir $(shell go env GOWORK))/examples/_common/Makefile
50 changes: 50 additions & 0 deletions examples/features/2e-dynamic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
The <Dynamic> tag is useful when you render from data. It lets you pass either a string for a native element or a component function and it will render that with the rest of the provided props.
This is often more compact than writing a number of <Show> or <Switch> components.
*/
package main

import (
"context"
"syscall/js"

"github.com/VJftw/vigor"
"github.com/VJftw/vigor/html"
"github.com/VJftw/vigor/web"
)

func App() html.Node {
selected, setSelected := vigor.CreateSignal("red")

options := map[string]html.Node{
"red": html.El("strong", html.Style("color", "red"), "Red Thing"),
"green": html.El("strong", html.Style("color", "green"), "Green Thing"),
"blue": html.El("strong", html.Style("color", "blue"), "Blue Thing"),
}
optionKeys := []any{"red", "green", "blue"} // static order

return html.El("div",
html.El("select",
html.Property("value", selected),
html.On("input", func(this js.Value, args []js.Value) {
setSelected(args[0].Get("currentTarget").Get("value").String())
}),
html.For(optionKeys, func(i int, v any) html.Node {
return html.El("option", html.Attr("value", v), html.Text(v))
}),
),
html.Dynamic(func(s vigor.Subscriber) html.Node {
return options[selected(s).(string)]
}),
)
}

func main() {
if err := web.RenderToElementID(
context.Background(),
App(), "app",
); err != nil {
panic(err)
}
}
40 changes: 40 additions & 0 deletions examples/features/2e-dynamic/test/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/go-rod/rod"
"github.com/stretchr/testify/assert"
)

func TestApp(t *testing.T) {
fs := http.FileServer(http.Dir("../build"))
s := httptest.NewServer(fs)
defer s.Close()

page := rod.New().MustConnect().Timeout(10 * time.Second).Logger(rod.DefaultLogger).MustPage(s.URL)
defer page.MustClose()
page.Race().Element("#vigor-info").MustDo()

appEl := page.MustElement("#app")

assert.Equal(t,
`<div id="app"><div><select><option value="red">red</option><option value="green">green</option><option value="blue">blue</option></select><strong style="color: red;">Red Thing</strong></div></div>`,
appEl.MustHTML(),
)

page.MustElement("select").MustSelect("green")
assert.Equal(t,
`<div id="app"><div><select><option value="red">red</option><option value="green">green</option><option value="blue">blue</option></select><strong style="color: green;">Green Thing</strong></div></div>`,
appEl.MustHTML(),
)

page.MustElement("select").MustSelect("blue")
assert.Equal(t,
`<div id="app"><div><select><option value="red">red</option><option value="green">green</option><option value="blue">blue</option></select><strong style="color: blue;">Blue Thing</strong></div></div>`,
appEl.MustHTML(),
)
}
38 changes: 38 additions & 0 deletions html/dynamic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package html

import (
"syscall/js"

"github.com/VJftw/vigor"
)

type nodeDynamic struct {
fnThatReturnsNode func(vigor.Subscriber) Node
}

func Dynamic(fn func(vigor.Subscriber) Node) Node {
return &nodeDynamic{
fnThatReturnsNode: fn,
}
}

func (n *nodeDynamic) DOMObject(doc js.Value) js.Value {
obj := doc.Call("createDocumentFragment")
fragmentRendered := false

subscriber := vigor.NewFnSubscriber()
subscriber.SetFn(func() {
newObj := n.fnThatReturnsNode(subscriber).DOMObject(doc)

if !fragmentRendered {
obj.Call("replaceChildren", newObj)
fragmentRendered = true
} else {
obj.Call("replaceWith", newObj)
}

obj = newObj
}).Run()

return obj
}
1 change: 1 addition & 0 deletions html/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func El(name string, children ...any) Node {
name: name,
events: map[string][]JSEventFunc{},
plugins: map[ElementPlugin]struct{}{
NewPropertyElementPlugin(): {},
NewAttributeElementPlugin(): {},
NewClassElementPlugin(): {},
NewChildrenElementPlugin(): {},
Expand Down
53 changes: 53 additions & 0 deletions html/element_property.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package html

import (
"syscall/js"

"github.com/VJftw/vigor"
)

type elementProperty struct {
k string
v any
}

type PropertyElementPlugin struct {
props map[string]any
}

func NewPropertyElementPlugin() ElementPlugin {
return &PropertyElementPlugin{
props: map[string]any{},
}
}

func (p *PropertyElementPlugin) HandleChild(child any) (bool, error) {
if x, ok := child.(*elementProperty); ok {
p.props[x.k] = x.v

return true, nil
}

return false, nil
}

func (p *PropertyElementPlugin) Render(doc, obj js.Value) error {
subscriber := vigor.NewFnSubscriber()
subscriber.SetFn(func() {
for k, v := range p.props {
if x, ok := v.(vigor.GetterFn); ok {
v = x(subscriber)
}
obj.Set(k, v)
}
}).Run()

return nil
}

func Property(k string, v any) *elementProperty {
return &elementProperty{
k: k,
v: v,
}
}
33 changes: 26 additions & 7 deletions html/for.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,47 @@ import (

type nodeFor struct {
fn func(i int, v any) Node
v vigor.GetterFn
v any
}

func For(v vigor.GetterFn, fn func(i int, v any) Node) Node {
func For(v any, fn func(i int, v any) Node) Node {
return &nodeFor{
fn: fn,
v: v,
}
}

func (n *nodeFor) DOMObject(doc js.Value) js.Value {
obj := doc.Call("createElement", "vigor-for")
obj := doc.Call("createDocumentFragment")

currentItemObjs := []any{}

subscriber := vigor.NewFnSubscriber()
subscriber.SetFn(func() {
items := n.v(subscriber).([]any)
itemObjs := make([]any, len(items))
items := []any{}
if getterFn, ok := n.v.(vigor.GetterFn); ok {
items = getterFn(subscriber).([]any)
} else {
items = n.v.([]any)
}

newItemObjs := make([]any, len(items))
for i, item := range items {
itemObjs[i] = n.fn(i, item).DOMObject(doc)
newItemObjs[i] = n.fn(i, item).DOMObject(doc)
}

obj.Call("replaceChildren", itemObjs...)
if len(currentItemObjs) < 1 {
obj.Call("replaceChildren", newItemObjs...)
} else {
for _, newItemObj := range newItemObjs {
currentItemObjs[len(currentItemObjs)-1].(js.Value).Call("insertBefore", newItemObj)
}
for _, currentItemObj := range currentItemObjs {
currentItemObj.(js.Value).Call("remove")
}

currentItemObjs = newItemObjs
}
}).Run()

return obj
Expand Down
2 changes: 1 addition & 1 deletion html/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func Index(v vigor.GetterFn, fn func(i int, v any) Node) Node {
}

func (n *nodeIndex) DOMObject(doc js.Value) js.Value {
obj := doc.Call("createElement", "vigor-index")
obj := doc.Call("createDocumentFragment")

currentItems := []any{}
currentItemObjs := []js.Value{}
Expand Down
19 changes: 15 additions & 4 deletions html/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,26 @@ func Show(
}

func (n *nodeShow) DOMObject(doc js.Value) js.Value {
obj := doc.Call("createElement", "vigor-show")
obj := doc.Call("createDocumentFragment")
fragmentRendered := false

subscriber := vigor.NewFnSubscriber()
subscriber.SetFn(func() {
if n.when(subscriber) == true {
obj.Call("replaceChildren", n.truthy.DOMObject(doc))
var newObj js.Value
if n.when(subscriber).(bool) {
newObj = n.truthy.DOMObject(doc)
} else {
obj.Call("replaceChildren", n.falsey.DOMObject(doc))
newObj = n.falsey.DOMObject(doc)
}

if !fragmentRendered {
obj.Call("replaceChildren", newObj)
fragmentRendered = true
} else {
obj.Call("replaceWith", newObj)
}

obj = newObj
}).Run()

return obj
Expand Down
24 changes: 20 additions & 4 deletions html/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,34 @@ func Switch(defaultNode Node, cases ...*switchCase) Node {
}

func (n *nodeSwitch) DOMObject(doc js.Value) js.Value {
obj := doc.Call("createElement", "vigor-switch")
obj := doc.Call("createDocumentFragment")
fragmentRendered := false

subscriber := vigor.NewFnSubscriber()
subscriber.SetFn(func() {
var newObj js.Value

caseMatched := false
for _, c := range n.cases {
if c.when() {
obj.Call("replaceChildren", c.node.DOMObject(doc))
return
newObj = c.node.DOMObject(doc)
caseMatched = true
break
}
}

obj.Call("replaceChildren", n.defaultNode.DOMObject(doc))
if !caseMatched {
newObj = n.defaultNode.DOMObject(doc)
}

if !fragmentRendered {
obj.Call("replaceChildren", newObj)
fragmentRendered = true
} else {
obj.Call("replaceWith", newObj)
}

obj = newObj
}).Run()

for _, c := range n.cases {
Expand Down

0 comments on commit baa7db9

Please sign in to comment.