Designing facades in Scala.js

Designing facades in Scala.js

Designing facades in Scala.js

Scala.js builds a bridge from the Scala world to the JavaScript world. Today Scala developers can take all the magic of Scala with many powerful libraries and use it in front-end applications. But after passing the bridge our developer suddenly might find himself in very different environment. Does he know something about transition animation, or about event system on mobile devises?

Fortunately JavaScript itself is ready to give a huge bulk of awesome libraries for all cases. All we need to do is to find an appropriate library and write a facade.

You can find this in the Scala.js documentation:

When writing an application with Scala.js, it is expected that the main application logic be written in Scala.js, and that existing JavaScript libraries are leveraged. Calling JavaScript from Scala.js is therefore the most important direction of interoperability.

Facade types are zero-overhead typed APIs for JavaScript libraries. They are similar in spirit to TypeScript type definitions.

JavaScript has dynamic type checking. If we want to have some type checking on compile time, we must use some interface that fills the role of naming types. In Scala.js we can use traits. Scala.js documentation says that the trait must inherit directly or indirectly from js.Any (usually from js.Object). Native JS types should be annotated with @js.native.

OK. Let’s get down to business. Recently I had to use Sortable.js library in my Scala.js project and I wrote a facade. Now I can use this as an example.

Sortable is a minimalist JavaScript library for sortable drag-and-drop lists. I think it’s the best library of this sort. But let’s look at this library from the point of a facade builder.

It has:

  1. several dynamic methods
  2. the configuration object with a big list of options
  3. several static methods, placed in a special namespace Sortable.utils
  4. custom events

The minimum that we should do to start using the library is to write facade for dynamic methods:


@js.native
class Sortable(element: Element, props: js.Any) extends js.Any {
  val el: Element = js.native
  def option(name:String):js.Dynamic = js.native
  def option(name:String, value: js.Any):Unit = js.native
  def closest(el:Node, selector:String):js.UndefOr[Node] = js.native
  def closest(el:Node):js.UndefOr[Node] = js.native
  def toArray(): js.Array[String] = js.native
  def sort(order:js.Array[String]):Unit = js.native
  def save(): Unit = js.native
  def destroy(): Unit = js.native

Methods with same names and different signatures can be overloaded

It’s enough to make our first sortable list:


new Sortable(dom.document.getElementById("qux"), js.Dictionary(
      "group" -> js.Dictionary(
        "name" -> "qux",
        "put" -> js.Array("foo", "bar")),
      "animation" -> 100
    )
 )

In this javascript library Sortable has only one static method create. It’s a factory to create new Sortable instance. We can use it in our Scala code:


@js.native
object Sortable extends js.Object {
  @JSName("create")
  def apply(element:Element):Sortable = js.native
  @JSName("create")
  def apply(element:Element, props:js.Any):Sortable = js.native

}

We use the companion object of our Sortable class for static methods. Now we can omit new:



Sortable(dom.document.getElementById("qux"), js.Dictionary(
      "group" -> js.Dictionary(
        "name" -> "qux",
        "put" -> js.Array("foo", "bar")),
      "animation" -> 100
    )
 )

It’s progress! But we must go further! Look at the parameters. The first parameter is a html element. The second one is a javascript object (configuration object). We used js.Dictionary to form it. If we want type checking we need to use some technique.  Scala.js 0.6.14  now allows to have concrete members in @ScalaJSDefined traits, if their right-hand-side is = js.undefined

Let’s write a facade for our configuration object and custom events:


@ScalaJSDefined
trait EventS extends js.Object {
  val `type`: js.Any
  val to: js.Any
  val from: js.Any
  val item: js.Any
  val oldIndex: js.Any
  val newIndex: js.Any
}

@ScalaJSDefined //move event
trait EventM extends js.Object {
  val `type`: js.Any
  val to: js.Any
  val from: js.Any
  val dragged: js.Any
  val draggedRect: js.Any
  val related: js.Any
  val relatedRect: js.Any
}


@ScalaJSDefined
trait SortableProps extends js.Object {
  val group: js.UndefOr[String | js.Any] = js.undefined 
  val sort: js.UndefOr[Boolean] = js.undefined 
  val delay: js.UndefOr[Int] = js.undefined
  val disable: js.UndefOr[Boolean] = js.undefined
  val store: js.UndefOr[js.Any] = js.undefined
  val animation: js.UndefOr[Int] = js.undefined
  val handle: js.UndefOr[String] = js.undefined
  val filter: js.UndefOr[String] = js.undefined
  val draggable: js.UndefOr[String] = js.undefined
  val ghostClass: js.UndefOr[String] = js.undefined
  val chosenClass: js.UndefOr[String] = js.undefined
  val dragClass: js.UndefOr[String] = js.undefined
  val dataIdAttr: js.UndefOr[String] = js.undefined
  val forceFallback: js.UndefOr[Boolean] = js.undefined
  val fallbackClass: js.UndefOr[String] = js.undefined
  val fallbackOnBody: js.UndefOr[Boolean] = js.undefined 
  val fallbackTolerance: js.UndefOr[Int] = js.undefine
  val scroll: js.UndefOr[Boolean | Element] = js.undefined 
  val scrollFn: js.UndefOr[js.Function3[Int, Int, Event, Unit]] = js.undefined
  val scrollSensitivity: js.UndefOr[Int] = js.undefined
  val scrollSpeed: js.UndefOr[Int] = js.undefined
  val setData: js.UndefOr[js.Function2[DataTransfer, Element,Unit]] = js.undefined
  val onChoose: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onStart: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onEnd: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onAdd: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onUpdate: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onSort: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onRemove: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onFilter: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
  val onMove: js.UndefOr[js.Function2[EventM, Event, Unit]] = js.undefined
  val onClone: js.UndefOr[js.Function1[EventS, Unit]] = js.undefined
}

And now we can write this:


