summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-11-09 23:11:06 -0800
committerLi Haoyi <haoyi@dropbox.com>2014-11-09 23:11:06 -0800
commitaaea4afbf3b47d623f396cb1eae247fa92053032 (patch)
treed462a508e545d5ec3637557b632cd0b7366e56cd
parent52306f10ce3d1e462b171688de04b37174c0b74a (diff)
downloadhands-on-scala-js-aaea4afbf3b47d623f396cb1eae247fa92053032.tar.gz
hands-on-scala-js-aaea4afbf3b47d623f396cb1eae247fa92053032.tar.bz2
hands-on-scala-js-aaea4afbf3b47d623f396cb1eae247fa92053032.zip
.
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex132
-rw-r--r--book/src/main/scalatex/book/handson/PublishingModules.scalatex4
-rw-r--r--examples/crossBuilds/clientserver/build.sbt23
-rw-r--r--examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala3
-rw-r--r--examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala37
-rw-r--r--examples/crossBuilds/clientserver/js/shared/main/scala/simple/Simple.scala7
-rw-r--r--examples/crossBuilds/clientserver/js/shared/test/scala/simple/SimpleTest.scala13
-rw-r--r--examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala31
-rw-r--r--examples/crossBuilds/clientserver/jvm/shared/main/scala/simple/Simple.scala7
-rw-r--r--examples/crossBuilds/clientserver/jvm/shared/test/scala/simple/SimpleTest.scala13
-rw-r--r--examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala59
l---------examples/crossBuilds/clientserver/server/shared1
-rw-r--r--examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala21
-rw-r--r--examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala43
-rw-r--r--examples/crossBuilds/clientserver2/build.sbt28
-rw-r--r--examples/crossBuilds/clientserver2/js/shared/main/scala/simple/Simple.scala7
-rw-r--r--examples/crossBuilds/clientserver2/js/shared/test/scala/simple/SimpleTest.scala13
-rw-r--r--examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala34
-rw-r--r--examples/crossBuilds/clientserver2/jvm/shared/main/scala/simple/Simple.scala7
-rw-r--r--examples/crossBuilds/clientserver2/jvm/shared/test/scala/simple/SimpleTest.scala13
-rw-r--r--examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala61
-rw-r--r--examples/crossBuilds/clientserver2/project/build.sbt4
-rw-r--r--examples/crossBuilds/simple/js/shared/main/scala/simple/Simple.scala4
-rw-r--r--examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala6
-rw-r--r--examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala13
-rw-r--r--examples/crossBuilds/simple2/js/shared/main/scala/simple/Simple.scala4
-rw-r--r--examples/crossBuilds/simple2/js/shared/test/scala/simple/SimpleTest.scala15
-rw-r--r--examples/crossBuilds/simple2/js/src/main/scala/simple/Platform.scala2
-rw-r--r--examples/crossBuilds/simple2/jvm/src/main/scala/simple/Platform.scala9
29 files changed, 198 insertions, 416 deletions
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{<script>} tag above to kick off the client-side application.
+ Next, let's kick off the Spray server in our Scala-JVM main method:
- @hl.ref("examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala")
+ @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala")
@p
- Apart from the main method, we also set up an Ajax call to the POST route we defined earlier on the server, which will ask the server to do it's date formatting a second time when the page loads.
+ 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 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.
@p
- Now, if we go to the browser at @code{localhost:8080}, we should see our web-page!
+ The HTML template @hl.scala{Page.skeleton} is not shown above; I put it in a separate file for neatness:
- @div
- @h1
- Hello from Scala-JVM!
- @p
- December 31, 1969 4:00:00 PM PST January 13, 1970 2:15:41 AM PST
-
- @h1
- Hello from Scala.js!
- @p
- 12/31/1969, 4:00:00 PM 1/13/1970, 2:15:41 AM
+ @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala")
- @h1
- Hello from Ajax!
- @p
- December 31, 1969 4:00:00 PM PST January 13, 1970 2:15:41 AM PST
-
@p
- As you can see, this client-server app exercises it's shared code to render dates on both client and server!
-
+ This is a typical 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
- The app isn't a very pretty app: HTML strings everywhere, splicing strings to create the DOM, etc.. Neither is it very useful, to be able to format dates on the same way on both client and server! Nonetheless, it should give you a sense of the configuration necessary to wire up Scala.js into any existing SBT project: you simply dump the output executable from @code{fastOptJS}/@code{fullOptJS} in the web-server's resources, to let it get served to the world. Pretty neat!
-
- @p
- Although it's a minimal example, you can probably imagine what it would take to extend it in a traditional way: use proper client-side and server-side templating languages like @a("Twirl", href:="https://github.com/playframework/twirl") and @a("Mustache", href:="http://mustache.github.io/"). Add additional routes to our Spray server and using @hl.javascript{XMLHttpRequest} to make requests to them. Maybe add some @a("jQuery", href:="http://jquery.com/") on the client (which has a @a("Scala.js wrapper", href:="https://github.com/scala-js/scala-js-jquery") to add some interactivity. The list goes on.
-
- @p
- On the other hand, Scala.js doesn't just allow you to make websites the traditional way. There are many techniques which are effectively unique to the Scala.js way of doing things, and have the potential to make your program much more uniform, type-safe, and robust. We'll explore these next.
+ 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.
-@sect{Idiomatic Scala.js}
- @p
- The previous section showed you how to make an interactive client-server web application using Scala/Spray on the server and Scala.js on the client, but it was a pretty close-to-Javascript implementation that didn't take much advantage of the tools available in Scala.js. Let's set about remedying that!
+ @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala")
@p
- The main changes we will introduce in this section are:
-
- @ul
- @li
- Replacing the direct use of @hl.scala{dom.XMLHttpRequest} with @hl.scala{dom.extensions.Ajax}
- @li
- Swapping out HTML-strings with Scalatags, to make the HTML-generation code typo-safe
- @li
- Using uPickle to serialize the data being transfered between client & server, to replace the ad-hoc string concatenation and splitting
+ 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!
@p
- The first step we'll need to do is add Scalatags and uPickle to our @code{build.sbt} file. We want it added to both our @code{js} and @code{jvm} projects, so they'll go inside the @hl.scala{JsCrossBuild} where we put shared settings.
+ 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/clientserver2/build.sbt")
+ @hl.ref("examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala")
@p
- Next, let's re-write the server to use our new tools!
-
- @hl.ref("examples/crossBuilds/clientserver2/jvm/src/main/scala/simple/Platform.scala")
+ Now, if we go to the browser at @code{localhost:8080}, we should see our web-page!
+@sect{Client-Server Reflections}
@p
- The main changes here are:
+ By now you've already set up your first client-server application. However, it might not be immediately clear what we've done and why it's interesting! Here are some points to consider.
- @ul
- @li
- Moving the HTML template out into a separate object and using Scalatags to render it
- @li
- using @hl.scala{upickle.read} to deserialize the incoming Ajax call automatically, rather than having to pry it apart with @hl.scala{String.split}
+ @sect{Shared Templating}
- @p
- Lastly, we need to modify our client:
+ @p
+ In both the client code and the server code, we made use of the same Scalatags HTML generation library. This is pretty neat: transferring rendering logic between client and server no longer means an annoying/messy rewrite! You can simply C&P the Scalatags snippet over. That means it's easy if you want to e.g. shift the logic from one side to the other in order to optimize for performance or time-to-load or other things.
+ @p
+ One thing to take note of is that we're actually using subtly @i{different} implementations of Scalatags on both sides: on the server, we're importing from @hl.scala{scalatags.Text}, while on the client we're using @hl.scala{scalatags.JsDom}. The @hl.scala{Text} backend renders directly to Strings, and is available on both Scala-JVM and Scala.js. The @hl.scala{JsDom} backend, on the other hand, renders to @hl.scala{dom.HTMLElement}s which only exist on Scala.js. Thus while on the client you can do things like attach event listeners to the rendered @hl.scala{dom.HTMLElement} objects, or checking their runtime @code{.value}, on the server you can't. And that's exactly what you want!
- @hl.ref("examples/crossBuilds/clientserver2/js/src/main/scala/simple/Platform.scala")
+ @sect{Shared Code}
+ @p
+ One thing that we skimmed over is the fact that we could easily define our @hl.scala{case class FileData(name: String, size: Long)} in the @code{shared/} folder, and have it instantly and consistently available on both client and server. This perhaps does not seem so amazing: we've already done many similar things earlier when we were building Cross-platform Modules. Nevertheless, in the context of web development, it is a relatively novel idea to be able to ad-hoc share bits of code between client and server.
+ @p
+ Sharing code is not limited to class definitions: @i{anything} can be shared. Objects, classes, interfaces/traits, functions and algorithms, constants: all of these are things that you will likely want to share at some point or another. Traditionally, people have simply re-implemented the same code twice in two languages, or have resorted to awkward Ajax calls to push the logic to the server. With Scala.js, you no longer need to do so: you can easily, create ad-hoc bits of code which are available on both platforms.
- @p
- This involves swapping out direct use of @hl.scala{dom.XMLHttpRequest} with @hl.scala{dom.extensions.Ajax}, using Scalatags to render the fragments here, and @hl.scala{upickle.write} to deal with the serialization of the Ajax arguments.
+ @sect{Boilerplate-free Serialization}
+ @p
+ The Ajax/RPC layer is one of the more fragile parts of web applications. Often, you have your various Ajax endpoints written once on the server, have a set of routes written to connect those Ajax endpoints to URLs, and client code (traditionally Javascript) made calls to those URLs with "raw" data: basically whatever you wanted, packed in an ad-hoc mix of CSV and JSON and raw-strings.
- @p
- If we look at the output of the website, it would look exactly the same!
-
- @div
- @h1
- Hello from Scala-JVM!
- @p
- December 31, 1969 4:00:00 PM PST January 13, 1970 2:15:41 AM PST
-
- @h1
- Hello from Scala.js!
@p
- 12/31/1969, 4:00:00 PM 1/13/1970, 2:15:41 AM
+ This has always been annoying boilerplate, and Scala.js removes it. With uPickle, you can simply call @hl.scala{upickle.write(...)} and @hl.scala{upickle.read[T](...)} to convert your collections, primitives or case-classes to and from JSON. This means you do not need to constantly re-invent different ways of making Ajax calls: you can just fling the data right across the network from client to server and back again.
- @h1
- Hello from Ajax!
- @p
- December 31, 1969 4:00:00 PM PST January 13, 1970 2:15:41 AM PST
-
- @p
- But we've simplified the code considerably: most notably, all the manual string-munging we did in the previous version (to build HTML, to serialize the Ajax arguments) are all gone. They've been replaced with type-safe, functional equivalents.
+@hr
+@p
+ Hopefully this chapter has given you a glimpse of how a basic client-server application works using Scala.js. Although it is specific to a Spray server, there isn't any reason why you couldn't set up an equivalent thing for your Play, Scalatra or whichever other web framework that you're using.
-@sect{Ajax calls via Autowire}
- TODO \ No newline at end of file
+@p
+ It's probably worth taking a moment to play around with the existing client-server system you have set up. Ideas for improvement include:
+
+@ul
+ @li
+ Try adding additional functionality to the client-server interface: what about making it show the contents of a file if you've entered its full path? This can be added as a separate Ajax call or as part of the existing one.
+ @li
+ How about setting up the build.sbt so it serves the fully-optimized Scala.js blob, @code{client-opt.js}? This is probably what you want before deployment into production, and the same technique as we used to serve the fast-optimized version applies here too. \ No newline at end of file
diff --git a/book/src/main/scalatex/book/handson/PublishingModules.scalatex b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
index 387d120..7882283 100644
--- a/book/src/main/scalatex/book/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
@@ -62,7 +62,7 @@
@hl.ref("examples/crossBuilds/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.
+ 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.
@p
However, when it comes to actually formatting the date, we have a problem: Javascript and Java provide different utilities for formatting dates! They both let you format them, but they provide different APIs. Thus, to do the formatting of each individual date, we call out to the @hl.scala{Platform.format} function, which is implemented twice: once in @code{js/} and once in @code{jvm/}:
@@ -75,7 +75,7 @@
@hl.ref("examples/crossBuilds/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 @a("here", href:="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html")).
+ 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 @a("here", href:="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html")).
@p
Again, you can put as much platform-specific logic in these files as you want, to account for differences in the available APIs. Maybe you want to use @hl.scala{js.JSON.parse} for parsing JSON blobs in @code{js/}, but @hl.scala{Jackson} or @hl.scala{GSON} for parsing them in @code{jvm/}.
diff --git a/examples/crossBuilds/clientserver/build.sbt b/examples/crossBuilds/clientserver/build.sbt
index 434fb42..618ac8a 100644
--- a/examples/crossBuilds/clientserver/build.sbt
+++ b/examples/crossBuilds/clientserver/build.sbt
@@ -1,25 +1,34 @@
import utest.jsrunner.JsCrossBuild
import scala.scalajs.sbtplugin.ScalaJSPlugin._
import ScalaJSKeys._
-
-val cross = new JsCrossBuild(
- // Shared settings here
+val sharedSettings = Seq(
+ unmanagedSourceDirectories in Compile +=
+ baseDirectory.value / "shared" / "main" / "scala",
+ libraryDependencies ++= Seq(
+ "com.scalatags" %%% "scalatags" % "0.4.2",
+ "com.lihaoyi" %%% "upickle" % "0.2.5"
+ )
)
-lazy val js = cross.js.settings(
+lazy val client = project.in(file("client"))
+ .settings(scalaJSSettings:_*)
+ .settings(sharedSettings:_*)
+ .settings(
libraryDependencies ++= Seq(
"org.scala-lang.modules.scalajs" %%% "scalajs-dom" % "0.6"
)
)
-lazy val jvm = cross.jvm.settings(
+lazy val server = project.in(file("server"))
+ .settings(sharedSettings:_*)
+ .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
+ (fastOptJS in (client, Compile)).value
+ (artifactPath in (client, Compile, fastOptJS)).value
}
)
diff --git a/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala b/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala
new file mode 100644
index 0000000..f3a23f9
--- /dev/null
+++ b/examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala
@@ -0,0 +1,3 @@
+/*shared/main/scala/simple/Simple.scala*/
+package simple
+case class FileData(name: String, size: Long) \ No newline at end of file
diff --git a/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala b/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala
new file mode 100644
index 0000000..f066923
--- /dev/null
+++ b/examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala
@@ -0,0 +1,37 @@
+//js/src/main/scala/simple/Platform.scala
+package simple
+
+import scalatags.JsDom.all._
+import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
+import org.scalajs.dom
+import dom.extensions.Ajax
+import scala.scalajs.js.annotation.JSExport
+
+@JSExport
+object Client extends{
+ @JSExport
+ def main(container: dom.HTMLDivElement) = {
+ val inputBox = input.render
+ val outputBox = ul.render
+ def update() = Ajax.post("/ajax", inputBox.value).foreach{ xhr =>
+ val data = upickle.read[Seq[FileData]](xhr.responseText)
+ outputBox.innerHTML = ""
+ for(FileData(name, size) <- data){
+ outputBox.appendChild(
+ li(
+ b(name), " - ", size, " bytes"
+ ).render
+ )
+ }
+ }
+ inputBox.onkeyup = (e: dom.Event) => update()
+ update()
+ container.appendChild(
+ div(
+ h1("File Search"),
+ inputBox,
+ outputBox
+ ).render
+ )
+ }
+} \ No newline at end of file
diff --git a/examples/crossBuilds/clientserver/js/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver/js/shared/main/scala/simple/Simple.scala
deleted file mode 100644
index d3b0278..0000000
--- a/examples/crossBuilds/clientserver/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/clientserver/js/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver/js/shared/test/scala/simple/SimpleTest.scala
deleted file mode 100644
index ec6b29f..0000000
--- a/examples/crossBuilds/clientserver/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/clientserver/js/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala
deleted file mode 100644
index c82a112..0000000
--- a/examples/crossBuilds/clientserver/js/src/main/scala/simple/Platform.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-//js/src/main/scala/simple/Platform.scala
-package simple
-
-import org.scalajs.dom.XMLHttpRequest
-
-import scala.scalajs.js
-import org.scalajs.dom
-
-import scala.scalajs.js.annotation.JSExport
-
-@JSExport
-object Platform extends{
- def format(ts: Long) = {
- new js.Date(ts).toLocaleString()
- }
- @JSExport
- def main(container: dom.HTMLDivElement) = {
- container.innerHTML +=
- "<h1>Hello from Scala.js!</h1>" +
- s"<p>${Simple.formatTimes(Seq(0, 1 << 30))}</p>"
-
- val xhr = new XMLHttpRequest()
- xhr.open("POST", "/formatDates")
- xhr.onload = (e: dom.Event) => {
- container.innerHTML +=
- "<h1>Hello from Ajax!</h1>" +
- s"<p>${xhr.responseText}</p>"
- }
- xhr.send(Seq(0, 1 << 30).mkString(","))
- }
-} \ No newline at end of file
diff --git a/examples/crossBuilds/clientserver/jvm/shared/main/scala/simple/Simple.scala b/examples/crossBuilds/clientserver/jvm/shared/main/scala/simple/Simple.scala
deleted file mode 100644
index d3b0278..0000000
--- a/examples/crossBuilds/clientserver/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/clientserver/jvm/shared/test/scala/simple/SimpleTest.scala b/examples/crossBuilds/clientserver/jvm/shared/test/scala/simple/SimpleTest.scala
deleted file mode 100644
index ec6b29f..0000000
--- a/examples/crossBuilds/clientserver/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/clientserver/jvm/src/main/scala/simple/Platform.scala b/examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala
deleted file mode 100644
index 61fdc58..0000000
--- a/examples/crossBuilds/clientserver/jvm/src/main/scala/simple/Platform.scala
+++ /dev/null
@@ -1,59 +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
-
-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{
- val msg = Simple.formatTimes(Seq(0, 1 << 30))
- val boot =
- "Platform().main(document.getElementById('contents'))"
- HttpEntity(
- MediaTypes.`text/html`,
- s"""
- <html>
- <head>
- <script src="/js-fastopt.js"></script>
- </head>
- <body onload="$boot">
- <div id="contents">
- <h1>Hello from Scala-JVM!</h1>
- <p>$msg</p>
- </div>
- </body>
- </html>
- """
- )
- }
- } ~
- 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