Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

real JS classes in scala.js to develop webcomponents

I would like to develop some of the webcomponents in a Polymer 2.0 project with scala.js. While there is a wonderful demo-project on github demonstrating how it works with Polymer 1.0. I cannot get something similar to work with Polymer 2.0 and the native Element-registration technique.

A simple facade might look like the following

@ScalaJSDefined
class PolymerElement extends PolymerBase {
  def is: String = ""
  def properties: js.Dynamic = js.Dynamic.literal()
}

@js.native
@JSGlobal("Polymer.Element")
class PolymerBase extends HTMLElement

The actual Element:

@JSExportTopLevel("MyElement")
@ScalaJSDefined
class MyElement extends PolymerElement  {

   private var label = "init"

   override def is = "my-element"

   override def properties = js.Dynamic.literal(
    "label" -> Map(
      "type" -> "String",
      "value" -> "init",
      "notify" -> true
    ).toJSDictionary
  )

  def testMe = {
    println(label)
  }
}

object MyElement {
  @JSExportStatic
  val is: String = MyElement.is

  @JSExportStatic
  val properties: js.Dynamic = MyElement.properties

}

No matter whether I take the old style element registration Polymer(MyElement) or the platform native variant window.customElement.define(MyElement.is, MyElement) It obviously throws an exception as MyElement isn't instatiable with new MyElement. It throws the exception:

Uncaught TypeError: Class constructor PolymerElement cannot be invoked without 'new'

Studying the Scala.js facade writing guide, I already tried a lot of facade variants declaring PolymerElement and PolymerBase abstract.

A possible solution that comes to my mind is, writing a native JavaScript Class, that indeed is instantiable and using @js.native facades on them. But I'm looking for a way to achieve it with something Scala.js 0.6.16 provides.

like image 392
SeDav Avatar asked May 15 '17 09:05

SeDav


1 Answers


Updated version (2018-04)

Ok, this is possible helps to someone else too and I decided to publish my new version of it.

I'm using this pure ScalaJS solution to integrate with Polymer2/CustomElements.

My environment is:

  • Scala : 2.12
  • ScalaJS: 0.6.22
  • And also : "org.scala-js" %%% "scalajs-dom" % "0.9.2"

ScalaJS options:

"-P:scalajs:sjsDefinedByDefault"

I've created some ScalaJS facades for CustomElements and Polymer 2 and published them here - https://bitbucket.org/latestbit/scalymer/src/tip/src/main/scala/org/latestbit/sjs/polymer2/?at=default

They're not full-featured Polymer facades, just in the very beginning state, but they are working for me.

And you can use them easily without any hacks like:

@JSExportTopLevel(name = "TestElement")
class TestElement() extends Polymer.Element {

    override def connectedCallback(): Unit = {
        super.connectedCallback()
        global.console.log(s"Attribute name ${getAttribute("name")}. Body is ${dom.document.querySelector("body")}")
        global.console.log(s"${this.$.selectDynamic("testCl")}")
        global.console.log(s"${$$("testCl")}")
    }
}


object TestElement {
    @JSExportStatic
    def is = "test-element"

    @JSExportStatic
    def properties = js.Dictionary(
        "name" -> js.Dictionary(
            "type" -> "String"
        )
    )
}

Then register it also in Scala like:

object TestJsApplication {

    def main() : Unit = {
        Globals.customElements.define(TestElement.is,
            js.constructorOf[TestElement]
        )
    }

}

The html part is usual:

<dom-module id="test-element">

    <template>
        <span id="testCl">Not much here yet.</span>
        This is <b>[[name]]</b>.
    </template>

</dom-module>

You will find the complete example here - https://bitbucket.org/latestbit/scalymer/src


An old try to solve (for historical purposes)

Ok, this is the best 'solution' I've found.

This is not solving it completely, but I hope it'll be a helpful trick while we're expecting sjs improvements in this area.

  1. Get any library to 'mixin' js classes. I've used https://github.com/rse/aggregation.

  2. Create your ScalaJS component but don't try to inherit it from Polymer.Element directly:

@ScalaJSDefined
@JSExportTopLevel("TestPolymerElement")
class TestPolymerElement extends js.Object {
    def test = g.console.log("Hello from scala")
}

object TestPolymerElement {
    @JSExportStatic
    def is = "test-polymer-element"
}
  1. Create a JS pure "companion" class to inherit Polymer.Element and ScalaJS component as a mixin and register it:
class TestPolymerElementJs extends aggregation(Polymer.Element,TestPolymerElement) {
}
customElements.define(TestPolymerElementJs.is, TestPolymerElementJs);

Also, you can define the properties and manage them in ScalaJS like:

@ScalaJSDefined
@JSExportTopLevel("TestPolymerElement")
class TestPolymerElement(val name : String) extends js.Object {

    def test = g.console.log(s"Hello from ${name}")

}

object TestPolymerElement {
    @JSExportStatic
    def is = "test-polymer-element"

    @JSExportStatic
    def properties = js.Dictionary (
        "name" -> js.Dictionary(
            "type" -> "String"
        )
    )
}
like image 134
abdolence Avatar answered Oct 22 '22 21:10

abdolence