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.
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.
Get any library to 'mixin' js classes. I've used https://github.com/rse/aggregation.
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"
}
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"
)
)
}
Related
I'm building a JSON serialization tool and I want to be able to define custom serialize and deserialize methods on classes in Scala. Unfortunately, though, since Scala doesn't have static methods, I haven't been able to figure out a nice way to express the fromJSON method.
What I would like to write is:
class Foo(val x: Int) extends Serializable {
def toJSON = JInteger(x)
static val fromJSON: PartialFunction[JValue, Foo] = {
JInteger(x) => new Foo(x)
}
}
... but static doesn't exist. I could write the static method in a companion object:
object Foo {
val fromJSON: PartialFunction[JValue, Foo] = {
JInteger(x) => new Foo(x)
}
}
class Foo(val x: Int) extends Serializable {
def toJSON = JInteger(x)
}
but then the compiler won't check for its presence in every Serializable class, and mistakes could creep in at runtime if it accidentally wasn't defined.
I'm new to Scala — what's a good way to express this? I want to write my serialize/deserialize methods in or close to the classes they represent.
I have a problem with my custom subclass of Page. Here is a short example.
def main(args: Array[String]): Unit = {
val pane = new TabbedPane
pane.pages += new LanguagePage("common_yes", new Label)
println(pane.pages(0).isInstanceOf[LanguagePage])
}
class LanguagePage(languageKey: String, com: Component)
extends Page("", com, null) {
def method() {...}
}
When I run the program, false is printed. It would be nice to know why this happens and how I can access my added page again. In my case I need to run the method that the subclass has to adjust the title string based on the language that is set.
If you look at the source
class TabbedPane extends Component with Publisher {
object pages extends BufferWrapper[Page] {
def apply(n: Int) =
new Page(TabbedPane.this, peer.getTitleAt(n),
UIElement.cachedWrapper[Component]
(peer.getComponentAt(n).asInstanceOf[javax.swing.JComponent]),
peer.getToolTipTextAt(n))
you always get a new instance.
Looking at
class TabbedPane extends Component with Publisher {
object pages extends BufferWrapper[Page] {
def +=(t: Page): this.type = {
t.parent = TabbedPane.this
peer.addTab(t.title, null, t.content.peer, t.tip)
this
}
the argument t is not recorded at all.
But you can change the title using
pane.pages(0).title = "Title 2"
so you could define a logical page (a companion in addition to the Swing page) separately. Using a reference to the pane and the tab index this class can change the title.
I'm trying to write automated tests for a gui application written in scala with swing. I'm adapting the code from this article which leverages getName() to find the right ui element in the tree.
This is a self-contained version of my code. It's incomplete, untested, and probably broken and/or non-idiomatic and/or suboptimal in various ways, but my question is regarding the compiler error listed below.
import scala.swing._
import java.awt._
import ListView._
import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
import org.scalatest.FunSpec
import org.scalatest.matchers.ShouldMatchers
object MyGui extends SimpleSwingApplication {
def top = new MainFrame {
title = "MyGui"
val t = new Table(3, 3)
t.peer.setName("my-table")
contents = t
}
}
object TestUtils {
def getChildNamed(parent: UIElement, name: String): UIElement = {
// Debug line
println("Class: " + parent.peer.getClass() +
" Name: " + parent.peer.getName())
if (name == parent.peer.getName()) { return parent }
if (parent.peer.isInstanceOf[java.awt.Container]) {
val children = parent.peer.getComponents()
/// COMPILER ERROR HERE ^^^
for (child <- children) {
val matching_child = getChildNamed(child, name)
if (matching_child != null) { return matching_child }
}
}
return null
}
}
#RunWith(classOf[JUnitRunner])
class MyGuiSpec extends FunSpec {
describe("My gui window") {
it("should have a table") {
TestUtils.getChildNamed(MyGui.top, "my-table")
}
}
}
When I compile this file I get:
29: error: value getComponents is not a member of java.awt.Component
val children = parent.peer.getComponents()
^
one error found
As far as I can tell, getComponents is in fact a member of java.awt.Component. I used the code in this answer to dump the methods on parent.peer, and I can see that getComponents is in the list.
Answers that provide another approach to the problem (automated gui testing in a similar manner) are welcome, but I'd really like to understand why I can't access getComponents.
The issue is that you're looking at two different classes. Your javadoc link points to java.awt.Container, which indeed has getComponents method. On the other hand, the compiler is looking for getComponents on java.awt.Component (the parent class), which is returned by parent.peer, and can't find it.
Instead of if (parent.peer.isInstanceOf[java.awt.Container]) ..., you can verify the type of parent.peer and cast it like this:
parent.peer match {
case container: java.awt.Container =>
// the following works because container isA Container
val children = container.getComponents
// ...
case _ => // ...
}
I wrote my first console app in Scala, and I wrote my first Swing app in Scala -- in case of the latter, the entry point is top method in my object extending SimpleSwingApplication.
However I would like to still go through main method, and from there call top -- or perform other equivalent actions (like creating a window and "running" it).
How to do it?
Just in case if you are curious why, the GUI is optional, so I would like to parse the command line arguments and then decide to show (or not) the app window.
If you have something like:
object MySimpleApp extends SimpleSwingApplication {
// ...
}
You can just call MySimpleApp.main to start it from the console. A main method is added when you mix SimpleSwingApplication trait. Have a look at the scaladoc.
Here is the most basic example:
import swing._
object MainApplication {
def main(args: Array[String]) = {
GUI.main(args)
}
object GUI extends SimpleSwingApplication {
def top = new MainFrame {
title = "Hello, World!"
}
}
}
Execute scala MainApplication.scala from the command line to start the Swing application.
If you override the main method that you inherit from SimpleSwingApplication, you can do whatever you want:
object ApplicationWithOptionalGUI extends SimpleSwingApplication {
override def main(args: Array[String]) =
if (parseCommandLine(args))
super.main(args) // Starts GUI
else
runWithoutGUI(args)
...
}
I needed main.args to be usable in SimpleSwingApplication. I wanted to give a filename to process as an argument in CLI, or then use JFileChooser in case the command line argument list was empty.
I do not know if there is simple method to use command line args in SimpleSwingApplication, but how I got it working was to define, in demoapp.class:
class demoSSA(args: Array[String]) extends SimpleSwingApplication {
....
var filename: String = null
if (args.length > 0) {
filename = args(0)
} else {
// use JFileChooser to select filename to be processed
// https://stackoverflow.com/questions/23627894/scala-class-running-a-file-chooser
}
....
}
object demo {
def main(args: Array[String]): Unit = {
val demo = new demoSSA(args)
demo.startup(args)
}
}
Then start the app by
demo [args], by giving filename as a CLI argument or leaving it empty and program uses JFileChooser to ask it.
Is there a way to get to the args of main() in SimpleSwingApplication singleton object?
Because it doesn't work to parse 'filename' in overridden SimpleSwingApplication.main and to use 'filename' in the singleton object it needs to be declared (val args: Array[String] = null;), not just defined (val args: Array[String];). And singleton objecs inherited from SSA cannot have parameters.
(Edit)
Found one another way:
If whole demoSSA is put inside overridden main() or startup(), then top MainFrame needs to defined outside as top = null and re-declare it from startup() as this.top:
object demo extends SimpleSwingApplication {
var top: MainFrame = null
override def startup(args: Array[String]) {
... // can use args here
this.top = new MainFrame {
... // can use args here also
};
val t = this.top
if (t.size == new Dimension(0,0)) t.pack()
t.visible = true
}
}
But I think I like the former method more, with separated main object.It has at least one level indentation less than the latter method.
I have found following function calls in several frameworks which appear to me as if the framework extends some base classes. Some examples:
within(500 millis)
or
"Testcase description" in
{ .... }
First example returns a duration object with the duration of 500 milliseconds from akka and second is the definition of a testcase from scalatest.
I would like to know how this behavior is achieved and how it is called.
This is done with the "Pimp my library" technique.
To add non existing methods to a class, you define an implicit method that converts objects of that class to objects of a class that has the method:
class Units(i: Int) {
def millis = i
}
implicit def toUnits(i: Int) = new Units(i)
class Specs(s: String) {
def in(thunk: => Unit) = thunk
}
implicit def toSpecs(s: String) = new Specs(s)
See also "Where does Scala looks for Implicits?"
If I'm not mistaken, those pieces of code can be desugared as
within(500.millis)
and
"Testcase description".in({ ... })
This should make it easier to see what's going on.