> jsx --watch src/ build/

QuizScreen.scala

@JSExport object QuizScreen { @JSExport def main(target: html.Div) = { val quizData = // … Ajax call target.appendChild( span(`class` := "alignleft", "Score: " + quizData.score) // ... more view code here ) } }



Notice that span is a Scala method (the Scalatags library has to be imported). Assuming you've configured SBT to use Scala.js (see below), this is converted to JavaScript in SBT by calling:





> fastOptJS





A good thing about the Scala.js compiler is that it keeps the target JavaScript small by eliminating any code from included libraries that is not used. To stop this from happening on the entry point itself in QuizScreen , it is necessary to use the @JSExport annotation both on the object and the main method. This guarantees that main() will be callable from JavaScript.

So now we have seen the React way and the Scala.js way. How do we combine them? A good option is to use the scalajs-react library from David Barri. Now the ScoreText component looks like this:



val ScoreText = ReactComponentB[String]("ScoreText") .render(scoreText => <.span(^.className := "alignleft", "Score: " + scoreText)) .build

render()

componentDidMount()





Notice also that it uses a Scala span . A specialized version of Scalatags is used here. At first the extra symbols look intimidating, but just remember that < is used for tags and ^ is used for attributes, and they are imported like this:



import japgolly.scalajs.react.vdom.prefix_<^._

this.state

getInitialState()

getInitialState: function() { return {data: []}; }



The Scala version lets us define the state more clearly because it is a strongly typed language. For example, the state for the QuizScreen looks like this:

case class State(userToken: String, currentQuizItem: Option[QuizItemReact] = None, prevQuizItem: Option[QuizItemReact] = None, scoreText: String = "", chosen: Option[String] = None, status: String = "")

QuizScreen

ScoreText

val QuizScreen = ReactComponentB[Unit]("QuizScreen") .initialState(State(generateUserToken)) .backend(new Backend(_)) .render((_, state, backend) => state.currentQuizItem match { // Only show the page if there is a quiz item case Some(currentQuizItem: QuizItemReact) => <.div( <.span(^.id := "header-wrapper", ScoreText(state.scoreText), <.span(^.className := "alignright", <.button(^.id := "delete-button", ^.onClick --> backend.removeCurrentWordAndShowNextItem(currentQuizItem), "DELETE WORD")) ), QuestionArea(Question(currentQuizItem.prompt, currentQuizItem.responseType, currentQuizItem.numCorrectResponsesInARow)), <.span(currentQuizItem.allChoices.map { choice => <.div( <.p(<.button( ^.className := "response-choice-button", ^.className := cssClassForChosen(choice, state.chosen, currentQuizItem.correctResponse), ^.onClick --> backend.submitResponse(choice, currentQuizItem), choice)) )}), PreviousQuizItemArea(state.prevQuizItem), StatusText(state.status)) case None => if (!state.quizEnded) <.div("Loading...") else <.div("Congratulations! Quiz complete. Score: " + state.scoreText) }) .buildU

QuizScreen

State

Backend

submitResponse

backend

class Backend(scope: BackendScope[Unit, State]) { def submitResponse(choice: String, curQuizItem: QuizItemReact) { scope.modState(_.copy(chosen = Some(choice))) val url = "/processUserResponse" val response = QuizItemAnswer.construct(scope.state.userToken, curQuizItem, choice) val data = upickle.write(response) val sleepMillis: Double = if (response.isCorrect) 200 else 1000 Ajax.post(url, data).foreach { xhr => setTimeout(sleepMillis) { updateStateFromAjaxCall(xhr.responseText, scope) } } } def updateStateFromAjaxCall(responseText: String, scope: BackendScope[Unit, State]): Unit = { val curQuizItem = scope.state.currentQuizItem upickle.read[DataToClient](responseText) match { case quizItemData: DataToClient => val newQuizItem = quizItemData.quizItemReact // Set new quiz item and switch curQuizItem into the prevQuizItem position scope.setState(State(scope.state.userToken, newQuizItem, curQuizItem, quizItemData.scoreText)) } } // more backend methods... }

submitResponse

State

State

processUserResponse

object Server extends SimpleRoutingApp { def main(args: Array[String]): Unit = { implicit val system = ActorSystem() lazy val config = ConfigFactory.load() val port = config.getInt("libanius.port") startServer("0.0.0.0", port = port) { // .. get route not shown here post { path("processUserResponse") { extract(_.request.entity.asString) { e => complete { val quizItemAnswer = upickle.read[QuizItemAnswer](e) upickle.write(QuizService.processUserResponse(quizItemAnswer)) } } } } } } }

QuizService

QuizScreen

get { pathSingleSlash { complete{ HttpEntity( MediaTypes.`text/html`, QuizScreen.skeleton.render ) } }

QuizScreen

QuizScreen

QuizScreen

QuizScreen

object QuizScreen { val skeleton = html( head( link( rel:="stylesheet", href:="quiz.css" ), script(src:="/app-jsdeps.js") ), body( script(src:="/app-fastopt.js"), div(cls:="center", id:="container"), script("com.oranda.libanius.scalajs.QuizScreen().main()") ) ) }

script

@JSExport

QuizScreen().main()

@JSExport def main(): Unit = { QuizScreen() render document.getElementById("container") }

app-fastopt.js : In a Scala.js application, the *-fastopt.js file is the final output of the fastOptJS task, containing the JavaScript code that has been generated from your Scala code.

: In a Scala.js application, the file is the final output of the task, containing the JavaScript code that has been generated from your Scala code. app-jsdeps.js : In a Scala.js application, the *-jsdeps.js , contains all additional JavaScript libraries: in our case, the only thing it incorporates is react-with-addons.min.js .

import sbt.Keys._ name := "Libanius Scala.js front-end" // Set the JavaScript environment to Node.js, assuming that it is installed, rather than the default Rhino scalaJSStage in Global := FastOptStage // Causes a *-jsdeps.js file to be generated, including (here) React skip in packageJSDependencies := false val app = crossProject.settings( unmanagedSourceDirectories in Compile += baseDirectory.value / "shared" / "main" / "scala", libraryDependencies ++= Seq( "com.lihaoyi" %%% "scalatags" % "0.5.1", "com.lihaoyi" %%% "utest" % "0.3.0", "com.lihaoyi" %%% "upickle" % "0.2.8" ), scalaVersion := "2.11.6", testFrameworks += new TestFramework("utest.runner.Framework") ).jsSettings( libraryDependencies ++= Seq( "org.scala-js" %%% "scalajs-dom" % "0.8.0", "com.github.japgolly.scalajs-react" %%% "core" % "0.8.3", "com.github.japgolly.scalajs-react" %%% "extra" % "0.8.3", "com.lihaoyi" %%% "scalarx" % "0.2.8" ), // React itself (react-with-addons.js can be react.js, react.min.js, react-with-addons.min.js) jsDependencies += "org.webjars" % "react" % "0.13.1" / "react-with-addons.js" commonJSName "React" ).jvmSettings( libraryDependencies ++= Seq( "io.spray" %% "spray-can" % "1.3.2", "io.spray" %% "spray-routing" % "1.3.2", "com.typesafe.akka" %% "akka-actor" % "2.3.6", "org.scalaz" %% "scalaz-core" % "7.1.2" ) ) lazy val appJS = app.js.settings( // make the libanius core JAR available // ... unmanagedBase <<= baseDirectory(_ / "../shared/lib") ) lazy val appJVM = app.jvm.settings( // make sure app-fastopt.js, app-jsdeps.js, quiz.css, the libanius core JAR, application.conf // and shared source code is copied to the server // ... )

js

jvm

shared

js

jvm

shared

js

jvm

crossProject

js

jvm

scalaJSStage in Global := FastOptStage

libanius-scalajs-react/ build.sbt app/ js/ src/ main/ scala/ com.oranda.libanius.scalajs/ QuizScreen.scala target/ jvm/ src/ main/ resources/ application.conf scala/ com.oranda.libanius.sprayserver/ QuizScreen.scala QuizService.scala Server.scala target/ shared/ lib/ libanius-0.982.jar src/ main/ resources/ quiz.css scala/ com.oranda.libanius.scalajs/ ClientServerObjects.scala QuizItemReact

QuizScreen

quiz.css file

QuizScreen

This is using standard React so far. However this is not the way we do it with Scala.js.Firstly, there exists a Scala library from Haoyi Li called Scalatags , that includes Scala equivalents for HTML tags and attributes. Let's assume we have a filein which we are writing the view. The core code may start off looking a bit like this:Compare this with the JSX version. The Scala code is more concise. Notice that themethod is present. It's also possible to use other React lifecycle methods if necessary, likefor initializing the component.In classic JavaScript React, a component can hold state, as seen in references toand themethod, which might look like this.It is best to centralize the state for the screen like this and pass it down to components, rather than having each sub-component have its own separate state object. That could get out of hand!By the way, you can compose components just as you can in classic React. The central component of theobject is the QuizScreen component, and it contains thecomponent along with the various other bits and pieces. The code below shows how this is all put together.The central component () contains the other components (implementations not shown) and also has access to aand a. The backend contains logic that is a bit more extended. For example, in the code above, observe thatis called above on thewhen a button is clicked by the user. The code invoked is:makes an Ajax POST call to the server, collects the results and updates theobject. The React framework will take care of the rest, i.e. updating the DOM to reflect the changes toIn making the Ajax call, the upickle library (again from Haoyi Li) is used for serialization/deserialization. This is also used on the server side of our Scala.js application. The core of the server side is a Spray server. A simple router is defined which recognizes the call tomade above:The "processUserResponse" path extracts the post data using upickle then passes the call on to thesingleton which contains the mid-tier business logic, and relies on the core Libanius library to run the main back-end functionality on the Quiz. I won't go into detail about this logic, but note that both for historical reasons and future portability it relies on simple files to hold quiz data rather than a database system.Back to the Spray server: when thepage is initially loaded, this route is used:Thementioned here istheon the client-side that is described above. In fact, it is athat makes a call to the client-side. Like this:Again the tags are from Scalatags. The main call is in the lasttag. Recall that on the client-side we useto make theavailable:Also notice in the skeleton above, there are two included JavaScript libraries:Here are the essentials of the SBT configuration, which can be used as a starting point for other Scala.js projects, as it just uses the most basic dependencies, including Scalatags, upickle for serialization, and utest for testing.As you can see, the special thing about a Scala.js client-server SBT configuration is that it is divided into three parts:, and. Thefolder contains code to be compiled by ScalaJS, thefolder contains regular Scala code used on the server-side, and thefolder contains code and configuration that should be accessible to bothand. This is achieved by using thebuilder from Scala.js, which constructs two separate projects, theone and theone.So far we've been assuming that any generated JavaScript will run in a browser. However, Scala.js also works with "headless runtimes" like Node.js or PhantomJS to ensure you can run it from the command-line on the server-side too: this is important in testing. Notice theline above.Now for a grand overview of the web application, let's look at the directory structure. You can see how slim the application really is: there are only a few key source files.Again notice there is aon both the server-side and client-side: the former calls the latter.One thing that I didn't mention yet is thethat is used in the client-side. This is just an old-fashioned CSS file, but of course it also possible to use LESS files. Furthermore, if you don't anticipate having a graphic designer want to change your styles, you could even go the whole way in making your application, and write your styles in Scala with ScalaCSS The full code for this front-end to Libanius is on Github . As of writing there is a deployment on Heroku (may not be supported indefinitely). For a full tutorial on Scala.js, see Hands-on Scala.js from Haoyi Li. There is also a small official tutorial