From aaea4afbf3b47d623f396cb1eae247fa92053032 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 9 Nov 2014 23:11:06 -0800 Subject: . --- .../scalatex/book/handson/ClientServer.scalatex | 132 +++++++-------------- .../book/handson/PublishingModules.scalatex | 4 +- examples/crossBuilds/clientserver/build.sbt | 23 ++-- .../client/shared/main/scala/simple/FileData.scala | 3 + .../client/src/main/scala/simple/Client.scala | 37 ++++++ .../js/shared/main/scala/simple/Simple.scala | 7 -- .../js/shared/test/scala/simple/SimpleTest.scala | 13 -- .../js/src/main/scala/simple/Platform.scala | 31 ----- .../jvm/shared/main/scala/simple/Simple.scala | 7 -- .../jvm/shared/test/scala/simple/SimpleTest.scala | 13 -- .../jvm/src/main/scala/simple/Platform.scala | 59 --------- examples/crossBuilds/clientserver/server/shared | 1 + .../server/src/main/scala/simple/Page.scala | 21 ++++ .../server/src/main/scala/simple/Server.scala | 43 +++++++ examples/crossBuilds/clientserver2/build.sbt | 28 ----- .../js/shared/main/scala/simple/Simple.scala | 7 -- .../js/shared/test/scala/simple/SimpleTest.scala | 13 -- .../js/src/main/scala/simple/Platform.scala | 34 ------ .../jvm/shared/main/scala/simple/Simple.scala | 7 -- .../jvm/shared/test/scala/simple/SimpleTest.scala | 13 -- .../jvm/src/main/scala/simple/Platform.scala | 61 ---------- .../crossBuilds/clientserver2/project/build.sbt | 4 - .../js/shared/main/scala/simple/Simple.scala | 4 +- .../simple/js/src/main/scala/simple/Platform.scala | 6 +- .../jvm/src/main/scala/simple/Platform.scala | 13 +- .../js/shared/main/scala/simple/Simple.scala | 4 +- .../js/shared/test/scala/simple/SimpleTest.scala | 15 ++- .../js/src/main/scala/simple/Platform.scala | 2 +- .../jvm/src/main/scala/simple/Platform.scala | 9 +- 29 files changed, 198 insertions(+), 416 deletions(-) create mode 100644 examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala create mode 100644 examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala delete mode 100644 examples/crossBuilds/clientserver/js/shared/main/scala/simple/Simple.scala delete mode 100644 examples/crossBuilds/clientserver/js/shared/test/scala/simple/SimpleTest.scala delete mode 100644 examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala delete mode 100644 examples/crossBuilds/clientserver/jvm/shared/main/scala/simple/Simple.scala delete mode 100644 examples/crossBuilds/clientserver/jvm/shared/test/scala/simple/SimpleTest.scala delete mode 100644 examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala create mode 120000 examples/crossBuilds/clientserver/server/shared create mode 100644 examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala create mode 100644 examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala delete mode 100644 examples/crossBuilds/clientserver2/build.sbt delete mode 100644 examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala delete mode 100644 examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala delete mode 100644 examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala delete mode 100644 examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala delete mode 100644 examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala delete mode 100644 examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala delete mode 100644 examples/crossBuilds/clientserver2/project/build.sbt diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex index b936482..b29df03 100644 --- a/book/src/main/scalatex/book/handson/ClientServer.scalatex +++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex @@ -13,129 +13,85 @@ @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! -@sect{Client-Server Configuration} +@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 (Play, Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. @p - First, let's do the wiring in @code{build.sbt} to add Spray as a dependency, + First, let's do the wiring in @code{build.sbt}: @hl.ref("examples/crossBuilds/clientserver/build.sbt") - - @p - This does the standard boilerplate for including dependencies via SBT, and does on additional thing: we add the output of @code{fastOptJS} from the client to the @code{resources} on the server. @p - Next, let's kick off the Spray server in our Scala-JVM main method: - - @hl.ref("examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala") - + 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 Cross-platform Modules) and the settings to add Scalatags and 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. @p - This is a not-very-interesting @a("spray-routing", href:="http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve a HTML string, and have other 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 format dates any time it wants via Ajax. - + The @code{client} subproject is uneventful, with a dependency on the by-now-familiar @code{scalajs-dom} library. The @code{server} project, on the other hand, is interesting: it contains the dependencies required for us to set up out Spray server, and one additioanl thing: we add the output of @code{fastOptJS} from the client to the @code{resources} on the server. This will allow the @code{server} to serve the compiled-javascript from our @code{client} project from its resources. + @p - Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{ - - -
-

Hello from Scala-JVM!

-

$msg

-
- - - """ - ) - } - } ~ - getFromResourceDirectory("") - } ~ - post{ - path("formatDates"){ - extract(_.request.entity.asString) { e => - complete { - Simple.formatTimes(e.split(",").map(_.toLong)) - } - } - } - } - } - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver/server/shared b/examples/crossBuilds/clientserver/server/shared new file mode 120000 index 0000000..f32be42 --- /dev/null +++ b/examples/crossBuilds/clientserver/server/shared @@ -0,0 +1 @@ +../client/shared \ No newline at end of file diff --git a/examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala b/examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala new file mode 100644 index 0000000..ce6617c --- /dev/null +++ b/examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala @@ -0,0 +1,21 @@ +package simple +import scalatags.Text.all._ + +object Page{ + val boot = + "Client().main(document.getElementById('contents'))" + val skeleton = + html( + head( + script(src:="/client-fastopt.js"), + link( + rel:="stylesheet", + href:="http://yui.yahooapis.com/pure/0.5.0/pure-min.css" + ) + ), + body( + onload:=boot, + div(id:="contents") + ) + ) +} diff --git a/examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala b/examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala new file mode 100644 index 0000000..6b2fea3 --- /dev/null +++ b/examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala @@ -0,0 +1,43 @@ +package simple + +import akka.actor.ActorSystem +import spray.http.{HttpEntity, MediaTypes} +import spray.routing.SimpleRoutingApp + +object Server extends SimpleRoutingApp{ + + def main(args: Array[String]): Unit = { + implicit val system = ActorSystem() + startServer("localhost", port = 8080){ + get{ + pathSingleSlash{ + complete{ + HttpEntity( + MediaTypes.`text/html`, + Page.skeleton.render + ) + } + } ~ + getFromResourceDirectory("") + } ~ + post{ + path("ajax"){ + extract(_.request.entity.asString) { e => + complete { + val (dir, last) = e.splitAt(e.lastIndexOf("/") + 1) + val files = + Option(new java.io.File("./" + dir).listFiles()) + .toSeq.flatten + upickle.write( + for{ + f <- files + if f.getName.startsWith(last) + } yield FileData(f.getName, f.length()) + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/build.sbt b/examples/crossBuilds/clientserver2/build.sbt deleted file mode 100644 index 7e5bddb..0000000 --- a/examples/crossBuilds/clientserver2/build.sbt +++ /dev/null @@ -1,28 +0,0 @@ -import utest.jsrunner.JsCrossBuild -import scala.scalajs.sbtplugin.ScalaJSPlugin._ -import ScalaJSKeys._ - -val cross = new JsCrossBuild( - libraryDependencies ++= Seq( - "com.scalatags" %%% "scalatags" % "0.4.2", - "com.lihaoyi" %%% "upickle" % "0.2.5" - ) -) - -lazy val js = cross.js.settings( - libraryDependencies ++= Seq( - "org.scala-lang.modules.scalajs" %%% "scalajs-dom" % "0.6" - ) -) - -lazy val jvm = cross.jvm.settings( - libraryDependencies ++= Seq( - "io.spray" %% "spray-can" % "1.3.2", - "io.spray" %% "spray-routing" % "1.3.2", - "com.typesafe.akka" %% "akka-actor" % "2.3.6" - ), - (resources in Compile) += { - (fastOptJS in (js, Compile)).value - (artifactPath in (js, Compile, fastOptJS)).value - } -) diff --git a/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala deleted file mode 100644 index d3b0278..0000000 --- a/examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala +++ /dev/null @@ -1,7 +0,0 @@ -/*shared/main/scala/simple/Simple.scala*/ -package simple -object Simple{ - def formatTimes(timestamps: Seq[Long]): String = { - timestamps.map(Platform.format).mkString("\n") - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala deleted file mode 100644 index ec6b29f..0000000 --- a/examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala +++ /dev/null @@ -1,13 +0,0 @@ -/*js/shared/test/scala/simple/SimpleTest.scala*/ -/*jvm/shared/test/scala/simple/SimpleTest.scala*/ -package simple -import utest._ -object SimpleTest extends TestSuite{ - val tests = TestSuite{ - 'format{ - 'nil - assert(Simple.formatTimes(Nil) == "") - 'timeZero - assert( - Simple.formatTimes(Seq(0)) == "December 31, 1969 4:00:00 PM PST") - } - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala deleted file mode 100644 index 906329e..0000000 --- a/examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala +++ /dev/null @@ -1,34 +0,0 @@ -//js/src/main/scala/simple/Platform.scala -package simple - -import scala.scalajs.js -import org.scalajs.dom -import dom.extensions.Ajax -import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow -import scala.scalajs.js.annotation.JSExport -import scalatags.JsDom.all._ -@JSExport -object Platform extends{ - def format(ts: Long) = { - new js.Date(ts).toLocaleString() - } - @JSExport - def main(container: dom.HTMLDivElement) = { - container.appendChild( - div( - h1("Hello from Scala.js!"), - p(Simple.formatTimes(Seq(0, 1 << 30))) - ).render - ) - val payload = upickle.write(Seq(0L, 1L << 30)) - Ajax.post("/formatDates", payload).foreach{ xhr => - container.appendChild( - div( - h1("Hello from Ajax!"), - p(xhr.responseText) - ).render - ) - } - - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala deleted file mode 100644 index d3b0278..0000000 --- a/examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala +++ /dev/null @@ -1,7 +0,0 @@ -/*shared/main/scala/simple/Simple.scala*/ -package simple -object Simple{ - def formatTimes(timestamps: Seq[Long]): String = { - timestamps.map(Platform.format).mkString("\n") - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala deleted file mode 100644 index ec6b29f..0000000 --- a/examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala +++ /dev/null @@ -1,13 +0,0 @@ -/*js/shared/test/scala/simple/SimpleTest.scala*/ -/*jvm/shared/test/scala/simple/SimpleTest.scala*/ -package simple -import utest._ -object SimpleTest extends TestSuite{ - val tests = TestSuite{ - 'format{ - 'nil - assert(Simple.formatTimes(Nil) == "") - 'timeZero - assert( - Simple.formatTimes(Seq(0)) == "December 31, 1969 4:00:00 PM PST") - } - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala deleted file mode 100644 index f4ef986..0000000 --- a/examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala +++ /dev/null @@ -1,61 +0,0 @@ -//jvm/src/main/scala/simple/Platform.scala -package simple - -import java.text.SimpleDateFormat -import akka.actor.ActorSystem -import spray.http.{HttpEntity, MediaTypes} -import spray.routing.SimpleRoutingApp -import scalatags.Text.all._ - -object Static{ - val msg = Simple.formatTimes(Seq(0, 1 << 30)) - val boot = - "Platform().main(document.getElementById('contents'))" - val page = html( - head( - script(src:="/js-fastopt.js") - ), - body( - onload:=boot, - div(id:="contents")( - h1("Hello from Scala-JVM!"), - p(msg) - ) - ) - ) -} -object Platform extends SimpleRoutingApp{ - def format(ts: Long) = { - val fmt = - "MMMM d, yyyy h:mm:ss aaa z" - new SimpleDateFormat(fmt).format( - new java.util.Date(ts) - ) - } - - def main(args: Array[String]): Unit = { - implicit val system = ActorSystem() - startServer("localhost", port = 8080){ - get{ - pathSingleSlash{ - complete{ - HttpEntity( - MediaTypes.`text/html`, - Static.page.render - ) - } - } ~ - getFromResourceDirectory("") - } ~ - post{ - path("formatDates"){ - extract(_.request.entity.asString) { e => - complete { - Simple.formatTimes(upickle.read[Seq[Long]](e)) - } - } - } - } - } - } -} \ No newline at end of file diff --git a/examples/crossBuilds/clientserver2/project/build.sbt b/examples/crossBuilds/clientserver2/project/build.sbt deleted file mode 100644 index 5bd83ce..0000000 --- a/examples/crossBuilds/clientserver2/project/build.sbt +++ /dev/null @@ -1,4 +0,0 @@ -/*project/build.sbt*/ -addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5") - -addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.4") \ No newline at end of file diff --git a/examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala index d3b0278..4ed0285 100644 --- a/examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala +++ b/examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala @@ -1,7 +1,7 @@ /*shared/main/scala/simple/Simple.scala*/ package simple object Simple{ - def formatTimes(timestamps: Seq[Long]): String = { - timestamps.map(Platform.format).mkString("\n") + def formatTimes(timestamps: Seq[Long]): Seq[String] = { + timestamps.map(Platform.format).map(_.dropRight(5)) } } \ No newline at end of file diff --git a/examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala b/examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala index ee2f5da..35ebe1d 100644 --- a/examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala +++ b/examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala @@ -4,12 +4,12 @@ import scala.scalajs.js object Platform extends js.JSApp{ def format(ts: Long) = { - new js.Date(ts).toLocaleString() + new js.Date(ts).toISOString() } def main() = { val times = Seq( - System.currentTimeMillis(), - System.currentTimeMillis() + 1000 + 0L, + 1L << 32 ) println("Running on JS! " + 1.0d) println(Simple.formatTimes(times)) diff --git a/examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala b/examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala index e8e7a65..c5e8216 100644 --- a/examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala +++ b/examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala @@ -1,19 +1,20 @@ //jvm/src/main/scala/simple/Platform.scala package simple import java.text.SimpleDateFormat +import java.util.{TimeZone, Locale} object Platform{ def format(ts: Long) = { - val fmt = - "MMMM d, yyyy h:mm:ss aaa z" - new SimpleDateFormat(fmt).format( - new java.util.Date(ts) + val fmt = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.sss'Z'" ) + fmt.setTimeZone(TimeZone.getTimeZone("UTC")) + fmt.format(new java.util.Date(ts)) } def main(args: Array[String]) = { val times = Seq( - System.currentTimeMillis(), - System.currentTimeMillis() + 1000 + 0L, + 1L << 32 ) println("Running on JVM! " + 1.0d) println(Simple.formatTimes(times)) diff --git a/examples/crossBuilds/simple2/js/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/simple2/js/shared/main/scala/simple/Simple.scala index d3b0278..4ed0285 100644 --- a/examples/crossBuilds/simple2/js/shared/main/scala/simple/Simple.scala +++ b/examples/crossBuilds/simple2/js/shared/main/scala/simple/Simple.scala @@ -1,7 +1,7 @@ /*shared/main/scala/simple/Simple.scala*/ package simple object Simple{ - def formatTimes(timestamps: Seq[Long]): String = { - timestamps.map(Platform.format).mkString("\n") + def formatTimes(timestamps: Seq[Long]): Seq[String] = { + timestamps.map(Platform.format).map(_.dropRight(5)) } } \ No newline at end of file diff --git a/examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala index ec6b29f..b348c6f 100644 --- a/examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala +++ b/examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala @@ -5,9 +5,16 @@ import utest._ object SimpleTest extends TestSuite{ val tests = TestSuite{ 'format{ - 'nil - assert(Simple.formatTimes(Nil) == "") - 'timeZero - assert( - Simple.formatTimes(Seq(0)) == "December 31, 1969 4:00:00 PM PST") - } + 'nil - assert(Simple.formatTimes(Nil) == Nil) + 'timeZero - { + val timestamps = Seq(0L, 1L << 32) + val expected = Seq( + "1970-01-01T00:00:00", + "1970-02-19T17:02:47" + ) + val formatted = Simple.formatTimes(timestamps) + assert(formatted == expected) + } + } } } \ No newline at end of file diff --git a/examples/crossBuilds/simple2/js/src/main/scala/simple/Platform.scala b/examples/crossBuilds/simple2/js/src/main/scala/simple/Platform.scala index 2a02a5e..42cc7dc 100644 --- a/examples/crossBuilds/simple2/js/src/main/scala/simple/Platform.scala +++ b/examples/crossBuilds/simple2/js/src/main/scala/simple/Platform.scala @@ -4,6 +4,6 @@ import scala.scalajs.js object Platform{ def format(ts: Long) = { - new js.Date(ts).toLocaleString() + new js.Date(ts).toISOString() } } \ No newline at end of file diff --git a/examples/crossBuilds/simple2/jvm/src/main/scala/simple/Platform.scala b/examples/crossBuilds/simple2/jvm/src/main/scala/simple/Platform.scala index 1b7be56..4713005 100644 --- a/examples/crossBuilds/simple2/jvm/src/main/scala/simple/Platform.scala +++ b/examples/crossBuilds/simple2/jvm/src/main/scala/simple/Platform.scala @@ -1,13 +1,14 @@ //jvm/src/main/scala/simple/Platform.scala package simple import java.text.SimpleDateFormat +import java.util.TimeZone object Platform{ def format(ts: Long) = { - val fmt = - "MMMM d, yyyy h:mm:ss aaa z" - new SimpleDateFormat(fmt).format( - new java.util.Date(ts) + val fmt = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.sss'Z'" ) + fmt.setTimeZone(TimeZone.getTimeZone("UTC")) + fmt.format(new java.util.Date(ts)) } } \ No newline at end of file -- cgit v1.2.3