  val get: js.Function1[Sortable, js.Array[String]] = { (sortable: Sortable) => {

    import js.JSConverters._ //this adds additional 30kB :((

    val order = window.localStorage.getItem(sortable.option("group").name.asInstanceOf[String])
    order match {
      case x:String => x.split("|").toJSArray
      case _ => js.Array[String]()
    }
  }}

  val set: js.Function1[Sortable, Unit] = { (sortable: Sortable) => {
    val order = sortable.toArray()
    window.localStorage.setItem("localStorage-example", order.join("|"))
  }}


  val storeProps = new SortableProps{
    override val group = "localStorage-example"
    override val store = js.Dictionary(
      "get" -> get,
      "set" -> set

    )
    override val onStart: UndefOr[Function1[EventS, Unit]] = js.defined{
      (event:EventS) => console log event
    }
  }

  Sortable(dom.document.getElementById("storage"), storeProps)

Is this technique more convenient than using js.Dictionary? It’s up to you.

A local storage is used to store the order of a sortable list. Two functions are defined (get and set). The set function takes a Sortable object, and sets a string representation of its order to the local storage. The get function gets this string from the local storage and returns a js.Array. The import of  js.JSConverters._ is very important. It needs for implicit conversion of Array to js.Array.

Look how is the property onStart defined in this example. js.defined explicitly upcasts an A to a js.UndefOr[A].

 

This JavaScript library has an object Sortable.utils with an additional set of static methods. To write a facade it’s necessary to use annotation @JSName:


@JSName("Sortable.utils")
@js.native
object Utils extends js.Object {
  def on(el:Node, event:String, fn: js.Function1[Event, Unit]):Unit = js.native
  def off(el:Node, event:String, fn: js.Function1[Event, Unit]):Unit = js.native
  def css(el:Node):js.Object = js.native
  def css(el:Node, prop:String):js.Any = js.native
  def css(el:Node, prop:String, value:String):Unit = js.native
  def find(ctx:Node, tagName:String):js.Array[Node] = js.native
  def find(ctx:Node, tagName:String, iterator:js.Function2[Node, Int, Unit]):js.Array[Node] = js.native
  def bind(ctx:js.Any, fn:js.Function):js.Function = js.native
  def is(el:Node, selector:String):Boolean = js.native
  def closest(el:Node, selector:String, ctx:Node):js.UndefOr[Node] = js.native
  def closest(el:Node, selector:String):js.UndefOr[Node] = js.native
  def clone(el:Node):Node = js.native
  def toggleClass(el:Node, name:String, state:Boolean):Unit = js.native

 

I hope that my post has been useful and it will help you not to be afraid of writing you own facades.

If you are interested in sortable-js-facade you can use it by adding to your build.sbt:



libraryDependencies ++= Seq(
  "org.scala-js" %%% "scalajs-dom" % "0.9.1",
  "net.scalapro" %%% "sortable-js-facade" % "0.2.1"
)

jsDependencies ++= Seq(
  "org.webjars.bower" % "github-com-RubaXa-Sortable" % "1.4.2"
    / "1.4.2/Sortable.js" minified "Sortable.min.js"
)

If you have questions don’t hesitate to make a comment.

If you found my post useful it would be kind of you to share it with others.

 

 

See full code on GitHub

See Demo

 

Please follow and like us:

About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Leave a Reply

Your email address will not be published. Required fields are marked *