summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2015-01-25 18:12:14 -0800
committerLi Haoyi <haoyi@dropbox.com>2015-01-25 18:12:14 -0800
commitd66fd4a85cd13006acad380dd36634277fdf4988 (patch)
tree356dd1cc16486362a51beea363e166511ea5751d
parentc58c7d8ad41c61d35f5443ed9c528acefe420bbf (diff)
downloadhands-on-scala-js-d66fd4a85cd13006acad380dd36634277fdf4988.tar.gz
hands-on-scala-js-d66fd4a85cd13006acad380dd36634277fdf4988.tar.bz2
hands-on-scala-js-d66fd4a85cd13006acad380dd36634277fdf4988.zip
WIP
-rw-r--r--book/src/main/scala/book/BookData.scala30
-rw-r--r--book/src/main/scala/book/Main.scala39
-rw-r--r--book/src/main/scalatex/book/Index.scalatex2
-rw-r--r--book/src/main/scalatex/book/handson/CanvasApp.scalatex19
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex42
-rw-r--r--book/src/main/scalatex/book/handson/GettingStarted.scalatex20
-rw-r--r--book/src/main/scalatex/book/handson/PublishingModules.scalatex111
-rw-r--r--book/src/main/scalatex/book/handson/WebPage.scalatex36
-rw-r--r--book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex34
-rw-r--r--book/src/main/scalatex/book/indepth/JavaAPIs.scalatex2
-rw-r--r--build.sbt4
-rw-r--r--project/build.sbt2
12 files changed, 128 insertions, 213 deletions
diff --git a/book/src/main/scala/book/BookData.scala b/book/src/main/scala/book/BookData.scala
index 8484f10..b21f660 100644
--- a/book/src/main/scala/book/BookData.scala
+++ b/book/src/main/scala/book/BookData.scala
@@ -3,33 +3,31 @@ package book
import java.io.File
import acyclic.file
+import ammonite.all._
import scalatags.Text.TypedTag
import scalatags.Text.all._
object BookData {
- val cloneRoot = System.getProperty("clone.root") + "/"
+ val wd = processWorkingDir
+ val cloneRoot = root/System.getProperty("clone.root").split('/')
lazy val javaAPIs = {
import java.io.File
- def recursiveListFiles(f: File): Array[File] = {
- val these = f.listFiles
- these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
- }
+
val roots = Seq(
- "scala-js/javalanglib/src/main/scala",
- "scala-js/javalib/src/main/scala"
+ "scala-js"/'javalanglib/'src/'main/'scala,
+ "scala-js"/'javalib/'src/'main/'scala
)
for{
root <- roots
- file <- recursiveListFiles(new File(cloneRoot + root))
- if file != null
- if file.isFile
+ file <- ls.rec! cloneRoot/root
+ if file.ext == "scala"
} yield{
- val path = file.getPath
- .drop(cloneRoot.length + root.length + 1)
- .dropRight(".scala".length)
+
+ val path = (file - cloneRoot).toString.stripSuffix(".scala")
val filename = path.replace('/', '.')
+
val docpath = s"https://docs.oracle.com/javase/7/docs/api/$path.html"
filename -> docpath
}
@@ -52,9 +50,9 @@ object BookData {
val hl = new scalatex.site.Highlighter {
override val pathMappings = Seq(
- s"$cloneRoot/scala-js" -> "https://github.com/scala-js/scala-js",
- s"$cloneRoot/workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app",
- "" -> "https://github.com/lihaoyi/hands-on-scala-js"
+ cloneRoot/"scala-js" -> "https://github.com/scala-js/scala-js/blob/master",
+ cloneRoot/"workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app/blob/master",
+ wd -> "https://github.com/lihaoyi/hands-on-scala-js/blob/master"
)
override val suffixMappings = Map(
"scala" -> "scala",
diff --git a/book/src/main/scala/book/Main.scala b/book/src/main/scala/book/Main.scala
index 90b0738..9696874 100644
--- a/book/src/main/scala/book/Main.scala
+++ b/book/src/main/scala/book/Main.scala
@@ -3,13 +3,16 @@ import acyclic.file
import java.io.InputStream
import java.nio.file.{Paths, Files}
+import ammonite.ops.Path
+
import scalatags.Text.{attrs, tags2, all}
import scalatags.Text.all._
import scalatex.site.Section.Tree
import scalatex.site.Site
-
+import ammonite.all.{rel => _, _}
object Main {
+ val wd = processWorkingDir
def main(args: Array[String]): Unit = {
val googleAnalytics =
"""
@@ -27,22 +30,23 @@ object Main {
val s = new Site {
def content = Map("index.html" -> Index())
- override def autoResources = super.autoResources | Set(
- "META-INF/resources/webjars/pure/0.5.0/grids-responsive-min.css",
- "css/side-menu.css",
- "example-opt.js",
- "webpage/weather.js",
- "favicon.svg",
- "favicon.png"
+ override def autoResources = super.autoResources ++ Seq(
+ wd/"META-INF"/'resources/'webjars/'pure/"0.5.0"/"grids-responsive-min.css",
+ wd/'css/"side-menu.css",
+ wd/"example-opt.js",
+ wd/'webpage/"weather.js",
+ wd/"favicon.svg",
+ wd/"favicon.png"
)
- override def manualResources = super.manualResources | Set(
- "images/javascript-the-good-parts-the-definitive-guide.jpg",
- "images/Hello World.png",
- "images/Hello World White.png",
- "images/Hello World Console.png",
- "images/IntelliJ Hello.png",
- "images/Dropdown.png",
- "images/Scalatags Downloads.png"
+
+ override def manualResources = super.manualResources ++ Seq(
+ wd/'images/"javascript-the-good-parts-the-definitive-guide.jpg",
+ wd/'images/"Hello World.png",
+ wd/'images/"Hello World White.png",
+ wd/'images/"Hello World Console.png",
+ wd/'images/"IntelliJ Hello.png",
+ wd/'images/"Dropdown.png",
+ wd/'images/"Scalatags Downloads.png"
)
override def headFrags = super.headFrags ++ Seq(
meta(charset:="utf-8"),
@@ -72,10 +76,9 @@ object Main {
),
onload:=s"Controller().main($data)"
)
-
}
- s.renderTo(System.getProperty("output.root") + "/")
+ s.renderTo(Path(System.getProperty("output.root")))
val allNames = {
diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex
index bf33d19..35c2fb5 100644
--- a/book/src/main/scalatex/book/Index.scalatex
+++ b/book/src/main/scalatex/book/Index.scalatex
@@ -9,7 +9,7 @@ is a set of detailed expositions on various parts of the Scala.js platform. Noth
@sect("Hands-on Scala.js", "Writing client-side web applications in Scala")
@split
@more
- @hl.ref("examples/demos/src/main/scala/Splash.scala", "var x")
+ @hl.ref(wd/'examples/'demos/'src/'main/'scala/"Splash.scala", "var x")
@less
@BookData.example(canvas, "Splash().main")
diff --git a/book/src/main/scalatex/book/handson/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
index 230ae67..b49f1e5 100644
--- a/book/src/main/scalatex/book/handson/CanvasApp.scalatex
+++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
@@ -1,4 +1,5 @@
@import BookData._
+@val canvasapp = wd/'examples/'demos/'src/'main/'scala/'canvasapp
@p
By this point, you've already cloned and got your hands dirty fiddling around with the toy @lnk("workbench-example-app", "https://github.com/lihaoyi/workbench-example-app"). You have your editor set up, SBT installed, and have published the example application in a way you can host online for other people to see. Maybe you've even made some changes to the application to see what happens. Hopefully you're curious, and want to learn more.
@@ -21,7 +22,7 @@
@p
To begin with, lets remove all the existing stuff in our @code{.scala} file and leave only the @hl.scala{object} and the @hl.scala{main} method. Let's start off with some necessary boilerplate:
- @hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*setup*/", end = "/*code*/")
+ @hl.ref(canvasapp/"ScratchPad.scala", "/*setup*/", end = "/*code*/")
@p
As described earlier, this code uses the @lnk.dom.getElementById} function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page.
@@ -31,7 +32,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*code*/")
+ @hl.ref(canvasapp/"ScratchPad.scala", "/*code*/")
@less
@BookData.example(canvas, "ScratchPad().main")
@@ -55,7 +56,7 @@
@p
Again, we need roughly the same boilerplate as just now to set up the canvas:
- @hl.ref("examples/demos/src/main/scala/canvasapp/Clock.scala", "/*setup*/", "/*code*/")
+ @hl.ref(canvasapp/"Clock.scala", "/*setup*/", "/*code*/")
@p
The only thing unusual here is that I'm going to create a @hl.scala{linearGradient} in order to make the stopwatch look pretty. This is by no means necessary, and you could simply make the @hl.scala{fillStyle} @hl.scala{"black"} if you want to keep things simple.
@@ -65,7 +66,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/canvasapp/Clock.scala", "/*code*/")
+ @hl.ref(canvasapp/"Clock.scala", "/*code*/")
@less
@BookData.example(canvas, "Clock().main")
@@ -97,7 +98,7 @@
It's a relatively simple game, but there should be enough "business logic" in here that we won't be simply gluing together APIs. Let's start!
@sect{Setting Up the Canvas}
- @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "/*setup*/", end="/*variables*/")
+ @hl.ref(canvasapp/"FlappyLine.scala", "/*setup*/", end="/*variables*/")
@p
This section of the code is peripherally necessary, but not core to the implementation or logic of Flappy Box. We see the same @hl.scala{canvas}/@hl.scala{renderer} logic we've seen in all our examples, along with some logic to make the canvas a reasonable size, and some configuration of how we will render text to the canvas.
@@ -106,7 +107,7 @@
In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in helper functions, so the verbosity and non-idiomatic-scala-ness doesn't bother you much.
@sect{Defining our State}
- @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "/*variables*/", end="def runLive")
+ @hl.ref(canvasapp/"FlappyLine.scala", "/*variables*/", end="def runLive")
@p
This is where we start defining things that are relevant to Flappy Box. There are roughly two groups of values here: immutable constants in the top group, and mutable variables in the bottom. The rough meaning of each variable is documented in the comments, and we'll see exactly how we use them later.
@@ -115,7 +116,7 @@
One notable thing is that we're using a @lnk("collection.mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html") to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js.
@sect{Game Logic}
- @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def runLive", "def runDead")
+ @hl.ref(canvasapp/"FlappyLine.scala", "def runLive", "def runDead")
@p
The @hl.scala{runLive} function is the meat of Flappy Box. In it, we
@@ -135,13 +136,13 @@
@p
This function basically contains all the game logic, from motion, to collision-detection, to rendering, so it's pretty large. Not that large though! And entirely understandable, even if it takes a few moments to read through.
- @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def runDead", "def run()")
+ @hl.ref(canvasapp/"FlappyLine.scala", "def runDead", "def run()")
@p
This is the function that handles what happens when you're dead. Essentially, we reset all the mutable variables to their initial state, and just count down the @hl.scala{dead} counter until it reaches zero and we're considered alive again.
@sect{A Working Product}
- @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def run()")
+ @hl.ref(canvasapp/"FlappyLine.scala", "def run()")
@p
And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @lnk.dom.onclick handler to make the player jump by tweaking his velocity, and we call @lnk.dom.setInterval to run the @hl.scala{run} function every 20 milliseconds.
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
index 3ddb88b..8cbf9cf 100644
--- a/book/src/main/scalatex/book/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -21,6 +21,9 @@
@p
Scala.js lets you share code between client and server relatively straightforwardly. As we saw in the previous chapter, where we made a shared module. Let's work to turn that shared module into a working client-server application!
+@val server = wd/'examples/'crossBuilds/'clientserver/'server/'src/'main/'scala/'simple
+@val client = wd/'examples/'crossBuilds/'clientserver/'client/'src/'main/'scala/'simple
+
@sect{A Client-Server Setup}
@p
Getting started with client-server integration, let's go with the simplest configuration possible: a Spray server and a Scala.js client. Most of the other web-frameworks (@lnk.misc.Play, @lnk.misc.Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. Our project will look like this:
@@ -43,7 +46,7 @@
@p
First, let's do the wiring in @code{build.sbt}:
- @hl.ref("examples/crossBuilds/clientserver/build.sbt")
+ @hl.ref(wd/'examples/'crossBuilds/'clientserver/"build.sbt")
@p
We have two projects: @code{client} and @code{server}, one of which is a Scala.js project (indicated by the presence of @hl.scala{scalaJSSettings}). Both projects share a number of settings: the presence of the @code{shared/} folder, which shared code can live in (similar to what we saw in @sect.ref{Cross Publishing Libraries}) and the settings to add @lnk.github.Scalatags and @lnk.github.uPickle to the build. Note that those two dependencies use the triple @code{%%%} instead of the double @code{%%} to declare: this means that for each dependency, we will pull in the Scala-JVM or Scala.js version depending on whether it's being used in a Scala.js project. Note also the @hl.scala{packageArchetype.java_application} setting, which isn't strictly necessary depending on what you want to do with the application, but this example needs it as part of the deployment to Heroku.
@@ -54,7 +57,8 @@
@p
Next, let's kick off the Spray server in our Scala-JVM main method:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala")
+
+ @hl.ref(server/"Server.scala")
@p
This is a not-very-interesting @lnk("spray-routing", "http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve the main page on GET, and have other GET URLs serve resources. This includes the @code{js-fastopt.js} file that is now in our resources because of our @code{build.sbt} config earlier! We also add a POST route to allow the client ask the server to list files various directories.
@@ -62,14 +66,14 @@
@p
The HTML template @hl.scala{Page.skeleton} is not shown above; I put it in a separate file for neatness:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala")
+ @hl.ref(server/"Page.scala")
@p
This is a typical @lnk.github.Scalatags HTML snippet. Note that since we're serving it directly from the server in Scala code, we do not need to leave a @code{.html} file somewhere on the filesystem! We can declare all HTML, including the skeleton of the page, in Scalatags. Otherwise it's the same as what we saw in earlier chapters: A simple HTML page which includes a script tag to run our Scala.js application.
@p
Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{<script>} tag above to kick off the client-side application.
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala")
+ @hl.ref(client/"Client.scala")
@p
Again this is a simple Scala.js application, not unlike what we saw in earlier chapters. However, there is one difference: earlier, we made our Ajax calls to @code{api.openweathermap.org/...}. Here, we're making it to @code{/ajax}: the same server the page is served from!
@@ -77,7 +81,7 @@
@p
You may have noticed in both client and server, we have made reference to a mysterious @hl.scala{FileData} type which holds the name and size of each file. @hl.scala{FileData} is defined in the @code{shared/} folder, so it can be accessed from both Scala-JVM and Scala.js:
- @hl.ref("examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala")
+ @hl.ref(client/"FileData.scala")
@p
Now, if we go to the browser at @code{localhost:8080}, we should see our web-page!
@@ -120,10 +124,10 @@
@p
However, there is still some amount of duplication in the code. In particular, the definition of the endpoint name "list" is duplicated 4 times:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", """path("ajax" / "list")""", "")
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", "list(", "")
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", "def list", "")
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
+ @hl.ref(server/"Server.scala", """path("ajax" / "list")""", "")
+ @hl.ref(server/"Server.scala", "list(", "")
+ @hl.ref(server/"Server.scala", "def list", "")
+ @hl.ref(client/"Client.scala", "ajax/list", "")
@p
Three times on the server and once on the client! What's worse, two of the appearances of @hl.scala{"list"} are in string literals, which are not checked by the compiler to match up with themselves or the name of the method @hl.scala{list}. Apart from this, there is one other piece of duplication that is unchecked: the type being returned from @hl.scala{list} (@hl.scala{Seq[FileData]}) is being repeated on the client in @hl.scala{upickle.read[Seq[FileData]]} in order to de-serialize the serialized data. This leaves three opportunities for error wide-open:
@@ -146,12 +150,14 @@
@p
@lnk("Autowire", "https://github.com/lihaoyi/autowire") is a library that turns your request routing layer from a fragile, hand-crafted mess into a solid, type-checked, boilerplate-free experience. Autowire basically turns what was previously a stringly-typed, hand-crafted Ajax call and route:
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
+ @hl.ref(client/"Client.scala", "ajax/list", "")
@p
Into a safe, type-checked function call:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", ".call()", "")
+ @val client2 = wd/'examples/'crossBuilds/'clientserver2/'client/'src/'main/'scala/'simple
+ @val server2 = wd/'examples/'crossBuilds/'clientserver2/'server/'src/'main/'scala/'simple
+ @hl.ref(client2/"Client.scala", ".call()", "")
@p
Let's see how we can do that.
@@ -172,7 +178,7 @@
@p
Let's start with our client-server interface definition
- @hl.ref("examples/crossBuilds/clientserver2/client/shared/main/scala/simple/Shared.scala")
+ @hl.ref(client2/"Shared.scala")
@p
Here, you can see that in addition to sharing the @hl.scala{FileData} class, we are also creating an @hl.scala{Api} trait which contains the signature of our @hl.scala{list} method. The exact name of the trait doesn't matter. We need it to be in @code{shared/} so that the code in both client and server can reference it.
@@ -180,16 +186,16 @@
@p
Next, let's look at modifying our server code to make use of Autowire:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala")
+ @hl.ref(server2/"Server.scala")
@p
Now, instead of hard-coding the route @hl.scala{"ajax" / "list"}, we now take in any route matching @hl.scala{"ajax" / Segments}, feeding the resultant path segments into the @hl.scala{Router} object:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala", "path(")
+ @hl.ref(server2/"Server.scala", "path(")
@p
The @hl.scala{Router} object in turn simply defines how you intend the objects to be serialized and deserialized:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala", "object Router", "object Server")
+ @hl.ref(server2/"Server.scala", "object Router", "object Server")
@p
In this case using uPickle. Note how the @hl.scala{route} call explicitly states the type (here @hl.scala{Api}) that it is to generate routes against; this ensures that only methods which you explicitly put in your public interface @hl.scala{Api} are publically reachable.
@@ -197,12 +203,12 @@
@p
Next, let's look at the modified client code:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala")
+ @hl.ref(client2/"Client.scala")
@p
There are two main modifications here: the existence of the new @hl.scala{Ajaxer} object, and the modification to the Ajax call-site. Let's first look at @hl.scala{Ajaxer}:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", "object Ajaxer", "@JSExport")
+ @hl.ref(client2/"Client.scala", "object Ajaxer", "@JSExport")
@p
Like the @hl.scala{Router} object, @hl.scala{Ajaxer} also defines how you perform the serialization and deserialization of data-structures, again using uPickle. Unlike the @hl.scala{Router} object, @hl.scala{Ajaxer} also defines how the out-going Ajax call gets sent over the network. Here we're doing it using the @hl.scala{Ajax.post} method.
@@ -210,7 +216,7 @@
@p
Lastly, let's look at the modified callsite for the ajax call itself:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", "def update", "")
+ @hl.ref(client2/"Client.scala", "def update", "")
@p
There are a few things of note here:
diff --git a/book/src/main/scalatex/book/handson/GettingStarted.scalatex b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
index d2486c2..a77245d 100644
--- a/book/src/main/scalatex/book/handson/GettingStarted.scalatex
+++ b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
@@ -98,18 +98,18 @@
@p
We've downloaded, compiled, ran, and made changes to our first Scala.js application. Let's now take a closer look at the code that we just ran:
-
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala")
+ @val example = cloneRoot/"workbench-example-app"/'src/'main/'scala/'example
+ @hl.ref(example/"ScalaJSExample.scala")
@p
It's a good chunk of code, though not a huge amount. To someone who didn't know about Scala.js, they would just think it's normal Scala, albeit with this unusual @hl.scala{dom} library and a few weird annotations. Let's pick it apart starting from the top:
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "case class Point", "@JSExport")
+ @hl.ref(example/"ScalaJSExample.scala", "case class Point", "@JSExport")
@p
Here we are defining a @hl.scala{Point} case class which represents a X/Y position, with some basic operators defined on it. This is done mostly for convenience later on, when we want to manipulate these two-dimensional points. Scala.js is Scala, and supports the entirety of the Scala language. @hl.scala{Point} here behaves identically as it would if you had run Scala on the JVM.
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "@JSExport", "val ctx")
+ @hl.ref(example/"ScalaJSExample.scala", "@JSExport", "val ctx")
@p
This @hl.scala("@JSExport") annotation is used to tell Scala.js that you want this method to be visible and callable from Javascript. By default, Scala.js does @sect.ref("Fast Optimization", "dead code elimination") and removes any methods or classes which are not used. This is done to keep the compiled executables a reasonable size, since most projects use only a small fraction of e.g. the standard library. @hl.scala("@JSExport") is used to tell Scala.js that the @hl.scala{ScalaJSExample} object and its @hl.scala{def main} method are entry points to the program. Even if they aren't called anywhere internally, they are called externally by Javascript that the Scala.js compiler is not aware of, and should not be removed. In this case, we are going to call this method from Javascript to start the Scala.js program.
@@ -117,7 +117,7 @@
@p
Apart from this annotation, @hl.scala{ScalaJSExample} is just a normal Scala @hl.scala{object}, and behaves like one in every way. Note that the main-method in this case takes a @lnk.dom.HTMLCanvasElement: your exported methods can have any signature, with arbitrary arity or types for parameters or the return value. This is in contrast to the main method on the JVM which always takes an @hl.scala{Array[String]} and returns @hl.scala{Unit}. In fact, there's nothing special about this method at all! It's like any other exported method, we just happen to attribute it the "main" entry point. It is entirely possible to define multiple exported classes and methods, and build a "library" using Scala.js of methods that are intended for external Javascript to use.
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "val ctx", "var count")
+ @hl.ref(example/"ScalaJSExample.scala", "val ctx", "var count")
@p
Here we are retrieving a handle to the canvas we will draw on using @hl.scala{document.getElementById}, and from it we can get a @lnk.dom.CanvasRenderingContext2D which we actually use to draw on it.
@@ -125,7 +125,7 @@
@p
We need to perform the @hl.scala{asInstanceOf} call because depending on what you pass to @hl.scala{getElementById} and @hl.scala{getContext}, you could be returned elements and contexts of different types. Hence we need to tell the compiler explicitly that we're expecting a @lnk.dom.HTMLCanvasElement and @lnk.dom.CanvasRenderingContext2D back from these methods for the strings we passed in.
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "def run", "dom.setInterval")
+ @hl.ref(example/"ScalaJSExample.scala", "def run", "dom.setInterval")
@p
This is the part of the Scala.js program which does the real work. It runs 10 iterations of a @lnk("small algorithm", "http://en.wikipedia.org/wiki/Sierpinski_triangle#Chaos_game") that generates a Sierpinski Triangle point-by-point. The steps, as described by the linked article, are roughly:
@@ -143,7 +143,7 @@
@p
In this example, the triangle is hard-coded to be 255 pixels high by 255 pixels wide, and some math is done to pick a color for each dot which will give the triangle a pretty gradient.
- @hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "dom.setInterval")
+ @hl.ref(example/"ScalaJSExample.scala", "dom.setInterval")
@p
Now this is the call that actually does the useful work. All this method does is call @hl.scala{dom.setInterval}, which tells the browser to run the @hl.scala{run} method every 50 milliseconds. As mentioned earlier, the @hl.scala{dom.*} methods are simply facades to their native Javascript equivalents, and @hl.scala{dom.setInterval} is @lnk("no different", "https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers.setInterval"). Note how you can pass a Scala lambda to @hl.scala{setInterval} to have it called by the browser, where in Javascript you'd need to pass a Javascript @hl.javascript{function(){...}}
@@ -154,7 +154,7 @@
We've already taken a look at the application code for a simple, self-contained Scala.js application, but this application is not @i{entirely} self contained. It's wrapped in a small SBT project that sets up the necessary dependencies and infrastructure for this application to work.
@sect{project/build.sbt}
- @hl.ref(cloneRoot + "workbench-example-app/project/build.sbt")
+ @hl.ref(cloneRoot/"workbench-example-app"/'project/"build.sbt")
@p
This is the list of SBT plugins used by this small example application. There are two of them: the Scala.js plugin (which contains the Scala.js compiler and other things, e.g. tasks such as @code{fastOptJS}) and the @lnk("Workbench", "https://github.com/lihaoyi/workbench") plugin, which is used to provide the auto-reload-on-change behavior and the forwarding of SBT logspam to the browser console.
@@ -164,7 +164,7 @@
@sect{build.sbt}
- @hl.ref(cloneRoot + "workbench-example-app/build.sbt")
+ @hl.ref(cloneRoot/"workbench-example-app"/"build.sbt")
@p
The @code{build.sbt} project file for this application is similarly unremarkable: It includes the settings for the two SBT plugins we saw earlier, as well as boilerplate @hl.scala{name}/@hl.scala{version}/@hl.scala{scalaVersion} values common to all projects.
@@ -176,7 +176,7 @@
Lastly, we have two Workbench related settings: @hl.scala{bootSnippet} basically tells Workbench how to restart your application when a new compilation run finishes, and @hl.scala{updateBrowsers} actually tells it to perform this application-restarting.
@sect{src/main/resources/index-dev.html}
- @hl.ref(cloneRoot + "workbench-example-app/src/main/resources/index-dev.html")
+ @hl.ref(cloneRoot/"workbench-example-app"/'src/'main/'resources/"index-dev.html")
@p
This is the HTML page which our toy app lives in, and the same page that we have so far been using to view the app in the browser. To anyone who has used HTML, most of it is probably familiar. Things of note are the @hl.html{<script>} tags: @hl.scala{"../example-fastopt.js"} Is the executable blob spat out by the compiler, which we need to include in the HTML page for anything to happen. This is where the results of your compiled Scala code appear. @hl.scala{"workbench.js"} is the client for the Workbench plugin that connects to SBT, reloads the browser and forwards logspam to the browser console.
diff --git a/book/src/main/scalatex/book/handson/PublishingModules.scalatex b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
index b5a0786..d2fb269 100644
--- a/book/src/main/scalatex/book/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
@@ -32,22 +32,22 @@
@p
From the bash shell in the project root. Let's take a look at the various files that make up this project. First, the @code{build.sbt} files:
- @hl.ref("examples/crossBuilds/simple/project/build.sbt")
+ @hl.ref(wd/'examples/'crossBuilds/'simple/'project/"build.sbt")
@p
The @code{project/build.sbt} file is uneventful: it simply includes the Scala.js SBT plugin. However, the @code{build.sbt} file is a bit more interesting:
- @hl.ref("examples/crossBuilds/simple/build.sbt")
+ @hl.ref(wd/'examples/'crossBuilds/'simple/"build.sbt")
@p
Unlike the equivalent @code{build.sbt} files you saw in earlier chapters, this does not simply add @hl.scala{scalaJSSettings} to the root project. Rather, it sets up two projects: one in the @code{js/} folder and one in the @code{jvm/} folder, with the @code{js/} version getting the settings from the Scala.js plugin. To both of these, we add @code{shared/main/scala} to the list of source directories. This means that both projects will pick up the sources we symlinked between @code{js/shared/} and @code{jvm/shared/}.
@sect{Source Files}
-
+ @val simple = wd/'examples/'crossBuilds/'simple
@p
Now, let's look at the contents of the @code{.scala} files that make up the meat of this project:
- @hl.ref("examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala")
+ @hl.ref(simple/'js/'shared/'main/'scala/'simple/"Simple.scala")
@p
In @code{Simple.scala} we have the shared, cross-platform API of our library: a single @hl.scala{object} with a single method @hl.scala{def} which does what we want, which can then be used in either Scala.js or Scala-JVM. In general, you can put as much shared logic here as you want: classes, objects, methods, anything that can run on both Javascript and on the JVM. We're chopping off the last 5 characters (the milliseconds) to keep the formatted dates slightly less verbose.
@@ -57,10 +57,10 @@
@split
@half
- @hl.ref("examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala")
+ @hl.ref(simple/'js/'src/'main/'scala/'simple/"Platform.scala")
@half
- @hl.ref("examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala")
+ @hl.ref(simple/'jvm/'src/'main/'scala/'simple/"Platform.scala")
@p
In the @code{js/} version, we are using the Javascript @hl.javascript{Date} object to take the millis and do what we want. In the @code{jvm/} version, we instead use @hl.scala{java.text.SimpleDateFormat} with a custom formatter (The syntax is defined @lnk("here", "http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html")).
@@ -96,102 +96,3 @@
@p
You've by this point set up a basic cross-building Scala.js/Scala-JVM project!
-
- @hr
-
- @p
- If you wish, you can do more things with this project you've set up:
-
- @ul
- @li
- Flesh it out! Currently this module only does a single, trivial thing. If you've done any web development before, I'm sure you can find some code snippet, function or algorithm that you'd like to share between client and server. Try implementing it in the @code{shared/} folder to be usable in both Scala.js and Scala-JVM
- @li
- Publish it! Both @code{sbt publishLocal} and @code{sbt publishSigned} work on this module, for publishing either locally, Maven Central via Sonatype, or Bintray. Running the command bare should be sufficient to publish both the @code{js} or @code{jvm} projects, or you can also specify which one e.g. @code{jvm/publishLocal} to publish only one subproject.
-
- @p
- This @code{jvm} project works identically to any other Scala-JVM project, and the @code{js} project works identically to the Command Line API described earlier. Thus you can do things like @code{fastOptStage::run} to run the code on Node.js, setting @hl.scala{jsDependencies += RuntimeDOM}, run @code{fullOptStage::run} to run the code with full, aggressive optimizations. And of course, things that work in both Scala.js and Scala-JVM can be run on both, basic commands such as @code{run} or @code{test}.
-
- @p
- You can also run tests using this code, if you have a testing library set up. The next section will go into detail as to how to set that up.
-
-@sect{Cross-Testing with uTest}
- @p
- @lnk("uTest", "https://github.com/lihaoyi.utest") is a small unit-testing library for Scala programs, that works on both Scala-JVM and Scala.js. At the time it was written, it was the first one out there, though now there are others such as @lnk("little-spec", "https://github.com/eecolor/little-spec") or @lnk("otest", "https://github.com/cgta/otest"). Notably, Scala's traditional testing libraries such as @lnk("Scalatest", "http://www.scalatest.org/") or @lnk("Specs2", "http://etorreborre.github.io/specs2/") do not work with Scala.js, as they make use of Reflection or other things not supported on Scala.js
-
- @sect{uTest Configuration}
- @p
- To make your code use uTest, there are a few changes you need to make. First, you need to add the uTest SBT plugin:
-
- @hl.ref("examples/crossBuilds/simple2/project/build.sbt")
-
- @p
- Here, in @code{project/build.sbt}, we see it used next to the Scala.js SBT plugin. Next, we need to modify our @code{build.sbt} file
-
- @hl.ref("examples/crossBuilds/simple2/build.sbt")
-
- @p
- The main thing we've done is make use of uTest's @hl.scala{JsCrossBuild}: this does the work we've previously done to setup the shared-source-directory in SBT, as well as doing the neccessary configuration for uTest itself, providing you with ready-made @hl.scala{js} and @hl.scala{jvm} projects you can work with.
-
- @sect{Your First Tests!}
- @p
- Lastly, we need to start writing tests! @lnk("uTest", "https://github.com/lihaoyi.utest") is well documented, but to get started here's a simple test suite for our @hl.scala{formatDates} function:
-
- @hl.ref("examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala")
-
- @p
- Since this is in @code{shared/}, it is automatically symlinked and is picked up by both @code{js} and @code{jvm} subprojects. With that done, you just need to run the @code{test} commands:
-
- @hl.bash
- > ; js/test; jvm/test
- [info] 1/4 simple.SimpleTest.format.nil Success
- [info] 2/4 simple.SimpleTest.format.timeZero Success
- [info] 3/4 simple.SimpleTest.format Success
- [info] 4/4 simple.SimpleTest Success
- [info] -----------------------------------Results-----------------------------------
- [info] simple.SimpleTest Success
- [info] format Success
- [info] nil Success
- [info] timeZero Success
- [info] Failures:
- [info]
- [info] Tests: 4
- [info] Passed: 4
- [info] Failed: 0
- [success] Total time: 4 s, completed Nov 8, 2014 7:42:39 PM
- [info] 1/4 simple.SimpleTest.format.nil Success
- [info] 2/4 simple.SimpleTest.format.timeZero Success
- [info] 3/4 simple.SimpleTest.format Success
- [info] 4/4 simple.SimpleTest Success
- [info] -----------------------------------Results-----------------------------------
- [info] simple.SimpleTest Success
- [info] format Success
- [info] nil Success
- [info] timeZero Success
- [info] Failures:
- [info]
- [info] Tests: 4
- [info] Passed: 4
- [info] Failed: 0
- [success] Total time: 0 s, completed Nov 8, 2014 7:42:39 PM
-
- @p
- And you'll see that we've run our unit tests twice: once on Scala.js in Rhino, and once on Scala-JVM! As expected, the first run in Rhino took much longer (4 seconds!) than the second, as Rhino is much slower than running code directly on the JVM. You can configure the Scala.js run to run in Node.js or PhantomJS, as well as running under different optimization levels.
-
- @hr
-
- @p
- Now that you've got a basic cross-platform Scala module building and testing, what next? One thing you may want to do is add things to the project. Depending on where you want your code to run, there's a place for everything:
-
- @ul
- @li
- @code{js/shared/main/scala}/@code{jvm/shared/main/scala} is where your shared library code goes. This code will be run on both Scala.js and Scala-JVM
- @li
- @code{jvm/src/main/scala} Scala-JVM only code
- @li
- @code{js/src/main/scala} Scala.js only code
-
- @p
- It is entirely possible for your modules to have slightly different implementations and APIs on Scala.js and Scala-JVM. @lnk.github.Scalatags exposes additional DOM-related functionality only for it's Scala.js version, while @lnk.github.uPickle uses different JSON libraries (@lnk("Jawn", "https://github.com/non/jawn") v.s. @lnk("DOM", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse")) on the backend while the exposed interface remains the same. You have the flexibility to pick and choose which bits of your library you wish to share and which bits will be different.
-
- @p
- Everything above also applies to your unit tests, which fall in @code{test/} folders mirroring the @code{main/} folders listed above. You can also choose to share or not-share your unit test code as you see fit. \ No newline at end of file
diff --git a/book/src/main/scalatex/book/handson/WebPage.scalatex b/book/src/main/scalatex/book/handson/WebPage.scalatex
index 118dd0c..df60176 100644
--- a/book/src/main/scalatex/book/handson/WebPage.scalatex
+++ b/book/src/main/scalatex/book/handson/WebPage.scalatex
@@ -6,6 +6,8 @@
@p
At this point, you are already competent at using Scala.js to make basic, self-contained canvas applications. In this chapter, we will cover how to use Scala.js to build the sort of interactive-web-pages that make up the bulk of the modern-day internet. We'll cover how to use powerful libraries that turn front-end development form the typical fragile-mess into a structured, robust piece of software.
+@val webpage = wd/'examples/'demos/'src/'main/'scala/'webpage
+
@sect{Hello World: HTML}
@p
@@ -13,7 +15,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/HelloWorld0.scala")
+ @hl.ref(webpage/"HelloWorld0.scala")
@less
@BookData.example(div, "HelloWorld0().main")
@@ -37,14 +39,14 @@
@p
@lnk("Scalatags", "https://github.com/lihaoyi/scalatags") is a cross-platform Scala.js/Scala-JVM library that is designed to generate HTML. To use Scalatags, you need to add it as a dependency to your Scala.js SBT project, in the @code{build.sbt} file:
- @hl.ref("examples/demos/build.sbt", "com.scalatags", "")
+ @hl.ref(wd/'examples/'demos/"build.sbt", "com.scalatags", "")
@p
With that, the above snippet of code re-written using Scalatags looks as follows:
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/HelloWorld1.scala")
+ @hl.ref(webpage/"HelloWorld1.scala")
@less
@BookData.example(div, "HelloWorld1().main")
@@ -58,7 +60,7 @@
@sect{User Input}
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Inputs.scala", "val box")
+ @hl.ref(webpage/"Inputs.scala", "val box")
@less
@BookData.example(div(height:="150px"), "Inputs().main")
@@ -76,12 +78,12 @@
@p
To begin with, let's define our list of items: Fruits!
- @hl.ref("examples/demos/src/main/scala/webpage/Search0.scala", "val listings", "def")
+ @hl.ref(webpage/"Search0.scala", "val listings", "def")
@p
Next, let's think about how we want to render these fruits. One natural way would be as a list, which in HTML is represented by a @hl.html{<ul>} with @hl.html{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hl.scala{def}, because we know up-front we're going to need to re-render this listing as the search query changes. Lastly, we know we want 1 list item for each fruit, but only if the fruit starts with the search query.
- @hl.ref("examples/demos/src/main/scala/webpage/Search0.scala", "def renderListings", "lazy val")
+ @hl.ref(webpage/"Search0.scala", "def renderListings", "lazy val")
@p
Using a @hl.scala{for}-loop with a filter inside the Scalatags fragment is just normal Scala, since you can nest arbitrary Scala expressions inside a Scalatags snippet. In this case, we're converting both the fruit and the search query to lower case so we can compare them case-insensitively.
@@ -91,7 +93,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Search0.scala", "val output")
+ @hl.ref(webpage/"Search0.scala", "val output")
@less
@BookData.example(div, "Search0().main")
@@ -101,7 +103,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Search1.scala", "def renderListings", "lazy val")
+ @hl.ref(webpage/"Search1.scala", "def renderListings", "lazy val")
@less
@BookData.example(div, "Search1().main")
@@ -133,7 +135,7 @@
@sect{Raw Javascript}
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Weather0.scala", "val xhr")
+ @hl.ref(webpage/"Weather0.scala", "val xhr")
@less
@BookData.example(div(height:="400px"), "Weather0().main")
@@ -145,7 +147,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/resources/webpage/weather.js", "var xhr")
+ @hl.ref(webpage/"weather.js", "var xhr")
@less
@BookData.example(div, "WeatherJs")
@@ -171,7 +173,7 @@
@p
With Scala.js, we provide a simpler API that is more clearly functional. First, you need to import some things into scope:
- @hl.ref("examples/demos/src/main/scala/webpage/Weather1.scala", "import dom", "val url =")
+ @hl.ref(webpage/"Weather1.scala", "import dom", "val url =")
@p
The first import brings in Scala adapters to several DOM APIs, which allow you to use them more idiomatically from Scala. The second brings in an implicit @hl.scala{scala.concurrent.ExecutionContext} that we'll need to run our asynchronous operations.
@@ -181,7 +183,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Weather1.scala", "val url")
+ @hl.ref(webpage/"Weather1.scala", "val url")
@less
@BookData.example(div(height:="400px", overflow:="scroll"), "Weather1().main")
@@ -198,7 +200,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Weather2.scala", "Ajax.get")
+ @hl.ref(webpage/"Weather2.scala", "Ajax.get")
@less
@BookData.example(div(height:="400px"), "Weather2().main")
@@ -211,7 +213,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/webpage/Weather3.scala", "Ajax.get")
+ @hl.ref(webpage/"Weather3.scala", "Ajax.get")
@less
@BookData.example(div(height:="400px", overflow:="scroll"), "Weather3().main")
@@ -226,12 +228,12 @@
@p
At this point we've made a small app that allows us to search from a pre-populated list of words, as well as a small app that lets us query a remote web-service to find the weather in Singapore. The natural thing to do is to put these things together to make a app that will let us search from a list of countries and query the weather in any country we desire. Let's start!
- @hl.ref("examples/demos/src/main/scala/webpage/WeatherSearch.scala", "lazy val box", "def fetchWeather")
+ @hl.ref(webpage/"WeatherSearch.scala", "lazy val box", "def fetchWeather")
@p
This sets up the basics: an input box, an output div, and sets an @hl.scala{onkeyup} that fetches the weather data each time you hit a key. It then renders all these components and sticks them into the @hl.scala{target} div. This is basically the same stuff we saw in the early examples, with minor tweaks e.g. adding a @hl.scala{maxHeight} and @hl.scala{overflowY:="scroll"} to the @hl.scala{output} box in case the output is too large. Whenever we enter something in the box, we call the function @hl.scala{fetchWeather}, which is defined as:
- @hl.ref("examples/demos/src/main/scala/webpage/WeatherSearch.scala", "def fetchWeather", "def showResults")
+ @hl.ref(webpage/"WeatherSearch.scala", "def fetchWeather", "def showResults")
@p
This is where the actual data fetching happens. It's relatively straightforward: we make an @hl.scala{Ajax.get} request, @hl.scala{JSON.parse} the response, and feed it into the callback function. We're using a slightly different API from earlier: we now have the @hl.scala{"type=like"} flag, which is documented in the @lnk("OpenWeatherMap API docs", "http://openweathermap.org/current#other") to return multiple results for each city whose name matches your query.
@@ -239,7 +241,7 @@
@p
Notably, before we re-render the results, we check whether the @hl.scala{query} that was passed in is the same value that's in the @hl.scala{box}. This is to prevent a particularly slow ajax call from finishing out-of-order, potentially stomping over the results of more recent searches. We also check whether the @hl.scala{.list: js.Dynamic} property we want is an instance of @hl.scala{js.Array}: if it isn't, it means we don't have any results to show, and we can skip the whole render-output step.
- @hl.ref("examples/demos/src/main/scala/webpage/WeatherSearch.scala", "def showResults")
+ @hl.ref(webpage/"WeatherSearch.scala", "def showResults")
@p
Here is the meat and potatoes of this program: every time it gets called with an array of weather-data, we iterate over the cities in that array. It then does a similar sort of data-extraction that we did earlier, putting the results into the @hl.scala{output} div we defined above, including highlighting.
diff --git a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
index 98dccc2..098da08 100644
--- a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
@@ -16,6 +16,8 @@
@p
One note is that these are "Techniques" rather than "Libraries" because they have not been packaged up in a way that is sufficiently nice that you can use them out-of-the-box just by adding a dependency somewhere. Thus, they each require some small amount of boilerplate before use, though the amount of boilerplate is fixed: it does not grow with the size of your program, and anyway gives you a chance to tweak it to do exactly what you want.
+@val advanced = wd/'examples/'demos/'src/'main/'scala/'advanced
+
@sect{Functional-Reactive UIs}
@p
@lnk("Functional-reactive Programming", "http://en.wikipedia.org/wiki/Functional_reactive_programming") (FRP) is a field with encompasses several things:
@@ -43,7 +45,7 @@
@p
@lnk("Scala.Rx", "https://github.com/lihaoyi/scala.rx") is a change-propagation library that implements the @b{Continuous} style of FRP. To begin with, we need to include it in our @code{build.sbt} dependencies:
- @hl.ref("examples/demos/build.sbt", "com.scalarx")
+ @hl.ref(wd/'examples/'demos/"build.sbt", "com.scalarx")
@p
Scala.Rx provides you with smart variables that automatically track dependencies with each other, such that if one smart variable changes, the rest re-compute immediately and automatically. The main primitives in Scala.Rx are:
@@ -62,7 +64,7 @@
@p
To begin with, let's set up our imports:
- @hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "package advanced", "@JSExport")
+ @hl.ref(advanced/"BasicRx.scala", "package advanced", "@JSExport")
@p
Here we are seeing the same @hl.scala{dom} and @hl.scala{scalatags}, imports we saw in the hands-on tutorial, as well a new @hl.scala{import rx._} which bring all the Scala.Rx names into the local namespace.
@@ -70,7 +72,7 @@
@p
Scala.Rx does not "natively" bind to Scalatags, but integrating them yourself is simple enough that it's not worth putting into a separate library. He's a simple integration:
- @hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "implicit def")
+ @hl.ref(advanced/"BasicRx.scala", "implicit def")
@p
Scalatags requires that anything you want to embed in a Scalatags fragment be implicitly convertible to @hl.scala{Frag}; here we are providing one for any Scala.Rx @hl.scala{Rx[T]}s, as long as the @hl.scala{T} provided is itself convertible to a @hl.scala{Frag}. We call @hl.scala{r().render} to extract the "current" value of the @hl.scala{Rx}, and then set up an @hl.scala{Obs} that watches the @hl.scala{Rx}, replacing the previous value with the current one every time its value changes.
@@ -80,7 +82,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "val txt =")
+ @hl.ref(advanced/"BasicRx.scala", "val txt =")
@less
@example(div, "BasicRx().main")
@@ -104,7 +106,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "val fruits =")
+ @hl.ref(advanced/"BasicRx.scala", "val fruits =")
@less
@example(div, "BasicRx().main2")
@@ -154,22 +156,22 @@
@p
To begin with, let's write the scaffolding code, that will display the input box, deal with the listeners, and all that:
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "val myInput")
+ @hl.ref(advanced/"Futures.scala", "val myInput")
@p
So far so good. The only thing that's missing here is the mysterious @hl.scala{handle} function, which is given the list of names and the @hl.scala{output} div, and must handle the Ajax requests, aggregating the results, and displaying them in @hl.scala{output}. Let's also define a small number of helper functions that we'll use later:
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def urlFor", "def parseTemp")
+ @hl.ref(advanced/"Futures.scala", "def urlFor", "def parseTemp")
@p
@hl.scala{urlFor} encapsulates the messy URL-construction logic that we need to make the Ajax call to the right place.
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "parseTemp", "def formatResults")
+ @hl.ref(advanced/"Futures.scala", "parseTemp", "def formatResults")
@p
@hl.scala{parseTemp} encapsulates the messy result-extraction logic that we need to get the data we want (current temperature, in celsius) out of the structured JSON return blob.
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def formatResults", "def main")
+ @hl.ref(advanced/"Futures.scala", "def formatResults", "def main")
@p
@hl.scala{formatResults} encapsulates the conversion of the final @hl.scala{(name, celsius)} data back into readable HTML.
@@ -181,7 +183,7 @@
@sect{Direct Use of XMLHttpRequest}
@example(exampleDiv, "Futures().main0")
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle0", "main")
+ @hl.ref(advanced/"Futures.scala", "def handle0", "main")
@p
This is a simple solution that directly uses the @hl.scala{XMLHttpRequest} class that is available in Javascript in order to perform the Ajax call. Every Ajax call that returns, we aggregate in a @hl.scala{results} buffer, and when the @hl.scala{results} buffer is full we then append the formatted results to the output div.
@@ -193,7 +195,7 @@
@sect{Using dom.extensions.Ajax}
@example(exampleDiv, "Futures().main1")
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle1", "main")
+ @hl.ref(advanced/"Futures.scala", "def handle1", "main")
@p
This solution uses the @hl.scala{dom.extensions.Ajax} object, as described in @hl.scala{dom.extensions}. This basically wraps the messy @hl.scala{XMLHttpRequest} interface in a single function that returns a @hl.scala{scala.concurrent.Future}, which you can then map/foreach over to perform the action when the @hl.scala{Future} is complete.
@@ -202,7 +204,7 @@
@sect{Future Combinators}
@example(exampleDiv, "Futures().main2")
- @hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle2", "main")
+ @hl.ref(advanced/"Futures.scala", "def handle2", "main")
@p
Since we're using Scala's @hl.scala{Future}s, we aren't limited to just map/foreach-ing over them. @hl.scala{scala.concurrent.Future} provides a @lnk("rich api", "http://www.scala-lang.org/files/archive/nightly/docs/library/scala/concurrent/Future.html") that can be used to deal with common tasks like working with lists of futures in parallel, or aggregating the result of futures together.
@@ -234,7 +236,7 @@
@p
This is a toy example, but is enough to bring out the difficulty of doing things the "traditional" way, and why using Scala-Async with Scala.js is superior. To begin with, let's set the stage:
- @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "val renderer")
+ @hl.ref(advanced/"Async.scala", "val renderer")
@p
To initialize the canvas with the part of the code which will remain the same, so we can look more closely at the code which differs.
@@ -245,7 +247,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "// traditional")
+ @hl.ref(advanced/"Async.scala", "// traditional")
@less
@example(canvas, "Async().main0")
@@ -278,7 +280,7 @@
@split
@more
- @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "// async")
+ @hl.ref(advanced/"Async.scala", "// async")
@less
@example(canvas, "Async().main")
@@ -291,7 +293,7 @@
@p
You may be wondering what these @hl.scala{Channel} things are, and where they are coming from. Although these are not provided by Scala, they are pretty straightforward to define ourselves:
- @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "class Channel")
+ @hl.ref(advanced/"Async.scala", "class Channel")
@p
The point of @hl.scala{Channel} is to allow us to turn event-callbacks (like those provided by the DOM's @hl.scala{onmouseXXX} properties) into some kind of event-stream, that we can listen to asynchronously (via @hl.scala{apply} that returns a @hl.scala{Future}) or merge via @hl.scala{|}. This is a minimal implementation for what we need now, but it would be easy to provide more functionality (filter, map, etc.) as necessary.
diff --git a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
index 685fdfd..89389dc 100644
--- a/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
+++ b/book/src/main/scalatex/book/indepth/JavaAPIs.scalatex
@@ -39,7 +39,7 @@
@p
And other similar APIs will either need to be rewritten to not-use them. For example, @hl.scala{AtomicXXXs} can be written without threading/unsafe APIs because Javascript is single-threaded, making the implementation for e.g. an @hl.scala{AtomicBoolean} pretty trivial:
- @hl.ref(cloneRoot + "/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala")
+ @hl.ref(cloneRoot/"scala-js"/'javalib/'src/'main/'scala/'java/'util/'concurrent/'atomic/"AtomicBoolean.scala")
@p
Others can't be ported at all (e.g. @code{java.io.File}) simply because the API capabilities they provide (blocking reads & writes to files) do not exist in the Javascript runtime.
diff --git a/build.sbt b/build.sbt
index dde5431..a1170c6 100644
--- a/build.sbt
+++ b/build.sbt
@@ -24,7 +24,8 @@ lazy val book = Project(
libraryDependencies ++= Seq(
"com.scalatags" %% "scalatags" % "0.4.2",
"com.lihaoyi" %%% "upickle" % "0.2.5",
- "com.lihaoyi" %% "scalatex-site" % "0.1.0"
+ "com.lihaoyi" %% "scalatex-site" % "0.1.1",
+ "com.lihaoyi" %% "ammonite" % "0.1.0"
),
(resources in Compile) += {
(fullOptJS in (demos, Compile)).value
@@ -93,6 +94,7 @@ lazy val book = Project(
}
)
+
lazy val demos = project.in(file("examples/demos"))
lazy val simple = project.in(file("examples/crossBuilds/simple"))
diff --git a/project/build.sbt b/project/build.sbt
index 489e892..a80e91d 100644
--- a/project/build.sbt
+++ b/project/build.sbt
@@ -6,4 +6,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.4")
libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit" % "3.5.1.201410131835-r"
-addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.1.0")
+addSbtPlugin("com.lihaoyi" % "scalatex-sbt-plugin" % "0.1.1")