Inviting ScalaJS to the Party
For now Slimetrail is a plain old JVM-only project. We need to make it able to produce both JVM and JavaScript outputs: it will become a cross-project.
Cross-projectization
- Recommended time to spend on this part: 5 minutes
- Recommended finishing before: 10:15am
Add the following lines to project/plugins.sbt .
// ScalaJS
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.25")
A large amount of Scala libraries are available both as JVM and ScalaJS artifacts. So will we! The Text UI needs the toolbox and slimetrail to be compiled for the JVM while the Web UI needs them to be compiled in JavaScript. Cross-projects can define libraryDependencies using %%% instead of %% to ensure the correct artifacts are fetched.
In build.sbt, replace any %% in commonSettings by %%% .
We now need to adapt the toolbox and slimetrail project definitions to define these as cross-projects. To do so:
-
At the beginning of
build.sbtadd these lines:// shadow sbt-scalajs' crossProject and CrossType until Scala.js 1.0.0 is released import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType} -
In the
toolbox, project definition, replaceprojectby:crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) -
After the
toolbox, project definition, add:lazy val toolboxJS = toolbox.js lazy val toolboxJVM = toolbox.jvm -
In the
slimetrail, project definition, replaceprojectby:crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) -
After the
slimetrail, project definition, add:lazy val slimetrailJS = slimetrail.js lazy val slimetrailJVM = slimetrail.jvm -
In the
text, project definition, replaceslimetrailbyslimetrailJVM.
toolbox and slimetrail projects now exists in two flavors each:
toolboxJVMandslimetrailJVMare JVM projects.toolboxJSandslimetrailJSare ScalaJS projects.
Run sbt text/run to ensure everything is still working fine.
Setting up the Web UI
- Recommended time to spend on this part: 5 minutes
- Recommended finishing before: 10:20am
Add the new project definition for the Web UI:
// Web Interface
lazy val web =
project
.in(file("web"))
.enablePlugins(ScalaJSPlugin)
.settings(commonSettings: _*)
.settings(
name := "slimetrail-web",
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.6",
scalaJSUseMainModuleInitializer := true
)
.dependsOn(slimetrailJS)
Create the file web/src/main/scala/Main.scala whose content is:
package slimetrail.web
import org.scalajs.dom._
import scala.scalajs.js
import slimetrail._
/** Tree strucure to represent the DOM */
sealed trait Html[+A] {
final def render: Node =
??? // Replace by actual code
final def map[B](f: A => B): Html[B] =
??? // Replace by actual code
}
final case class ATextNode(value: String) extends Html[Nothing]
final case class AnElement[+A](
namespace: String,
tag: String,
attributes: Map[(Option[String], String), String],
eventListeners: List[(String, js.Function1[_ <: Event, A])],
children: List[Html[A]]
) extends Html[A]
/** Generic Web Application */
trait WebApplication extends Application {
def view(model: Model): Html[Msg]
final def run(initialNode: Node): Unit =
??? // Replace by actual code
}
/** The Slimetrail Web Application */
final class SlimetrailWebApp(size: Int)
extends SlimetrailApp(size)
with WebApplication {
def view(m: GameState): Html[Msg] =
??? // Replace by actual code
}
object Main {
def onLoading(a: => Unit): Unit =
document.addEventListener("DOMContentLoaded", (_: Event) => a)
def main(args: Array[String]): Unit =
onLoading {
new SlimetrailWebApp(10)
.run(document.getElementById("scalajs-controlled-node"))
}
}
The WebApplication trait is the exact Web counterpart of the TextApplication trait. The view method of WebApplication, just like in TextApplication, takes the current state of the application (GameState for Slimetrail) as input and produce events (Action for Slimetrail) that will be sent to update in order to compute the new state of the application and so on.
The two differences with TextApplication are:
- the Web UI needs to render some HTML/SVG/CSS in a browser page via the DOM instead of lines of text in a terminal console.
- the Web UI needs to produce user
Actionfrom DOM Events instead of reading lines from the keyboard.
To produce the JavaScript you have two options:
sbt web/fastOptJSwill compile thewebproject into aJavaScriptfile atweb/target/scala-2.12/slimetrail-web-fastopt.jsfast but without much optimizations.sbt web/fullOptJSwill compile thewebproject into an optimizedJavaScriptfile atweb/target/scala-2.12/slimetrail-web-opt.js.
Run bin/genHtml.sh to produce both fast.html and full.html which run the application using the corresponding JavaScript file.
Implementing the Web UI
- Recommended time to spend on this part: 1 hour and 40 minutes
- Recommended finishing before: 12:00am
Using the ScalaJS library scaladoc and ScalaJS DOM scaladoc you can now finish the Web UI by implementing the following functions:
-
(Optional) Implements
Html.map.It applies the function
fon any value of typeA. -
Implements
Html.render.It creates a new Node corresponding to this HTML/SVG tree using the DOM.
-
Implements
WebApplication.run.It runs a Web Application by replacing the
initialNodeby the rendering of the current state of the application. -
Implements
SlimetrailWebApp.view.It renders the current state of the Slimetrail application as an
Html[Action]tree.