Example #1
0
func RenderToDomId(c Component, domId string) error {
	wrap, err := wrapComponent(c)
	if err != nil {
		return err
	}

	reactjs.RenderToDomId(reactjs.CreateElement(wrap), domId)

	return nil
}
Example #2
0
// wrapComponent creates a React class for the given Component by
// creating a plain js.Object and mapping functions to it, and back.
//
// For example, the React `render()` method is assigned to the object.
// When React calls this method, the given Component's Render() method is
// called. The result of Component.Render() is recursively run through
// Wrap() as well, with the final result being a series of objects given
// to React that map to their respective components.
//
// This means that when a component calls the embedded *React methods,
// such as React.SetState() end up calling the functions in this closure
// which then call the normal React `this.setState()` methods.
//
// A lot of wiring, but the goal is that complexity in a Component is
// isolated from the complexity given to React. So all of the tomfoolery
// that React does to method Binding's are not actually done to the
// methods of your component.
//
// TL;DR: The overhead caused by Wrap() is to avoid React tomfoolery
// multiplying by GopherJS tomfoolery. Resulting in a (in theory)
// net tomfoolery reduction. Science.
//
// TODO: Create the *React pipeline to communicate from the Component
// to the jsObject wrapper.
//
// TODO: Write errors to console.error
//
// TODO: Drop error return. Don't think it's possible to have an error
// outside of the callbacks.
//
// TODO: Handle the following React warning:
//
// 		Warning: Each child in an array or iterator should have a
// 		unique "key" prop. Check the React.render call using <div>.
// 		See https://fb.me/react-warning-keys for more information.
//
// Though, the responsibility of handling this may be better off on the
// Component.
func wrapComponent(c Component) (jsComp *js.Object, err error) {
	fmt.Printf("wrapComponent(<%s>)\n", c.Tag())
	jsObject := js.Global.Get("Object").New()

	// This is a reference to the final, valid React object instance.
	//
	// React does a lot of binding/this tomfoolery, and it heavily
	// screws with GopherJS. As a result, custom functions won't have
	// valid `this` objects.
	//
	// React API functions can get a valid `this`, such as `render` and
	// `setInitialState`, but custom functions fail.
	//
	// Commented out, as it's likely being removed soon (no need for
	// the reference, Components have SetThis()
	//var this *js.Object

	// TODO: Find a way to support sane error handling. It's difficult
	// because the render() method is a callback from JS, and the return
	// value is consumed by React.js itself. Furthermore, even if we could
	// return an error value, we can't get access to it.
	render := func() *js.Object {
		fmt.Printf("<%s>.render\n", c.Tag())

		rendComp := c.Render()

		var jsClass *js.Object
		var content string
		// If a component is Rendered, wrap it and return.
		if rendComp != nil {
			// If this child has Content (String), return it directly.
			content = rendComp.Content()
			if content != "" {
				return reactjs.CreateElement(c.Tag(), c.Props(), content)
			}

			jsClass, _ = wrapComponent(rendComp)

			return reactjs.CreateElement(jsClass)
		}

		// The comp did not render anything, so try to create a Tag with
		// it's children.
		//
		// If it has no tag, we can't do that. Error out.
		if c.Tag() == "" {
			fmt.Println("Error: Component has no Children and no Tag")
			return nil
		}

		children := c.Children()
		childrenCount := len(children)

		// If this comp has no children, create an empty jsComp (eg: <foo />)
		if childrenCount == 0 {
			return reactjs.CreateElement(c.Tag(), c.Props())
		}

		// The == 1 check is done to avoid creating the slice, looping,
		// and (most importantly) avoid returning an array. By doing it
		// here, we can save a bit of overhead.. in theory.
		//
		// Premature optimization without benchmarks? heh
		var childComp Component
		if childrenCount == 1 {
			childComp = children[0]

			// If this child has Content (String), return it directly.
			content = childComp.Content()
			if content != "" {
				return reactjs.CreateElement(c.Tag(), c.Props(), content)
			}

			jsClass, _ = wrapComponent(childComp)

			return reactjs.CreateElement(c.Tag(), c.Props(),
				reactjs.CreateElement(jsClass))
		}

		// A slice of this Element's Childen. Note that in the event of
		// a Content child, string is added to this slice instead of a
		// jsObject.
		jsChildren := make([]interface{}, childrenCount)

		var i int
		for i, childComp = range children {
			// If this child has Content (String), push it onto Children
			// directly.
			content = childComp.Content()
			if content != "" {
				jsChildren[i] = content
				continue
			}

			// Wrap the component, returning it's class
			jsClass, _ = wrapComponent(childComp)
			// And create an element from the class, adding it to the slice.
			jsChildren[i] = reactjs.CreateElement(jsClass)
		}

		return reactjs.CreateElement(c.Tag(), c.Props(), jsChildren)
	}

	jsObject.Set("render", render)
	jsObject.Set("componentDidMount", c.ComponentDidMount)
	jsObject.Set("componentWillUnmount", c.ComponentWillUnmount)

	// Temporary implementation for early testing.
	jsObject.Set("getInitialState", js.MakeFunc(
		func(reactThis *js.Object, _ []*js.Object) interface{} {
			if err := c.SetThis(reactThis); err != nil {
				fmt.Println("Error: ", err.Error())
			}
			return nil
		},
	))

	return reactjs.CreateClass(jsObject), nil
}