Skip to content

Commit e0ca74c

Browse files
committed
Implemented class and style bindings. (#39)
Also, allowed data fields to not be exported.
1 parent d0b90db commit e0ca74c

File tree

13 files changed

+245
-53
lines changed

13 files changed

+245
-53
lines changed

context.go

+7-12
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"reflect"
66
)
77

8-
// Context is received by methods to interact with the component.
8+
// Context is received by functions to interact with the component.
99
type Context interface {
1010
Data() interface{}
1111
Get(field string) interface{}
@@ -34,23 +34,18 @@ func (vm *ViewModel) Get(field string) interface{} {
3434
}
3535
value := vm.compute(function)
3636
vm.mapField(field, value)
37-
return value.Interface()
37+
return value
3838
}
3939

4040
// Set assigns the data field to the given value.
4141
// Props and computed are excluded to set.
4242
func (vm *ViewModel) Set(field string, value interface{}) {
4343
data := reflect.Indirect(vm.data)
44-
oldVal := data.FieldByName(field)
45-
newVal := reflect.ValueOf(value)
44+
oldVal := reflect.Indirect(data.FieldByName(field))
45+
newVal := reflect.Indirect(reflect.ValueOf(value))
4646

47-
if changed := vm.mapField(field, newVal); !changed {
48-
return
49-
}
50-
51-
oldVal = reflect.Indirect(oldVal)
52-
newVal = reflect.Indirect(newVal)
5347
oldVal.Set(newVal)
48+
vm.mapField(field, value)
5449
}
5550

5651
// Go asynchronously calls the given method with optional arguments.
@@ -78,8 +73,8 @@ func (vm *ViewModel) call(method string, values []reflect.Value) {
7873
}
7974

