Converting Javascript to Scala on Scala-js

Besides the lessons (for functions) given by Sebastian, the author of Scala-js, here are some more lessons I learned while porting javascript code to Scala to use Scala-js.

  • Wrap your code. First, wrap your code in an object's body. You need this since scala does not authorize code outside of objects or classes. Extend
    JSApp
    JSApp to benefit from variables such as document, window, etc.
    object YourApp extends JSApp { ... }
    object YourApp extends JSApp { ... }
    Put your code inside the brackets.
  • Object literals: To convert the following javascript snippet:
    { action: "bind", data=42, check=true }
    { action: "bind", data=42, check=true }
    you can use the literal import and use one of the following two constructions:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    import js.Dynamic.{literal => l}
    l(action="bind", data=42, check=true)
    l("action"->"bind", "data"->42, "check"->true)
    import js.Dynamic.{literal => l} l(action="bind", data=42, check=true) l("action"->"bind", "data"->42, "check"->true)
    import js.Dynamic.{literal => l}
    l(action="bind", data=42, check=true)
    l("action"->"bind", "data"->42, "check"->true)
  • Order of val/var/def: The order of var/val and defs inside an anonymous function matter. You will have to correct all back-references. Hence, the following:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    function changeValue() { value = 5; }
    var value = 0
    function changeValue() { value = 5; } var value = 0
    function changeValue() { value = 5; }
    var value = 0

    which is valid in javascript, will has to be rewritten as

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    var value = 0
    function changeValue() { value = 5; }
    var value = 0 function changeValue() { value = 5; }
    var value = 0
    function changeValue() { value = 5; }
  • Implicit converters from Dynamic to Boolean: Because js.Dynamics are sometimes used in boolean conditions or for comparison, I added implicit converters so that it does not throw errors.
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    object Implicits {
    implicit def dynamicToBoolean(d: js.Dynamic): Boolean = d.asInstanceOf[Boolean]
    implicit def dynamicToString(d: js.Dynamic): String = d.asInstanceOf[String]
    }
    object Implicits { implicit def dynamicToBoolean(d: js.Dynamic): Boolean = d.asInstanceOf[Boolean] implicit def dynamicToString(d: js.Dynamic): String = d.asInstanceOf[String] }
    object Implicits {
      implicit def dynamicToBoolean(d: js.Dynamic): Boolean = d.asInstanceOf[Boolean]
      implicit def dynamicToString(d: js.Dynamic): String = d.asInstanceOf[String]
    }
  • Warning comparing dynamic with strings: To remove the annoying warnings which arrive when you compare dynamic with strings, you can add the following implicit:
  • Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    object Implicits {
    implicit class ComparisonOp(d: js.Dynamic) {
    def ==(other: String) = d.asInstanceOf[String] == other
    }
    }
    object Implicits { implicit class ComparisonOp(d: js.Dynamic) { def ==(other: String) = d.asInstanceOf[String] == other } }
    object Implicits {
      implicit class ComparisonOp(d: js.Dynamic) {
        def ==(other: String) = d.asInstanceOf[String] == other
      }
    }
  • Use of the javascript "in" keyword
    if(key in obj)
    if(key in obj) has to be translated to
    if(!js.isUndefined(obj(key)))
    if(!js.isUndefined(obj(key)))
  • for...in
    for(a in obj)
    for(a in obj) has to be translated to:
    for(a <- js.Object.keys(obj))
    for(a <- js.Object.keys(obj))
  • String extraction: Instead of extracting chars with
    string(index)
    string(index)
    prefer the following
    string.substring(index, 1)
    string.substring(index, 1)
    which returns a String instead of a Char. It is hard to convert from Char to String.
  • Zero-argument functions: Careful when you translate zero-arg functions to def. If you pass them as parameter, you need to add the modifier _ so that they are not evaluated. For example:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    function loadSomething() { ... }
    $("#button").change(loadSomething)
    function loadSomething() { ... } $("#button").change(loadSomething)
    function loadSomething() { ... }
    $("#button").change(loadSomething)

    has to be translated to:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    def loadSomething() { ... }
    $("#button").change(loadSomething _)
    def loadSomething() { ... } $("#button").change(loadSomething _)
    def loadSomething() { ... }
    $("#button").change(loadSomething _)
  • setTimeout:  You will have to translate setTimeout using another construct, with a lazy evaluated block. Thus, this:
    setTimeout(function() { ... }, 999)
    setTimeout(function() { ... }, 999)
    becomes:
    js.timers.setTimeout(999){ ... }
    js.timers.setTimeout(999){ ... }Do not put anonymous function inside the brackets like { () => ...} else they the function body will never be evaluated !
  • console.log: In order to use console.log directly in your code, import the following which provides a statically typed object
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    import org.scalajs.dom._
    console.log("Whatever")
    import org.scalajs.dom._ console.log("Whatever")
    import org.scalajs.dom._
    console.log("Whatever")

    or alternatively, you can use the dynamic global variable:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    import js.Dynamic.{global => g}
    g.console.log("Whatever")
    import js.Dynamic.{global => g} g.console.log("Whatever")
    import js.Dynamic.{global => g}
    g.console.log("Whatever")
  • Call object's main method. Last but not least. If you define your methods in an object, especially some jQuery onLoad event, you need to call at least one method from the object for it to be initialized. Indeed, objects are lazily created.
    For example, if your initializer object is:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    package your.app
    import scala.scalajs.js.JSApp
    import org.scalajs.jquery.{jQuery => $}
    object YourApp extends JsApp {
    $(document).ready(() => { ...
    })
    def main() = {}
    }
    package your.app import scala.scalajs.js.JSApp import org.scalajs.jquery.{jQuery => $} object YourApp extends JsApp { $(document).ready(() => { ... }) def main() = {} }
    package your.app
    import scala.scalajs.js.JSApp
    import org.scalajs.jquery.{jQuery => $}
    object YourApp extends JsApp {
      $(document).ready(() => { ...
       })
     def main() = {}
    }

    you need to include a script in your html like this:

    <body onload="your.app.YourApp().main()">
    <body onload="your.app.YourApp().main()">

  • Bracket access on custom objects. For the call
    point["name"]
    point["name"] with objects like the following, add a method for custom bracket access. You may factorize this method in a custom trait.
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    val point = new Object {
    val name = "Mikael"
    val age = 18
    @JSBracketAccess
    def apply(index: String): js.Dynamic = js.native
    }
    //point("name") will retrun "Mikael" as a js.Dynamic
    val point = new Object { val name = "Mikael" val age = 18 @JSBracketAccess def apply(index: String): js.Dynamic = js.native } //point("name") will retrun "Mikael" as a js.Dynamic
    val point = new Object {
      val name = "Mikael"
      val age = 18
      @JSBracketAccess
      def apply(index: String): js.Dynamic = js.native
    }
    //point("name") will retrun "Mikael" as a js.Dynamic
  • String-to-int conversion:
    • If you use JQuery and  observe
      $("something").value()*1
      $("something").value()*1, since  
      value()
      value() returns a
      js.Dynamic
      js.Dynamic, simply converting this to 
      $("something").value().toInt
      $("something").value().toInt will fail at run-time (the
      toInt
      toInt method is not defined on object of type string). However, you can do the following:
      $("something").value().asInstanceOf[String].toInt
      $("something").value().asInstanceOf[String].toInt
    • If you find the pattern
      1*s
      1*s , where
      s
      s is a string which might be null, don't use
      s.toInt
      s.toInt directly in scala. It fails if
      s
      s is
      null
      null, whereas
      1*null == 0
      1*null  == 0 in javascript. Instead, do the following:
      Plain text
      Copy to clipboard
      Open code in new window
      EnlighterJS 3 Syntax Highlighter
      implicit class OrIfNull[T](s: T) {
      def orIfNull(e: T): T = if(s == null) e else s
      }
      s.orIfNull("0").toInt
      implicit class OrIfNull[T](s: T) { def orIfNull(e: T): T = if(s == null) e else s } s.orIfNull("0").toInt
      implicit class OrIfNull[T](s: T) {
        def orIfNull(e: T): T = if(s == null) e else s
      }
      s.orIfNull("0").toInt

Remember, next time, to directly start with scala-js 🙂

One thought on “Converting Javascript to Scala on Scala-js”

Comments are closed.