In scalajs with scalajs-react how do I pass a scalajs defined component to a javascript defined component? - scala.js

My issue is also here:
https://gist.github.com/somanythings/8c3d34de754af311d7826ea837d160b4
In using scalajs with japgolly's scalajs-react (https://github.com/japgolly/scalajs-react) library. I am trying to wrap the griddle grid http://griddlegriddle.github.io/Griddle/customization.html
I want a custom column, and to do that requires I pass a columnMetadata structure which includes a component.
When I do, I can render a scalajs defined component that doesn't have properties, but if I try to access properties through renderP, or scope through renderS, they are both undefined in the scope of the render function. If I debug in the browser, they names are bound, and do have the expected values.
When I break on
def renderP(f: (DuringCallbackU[P, S, B], P) => ReactElement): Out =
render($ => f($, $.props))
then $.props is undefined
What am I missing? Is it a simple typing issue in the ReactComponentB dispatching.
Is it somehow related to https://github.com/japgolly/scalajs-react/issues/157, and I just haven't seen how?
// Confusion over how to pass a scalajs defined component to a javascript defined component
object GriddleComponentWrapper {
// for customComponent I've tried js.Any, ReactComponentU
#ScalaJSDefined
class ColumnMeta(val columnName: String, val order: Int, val customComponent: ReactClass=null) extends js.Object
}
case class GriddleComponentWrapper(results: js.Any, //Seq[Map[String, Any]],
columns: Seq[String],
columnMeta: Option[Seq[ColumnMeta]] = None,
showSettings: Boolean = true,
showFilter: Boolean = true
) {
def toJS = {
val p = js.Dynamic.literal()
p.updateDynamic("results")(results)
p.updateDynamic("columns")(columns)
p.updateDynamic("showSettings")(showSettings)
p.updateDynamic("showFilter")(showFilter)
(columnMeta).foreach { case cm => p.updateDynamic("columnMetadata")(cm.toJsArray) }
p
}
def apply(children: ReactNode*) = {
val f = React.asInstanceOf[js.Dynamic].createFactory(js.Dynamic.global.Bundle.griddle) // access real js component , make sure you wrap with createFactory (this is needed from 0.13 onwards)
f(toJS, children.toJsArray).asInstanceOf[ReactComponentU_]
}
}
object MyTestGrid {
#js.native
class ColumnMetaProps(val data: js.Object, val rowData: js.Object, val metadata: js.Object) extends js.Object
// I've tried making the Props argument js.Dynamic, and also the ColumnMetaProps above
#JSExport
val testComp = ReactComponentB[js.Dynamic]("Mine").renderP(
(sc, props: js.Dynamic) => {
//when debugging this in the browser, 'sc' and 'props' have inspectable object values with the expected members in the browser
//dev tools, BUT, they're undefined
log.info(s"what is ${sc.props}")
log.info(s"what is $props")
val string: Frag = if (!js.isUndefined(props)) props.data.toString() else "nothing!"
<.h1(string)
}).build
#JSExport
val aCompletelyStaticComponentWithNoPropsWillWork = ReactComponentB[js.Dynamic]("MyStaticComponent").renderP(
(sc, props: js.Dynamic) => <.h1("this renders!!") ).build
// am I passing the right thing to columnmeta with testComp.reactClass?
val columnMeta = (new ColumnMeta("c1", 1, testComp.reactClass) :: Nil).toJsArray
val results = Seq(
js.Dynamic.literal("c1" -> "row1c1", "c2" -> "row1c2"),
).toJsArray
val component = ReactComponentB[js.Dynamic]("MyTestGrid")
.render_P {
props =>
GriddleComponentWrapper(results, columns = "c1" :: "c2" :: Nil, columnMeta = Some(columnMeta))()
}.build
def apply() = component
}

React.JS requires that props and state always be an object (or null). Using a single Scala value like a primitive or case class instance, causes exceptions in React.JS. Therefore in scalajs-react, in order to allow users to use any props/state types in a type-safe manner, under the hood an object with a single key of "v" is used. I.e. instead of using 123 directly, {v:123} is used.
You likely will need to accommodate that boxing in your code.
Now, in the next major version (see the "neo" branch), the representations of components vastly improved such that there is no more hidden magic like I just described. Whereas v0.x wasn't designed to facilitate JSā†”Scala component interop, it will be explicit, obvious, and hopefully trivial in neo.

Related

When doing implicit resolution with type parameters, why does val placement matter?