8075
// compute calls the given function and returns the first element.
81-
func (vm *ViewModel) compute(function reflect.Value) reflect.Value {
76+
func (vm *ViewModel) compute(function reflect.Value) interface{} {
8277
values := []reflect.Value{reflect.ValueOf(vm)}
8378
rets := function.Call(values)
84-
return rets[0]
79+
return rets[0].Interface()
8580
}

examples/03-conditionals-with-methods/main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ type Data struct {
1313
Seen bool
1414
}
1515

16-
func ToggleSeen(context vue.Context) {
17-
data := context.Data().(*Data)
16+
func ToggleSeen(vctx vue.Context) {
17+
data := vctx.Data().(*Data)
1818
data.Seen = !data.Seen
1919
}
2020

examples/04-loops/main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type Todo struct {
2121
Text string
2222
}
2323

24-
func Add(context vue.Context) {
25-
data := context.Data().(*Data)
24+
func Add(vctx vue.Context) {
25+
data := vctx.Data().(*Data)
2626
data.Todos = append(data.Todos, Todo{"Build something wasm!"})
2727
}
2828

examples/05-handling-user-input/main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ type Data struct {
1717
Message string
1818
}
1919

20-
func ReverseMessage(context vue.Context) {
21-
data := context.Data().(*Data)
20+
func ReverseMessage(vctx vue.Context) {
21+
data := vctx.Data().(*Data)
2222
runes := []rune(data.Message)
2323
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
2424
runes[i], runes[j] = runes[j], runes[i]

examples/09-computed-properties/main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ type Data struct {
1515
Message string
1616
}
1717

18-
func ReversedMessage(context vue.Context) string {
19-
message := context.Get("Message").(string)
18+
func ReversedMessage(vctx vue.Context) string {
19+
message := vctx.Get("Message").(string)
2020
runes := []rune(message)
2121
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
2222
runes[i], runes[j] = runes[j], runes[i]

examples/10-watchers/main.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ type yesno struct {
2626
Answer string `json:"answer"`
2727
}
2828

29-
func Answer(context vue.Context, newQuestion, _ string) {
29+
func Answer(vctx vue.Context, newQuestion, _ string) {
3030
if !strings.HasSuffix(newQuestion, "?") {
31-
context.Set("Answer", "Questions usually contain a question mark.")
31+
vctx.Set("Answer", "Questions usually contain a question mark.")
3232
return
3333
}
3434

35-
context.Go("AsyncAnswer")
35+
vctx.Go("AsyncAnswer")
3636
}
3737

38-
func AsyncAnswer(context vue.Context) {
39-
data := context.Data().(*Data)
38+
func AsyncAnswer(vctx vue.Context) {
39+
data := vctx.Data().(*Data)
4040
res, err := http.Get("https://yesno.wtf/api")
4141
if err != nil {
4242
data.Answer = err.Error()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>11 - Class Binding</title>
6+
<script src="{{ .Script }}"></script>
7+
<style>
8+
.active {
9+
background-color: yellow;
10+
}
11+
12+
.text-danger {
13+
color: red;
14+
}
15+
</style>
16+
</head>
17+
<body>
18+
<div id="app"></div>
19+
<script src="{{ .Loader }}"></script>
20+
</body>
21+
</html>

examples/11-class-binding/main.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import (
4+
"github.com/norunners/vue"
5+
"math/rand"
6+
"time"
7+
)
8+
9+
const tmpl = `
10+
<p v-bind:class="Class">
11+
Hello WebAssembly!
12+
</p>
13+
`
14+
15+
type Data struct {
16+
Class Class
17+
}
18+
19+
type Class struct {
20+
Active bool `css:"active"`
21+
TextDanger bool `css:"text-danger"`
22+
}
23+
24+
func Change(vctx vue.Context) {
25+
data := vctx.Data().(*Data)
26+
data.Class.Active = rand.Intn(2) == 1
27+
data.Class.TextDanger = rand.Intn(2) == 1
28+
}
29+
30+
func main() {
31+
vm := vue.New(
32+
vue.El("#app"),
33+
vue.Template(tmpl),
34+
vue.Data(&Data{}),
35+
vue.Methods(Change),
36+
)
37+
38+
for tick := time.Tick(time.Second); ; {
39+
select {
40+
case <-tick:
41+
vm.Go("Change")
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>12 - Style Binding</title>
6+
<script src="{{ .Script }}"></script>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script src="{{ .Loader }}"></script>
11+
</body>
12+
</html>

examples/12-style-binding/main.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/norunners/vue"
6+
"math/rand"
7+
"time"
8+
)
9+
10+
const tmpl = `
11+
<p v-bind:style="Style">
12+
Hello WebAssembly!
13+
</p>
14+
`
15+
16+
type Data struct {
17+
r, g, b int
18+
px int
19+
}
20+
21+
type Styles struct {
22+
Color string `css:"color"`
23+
FontSize string `css:"font-size"`
24+
}
25+
26+
func Style(vctx vue.Context) *Styles {
27+
data := vctx.Data().(*Data)
28+
hex := fmt.Sprintf("#%02x%02x%02x", data.r, data.g, data.b)
29+
size := fmt.Sprintf("%dpx", data.px)
30+
return &Styles{
31+
Color: hex,
32+
FontSize: size,
33+
}
34+
}
35+
36+
func Change(vctx vue.Context) {
37+
data := vctx.Data().(*Data)
38+
data.r = int(rand.Float32() * 0xff)
39+
data.g = int(rand.Float32() * 0xff)
40+
data.b = int(rand.Float32() * 0xff)
41+
data.px = 8 + (data.px-7)%64
42+
}
43+
44+
func main() {
45+
vm := vue.New(
46+
vue.El("#app"),
47+
vue.Template(tmpl),
48+
vue.Data(&Data{px: 8}),
49+
vue.Computeds(Style),
50+
vue.Methods(Change),
51+
)
52+
53+
for tick := time.Tick(200 * time.Millisecond); ; {
54+
select {
55+
case <-tick:
56+
vm.Go("Change")
57+
}
58+
}
59+
}

option.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func Data(data interface{}) Option {
4242
// Method is the method option for components.
4343
// The given name and function is registered as a method for the component.
4444
// The function is required to accept context and allows optional arguments.
45-
// For example: func(ctx vue.Context) or func(ctx vue.Context, a1 Arg1, ..., ak ArgK)
45+
// For example: func(vctx vue.Context) or func(vctx vue.Context, a1 Arg1, ..., ak ArgK)
4646
func Method(name string, function interface{}) Option {
4747
return func(comp *Comp) {
4848
comp.methods[name] = reflect.ValueOf(function)
@@ -52,7 +52,7 @@ func Method(name string, function interface{}) Option {
5252
// Methods is the methods option for components.
5353
// The given functions are registered as methods for the component.
5454
// The functions are required to accept context and allows optional arguments.
55-
// For example: func(ctx vue.Context) or func(ctx vue.Context, a1 Arg1, ..., ak ArgK)
55+
// For example: func(vctx vue.Context) or func(vctx vue.Context, a1 Arg1, ..., ak ArgK)
5656
func Methods(functions ...interface{}) Option {
5757
return func(comp *Comp) {
5858
for _, function := range functions {
@@ -66,7 +66,7 @@ func Methods(functions ...interface{}) Option {
6666
// Computed is the computed option for components.
6767
// The given name and function is registered as a computed property for the component.
6868
// The function is required to accept context and return a value.
69-
// For example: func(ctx vue.Context) Type
69+
// For example: func(vctx vue.Context) Type
7070
func Computed(name string, function interface{}) Option {
7171
return func(comp *Comp) {
7272
fn := reflect.ValueOf(function)
@@ -77,7 +77,7 @@ func Computed(name string, function interface{}) Option {
7777
// Computeds is the computeds option for components.
7878
// The given functions are registered as computed properties for the component.
7979
// The functions are required to accept context and return a value.
80-
// For example: func(ctx vue.Context) Type
80+
// For example: func(vctx vue.Context) Type
8181
func Computeds(functions ...interface{}) Option {
8282
return func(comp *Comp) {
8383
for _, function := range functions {
@@ -92,7 +92,7 @@ func Computeds(functions ...interface{}) Option {
9292
// The given function is registered as a watcher for the data field.
9393
// All data fields are watchable, e.g. data, props and computed.
9494
// The function is required to accept context and both the new and old values.
95-
// For example: func(ctx vue.Context, newVal, oldVal Type)
95+
// For example: func(vctx vue.Context, newVal, oldVal Type)
9696
func Watch(field string, function interface{}) Option {
9797
return func(comp *Comp) {
9898
fn := reflect.ValueOf(function)

render.go

+15-17
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ func (vm *ViewModel) mapState() {
2727
n := elem.NumField()
2828
vm.state = make(map[string]interface{}, n)
2929
for i := 0; i < n; i++ {
30-
name := typ.Field(i).Name
31-
value := elem.Field(i)
32-
vm.mapField(name, value)
30+
field := elem.Field(i)
31+
if field.CanInterface() {
32+
name := typ.Field(i).Name
33+
value := field.Interface()
34+
vm.mapField(name, value)
35+
}
3336
}
3437
vm.mapProps()
3538
vm.mapComputed()
@@ -38,8 +41,7 @@ func (vm *ViewModel) mapState() {
3841
// mapProps maps props to state.
3942
func (vm *ViewModel) mapProps() {
4043
for field, prop := range vm.props {
41-
value := reflect.ValueOf(prop)
42-
vm.mapField(field, value)
44+
vm.mapField(field, prop)
4345
}
4446
}
4547

@@ -55,24 +57,20 @@ func (vm *ViewModel) mapComputed() {
5557

5658
// mapField maps a field to state.
5759
// Watchers are called on field changes.
58-
// Returns whether the field was changed.
59-
func (vm *ViewModel) mapField(field string, newVal reflect.Value) bool {
60+
func (vm *ViewModel) mapField(field string, value interface{}) {
6061
oldField, ok := vm.state[field]
62+
vm.state[field] = value
6163
if !ok {
62-
vm.state[field] = newVal.Interface()
63-
return true
64-
}
65-
66-
oldVal := reflect.ValueOf(oldField)
67-
if reflect.DeepEqual(newVal, oldVal) {
68-
return false
64+
return
6965
}
7066

7167
if watcher, ok := vm.comp.watchers[field]; ok {
68+
newVal := reflect.ValueOf(value)
69+
oldVal := reflect.ValueOf(oldField)
70+
if reflect.DeepEqual(newVal, oldVal) {
71+
return
72+
}
7273
values := append([]reflect.Value{reflect.ValueOf(vm)}, newVal, oldVal)
7374
watcher.Call(values)
7475
}
75-
76-
vm.state[field] = newVal.Interface()
77-
return true
7876
}

0 commit comments

Comments
 (0)