Polymer Binding (prototype)

191 views
Skip to first unread message

Eric Anderton

unread,
Jun 9, 2015, 10:53:52 PM6/9/15
to goph...@googlegroups.com
Since another Gopher mentioned Polymer bindings on the github page (https://github.com/gopherjs/gopherjs/issues/241) I figured I would mention this here to help spur competition and/or collaboration.


It's by no means comprehensive, or even all that well tested, but it's a minimally working prototype of a binding for GopherJS to Google's Polymer.

I'm presently tinkering on a v1.0 version, and have started using PhantomJS headless testing with a Go-wrapped jasmine binding (highly recommended).

One of the biggest challenges of binding to this library is that the Polymer() function expects a *prototype* Object that it uses to create new JavaScript objects that wrap web components.  For Go, this means turning the Polymer() function into a factory of sorts.  The design requires a "default instance" that it uses to factory-create new structs of the same type, when binding to web components.

type struct Foo {
  field1 string
  field 2 string
}

// provide a default struct instance when registering the tag
// NOTE: the instance is used for cloning into new components
Polymer('x-foo', &Foo{
  field1: "hello",
  field2: "world",
})

// this maps to the following markup:
<polymer-element>
  <template>
    Field 1: <input value="{{field1}}">
    Field 2: <input value="{{field2}}">
  </template>
</polymer-element>

The implementation makes heavy use of reflection and closures, and is kind of a mess thanks to the aforementioned 'prototype' pattern.  I'm open to critique if anyone is interested, especially if they can point out a more efficient way to do all this.  One interesting side effect of the reflection heavy design is that the Polymer() wrapper above can take *any* struct as a potential web component; tagging is provided to do finer things like naming field aliases or disallowing export into Polymer.

The only thing that would have made this easier was some way to go from a js.Object instance to a struct, and the other way around, without doing so implicitly on a function call. Something like a ToStruct(interface{}) method on js.Object, and a js.ToObject(interface{}) would have helped tremendously.  Doing this field-by-field on Go's reflection interface is just miserable by comparison.  Something like js.Object.ToStruct(i interface{}), that just "does the right thing".  Issue #241 above, that another Polymer enthusiast drafted, seems to echo this sentiment.

Another option would have been to use a .js shim of some kind to make things friendlier for GopherJS, but I wanted to avoid using eval().  That said, it might be helpful if there were some idiom or Gopherjs support, to bundle .js files with library bindings, in a way that would garantee that the GopherJS compiler would pick them up and bundle it into the compiled product.

Andre Moraes

unread,
Jun 21, 2015, 11:06:37 AM6/21/15
to goph...@googlegroups.com


Em terça-feira, 9 de junho de 2015 23:53:52 UTC-3, Eric Anderton escreveu:
Since another Gopher mentioned Polymer bindings on the github page (https://github.com/gopherjs/gopherjs/issues/241) I figured I would mention this here to help spur competition and/or collaboration.


I didn't knew about your library, will check it later.
 
It's by no means comprehensive, or even all that well tested, but it's a minimally working prototype of a binding for GopherJS to Google's Polymer.

You are working on Polymer 0.5 or Polymer 1.0? They are verry different. 

One of the biggest challenges of binding to this library is that the Polymer() function expects a *prototype* Object that it uses to create new JavaScript objects that wrap web components.  For Go, this means turning the Polymer() function into a factory of sorts.  The design requires a "default instance" that it uses to factory-create new structs of the same type, when binding to web components.

The problem described by me in the link above was a result of this "prototype" Object used by Polymer.

The implementation makes heavy use of reflection and closures, and is kind of a mess thanks to the aforementioned 'prototype' pattern.  I'm open to critique if anyone is interested, especially if they can point out a more efficient way to do all this.  One interesting side effect of the reflection heavy design is that the Polymer() wrapper above can take *any* struct as a potential web component; tagging is provided to do finer things like naming field aliases or disallowing export into Polymer.

The only thing that would have made this easier was some way to go from a js.Object instance to a struct, and the other way around, without doing so implicitly on a function call. Something like a ToStruct(interface{}) method on js.Object, and a js.ToObject(interface{}) would have helped tremendously.  Doing this field-by-field on Go's reflection interface is just miserable by comparison.  Something like js.Object.ToStruct(i interface{}), that just "does the right thing".  Issue #241 above, that another Polymer enthusiast drafted, seems to echo this sentiment.


Yes, agreed. That problem seems to be a by-product of how internalize/externalize.
  
Another option would have been to use a .js shim of some kind to make things friendlier for GopherJS, but I wanted to avoid using eval().  That said, it might be helpful if there were some idiom or Gopherjs support, to bundle .js files with library bindings, in a way that would garantee that the GopherJS compiler would pick them up and bundle it into the compiled product.

One option is to pack the js as a const in the Go code, and when the code is loaded, do som DOM manipulation to run that code.

text := GetJsFile("a.js") // this will return the value of a const named "a_dot_js"
scriptTag := js.Global.Get("document").Call("createElement", "SCRIPT")
scriptTag.Call("setAttribute", "type", "text/javascript") // not needed on recent browsers
scriptTag.Call("setAttribute", "data-src-ref", "a.js") // for further reference document.querySelector('script[data-src-ref="a.js"]');
scriptTag.Set("innerHTML", text) // avoid escaping the text

// add a listener to wait for script evaluation
done := make(chan struc{})
js.Global.Call("addEventListener", "code-loaded", func(ev *js.Object) {
  srcref := ev.Get("detail").Get("srcref").String()
  if srcref == "a.js" {
    go func() { done <- struct{}{} }()
  }
}, false)

js.Global.Get("document").Get("head").Call("appendChild", scriptTag) // adding it to the DOM forces the evaluation

// block until a.js is evaluated, add a timeout to avoid a problematic script from blocking here forever.
<-done

Then inside "a.js" you could write some code to fire a customEvent on window indicating that the evaluation was completed. 

// this should be the last line of a.js
window.trigger("code-loaded", { srcref: "a.js" });
 
Reply all
Reply to author
Forward
0 new messages