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.sbt
add 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, replaceproject
by: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, replaceproject
by: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, replaceslimetrail
byslimetrailJVM
.
toolbox
and slimetrail
projects now exists in two flavors each:
toolboxJVM
andslimetrailJVM
are JVM projects.toolboxJS
andslimetrailJS
are 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
Action
from DOM Events instead of reading lines from the keyboard.
To produce the JavaScript you have two options:
sbt web/fastOptJS
will compile theweb
project into aJavaScript
file atweb/target/scala-2.12/slimetrail-web-fastopt.js
fast but without much optimizations.sbt web/fullOptJS
will compile theweb
project into an optimizedJavaScript
file 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
f
on 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
initialNode
by 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.