github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/react.go (about)

     1  // Copyright (c) 2016 Paul Jolly <paul@myitcv.org.uk>, all rights reserved.
     2  // Use of this document is governed by a license found in the LICENSE document.
     3  
     4  /*
     5  
     6  Package react is a set of GopherJS bindings for Facebook's React, a Javascript
     7  library for building user interfaces.
     8  
     9  For more information see https://github.com/myitcv/react/wiki
    10  
    11  */
    12  package react // import "myitcv.io/react"
    13  
    14  //go:generate cssGen
    15  //go:generate coreGen
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  
    21  	"honnef.co/go/js/dom"
    22  
    23  	"github.com/gopherjs/gopherjs/js"
    24  	"github.com/gopherjs/jsbuiltin"
    25  
    26  	// imported for the side effect of bundling react
    27  	// build tags control whether this actually includes
    28  	// js files or not
    29  	_ "myitcv.io/react/internal/bundle"
    30  	"myitcv.io/react/internal/core"
    31  )
    32  
    33  const (
    34  	reactCompProps                     = "props"
    35  	reactCompLastState                 = "__lastState"
    36  	reactComponentBuilder              = "__componentBuilder"
    37  	reactCompDisplayName               = "displayName"
    38  	reactCompSetState                  = "setState"
    39  	reactCompForceUpdate               = "forceUpdate"
    40  	reactCompState                     = "state"
    41  	reactCompGetInitialState           = "getInitialState"
    42  	reactCompShouldComponentUpdate     = "shouldComponentUpdate"
    43  	reactCompComponentDidMount         = "componentDidMount"
    44  	reactCompComponentWillReceiveProps = "componentWillReceiveProps"
    45  	reactCompComponentWillMount        = "componentWillMount"
    46  	reactCompComponentWillUnmount      = "componentWillUnmount"
    47  	reactCompRender                    = "render"
    48  
    49  	reactCreateElement = "createElement"
    50  	reactCreateClass   = "createClass"
    51  	reactDOMRender     = "render"
    52  
    53  	nestedChildren         = "_children"
    54  	nestedProps            = "_props"
    55  	nestedState            = "_state"
    56  	nestedComponentWrapper = "__ComponentWrapper"
    57  )
    58  
    59  var react = js.Global.Get("React")
    60  var reactDOM = js.Global.Get("ReactDOM")
    61  var object = js.Global.Get("Object")
    62  var symbolFragment = react.Get("Fragment")
    63  
    64  // ComponentDef is embedded in a type definition to indicate the type is a component
    65  type ComponentDef struct {
    66  	elem *js.Object
    67  }
    68  
    69  var compMap = make(map[reflect.Type]*js.Object)
    70  
    71  // S is the React representation of a string
    72  type S = core.S
    73  
    74  func Sprintf(format string, args ...interface{}) S {
    75  	return S(fmt.Sprintf(format, args...))
    76  }
    77  
    78  type elementHolder = core.ElementHolder
    79  
    80  type Element = core.Element
    81  
    82  type Component interface {
    83  	RendersElement() Element
    84  }
    85  
    86  type componentWithWillMount interface {
    87  	Component
    88  	ComponentWillMount()
    89  }
    90  
    91  type componentWithDidMount interface {
    92  	Component
    93  	ComponentDidMount()
    94  }
    95  
    96  type componentWithWillReceiveProps interface {
    97  	Component
    98  	ComponentWillReceivePropsIntf(i interface{})
    99  }
   100  
   101  type componentWithGetInitialState interface {
   102  	Component
   103  	GetInitialStateIntf() State
   104  }
   105  
   106  type componentWithWillUnmount interface {
   107  	Component
   108  	ComponentWillUnmount()
   109  }
   110  
   111  type Props interface {
   112  	IsProps()
   113  	EqualsIntf(v Props) bool
   114  }
   115  
   116  type State interface {
   117  	IsState()
   118  	EqualsIntf(v State) bool
   119  }
   120  
   121  func (c ComponentDef) Props() Props {
   122  	return *(unwrapValue(c.elem.Get(reactCompProps).Get(nestedProps)).(*Props))
   123  }
   124  
   125  func (c ComponentDef) Children() []Element {
   126  	v := c.elem.Get(reactCompProps).Get(nestedChildren)
   127  
   128  	if v == js.Undefined {
   129  		return nil
   130  	}
   131  
   132  	return *(unwrapValue(v).(*[]Element))
   133  }
   134  
   135  func (c ComponentDef) SetState(i State) {
   136  	rs := c.elem.Get(reactCompState)
   137  	is := rs.Get(nestedState)
   138  
   139  	cur := *(unwrapValue(is.Get(reactCompLastState)).(*State))
   140  
   141  	if i.EqualsIntf(cur) {
   142  		return
   143  	}
   144  
   145  	is.Set(reactCompLastState, wrapValue(&i))
   146  	c.elem.Call(reactCompForceUpdate)
   147  }
   148  
   149  func (c ComponentDef) State() State {
   150  	rs := c.elem.Get(reactCompState)
   151  	is := rs.Get(nestedState)
   152  
   153  	cur := *(unwrapValue(is.Get(reactCompLastState)).(*State))
   154  
   155  	return cur
   156  }
   157  
   158  func (c ComponentDef) ForceUpdate() {
   159  	c.elem.Call(reactCompForceUpdate)
   160  }
   161  
   162  type ComponentBuilder func(elem ComponentDef) Component
   163  
   164  func CreateElement(buildCmp ComponentBuilder, newprops Props, children ...Element) Element {
   165  	cmp := buildCmp(ComponentDef{})
   166  	typ := reflect.TypeOf(cmp)
   167  
   168  	comp, ok := compMap[typ]
   169  	if !ok {
   170  		comp = buildReactComponent(typ, buildCmp)
   171  		compMap[typ] = comp
   172  	}
   173  
   174  	propsWrap := object.New()
   175  	if newprops != nil {
   176  		propsWrap.Set(nestedProps, wrapValue(&newprops))
   177  	}
   178  
   179  	if children != nil {
   180  		propsWrap.Set(nestedChildren, wrapValue(&children))
   181  	}
   182  
   183  	args := []interface{}{comp, propsWrap}
   184  
   185  	for _, v := range children {
   186  		args = append(args, v)
   187  	}
   188  
   189  	return &elementHolder{
   190  		Elem: react.Call(reactCreateElement, args...),
   191  	}
   192  }
   193  
   194  func createElement(cmp interface{}, props interface{}, children ...Element) Element {
   195  	args := []interface{}{cmp, props}
   196  
   197  	for _, v := range children {
   198  		args = append(args, v)
   199  	}
   200  
   201  	return &elementHolder{
   202  		Elem: react.Call(reactCreateElement, args...),
   203  	}
   204  }
   205  
   206  func buildReactComponent(typ reflect.Type, builder ComponentBuilder) *js.Object {
   207  	compDef := object.New()
   208  	compDef.Set(reactCompDisplayName, fmt.Sprintf("%v(%v)", typ.Name(), typ.PkgPath()))
   209  	compDef.Set(reactComponentBuilder, builder)
   210  
   211  	compDef.Set(reactCompGetInitialState, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   212  		elem := this
   213  		cmp := builder(ComponentDef{elem: elem})
   214  
   215  		var wv *js.Object
   216  
   217  		res := object.New()
   218  		is := object.New()
   219  
   220  		if cmp, ok := cmp.(componentWithGetInitialState); ok {
   221  			x := cmp.GetInitialStateIntf()
   222  			wv = wrapValue(&x)
   223  		}
   224  
   225  		res.Set(nestedState, is)
   226  		is.Set(reactCompLastState, wv)
   227  
   228  		return res
   229  	}))
   230  
   231  	compDef.Set(reactCompShouldComponentUpdate, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   232  		var nextProps Props
   233  		var curProps Props
   234  
   235  		// whether a component should update is only a function of its props
   236  		// ... and a component does not need to have props
   237  		//
   238  		// the only way we have of determining that here is whether the this
   239  		// object has a props property that has a non-nil nestedProps property
   240  
   241  		if this != nil {
   242  			if p := this.Get(reactCompProps); p != nil {
   243  				if ok, err := jsbuiltin.In(nestedProps, p); err == nil && ok {
   244  					if v := (p.Get(nestedProps)); v != nil {
   245  						curProps = *(unwrapValue(v).(*Props))
   246  					}
   247  				} else {
   248  					return false
   249  				}
   250  			}
   251  		}
   252  
   253  		if arguments[0] != nil {
   254  			if ok, err := jsbuiltin.In(nestedProps, arguments[0]); err == nil && ok {
   255  				nextProps = *(unwrapValue(arguments[0].Get(nestedProps)).(*Props))
   256  			}
   257  		}
   258  
   259  		return !curProps.EqualsIntf(nextProps)
   260  	}))
   261  
   262  	compDef.Set(reactCompComponentDidMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   263  		elem := this
   264  		cmp := builder(ComponentDef{elem: elem})
   265  
   266  		if cmp, ok := cmp.(componentWithDidMount); ok {
   267  			cmp.ComponentDidMount()
   268  		}
   269  
   270  		return nil
   271  	}))
   272  
   273  	compDef.Set(reactCompComponentWillReceiveProps, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   274  		elem := this
   275  		cmp := builder(ComponentDef{elem: elem})
   276  
   277  		if cmp, ok := cmp.(componentWithWillReceiveProps); ok {
   278  			ourProps := *(unwrapValue(arguments[0].Get(nestedProps)).(*Props))
   279  			cmp.ComponentWillReceivePropsIntf(ourProps)
   280  		}
   281  
   282  		return nil
   283  	}))
   284  
   285  	compDef.Set(reactCompComponentWillUnmount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   286  		elem := this
   287  		cmp := builder(ComponentDef{elem: elem})
   288  
   289  		if cmp, ok := cmp.(componentWithWillUnmount); ok {
   290  			cmp.ComponentWillUnmount()
   291  		}
   292  
   293  		return nil
   294  	}))
   295  
   296  	compDef.Set(reactCompComponentWillMount, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   297  		elem := this
   298  		cmp := builder(ComponentDef{elem: elem})
   299  
   300  		// TODO we can make this more efficient by not doing the type check
   301  		// within the function body; it is known at the time of setting
   302  		// "componentWillMount" on the compDef
   303  		if cmp, ok := cmp.(componentWithWillMount); ok {
   304  			cmp.ComponentWillMount()
   305  		}
   306  
   307  		return nil
   308  	}))
   309  
   310  	compDef.Set(reactCompRender, js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
   311  		elem := this
   312  		cmp := builder(ComponentDef{elem: elem})
   313  
   314  		renderRes := cmp.RendersElement()
   315  
   316  		return renderRes
   317  	}))
   318  
   319  	return react.Call(reactCreateClass, compDef)
   320  }
   321  
   322  func Render(el Element, container dom.Element) Element {
   323  	v := reactDOM.Call(reactDOMRender, el, container)
   324  
   325  	return &elementHolder{Elem: v}
   326  }