Skip to content
This repository was archived by the owner on Aug 27, 2018. It is now read-only.

Refactor events to be interface-based #53

Merged
merged 1 commit into from
Apr 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 84 additions & 42 deletions cmd/reactGen/field_exploder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ import (
"go/token"
"os"
"path"
"reflect"
"strings"
)

type field struct {
Name string
Type string
TName string
Name string
FName string
Type string

GapBefore bool

IsEvent bool

Omit bool
}
Expand All @@ -28,24 +35,12 @@ type fieldExploder struct {
imps map[*ast.ImportSpec]struct{}
}

func (fe *fieldExploder) explode() error {
tf, err := loadStructType(fe.pkgStr, fe.sn)
if err != nil {
return err
}

if tf == nil {
// we can't do any better...
return nil
}
func (fe *fieldExploder) explode(tf *typeFile) error {

st := tf.ts.Type.(*ast.StructType)

for ind, f := range st.Fields.List {
if f.Names == nil {
// embedded struct
// cannot be a paren expr

t := f.Type

if v, ok := t.(*ast.StarExpr); ok {
Expand Down Expand Up @@ -96,8 +91,7 @@ func (fe *fieldExploder) explode() error {
}

if !foundImp {
// let's move on for now
continue
return fmt.Errorf("failed to find import %v", x.Name)
}

nfe = &fieldExploder{
Expand All @@ -108,47 +102,82 @@ func (fe *fieldExploder) explode() error {
}
}

err := nfe.explode()
if err != nil {
return err
tf, err := loadStructType(nfe.pkgStr, nfe.sn)
if err == nil && tf != nil {
err := nfe.explode(tf)
if err != nil {
return err
}

fe.fields = append(fe.fields, nfe.fields...)
continue
}

fe.fields = append(fe.fields, nfe.fields...)
}

} else {
// real fields - we can print the type... but need
// to collect the imports first
// at this point we know we do _not_ have an embedded
// struct type

newImps := make(map[*ast.ImportSpec]struct{})
newImps := make(map[*ast.ImportSpec]struct{})

i := &importFinder{
imports: tf.file.Imports,
matches: newImps,
}
i := &importFinder{
imports: tf.file.Imports,
matches: newImps,
}

ast.Walk(i, f.Type)
ast.Walk(i, f.Type)

ts := astNodeString(f.Type)
ts := astNodeString(f.Type)

var omit bool
var fn string

if f.Tag != nil {
omit = strings.Contains(f.Tag.Value, `react:"omitempty"`)
}
// TODO we need a more definitive way to ascertain whether
// the type of this field is an interface that extends the
// Event interface
isEvent := strings.HasPrefix(ts, "On")

if ind == 0 && fe.first && i.isJs {
continue
// TODO better support for omit based on type of field
// need to check vs zero value
var omit bool

if f.Tag != nil {
s := reflect.StructTag(strings.Trim(f.Tag.Value, "`"))

if v, ok := s.Lookup("js"); ok {
fn = v
}

for k := range newImps {
fe.imps[k] = struct{}{}
if v, ok := s.Lookup("react"); ok {
omit = v == "omitempty"
}
}

if ind == 0 && fe.first && i.isJs {
continue
}

for k := range newImps {
fe.imps[k] = struct{}{}
}

if len(f.Names) == 0 {
fe.fields = append(fe.fields, field{
TName: typeToName(f.Type),
FName: fn,
Type: ts,
IsEvent: isEvent,
Omit: omit,
})

} else {
for _, n := range f.Names {
fe.fields = append(fe.fields, field{
Name: n.Name,
Type: ts,
Omit: omit,
Name: n.Name,
TName: n.Name,
FName: fn,
Type: ts,
IsEvent: isEvent,
Omit: omit,
})
}
}
Expand All @@ -157,6 +186,19 @@ func (fe *fieldExploder) explode() error {
return nil
}

func typeToName(t ast.Expr) string {
switch t := t.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return typeToName(t.X)
case *ast.ParenExpr:
return typeToName(t.X)
default:
panic(fmt.Errorf("don't know how to handle %T", t))
}
}

var pkgCache = make(map[string]*ast.Package)

func loadStructType(pkgStr string, sn string) (*typeFile, error) {
Expand Down
39 changes: 27 additions & 12 deletions cmd/reactGen/props_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import (
type propsGen struct {
*coreGen

Recv string
Name string
Doc string
Recv string
Name string
TName string
Doc string

Fields []field
}
Expand Down Expand Up @@ -50,15 +51,23 @@ func (g *gen) genProps(defName string, t typeFile) {
imps: make(map[*ast.ImportSpec]struct{}),
}

err := fe.explode()
err := fe.explode(&t)
if err != nil {
fatalf("could not explode fields: %v", err)
}

sort.Slice(fe.fields, func(i, j int) bool {
return fe.fields[i].Name < fe.fields[j].Name
return fe.fields[i].TName < fe.fields[j].TName
})

if len(fe.fields) > 2 {
for i := 1; i < len(fe.fields)-1; i++ {
if fe.fields[i].IsEvent != fe.fields[i-1].IsEvent {
fe.fields[i].GapBefore = true
}
}
}

pg.Fields = fe.fields

pg.pf("// Code generated by %v. DO NOT EDIT.\n", reactGenCmd)
Expand All @@ -85,24 +94,30 @@ func (g *gen) genProps(defName string, t typeFile) {

{{.Doc}}
type {{.Name}} struct {
{{range .Fields}}
{{.Name}} {{.Type}}
{{range $i, $v := .Fields}}
{{if $v.GapBefore}}
{{end -}}
{{$v.Name}} {{$v.Type}}
{{- end}}
}

func ({{$recv}} *{{.Name}}) assign(v *_{{.Name}}) {
{{- range .Fields}}
{{ if .Omit }}
if {{$recv}}.{{.Name}} != "" {
v.{{.Name}} = {{$recv}}.{{.Name}}
if {{$recv}}.{{.TName}} != "" {
v.{{.TName}} = {{$recv}}.{{.TName}}
}
{{else}}
{{if eq .Name "Style"}}
{{if .IsEvent}}
if {{$recv}}.{{.TName}} != nil {
v.o.Set("{{.FName}}", {{$recv}}.{{.TName}}.{{.TName}})
}
{{else if eq .Name "Style"}}
// TODO: until we have a resolution on
// https://github.com/gopherjs/gopherjs/issues/236
v.{{.Name}} = {{$recv}}.{{.Name}}.hack()
v.{{.TName}} = {{$recv}}.{{.TName}}.hack()
{{else}}
v.{{.Name}} = {{$recv}}.{{.Name}}
v.{{.TName}} = {{$recv}}.{{.TName}}
{{end}}
{{end}}
{{- end}}
Expand Down
4 changes: 2 additions & 2 deletions core.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type BasicHTMLElement struct {
Role string `js:"role"`
Style *CSS `js:"style"`

OnChange func(e *SyntheticEvent) `js:"onChange"`
OnClick func(e *SyntheticMouseEvent) `js:"onClick"`
OnChange `js:"onChange"`
OnClick `js:"onClick"`

DangerouslySetInnerHTML *DangerousInnerHTMLDef `js:"dangerouslySetInnerHTML"`
}
Expand Down
15 changes: 15 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package react

type Event interface{}

type OnChange interface {
Event

OnChange(e *SyntheticEvent)
}

type OnClick interface {
Event

OnClick(e *SyntheticMouseEvent)
}
26 changes: 17 additions & 9 deletions examples/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,31 @@ func (p *ExamplesDef) buildExampleNavTab(key exampleKey, t tab, title string) *r
return r.Li(
lip,
r.A(
&r.AProps{Href: "#", OnClick: p.handleTabChange(key, t)},
&r.AProps{Href: "#", OnClick: tabChange{p, key, t}},
r.S(title),
),
)

}

func (p *ExamplesDef) handleTabChange(key exampleKey, t tab) func(*r.SyntheticMouseEvent) {
return func(e *r.SyntheticMouseEvent) {
cts := p.State().selectedTabs
newSt := p.State()
type tabChange struct {
e *ExamplesDef
key exampleKey
t tab
}

newSt.selectedTabs = cts.Set(key, t)
p.SetState(newSt)
func (tc tabChange) OnClick(e *r.SyntheticMouseEvent) {
p := tc.e
key := tc.key
t := tc.t

e.PreventDefault()
}
cts := p.State().selectedTabs
newSt := p.State()

newSt.selectedTabs = cts.Set(key, t)
p.SetState(newSt)

e.PreventDefault()
}

func plainPanel(children ...r.Element) r.Element {
Expand Down
3 changes: 0 additions & 3 deletions examples/hellomessage/hello_message.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Copyright (c) 2016 Paul Jolly <paul@myitcv.org.uk>, all rights reserved.
// Use of this document is governed by a license found in the LICENSE document.

package hellomessage // import "myitcv.io/react/examples/hellomessage"

import (
Expand Down
22 changes: 21 additions & 1 deletion examples/imm_examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,33 @@ func (p *ImmExamplesDef) buildExampleNavTab(key exampleKey, t tab, title string)
return r.Li(
lip,
r.A(
&r.AProps{Href: "#", OnClick: p.handleTabChange(key, t)},
&r.AProps{Href: "#", OnClick: immTabChange{p, key, t}},
r.S(title),
),
)

}

type immTabChange struct {
e *ImmExamplesDef
key exampleKey
t tab
}

func (tc immTabChange) OnClick(e *r.SyntheticMouseEvent) {
p := tc.e
key := tc.key
t := tc.t

cts := p.State().selectedTabs
newSt := p.State()

newSt.selectedTabs = cts.Set(key, t)
p.SetState(newSt)

e.PreventDefault()
}

func (p *ImmExamplesDef) handleTabChange(key exampleKey, t tab) func(*r.SyntheticMouseEvent) {
return func(e *r.SyntheticMouseEvent) {
cts := p.State().selectedTabs
Expand Down
Loading