2017-06-11 12:01:42 +00:00
|
|
|
// 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 react is a set of GopherJS bindings for Facebook's React, a Javascript
|
|
|
|
library for building user interfaces.
|
|
|
|
|
|
|
|
For more information see https://github.com/myitcv/react/wiki
|
|
|
|
|
|
|
|
*/
|
|
|
|
package react // import "myitcv.io/react"
|
|
|
|
|
|
|
|
//go:generate reactGen
|
|
|
|
|
|
|
|
import (
|
2017-11-23 15:55:44 +00:00
|
|
|
"fmt"
|
2017-06-11 12:01:42 +00:00
|
|
|
"reflect"
|
|
|
|
|
|
|
|
"honnef.co/go/js/dom"
|
|
|
|
|
|
|
|
"github.com/gopherjs/gopherjs/js"
|
|
|
|
"github.com/gopherjs/jsbuiltin"
|
|
|
|
|
|
|
|
// imported for the side effect of bundling react
|
|
|
|
// build tags control whether this actually includes
|
|
|
|
// js files or not
|
|
|
|
_ "myitcv.io/react/internal/bundle"
|
2017-11-23 15:55:44 +00:00
|
|
|
"myitcv.io/react/internal/core"
|
2017-06-11 12:01:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
reactInternalInstance = "_reactInternalInstance"
|
|
|
|
reactCompProps = "props"
|
|
|
|
reactCompLastState = "__lastState"
|
|
|
|
reactComponentBuilder = "__componentBuilder"
|
|
|
|
reactCompDisplayName = "displayName"
|
|
|
|
reactCompSetState = "setState"
|
|
|
|
reactCompState = "state"
|
|
|
|
reactCompGetInitialState = "getInitialState"
|
|
|
|
reactCompShouldComponentUpdate = "shouldComponentUpdate"
|
|
|
|
reactCompComponentDidMount = "componentDidMount"
|
|
|
|
reactCompComponentWillReceiveProps = "componentWillReceiveProps"
|
|
|
|
reactCompComponentWillMount = "componentWillMount"
|
|
|
|
reactCompComponentWillUnmount = "componentWillUnmount"
|
|
|
|
reactCompRender = "render"
|
|
|
|
|
|
|
|
reactCreateElement = "createElement"
|
|
|
|
reactCreateClass = "createClass"
|
|
|
|
reactDOMRender = "render"
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
nestedChildren = "_children"
|
2017-06-11 12:01:42 +00:00
|
|
|
nestedProps = "_props"
|
|
|
|
nestedState = "_state"
|
|
|
|
nestedComponentWrapper = "__ComponentWrapper"
|
|
|
|
)
|
|
|
|
|
|
|
|
var react = js.Global.Get("React")
|
|
|
|
var reactDOM = js.Global.Get("ReactDOM")
|
|
|
|
var object = js.Global.Get("Object")
|
|
|
|
|
|
|
|
// ComponentDef is embedded in a type definition to indicate the type is a component
|
|
|
|
type ComponentDef struct {
|
|
|
|
elem *js.Object
|
|
|
|
}
|
|
|
|
|
|
|
|
var compMap = make(map[reflect.Type]*js.Object)
|
|
|
|
|
|
|
|
// S is the React representation of a string
|
2017-11-23 15:55:44 +00:00
|
|
|
type S = core.S
|
2017-06-11 12:01:42 +00:00
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
type elementHolder = core.ElementHolder
|
2017-06-11 12:01:42 +00:00
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
type Element = core.Element
|
2017-06-11 12:01:42 +00:00
|
|
|
|
|
|
|
type Component interface {
|
|
|
|
ShouldComponentUpdateIntf(nextProps Props, prevState, nextState State) bool
|
|
|
|
Render() Element
|
|
|
|
}
|
|
|
|
|
|
|
|
type componentWithWillMount interface {
|
|
|
|
Component
|
|
|
|
ComponentWillMount()
|
|
|
|
}
|
|
|
|
|
|
|
|
type componentWithDidMount interface {
|
|
|
|
Component
|
|
|
|
ComponentDidMount()
|
|
|
|
}
|
|
|
|
|
|
|
|
type componentWithWillReceiveProps interface {
|
|
|
|
Component
|
|
|
|
ComponentWillReceivePropsIntf(i interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
type componentWithGetInitialState interface {
|
|
|
|
Component
|
|
|
|
GetInitialStateIntf() State
|
|
|
|
}
|
|
|
|
|
|
|
|
type componentWithWillUnmount interface {
|
|
|
|
Component
|
|
|
|
ComponentWillUnmount()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Props interface {
|
|
|
|
IsProps()
|
|
|
|
EqualsIntf(v Props) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type State interface {
|
|
|
|
IsState()
|
|
|
|
EqualsIntf(v State) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c ComponentDef) Props() Props {
|
2017-11-23 15:55:44 +00:00
|
|
|
return unwrapValue(c.instance().Get(reactCompProps).Get(nestedProps)).(Props)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c ComponentDef) Children() []Element {
|
|
|
|
v := c.instance().Get(reactCompProps).Get(nestedChildren)
|
|
|
|
|
|
|
|
if v == js.Undefined {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return unwrapValue(v).([]Element)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c ComponentDef) instance() *js.Object {
|
|
|
|
return c.elem.Get("_instance")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c ComponentDef) SetState(i State) {
|
|
|
|
cur := c.State()
|
|
|
|
|
|
|
|
if i.EqualsIntf(cur) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
res := object.New()
|
2017-11-23 15:55:44 +00:00
|
|
|
res.Set(nestedState, wrapValue(i))
|
2017-06-11 12:01:42 +00:00
|
|
|
c.instance().Set(reactCompLastState, res)
|
|
|
|
c.instance().Call(reactCompSetState, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c ComponentDef) State() State {
|
|
|
|
ok, err := jsbuiltin.In(reactCompLastState, c.instance())
|
|
|
|
if err != nil {
|
|
|
|
// TODO better handle this case... does that function even need to
|
|
|
|
// return an error?
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
s := c.instance().Get(reactCompState)
|
|
|
|
c.instance().Set(reactCompLastState, s)
|
|
|
|
}
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
return unwrapValue(c.instance().Get(reactCompLastState).Get(nestedState)).(State)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ComponentBuilder func(elem ComponentDef) Component
|
|
|
|
|
|
|
|
func CreateElement(buildCmp ComponentBuilder, newprops Props, children ...Element) Element {
|
|
|
|
cmp := buildCmp(ComponentDef{})
|
|
|
|
typ := reflect.TypeOf(cmp)
|
|
|
|
|
|
|
|
comp, ok := compMap[typ]
|
|
|
|
if !ok {
|
|
|
|
comp = buildReactComponent(typ, buildCmp)
|
|
|
|
compMap[typ] = comp
|
|
|
|
}
|
|
|
|
|
|
|
|
propsWrap := object.New()
|
|
|
|
if newprops != nil {
|
2017-11-23 15:55:44 +00:00
|
|
|
propsWrap.Set(nestedProps, wrapValue(newprops))
|
|
|
|
}
|
|
|
|
|
|
|
|
if children != nil {
|
|
|
|
propsWrap.Set(nestedChildren, wrapValue(children))
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
args := []interface{}{comp, propsWrap}
|
|
|
|
|
|
|
|
for _, v := range children {
|
2017-11-23 15:55:44 +00:00
|
|
|
args = append(args, v)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
return &elementHolder{
|
|
|
|
Elem: react.Call(reactCreateElement, args...),
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createElement(cmp string, props interface{}, children ...Element) Element {
|
|
|
|
args := []interface{}{cmp, props}
|
|
|
|
|
|
|
|
for _, v := range children {
|
2017-11-23 15:55:44 +00:00
|
|
|
args = append(args, v)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
return &elementHolder{
|
|
|
|
Elem: react.Call("createElement", args...),
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildReactComponent(typ reflect.Type, builder ComponentBuilder) *js.Object {
|
|
|
|
compDef := object.New()
|
2017-11-23 15:55:44 +00:00
|
|
|
compDef.Set(reactCompDisplayName, fmt.Sprintf("%v(%v)", typ.Name(), typ.PkgPath()))
|
2017-06-11 12:01:42 +00:00
|
|
|
compDef.Set(reactComponentBuilder, builder)
|
|
|
|
|
|
|
|
compDef.Set(reactCompGetInitialState, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
if cmp, ok := cmp.(componentWithGetInitialState); ok {
|
|
|
|
x := cmp.GetInitialStateIntf()
|
|
|
|
if x == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
res := object.New()
|
2017-11-23 15:55:44 +00:00
|
|
|
res.Set(nestedState, wrapValue(x))
|
2017-06-11 12:01:42 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompShouldComponentUpdate, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
var nextProps Props
|
|
|
|
var prevState State
|
|
|
|
var nextState State
|
|
|
|
|
|
|
|
if arguments[0] != nil {
|
|
|
|
if ok, err := jsbuiltin.In(nestedProps, arguments[0]); err == nil && ok {
|
2017-11-23 15:55:44 +00:00
|
|
|
nextProps = unwrapValue(arguments[0].Get(nestedProps)).(Props)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if arguments[1] != nil {
|
|
|
|
if ok, err := jsbuiltin.In(nestedState, arguments[1]); err == nil && ok {
|
2017-11-23 15:55:44 +00:00
|
|
|
nextState = unwrapValue(arguments[1].Get(nestedState)).(State)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// here we _deliberately_ get React's version of the current state
|
|
|
|
// as opposed to the last state value
|
|
|
|
if this != nil {
|
|
|
|
if s := this.Get(reactCompState); s != nil {
|
2017-11-23 15:55:44 +00:00
|
|
|
if v := unwrapValue(s.Get(nestedState)); v != nil {
|
|
|
|
prevState = v.(State)
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmp.ShouldComponentUpdateIntf(nextProps, prevState, nextState)
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompComponentDidMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
if cmp, ok := cmp.(componentWithDidMount); ok {
|
|
|
|
cmp.ComponentDidMount()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompComponentWillReceiveProps, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
if cmp, ok := cmp.(componentWithWillReceiveProps); ok {
|
2017-11-23 15:55:44 +00:00
|
|
|
ourProps := unwrapValue(arguments[0].Get(nestedProps))
|
2017-06-11 12:01:42 +00:00
|
|
|
cmp.ComponentWillReceivePropsIntf(ourProps)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompComponentWillUnmount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
if cmp, ok := cmp.(componentWithWillUnmount); ok {
|
|
|
|
cmp.ComponentWillUnmount()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompComponentWillMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
// TODO we can make this more efficient by not doing the type check
|
|
|
|
// within the function body; it is known at the time of setting
|
|
|
|
// "componentWillMount" on the compDef
|
|
|
|
if cmp, ok := cmp.(componentWithWillMount); ok {
|
|
|
|
cmp.ComponentWillMount()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
compDef.Set(reactCompRender, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
|
|
|
|
elem := this.Get(reactInternalInstance)
|
|
|
|
cmp := builder(ComponentDef{elem: elem})
|
|
|
|
|
|
|
|
renderRes := cmp.Render()
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
return renderRes
|
2017-06-11 12:01:42 +00:00
|
|
|
}))
|
|
|
|
|
|
|
|
return react.Call(reactCreateClass, compDef)
|
|
|
|
}
|
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
func Render(el Element, container dom.Element) Element {
|
|
|
|
v := reactDOM.Call(reactDOMRender, el, container)
|
2017-06-11 12:01:42 +00:00
|
|
|
|
2017-11-23 15:55:44 +00:00
|
|
|
return &elementHolder{Elem: v}
|
2017-06-11 12:01:42 +00:00
|
|
|
}
|