Binding.scala with Semantic-UI

In this post, I will talk about how to use Binding.scala along with Semantic UI commit by commit.

Full Stack Scala Starter 4efc5fc

Let’s start with Full Stack Scala Starter. It is a simple project showing how to integrate a Play project with Binding.scala.

SBT Tips in Chinese: If you have problem resolving sbt dependencies, see the sbt part of Scala入门之工具篇 for tricks.

Introduce Semantic-UI 873948e

Javascript

We use WebJars. SemanticUI depends on jQuery and we need to use scala-js-jquery. However scala-js-jquery does not support jQuery 3, we use jQuery 2.1.3 recommended in scala-js-jquery’s README.

lazy val jQueryV = "2.1.3"
lazy val semanticV = "2.2.2"
jsDependencies ++= Seq(
  "org.webjars" % "jquery" % jQueryV / "jquery.js" minified "jquery.min.js",
  "org.webjars" % "Semantic-UI" % semanticV / "semantic.js" minified "semantic.min.js" dependsOn "jquery.js"
)

CSS

As for CSS, we use webjars-play.

Generated HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Play with Scala.js</title>
    <link rel="stylesheet" media="screen" href="/assets/stylesheets/main.css">
    <link rel="shortcut icon" type="image/png" href="/assets/images/favicon.png">
    <link rel='stylesheet' href='/webjars/Semantic-UI/2.2.2/semantic.min.css'>
  </head>
  <body>
    <script src="/assets/client-jsdeps.js" type="text/javascript"></script> 
    <script src="/assets/client-fastopt.js" type="text/javascript"></script>
    <script src="/assets/client-launcher.js" type="text/javascript"></script>
  </body>
</html>

After introducing javascripts and css, let us look into the generated HTML. Play serves the webjars contents at /webjars/ and we locate semantic.min.css use @routes.WebJarAssets.at(webJarAssets.locate("semantic.min.css")) provided by play-webjars. As for javascripts, jquery.min.js and semantic.min.js specified in jsDependencies are merged into one file /assets/client-jsdeps.js and our scala.js code is compiled and translated into /assets/client-fastopt.js.

Use SemanticUI a829234

  @dom
  def render = {
    val data = Var("")
    countRequest(data) // initial population
    <div>
      <button onclick={event: Event => countRequest(data) }>
        Boop
      </button>
      From Play: The server has been booped { data.bind } times. Shared Message: {shared.SharedMessages.itWorks}.
    </div>
  }

In this section, we make some changes to the render body:

  @dom
  def render = {
    val data = Var("")
    countRequest(data) // initial population
    <div>
      <select class="ui search dropdown">
        <option>State</option>
        <option value="AL">Alabama</option>
        <option value="AK">Alaska</option>
        <option value="AZ">Arizona</option>
      </select>
      <button class="ui primary button" onclick={event: Event => countRequest(data) }>
        Boop
      </button>
      From Play: The server has been booped { data.bind } times. Shared Message: {shared.SharedMessages.itWorks}.
    </div>
  }

A select is added, and the ugly button is beautified with blue background color and white foreground color. semantic ui button

It works! Comparing with other scala.js library, Binding.scala is straightforword and elegant. No need to memorize those so-called type-safe but ugly and hard-to-remember dsl.

Monkey Patching 074682b

However, in previous section, the select should be a searchable dropdown as it behaves at http://semantic-ui.com/modules/dropdown.html#search-selection, but it failed to work. Googling, I found http://stackoverflow.com/questions/30531410/correct-way-to-dynamically-add-semantic-ui-controls and we need to run

$('.ui.dropdown')
  .dropdown({
    on: 'hover'
  });

to activate it.

To run the equivelant code in scala.js, we need to know how scala.js interops with javascript. According to the quoted part from scala.js document:

In JavaScript, monkey patching is a common pattern, where some top-level object or class’ prototype is meant to be extended by third-party code. This pattern is easily encoded in Scala.js’ type system with implicit conversions.

For example, in jQuery, $.fn can be extended with new methods that will be available to so-called jQuery objects, of type JQuery. Such a plugin can be declared in Scala.js with a separate trait, say JQueryGreenify, and an implicit conversions from JQuery toJQueryGreenify. The implicit conversion is implemented with a hard cast, since in effect we just want to extend the API, not actually change the value.


@js.native
trait JQueryGreenify extends JQuery {
  def greenify(): this.type = ???
}

object JQueryGreenify {
  implicit def jq2greenify(jq: JQuery): JQueryGreenify =
    jq.asInstanceOf[JQueryGreenify]
}

Recall that jq.asInstanceOf[JQueryGreenify] will be erased when mapping to JavaScript because JQueryGreenify is a JS trait. The implicit conversion is therefore a no-op and can be inlined away, which means that this pattern does not have any runtime overhead.

we need:


object SemanticUI {
  @js.native
  trait SemanticJQuery extends JQuery {
    def dropdown(params: js.Any*): SemanticJQuery = js.native
  }

  implicit def jq2semantic(jq: JQuery): SemanticJQuery = jq.asInstanceOf[SemanticJQuery]
}

and run the effective code after dom rendering:

def main(): Unit = {
  dom.render(document.body, render)
  import SemanticUI.jq2semantic
  jQuery(".ui.dropdown").dropdown(js.Dynamic.literal(on = "hover"))
}

in this way, the searchable dropdown works: Searchable Dropdown

One more example for the Search module 454d568

http://semantic-ui.com/modules/search.html#local-search

In this case,

var content = [
  { title: 'Canada' },
  { title: 'China' },
  { title: 'USA' }

]

$('.ui.search')
  .search({
    source: content
  });

corresponds to

//import js.JSConverters._
//val content = List("China", "Canada", "USA")
//  .map(country => mutable.HashMap("title" -> country).toJSDictionary)
//  .toJSArray
val content = js.Array("China", "Canada", "USA")
  .map(country => js.Dictionary("title" -> country))
jQuery(".ui.search").search(js.Dynamic.literal(source = content))
Local Search
Local Search

References

  1. https://www.scala-js.org/doc/interoperability/facade-types.html
  2. http://www.scala-js.org/doc/interoperability/types.html