1

I have seen several methods of making a facade or wrapping Javascript components in scala.js, including doing so for react.js Components. They seem to vary in the degree they encapsulate or hide the functionality provided by the component itself, I suppose based on their needs (maybe they only need to call one method).

In my case, I need to use a component called react-sticky and I am first trying to determine how to proceed with making a facade for one of its just two component classes, StickyContainer, code provided here.

My questions are as follows:

  1. How do I deal with static Javascript methods in scala? Do I even need to care about this, or can I be agnostic about it?
  2. What is the minimum amount of "facade" I need to make to just include the component without any Props in another component's render()?
  3. What is fundamentally different about wrapping or facading a reactjs component as opposed to any other js native class?

Finally, which of the following strategies would be most appropriate?

@js.native
trait StickyContainer extends js.Object { ... }
object StickyContainer extends StickyContainer {}

or

case class StickyContainer(offset: js.UndefOr[Double],...) {
    def apply() = { React.createElement(ReactUniversal.Image, JSMacro[StickyContainer](this), children: _*)
}
tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126

2 Answers2

1

How do I deal with static Javascript methods in scala? Do I even need to care about this, or can I be agnostic about it?

You don't need worry about underlying implementations of react components,when you want to wrap it. But in general if you want specify static fields for a class

 @ScalaJSDefined
 class MyComponent extends ReactComponent {
  ...
}

val ctor = js.constructorOf[MyComponent]

ctor.childContextTypes = js.Dictionary("contextfield" -> React.PropTypes.`object`.isRequired)

If you're wondering about out of box static fields support in scala.js .. https://github.com/scala-js/scala-js/issues/1902

What is the minimum amount of "facade" I need to make to just include the component without any Props in another component's render()?

global.ReactSticky = require('react-sticky') // load js lib

@js.native
object ReactSticky extends js.Object {
  val StickyContainer : js.Dynamic = js.native
  val Sticky : js.Dynamic = js.native
}

//Using JSMacro
case class StickyContainer(fields ..) {
  def apply(children : ReactNode*) = {
     val props  = JSMacro[StickyContainer](this)
     React.createElement(ReactSticky.StickyContainer,props,children :_*) 
   }
}

//Using FunctionMacro
def StickyContainer(fields ..)(children : ReactNode*) : ReactElement = {
     val props  = FunctionMacro()
     React.createElement(ReactSticky.StickyContainer,props,children :_*) 
   }
}

What is fundamentally different about wrapping or facading a reactjs component as opposed to any other js native class?

If you see object ReactSticky extends js.Object ... there is no difference in writing facade. but in react world you need ReactElements thats where you need extra wrapper for - React.createElement(classCtor,props,children) call ..

invariant
  • 8,758
  • 9
  • 47
  • 61
0

Props to invariant for the correct answer on how to get SRI-based components working. The other japgolly-based ones are not as friendly in dealing with third-party JS.

Here is how I was able to get the library react-mt-svg-lines, an animation lib, working:

the clazz

case class MtSvgLines(
                      key: js.UndefOr[String] = js.undefined,
                      ref: js.UndefOr[String] = js.undefined,
                      animate: js.Any = true,
                      duration: Double = 10.0,
                      stagger: js.UndefOr[Double] = js.undefined,
                      timing: js.UndefOr[String] = js.undefined,
                      playback: js.UndefOr[String] = js.undefined,
                      fade: js.UndefOr[String] = js.undefined
                    )
{

  def apply(children: ReactNode*): ReactComponentU_ = {
    val props = JSMacro[MtSvgLines](this)
    val f = React.asInstanceOf[js.Dynamic].createFactory(js.Dynamic.global.SvgLines.default)
    if (children.isEmpty)
      f(props).asInstanceOf[ReactComponentU_]
    else if (children.size == 1)
      f(props, children.head).asInstanceOf[ReactComponentU_]
    else
      f(props, children.toJsArray).asInstanceOf[ReactComponentU_]
  }

}

Notably, make sure that the argument in createFactory is the global.Whatever you export to. Account for children's variances in numbers. Use js.UndefOr for every optional.

the client

def render(S: State, C: PropsChildren) = {
  <.header(^.key := UUID.randomUUID().toString, Styles.Header_TextIsCentered, Styles.Header_BackgroundIsGrayBlue_WithWhiteText)(
    <.div(^.className := "container")(
      <.h1()("justin shin, tampa fl"),
      MtSvgLines(animate = true, duration = 500) {
        import japgolly.scalajs.react.vdom.svg.all._

        svg(SvgStyles.svg,
          defs(
            maskTag(id := "mask", x := "0", y := "0", width := "100%", height := "100%",
              rect(id := "alpha", x := "0", y := "0", width := "100%", height := "100%"),
              text(id := "title", x := "50%",y := "0", dy := "1.58em")("Justin", transform := "translate(50, 0)"),
              text(id := "title", x := "50%",y := "0", dy := "1.58em")("Tampa")
            )
          ),
          rect(id := "base", x := "0", y := "0", width := "100%", height := "100%"),
          path(stroke := "green", strokeWidth := "10", fill := "none", d := "M20.8,51c0,0,20.8,18.2,21.5,18.2c0.6,0,33.3-38.5,33.3-38.5"),

        )
      }
    )
  )
}
tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126
  • hmm wrapper i mentioned above is not sri specific , it should work with any scala-react lib!. for some reason if scalajs-react doesn't support `React.createElement(classCtor,props,children :_*) ` then file an issue there :) (its official React API ) – invariant May 03 '16 at 02:16
  • I follow you... it's the same underlying JS so it sihould work, but ...try the `react.createElement`... I could not get it to work in the example above with `scalajs-react` – tacos_tacos_tacos May 03 '16 at 06:42
  • ah ok, looks like they need a overloaded version which accepts javascript ctor :) – invariant May 04 '16 at 00:36
  • Coming from a Backbone world into angular for about 4 months and then react for 2 and now this (albeit my own choosing but still), it is tough for me to distinguish design choices from hard requirements and scalajs oddities from reactjs oddities :) – tacos_tacos_tacos May 04 '16 at 05:25
  • 1
    i can understand your pain,recently i also went through the similar(trying out different tech..) scenario.for my app backend first i picked graphql as service then ditched it because of inflexiblity, rolled by own scala/graphql backend now i have to host my scala app and db(mongodb) :( . Finally i settled with cloudant for now :) – invariant May 04 '16 at 06:58