Skip to content

Commit

Permalink
Make This a component member
Browse files Browse the repository at this point in the history
Having to pass This as an argument to every method ended up to be very ugly and difficult.
  • Loading branch information
bep committed Jun 2, 2016
1 parent c455264 commit 786687f
Show file tree
Hide file tree
Showing 18 changed files with 326 additions and 168 deletions.
108 changes: 75 additions & 33 deletions component.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/bep/gr/support"
"github.com/gopherjs/gopherjs/js"
"reflect"
)

var (
Expand Down Expand Up @@ -180,7 +181,7 @@ type simpleRenderer struct {
}

// Implements the Renderer interface.
func (s simpleRenderer) Render(this *This) Component {
func (s simpleRenderer) Render() Component {
return s.c
}

Expand All @@ -197,6 +198,7 @@ type reactClass struct {
displayName string `js:"displayName"`

render *js.Object `js:"render"`
getDefaultProps *js.Object `js:"getDefaultProps"`
getInitialState *js.Object `js:"getInitialState"`
getChildContext *js.Object `js:"getChildContext"`
childContextTypes js.M `js:"childContextTypes"`
Expand All @@ -212,19 +214,53 @@ type reactClass struct {
}

type delegateRenderer struct {
delegate func(this *This) Component
delegate func() Component
}

// Render implements the Renderer interface.
func (d delegateRenderer) Render(this *This) Component {
return d.delegate(this)
func (d delegateRenderer) Render() Component {
return d.delegate()
}

// NewRenderer creates a Renderer with the provided func as the implementation.
func NewRenderer(renderFunc func(this *This) Component) Renderer {
func NewRenderer(renderFunc func() Component) Renderer {
return delegateRenderer{renderFunc}
}

func extractThisInitializer(r Renderer) ThisInitializer {
var thisInit ThisInitializer

rv := reflect.ValueOf(r)

if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}

rt := rv.Type()

if rt.Kind() == reflect.Struct {
for i := 0; i < rt.NumField(); i++ {
fieldVal := rv.Field(i)
if fieldVal.CanInterface() {
if init, ok := fieldVal.Interface().(ThisInitializer); ok {
if fieldVal.IsNil() {
newVal := reflect.New(rt.Field(i).Type.Elem())
fieldVal.Set(newVal)
thisInit = newVal.Interface().(ThisInitializer)
} else {
thisInit = init
}
break
}
}

}

}

return thisInit
}

// New creates a new Component given a Renderer and optinal option(s).
// Note that the Renderer is the minimum interface that needs to be implemented,
// but New will perform interface upgrades for other lifecycle interfaces.
Expand All @@ -241,14 +277,22 @@ func New(r Renderer, options ...Option) *ReactComponent {
//classProps.Set("mixins", nil) https://github.com/bep/gr/issues/24
//classProps.Set("statics", nil) https://github.com/bep/gr/issues/25

//root.reactClass.contextTypes = js.M{"color": react.Get("PropTypes").Get("string"), "id": react.Get("PropTypes").Get("number")}
// This is the first lifecycle method with 'this' provided.
// We use this to init the this object if provided.

thisInit := extractThisInitializer(r)

// Every component needs to render itself.
root.reactClass.render = makeRenderFunc(displayName, r.Render)

// Optional lifecycle implementations below.
if v, ok := r.(StateInitializer); ok {
root.reactClass.getInitialState = makeStateFunc(v.GetInitialState)
root.reactClass.getInitialState = makeStateFunc(thisInit, v.GetInitialState)
} else if thisInit != nil {
root.reactClass.getInitialState = js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
thisInit.InitThis(this)
return nil
})
}

if v, ok := r.(ChildContextProvider); ok {
Expand Down Expand Up @@ -369,28 +413,28 @@ func (r *ReactComponent) Render(elementID string, props Props) {
reactDOM.Call("render", elem.Node(), container)
}

func makeComponentUpdateFunc(f func(this *This, c Cops) bool) *js.Object {
func makeComponentUpdateFunc(f func(c Cops) bool) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
return f(extractComponentUpdateArgs(this, arguments))
return f(extractComponentUpdateArgs(arguments))
})
}

func makeComponentUpdateVoidFunc(f func(this *This, c Cops)) *js.Object {
func makeComponentUpdateVoidFunc(f func(c Cops)) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
f(extractComponentUpdateArgs(this, arguments))
f(extractComponentUpdateArgs(arguments))
return nil
})
}