In one file, I have:
trait JsonSchema[T] {
val propertyType: String
override def toString: String = propertyType
}
object JsonSchema {
implicit def stringSchema: JsonSchema[String] = new JsonSchema[String] {
override val propertyType: String = "string"
}
implicit def intSchema: JsonSchema[Int] = new JsonSchema[Int] {
override val propertyType: String = "integer"
}
implicit def booleanSchema: JsonSchema[Boolean] = new JsonSchema[Boolean] {
override val propertyType: String = "boolean"
}
}
In my main file:
case class MetaHolder[T](v: T)(implicit val meta: JsonSchema[T])
object JsonSchemaExample extends App {
println(MetaHolder(3).meta.toString)
println(MetaHolder("wow").meta.toString)
}
That works hunky-dory. Now suppose I do this instead:
case class MetaHolder[T](v: T) {
val meta: JsonSchema[T] = implicitly[JsonSchema[T]]
}
It no longer compiles. Why?
My goal is to modify the anonymous Endpoint classes in the scala Finch library by adding a val meta to everything. I've been able to do this without any fancy-business so far, but now I want to do some fancy implicit resolution with shapeless to provide a JsonSchema definition for arbitrary case classes. My question is how to do this while maintaining backward compatibility. As in: provide the jsonschema meta feature for people who want to opt in, don't change the compilation burden for anyone who does not want to use meta,
If instead I go the first route, with an added implicit parameter, wouldn't that require a special import to be added by everyone? Or am I missing something and would backward compatibility still be maintained?
There is big difference between implicit x: X among parameters and implicitly[X] inside body.
When you say implicitly[X] this means "check now whether in the current scope there is an implicit X".
When you say def foo(...)(implicit x: X) = ... this means "check later when foo is called that in the scope of the call site there will be an implicit X (and for now inside foo just assume without checking that there is)".
class Foo(...)(implicit x: X) is similar to the latter, "check when constructor is called that there will be an implicit X".
Regarding whether users have to import or not. If you put implicits for type X to companion object of X then they will be found automatically (implicits for type X[Y] should be put to companion object of either X or Y). If you put them somewhere else then they have to be imported to the current scope.
In order for implicitly[JsonSchema[T]] to compile, there must be a JsonSchema[T] in the implicit scope, which means that there must be a JsonSchema[T] (or something implicitly convertible to a JsonSchema[T]) passed through as an implicit argument, as you had with:
case class MetaHolder[T](v: T)(implicit val meta: JsonSchema[T])

Scala method which accepts multiple different objects, all of which extend the same trait

I have a trait Document[T], and two case classes which extend that trait:
Memo(...) extends Document[Memo]
Fax(...) extends Document[Fax]
Each represents a different type of text file.
I want to clean these documents using some text tools, but creating a method that takes both types has vexed me.
val rawText: Seq[String]
// The text of all documents where one string in the sequence is the text of one document
val documentType = "Memo"
// Can also be "Fax"
val documentObjects = documentType match {
case "Memo" => rawText.map(_.makeMemoDocument) // results in Seq[Memo]
case "Fax" => rawText.map(_.makeFaxDocument) // results in Seq[Fax]
}
// Here lies my dilemma...
def processDocuments(docs: Seq[Document[T]]): Seq[Document[T]] = {...}
val processedDocs = processDocuments(documentObjects)
I want to define documentObjects in a way so that it can be easily accepted by processDocuments, i.e. processDocuments should accept either a Seq[Memo] or a Seq[Fax] as an argument.
I am creating case classes to be run through a Stanford CoreNLP pipeline, and I want to be able to support adding multiple case classes extending the Document[T] trait in the future (e.g. later on, add support for Pamphlet(...) extends Document[Pamphlet]).
Any help would be appreciated. If you require more information, I'd be happy to provide it.
val documentObjects:Document[_] = documentType match {
case "Memo" => rawText.map(_.makeMemoDocument) // results in Seq[Memo]
case "Fax" => rawText.map(_.makeFaxDocument) // results in Seq[Fax]
}
def processDocuments[T](docs: Seq[Document[T]]): Seq[Document[T]] = ???
val processedDocs = processDocuments(Seq(documentObjects))
or remove type declaration on documentObjects and use
def processDocuments(docs: Seq[Document[_]]): Seq[Document[_]] = ???
//or
def processDocuments[T <: Document[_]](docs: Seq[T]): Seq[T]= ???

How do I create optional "options" in a facade?

I am trying to create a facade for the bootstrap popover function that can take up to 11 optional parameters. In my scalajs code I would like to only pass in the parameters I need to override from library maintained sensible defaults ie: PopoverOptions(animation = false) just like I would do in javascript. This seems to be the recommend way to make a facade but it makes all the parameters required:
trait PopoverOptions extends js.Object {
val animation: String = js.native
val container: String = js.native
val content: String = js.native
...
}
object PopoverOptions {
def apply(animation: String, container: String, content: String, ...): PopoverOptions = {
js.Dynamic.literal(animation=animation, container= container, content = content, ...).asInstanceOf[PopoverOptions ]
}
}
It looks like one way is to define an apply for every possible permutation of parameters but when there are lots of override parameters this gets excessive quick:
def apply(animation: String): ...
def apply(container: String): ...
def apply(animation: String, container: String): ...
...
What is the idiomatic way to create an options parameter facade with lots of override parameters that typically have sensible library maintained defaults?
Note: both answers have pros/cons so to decide it might be helpful to see both ways without leaving SO so here is a summary of JSOptionBuilder method:
import org.querki.jquery.JQuery
import org.querki.jsext._
#js.native
trait PopoverOptions extends js.Object
object PopoverOptions extends PopoverOptionBuilder(noOpts)
class PopoverOptionBuilder(val dict:OptMap) extends JSOptionBuilder[PopoverOptions, PopoverOptionBuilder](new PopoverOptionBuilder(_))
{
def animation(v:String) = jsOpt("animation", v)
def container(v:String) = jsOpt("container", v)
def content(v:String) = jsOpt("content", v)
...
}
To use: PopoverOptions.animation("yay").container("container").content("bottom")._result
You can use Scala's default values for parameters, combined with js.UndefOrs of the types of elements, like this:
object PopoverOptions {
#inline
def apply(
animation: js.UndefOr[String] = js.undefined,
container: js.UndefOr[String] = js.undefined,
content: js.UndefOr[String] = js.undefined,
...): PopoverOptions = {
val result = js.Dynamic.literal()
animation.foreach(result.animation = _)
container.foreach(result.container = _)
content.foreach(result.content = _)
...
result.asInstanceOf[PopoverOptions]
}
}
Then you can call with PopoverOptions(animation = false), as you wished.
It's a bit verbose at definition site, but it will get the job done.
The alternative approach is to use JSOptionBuilder, which was created for this purpose. (I write many jQuery facades, and they always have this problem.) JSOptionBuilder isn't quite as critical as it used to be (this was a major problem before the "|" operator was introduced), but I still find it's usually the best way to deal with complex facades.
Here's the full description of this approach. (Note that that's enormously detailed -- the first half is the key stuff, and the rest deals with all the somewhat-common edge cases.)

Scala Map to Arbitrary Class (Scala Reflection)

If I have a Scala Map
val map = Map("a" -> true,
"b" -> "hello"
"c" -> 5)
Is there a way I can convert this to an object (case class, regular class, anything really) such that I can access the field like this:
val obj = map.toObj
println(obj.a)
println(obj.b)
Without knowing what the parameters will be ahead of time?
Or even better, can it be an instance variable of the class I'm currently working with?
So I could actually just have
println(a)
println(b)
Have a look at Dynamic:
import scala.language.dynamics
class Wrapper(m: Map[String, Any]) extends Dynamic {
def selectDynamic(name: String) = {
m(name)
}
}
object Demo {
def main(args: Array[String]) {
val map = Map("a" -> true,
"b" -> "hello",
"c" -> 5)
val w = new Wrapper(map)
println(w.a)
println(w.b)
}
}
Dynamic gives you "properties on demand".
But it's not type-safe. In the snippet above, if you try to access a non-existing key in the map, a NoSuchElementException will be thrown. And you get the Any type, so you have to use asInstanceOf at the caller's side, for example.
Dynamic has further methods, so have a closer look at the documentation of Dynamic
I hope you understand by asking this question that you will loose all compile time guarantees when you rely on runtime data. If this is really what you want then you can rely on Dynamic:
object X extends App {
import scala.language.dynamics
class D(fields: Map[String, Any]) extends Dynamic {
def selectDynamic(str: String): Any =
fields.getOrElse(str, throw new NoSuchFieldException(str))
}
val fields = Map[String, Any]("a" -> true, "b" -> "hello")
val obj = new D(fields)
println(obj.a)
println(obj.b)
}
Dynamic is a compiler feature that translates all field/method calls to a call to the *Dynamic* methods. Because the compiler can't know anything about your program (how should it?), the return type you get here is Any and when you call a field/method that does not exist you get an exception at runtime instead of a compile time error. When some fields/methods are known to compile time you can combine Dynamic with macros to get at least some compile time checking (such a method is described in the linked answer).
Beside from that, that is the only syntax you can enable in Scala. If return types are important to you, you can at least add them as type parameter:
object X extends App {
import scala.language.dynamics
class D(fields: Map[String, Any]) extends Dynamic {
def selectDynamic[A : reflect.ClassTag](str: String): A =
fields.get(str) match {
case Some(f: A) => f
case _ => throw new NoSuchFieldException(str)
}
}
val fields = Map[String, Any]("a" -> true, "b" -> "hello")
val obj = new D(fields)
println(obj.a[Boolean])
println(obj.b[String])
}