func makeComponentPropertyReceiverFunc(f func(this *This, c Cops)) *js.Object {
func makeComponentPropertyReceiverFunc(f func(c Cops)) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
that, data := extractComponentUpdateArgs(this, arguments)
f(that, data)
data := extractComponentUpdateArgs(arguments)
f(data)
return nil
})
}

func extractComponentUpdateArgs(this *js.Object, arguments []*js.Object) (*This, Cops) {
func extractComponentUpdateArgs(arguments []*js.Object) Cops {
var (
props Props
state State
Expand All @@ -407,37 +451,38 @@ func extractComponentUpdateArgs(this *js.Object, arguments []*js.Object) (*This,
context = arguments[2].Interface().(map[string]interface{})
}

that := NewThis(this)

return that, Cops{Props: props, State: state, Context: context}
return Cops{Props: props, State: state, Context: context}
}

func makeVoidFunc(f func(this *This), assumeBlocking bool) *js.Object {
func makeVoidFunc(f func(), assumeBlocking bool) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
if assumeBlocking {
go func() {
f(NewThis(this))
f()
}()
} else {
f(NewThis(this))
f()
}
return nil
})
}

func makeStateFunc(f func(this *This) State) *js.Object {
func makeStateFunc(thisInit ThisInitializer, f func() State) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
return f(NewThis(this))
if thisInit != nil {
thisInit.InitThis(this)
}
return f()
})
}

func makeChildContextFunc(f func(this *This) Context) (*js.Object, js.M) {
func makeChildContextFunc(f func() Context) (*js.Object, js.M) {

getChildContext := js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
return f(NewThis(this))
return f()
})

childContextTypes := extractPropTypesFromTemplate(f(nil))
childContextTypes := extractPropTypesFromTemplate(f())

return getChildContext, childContextTypes
}
Expand Down Expand Up @@ -470,21 +515,18 @@ func (i *incrementor) next() int {
return i.counter
}