Syntactic sugar for compile-time object creation in Scala

Lets say I have
trait fooTrait[T] {
def fooFn(x: T, y: T) : T
}
I want to enable users to quickly declare new instances of fooTrait with their own defined bodies for fooFn. Ideally, I'd want something like
val myFoo : fooTrait[T] = newFoo((x:T, y:T) => x+y)
to work. However, I can't just do
def newFoo[T](f: (x:T, y:T) => T) = new fooTrait[T] { def fooFn(x:T, y:T):T = f(x,y); }
because this uses closures, and so results in different objects when the program is run multiple times. What I really need is to be able to get the classOf of the object returned by newFoo and then have that be constructable on a different machine. What do I do?
If you're interested in the use case, I'm trying to write a Scala wrapper for Hadoop that allows you to execute
IO("Data") --> ((x: Int, y: Int) => (x, x+y)) --> IO("Out")
The thing in the middle needs to be turned into a class that implements a particular interface and can then be instantiated on different machines (executing the same jar file) from just the class name.
Note that Scala does the right thing with the syntactic sugar that converts (x:Int) => x+5 to an instance of Function1. My question is whether I can replicate this without hacking the Scala internals. If this was lisp (as I'm used to), this would be a trivial compile-time macro ... :sniff:
Here's a version that matches the syntax of what you list in the question and serializes/executes the anon-function. Note that this serializes the state of the Function2 object so that the serialized version can be restored on another machine. Just the classname is insufficient, as illustrated below the solution.
You should make your own encode/decode function, if even to just include your own Base64 implementation (not to rely on Sun's Hotspot).
object SHadoopImports {
import java.io._
implicit def functionToFooString[T](f:(T,T)=>T) = {
val baos = new ByteArrayOutputStream()
val oo = new ObjectOutputStream(baos)
oo.writeObject(f)
new sun.misc.BASE64Encoder().encode(baos.toByteArray())
}
implicit def stringToFun(s: String) = {
val decoder = new sun.misc.BASE64Decoder();
val bais = new ByteArrayInputStream(decoder.decodeBuffer(s))
val oi = new ObjectInputStream(bais)
val f = oi.readObject()
new {
def fun[T](x:T, y:T): T = f.asInstanceOf[Function2[T,T,T]](x,y)
}
}
}
// I don't really know what this is supposed to do
// just supporting the given syntax
case class IO(src: String) {
import SHadoopImports._
def -->(s: String) = new {
def -->(to: IO) = {
val IO(snk) = to
println("From: " + src)
println("Applying (4,5): " + s.fun(4,5))
println("To: " + snk)
}
}
}
object App extends Application {
import SHadoopImports._
IO("MySource") --> ((x:Int,y:Int)=>x+y) --> IO("MySink")
println
IO("Here") --> ((x:Int,y:Int)=>x*y+y) --> IO("There")
}
/*
From: MySource
Applying (4,5): 9
To: MySink
From: Here
Applying (4,5): 25
To: There
*/
To convince yourself that the classname is insufficient to use the function on another machine, consider the code below which creates 100 different functions. Count the classes on the filesystem and compare.
object App extends Application {
import SHadoopImports._
for (i <- 1 to 100) {
IO(i + ": source") --> ((x:Int,y:Int)=>(x*i)+y) --> IO("sink")
}
}
Quick suggestion: why don't you try to create an implicit def transforming FunctionN object to the trait expected by the --> method.
I do hope you won't have to use any macro for this!