func makeRenderFunc(s string, f func(this *This) Component) *js.Object {
func makeRenderFunc(s string, f func() Component) *js.Object {
return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {

that := NewThis(this)

comp := f(that)
comp := f()

if comp == nil {
return nil
}

// TODO(bep) refactor
if e, ok := comp.(*Element); ok {
e.This = that
addEventListeners(comp, that)
addEventListeners(comp, NewThis(this))
idFactory := &incrementor{}
addMissingKeys(s, e, idFactory)
}
Expand All @@ -503,7 +545,7 @@ func addEventListeners(c Component, that *This) {
if l.preventDefault {
event.Call("preventDefault")
}
l.listener(that, &Event{Object: event})
l.listener(&Event{Object: event, This: that})
}

e.properties[l.name] = l.delegate
Expand Down
2 changes: 0 additions & 2 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ type Element struct {
// This is the actual ReactJS element.
// ReactElement, ReactText or a ReactFragment
element *js.Object

*This
}

// NewElement creates a new Element with the given tag.
Expand Down
7 changes: 4 additions & 3 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
// Event represents a browser event. See https://developer.mozilla.org/en-US/docs/Web/Events
type Event struct {
*js.Object
This *This
}

// Persist can be used to make sure the event survives Facebook React's recycling of
Expand All @@ -49,7 +50,7 @@ func (e *Event) Int(key string) int {
// An EventListener can be attached to a HTML element to listen for events, mouse clicks etc.
type EventListener struct {
name string
listener func(*This, *Event)
listener func(*Event)
preventDefault bool
delegate func(jsEvent *js.Object)
}
Expand All @@ -62,11 +63,11 @@ func (l *EventListener) PreventDefault() *EventListener {

// Listener is the signature for the func that needs to be implemented by the
// listener, e.g. the clickHandler etc.
type Listener func(*This, *Event)
type Listener func(*Event)

// NewEventListener creates a new EventListener. In most cases you will use the
// predefined event listeners in the evt package.
func NewEventListener(name string, listener func(*This, *Event)) *EventListener {
func NewEventListener(name string, listener func(*Event)) *EventListener {
l := &EventListener{name: name, listener: listener}

return l
Expand Down
16 changes: 9 additions & 7 deletions examples/ajax/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ type gist struct {
Description string `json:"description"`
}

type userGists int
type userGists struct {
*gr.This
}

// Implements the Renderer interface.
func (g userGists) Render(this *gr.This) gr.Component {
func (g userGists) Render() gr.Component {

elem := el.Div()

if s, ok := this.State()["gists"]; ok {
if s, ok := g.State()["gists"]; ok {
// The nice Gist type is lost once we entered the JavaScript world.
//
// What we get now is:
Expand Down Expand Up @@ -85,7 +87,7 @@ func tableRow(i interface{}) *gr.Element {
}

// Implements the ComponentDidMount interface
func (g userGists) ComponentDidMount(this *gr.This) {
func (g userGists) ComponentDidMount() {
println("ComponentDidMount")

var gists []gist
Expand All @@ -106,16 +108,16 @@ func (g userGists) ComponentDidMount(this *gr.This) {
panic(err)
}

this.SetState(gr.State{"gists": gists})
g.SetState(gr.State{"gists": gists})
}

// Implements the ComponentWillUnmount interface
func (g userGists) ComponentWillUnmount(this *gr.This) {
func (g userGists) ComponentWillUnmount() {
println("ComponentWillUnmount")
// TODO(bep): HTTP Cancelation
}

// Implements the ShouldComponentUpdate interface.
func (g userGists) ShouldComponentUpdate(this *gr.This, next gr.Cops) bool {
return this.State().HasChanged(next.State, "gists")
return g.State().HasChanged(next.State, "gists")
}
18 changes: 10 additions & 8 deletions examples/basic-click-counter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ func main() {
})
}

type clickCounter int
type clickCounter struct {
*gr.This
}

// Implements the StateInitializer interface.
func (c clickCounter) GetInitialState(this *gr.This) gr.State {
func (c clickCounter) GetInitialState() gr.State {
return gr.State{"counter": 0}
}

// Implements the Renderer interface.
func (c clickCounter) Render(this *gr.This) gr.Component {
counter := this.State()["counter"]
func (c clickCounter) Render() gr.Component {
counter := c.State()["counter"]
message := fmt.Sprintf(" Click me! Number of clicks: %v", counter)

elem := el.Div(
Expand All @@ -42,11 +44,11 @@ func (c clickCounter) Render(this *gr.This) gr.Component {
return examples.Example("Click Counter", elem)
}

func (c clickCounter) onClick(this *gr.This, event *gr.Event) {
this.SetState(gr.State{"counter": this.State().Int("counter") + 1})
func (c clickCounter) onClick(event *gr.Event) {
c.SetState(gr.State{"counter": c.State().Int("counter") + 1})
}

// Implements the ShouldComponentUpdate interface.
func (c clickCounter) ShouldComponentUpdate(this *gr.This, next gr.Cops) bool {
return this.State().HasChanged(next.State, "counter")
func (c clickCounter) ShouldComponentUpdate(next gr.Cops) bool {
return c.State().HasChanged(next.State, "counter")
}
10 changes: 6 additions & 4 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ func main() {
})
}

type elapser int
type elapser struct {
*gr.This
}

// Implements the Renderer interface.
func (e elapser) Render(this *gr.This) gr.Component {
elapsed := this.Props()["elapsed"]
func (e elapser) Render() gr.Component {
elapsed := e.Props()["elapsed"]
message := fmt.Sprintf("React has been successfully running for '%v' seconds.", elapsed)

elem := examples.Alert("info", el.Strong(gr.Text(message)))
Expand All @@ -38,5 +40,5 @@ func (e elapser) Render(this *gr.This) gr.Component {

// Implements the ShouldComponentUpdate interface.
func (e elapser) ShouldComponentUpdate(this *gr.This, next gr.Cops) bool {
return this.Props().HasChanged(next.Props, "elapsed")
return e.Props().HasChanged(next.Props, "elapsed")
}
Loading

0 comments on commit 786687f

Please sign in to comment.