summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlihaoyi <haoyi.sg@gmail.com>2014-11-23 16:47:15 -0800
committerlihaoyi <haoyi.sg@gmail.com>2014-11-23 16:47:15 -0800
commit3547ab4bd7e84818242d15ba813d9312d2456b09 (patch)
tree9595d4ca1781d60d93caa7ba947c745837a33f91
parent1c4cb72b209ab11c9e52c3bb490adf759f17fd0c (diff)
downloadhands-on-scala-js-3547ab4bd7e84818242d15ba813d9312d2456b09.tar.gz
hands-on-scala-js-3547ab4bd7e84818242d15ba813d9312d2456b09.tar.bz2
hands-on-scala-js-3547ab4bd7e84818242d15ba813d9312d2456b09.zip
Lots of changes
-rw-r--r--.idea/libraries/SBT__com_chuusai_shapeless_2_11_1_2_4.xml13
-rw-r--r--.idea/libraries/SBT__com_typesafe_akka_akka_actor_2_11_2_3_6.xml13
-rw-r--r--.idea/libraries/SBT__com_typesafe_config_1_2_1.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_can_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_http_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_httpx_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_io_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_routing_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__io_spray_spray_util_2_11_1_3_2.xml14
-rw-r--r--.idea/libraries/SBT__org_jvnet_mimepull_mimepull_1_9_4.xml13
-rw-r--r--.idea/libraries/SBT__org_parboiled_parboiled_core_1_1_6.xml14
-rw-r--r--.idea/libraries/SBT__org_parboiled_parboiled_scala_2_11_1_1_6.xml14
-rw-r--r--book/src/main/scala/book/BookData.scala17
-rw-r--r--book/src/main/scala/book/Utils.scala17
-rw-r--r--book/src/main/scalatex/book/Index.scalatex15
-rw-r--r--book/src/main/scalatex/book/Intro.scalatex7
-rw-r--r--book/src/main/scalatex/book/handson/CanvasApp.scalatex23
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex24
-rw-r--r--book/src/main/scalatex/book/handson/PublishingModules.scalatex7
-rw-r--r--book/src/main/scalatex/book/handson/WebPage.scalatex96
-rw-r--r--book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex72
-rw-r--r--book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex60
-rw-r--r--book/src/main/scalatex/book/indepth/DesignSpace.scalatex2
-rw-r--r--book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex54
-rwxr-xr-xscalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala1
25 files changed, 400 insertions, 160 deletions
diff --git a/.idea/libraries/SBT__com_chuusai_shapeless_2_11_1_2_4.xml b/.idea/libraries/SBT__com_chuusai_shapeless_2_11_1_2_4.xml
new file mode 100644
index 0000000..0ef1f4a
--- /dev/null
+++ b/.idea/libraries/SBT__com_chuusai_shapeless_2_11_1_2_4.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="SBT: com.chuusai:shapeless_2.11:1.2.4">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.chuusai/shapeless_2.11/jars/shapeless_2.11-1.2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.chuusai/shapeless_2.11/docs/shapeless_2.11-1.2.4-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.chuusai/shapeless_2.11/srcs/shapeless_2.11-1.2.4-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__com_typesafe_akka_akka_actor_2_11_2_3_6.xml b/.idea/libraries/SBT__com_typesafe_akka_akka_actor_2_11_2_3_6.xml
new file mode 100644
index 0000000..d697090
--- /dev/null
+++ b/.idea/libraries/SBT__com_typesafe_akka_akka_actor_2_11_2_3_6.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="SBT: com.typesafe.akka:akka-actor_2.11:2.3.6">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe.akka/akka-actor_2.11/jars/akka-actor_2.11-2.3.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe.akka/akka-actor_2.11/docs/akka-actor_2.11-2.3.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe.akka/akka-actor_2.11/srcs/akka-actor_2.11-2.3.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__com_typesafe_config_1_2_1.xml b/.idea/libraries/SBT__com_typesafe_config_1_2_1.xml
new file mode 100644
index 0000000..6a5807f
--- /dev/null
+++ b/.idea/libraries/SBT__com_typesafe_config_1_2_1.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: com.typesafe:config:1.2.1">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe/config/bundles/config-1.2.1.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe/config/jars/config-1.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe/config/docs/config-1.2.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/com.typesafe/config/srcs/config-1.2.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_can_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_can_2_11_1_3_2.xml
new file mode 100644
index 0000000..ba16743
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_can_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-can_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-can_2.11/bundles/spray-can_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-can_2.11/jars/spray-can_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-can_2.11/docs/spray-can_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-can_2.11/srcs/spray-can_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_http_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_http_2_11_1_3_2.xml
new file mode 100644
index 0000000..6882c9d
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_http_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-http_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-http_2.11/jars/spray-http_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-http_2.11/bundles/spray-http_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-http_2.11/docs/spray-http_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-http_2.11/srcs/spray-http_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_httpx_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_httpx_2_11_1_3_2.xml
new file mode 100644
index 0000000..52bd2f3
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_httpx_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-httpx_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-httpx_2.11/jars/spray-httpx_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-httpx_2.11/bundles/spray-httpx_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-httpx_2.11/docs/spray-httpx_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-httpx_2.11/srcs/spray-httpx_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_io_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_io_2_11_1_3_2.xml
new file mode 100644
index 0000000..5e6ac51
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_io_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-io_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-io_2.11/bundles/spray-io_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-io_2.11/jars/spray-io_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-io_2.11/docs/spray-io_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-io_2.11/srcs/spray-io_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_routing_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_routing_2_11_1_3_2.xml
new file mode 100644
index 0000000..070e877
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_routing_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-routing_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-routing_2.11/jars/spray-routing_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-routing_2.11/bundles/spray-routing_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-routing_2.11/docs/spray-routing_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-routing_2.11/srcs/spray-routing_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__io_spray_spray_util_2_11_1_3_2.xml b/.idea/libraries/SBT__io_spray_spray_util_2_11_1_3_2.xml
new file mode 100644
index 0000000..4eb7798
--- /dev/null
+++ b/.idea/libraries/SBT__io_spray_spray_util_2_11_1_3_2.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: io.spray:spray-util_2.11:1.3.2">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-util_2.11/bundles/spray-util_2.11-1.3.2.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-util_2.11/jars/spray-util_2.11-1.3.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-util_2.11/docs/spray-util_2.11-1.3.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/io.spray/spray-util_2.11/srcs/spray-util_2.11-1.3.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__org_jvnet_mimepull_mimepull_1_9_4.xml b/.idea/libraries/SBT__org_jvnet_mimepull_mimepull_1_9_4.xml
new file mode 100644
index 0000000..7fcd3a6
--- /dev/null
+++ b/.idea/libraries/SBT__org_jvnet_mimepull_mimepull_1_9_4.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="SBT: org.jvnet.mimepull:mimepull:1.9.4">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.jvnet.mimepull/mimepull/jars/mimepull-1.9.4.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.jvnet.mimepull/mimepull/docs/mimepull-1.9.4-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.jvnet.mimepull/mimepull/srcs/mimepull-1.9.4-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__org_parboiled_parboiled_core_1_1_6.xml b/.idea/libraries/SBT__org_parboiled_parboiled_core_1_1_6.xml
new file mode 100644
index 0000000..8cf44a6
--- /dev/null
+++ b/.idea/libraries/SBT__org_parboiled_parboiled_core_1_1_6.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: org.parboiled:parboiled-core:1.1.6">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-core/jars/parboiled-core-1.1.6.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-core/bundles/parboiled-core-1.1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-core/docs/parboiled-core-1.1.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-core/srcs/parboiled-core-1.1.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/.idea/libraries/SBT__org_parboiled_parboiled_scala_2_11_1_1_6.xml b/.idea/libraries/SBT__org_parboiled_parboiled_scala_2_11_1_1_6.xml
new file mode 100644
index 0000000..fd53a7a
--- /dev/null
+++ b/.idea/libraries/SBT__org_parboiled_parboiled_scala_2_11_1_1_6.xml
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+ <library name="SBT: org.parboiled:parboiled-scala_2.11:1.1.6">
+ <CLASSES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-scala_2.11/bundles/parboiled-scala_2.11-1.1.6.jar!/" />
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-scala_2.11/jars/parboiled-scala_2.11-1.1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-scala_2.11/docs/parboiled-scala_2.11-1.1.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$USER_HOME$/.ivy2/cache/org.parboiled/parboiled-scala_2.11/srcs/parboiled-scala_2.11-1.1.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component> \ No newline at end of file
diff --git a/book/src/main/scala/book/BookData.scala b/book/src/main/scala/book/BookData.scala
index 2f92c6c..d927b5f 100644
--- a/book/src/main/scala/book/BookData.scala
+++ b/book/src/main/scala/book/BookData.scala
@@ -1,7 +1,8 @@
package book
import acyclic.file
-
+import scalatags.Text.TypedTag
+import scalatags.Text.all._
object BookData {
lazy val javaAPIs = {
import java.io.File
@@ -28,5 +29,17 @@ object BookData {
filename -> docpath
}
}
+ var counter = 0
+ def example(t: TypedTag[String], main: String) = {
+ val tagId = "example"+counter
+ counter += 1
+ Seq(
+ t(id:=tagId, display.block),
+ script(s"$main(document.getElementById('$tagId'))")
+ )
+ }
+ def split = div(cls:="pure-g")
+ def more = div(cls:="pure-u-1 pure-u-md-13-24")
+ def less = div(cls:="pure-u-1 pure-u-md-11-24")
+ def half = div(cls:="pure-u-1 pure-u-md-1-2")
}
-
diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala
index 6770e31..171d0ad 100644
--- a/book/src/main/scala/book/Utils.scala
+++ b/book/src/main/scala/book/Utils.scala
@@ -171,14 +171,27 @@ object hl{
val whitespace = indent(lines(startLine))
val endLine = lines.indexWhere(
line => line.contains(end) || (indent(line) < whitespace && line.trim != ""),
- startLine
+ startLine + 1
)
val sliced =
if (endLine == -1) lines.drop(startLine)
else lines.slice(startLine, endLine)
+
val blob = sliced.map(_.drop(whitespace)).mkString("\n")
- pre(code(cls:=lang + " highlight-me", blob))
+ pre(
+ code(cls:=lang + " highlight-me", blob),
+ a(
+ cls:="header-link",
+ i(cls:="fa fa-link "),
+ position.absolute,
+ right:="0.5em",
+ bottom:="0.5em",
+ display.block,
+ fontSize:="24px",
+ href:="#"
+ )
+ )
}
} \ No newline at end of file
diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex
index c834bb0..d473239 100644
--- a/book/src/main/scalatex/book/Index.scalatex
+++ b/book/src/main/scalatex/book/Index.scalatex
@@ -1,12 +1,11 @@
-
+@import BookData._
@sect("Hands-on Scala.js", "Writing client-side web applications in Scala")
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/Splash.scala", "var x")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @canvas(id:="canvas1", display:="block")
- @script("Splash().main(document.getElementById('canvas1'))")
+ @less
+ @BookData.example(canvas, "Splash().main")
@p
@lnk("Scala.js", "http://www.scala-js.org/") is a compiler that compiles Scala source code to equivalent Javascript code. That lets you write Scala code that you can run in a web browser, or other environments (Chrome plugins, Node.js, etc.) where Javascript is supported.
@@ -15,8 +14,12 @@
This book contains something for all levels of experience with Scala.js: absolute beginners can get started with the @sect.ref{Intro to Scala.js} and @sect.ref{Hands On} tutorial, people who have used it before can skip ahead to the later parts of the tutorial: @sect.ref{Making a Canvas App} or @sect.ref{Interactive Web Pages}. Intermediate users will find interest in the chapters on @sect.ref{Cross Publishing Libraries} with Scala.js or @sect.ref{Integrating Client-Server}, and even experienced users will find the @sect.ref{In Depth} documention useful.
@p
+ Many of the code samples are taken from examples available on the book's @lnk("Github Page", "https://github.com/lihaoyi/scala-js-book"); for those code samples (e.g. the animation above), there is a link in the bottom-right corner of the snippet that you can click on to go to the original code. These come in handy if you find you need additional context around the snippet, and find yourself asking questions like "what imports do I need for this to work" or "I need the working example in its entirety to see what I'm doing wrong".
+
+ @p
This book does not spend time on pontifying a philosophy or ideology behind Scala.js or Scala. It instead spends its words on hands-on tutorials and in-depth dives into parts of the Scala.js platform, to try and get you acquainted with Scala.js as soon as possible, so you can make your own decisions about its merits or qualities.
+
@sect("Intro to Scala.js")
@Intro()
diff --git a/book/src/main/scalatex/book/Intro.scalatex b/book/src/main/scalatex/book/Intro.scalatex
index 33e0188..26eed70 100644
--- a/book/src/main/scalatex/book/Intro.scalatex
+++ b/book/src/main/scalatex/book/Intro.scalatex
@@ -1,8 +1,9 @@
+@import BookData._
@p
Scala.js compiles Scala code to equivalent, executable Javascript. Here's the compilation of a trivial hello-world example:
-@div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-1-2")
+@split
+ @half
@hl.scala
object Example extends js.JSApp{
def main() = {
@@ -12,7 +13,7 @@
}
}
- @div(cls:="pure-u-1 pure-u-md-1-2")
+ @half
@hl.javascript
ScalaJS.c.LExample$.prototype.main__V = (function() {
var x = 0;
diff --git a/book/src/main/scalatex/book/handson/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
index 662f541..41be815 100644
--- a/book/src/main/scalatex/book/handson/CanvasApp.scalatex
+++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
@@ -1,4 +1,4 @@
-
+@import BookData._
@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.
@@ -29,13 +29,12 @@
@p
Next, let's set some event handlers on the canvas:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*code*/")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @canvas(id:="canvas2", display:="block")
- @script("ScratchPad().main(document.getElementById('canvas2'))")
+ @less
+ @BookData.example(canvas, "ScratchPad().main")
@p
This code sets up the @lnk.dom.mousedown and @lnk.dom.mouseup events to keep track of whether or not the mouse has currently been clicked. It then draws black squares any time you move the mouse while the button is down. This lets you basically click-and-drag to draw pictures on the canvas. Try it out!
@@ -64,13 +63,12 @@
@p
Once that's done, it's only a few lines of code to set up a nice, live clock:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/canvasapp/Clock.scala", "/*code*/")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @canvas(id:="canvas3", display:="block")
- @script("Clock().main(document.getElementById('canvas3'))")
+ @less
+ @BookData.example(canvas, "Clock().main")
@p
As you can see, we're using more @lnk("Canvas APIs", "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D"), in this case dealing with rendering text on the canvas. Another thing we're using is the Javascript @lnk("Date", "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date") class, in Scala.js under the full name @hl.scala{scala.scalajs.js.Date}, here imported as @hl.scala{js.Date}.
@@ -152,8 +150,7 @@
At almost 100 lines of code, this is quite a meaty example! Nonetheless, when all is said and done, you will find that the example actually works! Try it out!
@div
- @canvas(id:="canvas4", display:="block")
- @script("FlappyLine().main(document.getElementById('canvas4'))")
+ @BookData.example(canvas, "FlappyLine().main")
@sect{Canvas Recap}
@p
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
index 3e30d42..ca8db07 100644
--- a/book/src/main/scalatex/book/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -104,10 +104,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")""", "extract")
- @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", "val")
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "val")
+ @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", "")
@p
Three times on the server and once on the client! What's worse, two of the appearances of @i{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 wide-open opportunities for error:
@@ -130,13 +130,13 @@
@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/server/src/main/scala/simple/Server.scala", """path("ajax" / "list")""", "extract")
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "val")
+ @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", """path("ajax" / "list")""", "")
+ @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
@p
Into a single, type-checked function call:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", ".call()", "outputBox")
+ @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", ".call()", "")
@p
Let's see how we can do that.
@@ -174,10 +174,10 @@
@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")
+ @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/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.
+ @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.
@p
Next, let's look at the modified client code:
@@ -187,7 +187,7 @@
@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", "object")
+ @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/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.
@@ -195,7 +195,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", "inputBox")
+ @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", "def update", "")
@p
There are a few things of note here:
diff --git a/book/src/main/scalatex/book/handson/PublishingModules.scalatex b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
index 7c55cd0..05aa87f 100644
--- a/book/src/main/scalatex/book/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
We've spent several chapters exploring the experience of making web apps using Scala.js, but any large app (web or not!) likely relies on a host of libraries in order to implement large chunks of its functionality. Ideally these libraries would be re-usable, and can be shared among different projects, teams or even companies.
@@ -54,11 +55,11 @@
@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/}:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-1-2")
+ @split
+ @half
@hl.ref("examples/crossBuilds/simple/js/src/main/scala/simple/Platform.scala")
- @div(cls:="pure-u-1 pure-u-md-1-2")
+ @half
@hl.ref("examples/crossBuilds/simple/jvm/src/main/scala/simple/Platform.scala")
@p
diff --git a/book/src/main/scalatex/book/handson/WebPage.scalatex b/book/src/main/scalatex/book/handson/WebPage.scalatex
index 2bcdf1c..8fbe1e8 100644
--- a/book/src/main/scalatex/book/handson/WebPage.scalatex
+++ b/book/src/main/scalatex/book/handson/WebPage.scalatex
@@ -1,4 +1,4 @@
-
+@import BookData._
@p
Most web applications aren't neat little games which live on a single canvas: they are large, structured HTML pages, which involve displaying data (whether from the user or from the web) in multiple ways, while allowing the user to make changes to the data that can be saved back to whatever remote web-service/database it came from.
@@ -11,13 +11,12 @@
@p
The most basic way of building interactive web pages using Scala.js is to use the Javascript APIs to blat HTML strings directly into some container div, often @code{document.body}. This approach works, as the following code snippet demonstrates:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/HelloWorld0.scala")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div1")
- @script("HelloWorld0().main(document.getElementById('div1'))")
+ @less
+ @BookData.example(div, "HelloWorld0().main")
@p
This approach works, as the above example shows, but has a couple of disadvantages:
@@ -40,13 +39,12 @@
@p
With that, the above snippet of code re-written using Scalatags looks as follows:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/HelloWorld1.scala")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div2")
- @script("HelloWorld1().main(document.getElementById('div2'))")
+ @less
+ @BookData.example(div, "HelloWorld1().main")
@p
Scalatags has some nice advantages over plain HTML: it's type-safe, so typos like @hl.scala{dvi} get caught at compile-time. It's also secure, such that you don't need to worry about script-tags in strings or similar. The @lnk("Scalatags Readme", "https://github.com/lihaoyi/scalatags#scalatags") elaborates on these points and other advantages. As you can see, it takes just 1 import at the top of the file to bring it in scope, and then you can use all of Scalatags' functionality.
@@ -55,13 +53,12 @@
The Scalatags github page has @lnk("comprehensive documentation", "https://github.com/lihaoyi/scalatags#hello-world") on how to express all manner of HTML fragments using Scalatags, so anyone who's familiar with how HTML works can quickly get up to speed. Instead of a detailed listing, we'll walk through some interactive examples to show Scalatags in action!
@sect{User Input}
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Inputs.scala", "val box")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div3")
- @script("Inputs().main(document.getElementById('div3'))")
+ @less
+ @BookData.example(div, "Inputs().main")
@p
In Scalatags, you build up fragments of type @hl.scala{Frag} using functions like @hl.scala{div}, @hl.scala{h1}, etc., and call @hl.scala{.render} on it to turn it into a real @lnk.dom.Element. Different fragments render to different things: e.g. @hl.scala{input.render} gives you a @lnk.dom.HTMLInputElement, @hl.scala{span.render} gives you a @lnk.dom.HTMLSpanElement. You can then access the properties of these elements: adding callbacks, checking their value, anything you want.
@@ -89,24 +86,22 @@
@p
Lastly, we just need to define the input box and output-container (as we did earlier), set the @lnk.dom.onkeyup event handler, and place it in a larger fragment, and then into our target:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Search0.scala", "val output")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div4")
- @script("Search0().main(document.getElementById('div4'))")
+ @less
+ @BookData.example(div, "Search0().main")
@p
And there you have it! A working search box. This is a relatively self-contained example: all the items its searching are available locally, no Ajax calls, and there's no fancy handling of the searched items. If we want to, for example, highlght the matched section of each fruit's name, we can modify the @hl.scala{def renderListings} call to do so:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Search1.scala", "def renderListings", "lazy val")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div5")
- @script("Search1().main(document.getElementById('div5'))")
+ @less
+ @BookData.example(div, "Search1().main")
@p
Here, instead of sticking the name of the matched fruits directly into the @hl.scala{li}, we instead first split off the part which matches the query, and then highlght the first section yellow. Easy!
@@ -133,27 +128,24 @@
One half of the web application faces forwards towards the user, managing and rendering HTML or Canvas for the user to view and interact with. Another half faces backwards, talking to various web-services or databases which turn the application from a standalone-widget into part of a greater whole. We've already seen how to make the front half, let's now talk about working with the back half.
@sect{Raw Javascript}
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Weather0.scala", "val xhr")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div6")
- @script("Weather0().main(document.getElementById('div6'))")
+ @less
+ @BookData.example(div, "Weather0().main")
@p
The above snippet of code uses the raw Javascript Ajax API in order to make a request to @lnk("openweathermap.org", "http://openweathermap.org/"), to get the weather data for the city of Singapore as a JSON blob. The part of the API that we'll be using is documented @lnk("here", "http://openweathermap.org/current"). For now, we're unceremoniously dumping it in a @hl.scala{pre} so you can see the raw response data.
@p
As you can see, using the raw Javascript API to make the Ajax call looks almost identical to actually doing this in Javascript, shown below:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/resources/webpage/weather.js", "var xhr")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div7")
- @script("WeatherJs(document.getElementById('div7'))")
- @script("WeatherJs(document.getElementById('div7'))")
+ @less
+ @BookData.example(div, "WeatherJs")
@p
The primary syntactic differences are:
@@ -184,13 +176,12 @@
@p
Then we need the code itself:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Weather1.scala", "val url")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div8")
- @script("Weather1().main(document.getElementById('div8'))")
+ @less
+ @BookData.example(div, "Weather1().main")
@p
A single call to @hl.scala{Ajax.get(...)}, with the URL, and we receive a @hl.scala{scala.concurrent.Future} that we can use to get access to the result when ready. Here we're just using it's @hl.scala{onSuccess}, but we could use it in a for-comprehension, with @lnk("Scala Async", "https://github.com/scala/async"), or however else we can use normal @hl.scala{Future}s
@@ -202,13 +193,12 @@
@p
First, let's make the call prettyprint the document, so at least we can see what it contains:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Weather2.scala", "Ajax.get")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div9")
- @script("Weather2().main(document.getElementById('div9'))")
+ @less
+ @BookData.example(div, "Weather1().main")
@p
We do this by taking @hl.scala{xhr.responseText} and putting it through both @hl.scala{JSON.parse} and @hl.scala{JSON.stringify}, passing in a @hl.scala{space} argument to tell @hl.scala{JSON.stringify} to spread it out nicely.
@@ -216,13 +206,12 @@
@p
Now that we've pretty-printed it, we can immediately see what data it contains and which part of the data we want. Let's change the previous example's @hl.scala{onSuccess} call to extract the @hl.scala{weather}, @hl.scala{temp} and @hl.scala{humidity} and put them in a nice, human-friendly format for us to enjoy:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/webpage/Weather3.scala", "Ajax.get")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div10")
- @script("Weather3().main(document.getElementById('div10'))")
+ @less
+ @BookData.example(div, "Weather3().main")
@p
First we parse the incoming response, extract a bunch of values from it, and then stick it in a Scalatags fragment for us to see. Note how we can use the names of the attributes e.g. @hl.scala{json.name} even though @hl.scala{name} is a dynamic property which you can't be sure exists: this is because @hl.scala{json} is of type @hl.scala{js.Dynamic}, which allows us to refer to arbitrary parameters and methods on the underlying object without type-checking.
@@ -252,8 +241,7 @@
@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.
- @div(id:="div11")
- @script("WeatherSearch().main(document.getElementById('div11'))")
+ @BookData.example(div, "WeatherSearch().main")
@p
And that's the working example! Try searching for cities like "Singapore" or "New York" or "San Francisco" and watch as the search narrows as you enter more characters into the text box. Note that the OpenWeatherMap API limits ambiguous searches to about a dozen results, so if a city doesn't turn up in a partial-search, try entering more characters to narrow it down.
diff --git a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
index ae56fa3..7728293 100644
--- a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
@sect.ref{Getting Started} walks you through how to set up some basic Scala.js applications, but that only scratches the surface of the things you can do with Scala.js. Apart from being able to use the same techniques you're used to in Scala-JVM in the browser, Scala.js opens up a whole range of possibilities and novel techniques that are not found in typical Scala-JVM applications.
@@ -77,13 +78,12 @@
@p
Now that the set-up is out of the way, let's consider a simple HTML widhet that lets you enter text in a @hl.html{<textarea>}, and keeps track of the number of words, characters, and counts how long each word is.
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "val txt =")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div19", display:="block")
- @script("BasicRx().main(document.getElementById('div19'))")
+ @less
+ @example(div, "BasicRx().main")
@p
This snippet sets up a basic data-flow graph. We have our @hl.scala{txt} @hl.scala{Var}, and a bunch of @hl.scala{Rx}s (@hl.scala{numChars}, @hl.scala{numWords}, @hl.scala{avgWordLength}) that are computed based on @hl.scala{txt}.
@@ -102,13 +102,12 @@
That was a pretty simple example to get you started with a simple Scala.Rx application. Let's look at a more meaty example to see how we can use Scala.Rx to help structure our interactive web application:
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/advanced/BasicRx.scala", "val fruits =")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @div(id:="div20", display:="block")
- @script("BasicRx().main2(document.getElementById('div20'))")
+ @less
+ @example(div, "BasicRx().main2")
@p
This is a basic re-implementation of the autocomplete widget we created in the chapter @sect.ref{Interactive Web Pages}, except done using Scala.Rx. Note that unlike the original implementation, we don't need to manage the clearing of the output area via @hl.scala{innerHTML = ""} and the re-rendering via @hl.scala{appendChild(...)}. All this is handled by the same @hl.scala{rxFrag} code we wrote earlier.
@@ -132,7 +131,7 @@
On Scala.js, things are different: multi-threaded concurrency is a non-starter, since Javascript engines are all single-threaded. As a result, there are virtually no blocking APIs in Javascript: all operations need to be asynchronous if you don't want them to freeze the user interface of the browser while the operation is happening. Scala.js uses standard Javascript APIs and is no different.
@p
- However, Scala.js has much more powerful tools to work with than your typical Javascript libraries. The Scala standard library comes wiith a rich API for Futures and Promises, which are thankfully 100% asynchronous. Though this design was chosen for performance on the JVM, it perfectly fits our 100% asynchronous Javascript APIs. We have tools like Scala-Async, which works perfectly with Scala.js, and lets you create asynchronous computations in a much less confusing manner.
+ However, Scala.js has much more powerful tools to work with than your typical Javascript libraries. The Scala standard library comes with a rich API for @sect.ref{Futures & Promises}, which are thankfully 100% asynchronous. Though this design was chosen for performance on the JVM, it perfectly fits our 100% asynchronous Javascript APIs. We have tools like @sect.ref{Scala-Async}, which works perfectly with Scala.js, and lets you create asynchronous computations in a much less confusing manner.
@sect{Futures & Promises}
@p
@@ -178,15 +177,13 @@
@p
Overall, these helper functions do nothing special, btu we're defining them first to avoid having to copy-&-paste code throughout the subsequent examples. Now that we've defined all the relevant scaffolding, let's walk through a few ways that we can implement the all-important @hl.scala{handle} method.
+ @def scrollDiv = div(
+ height:="200px",
+ overflow:="scroll"
+ )
@sect{Direct Use of XMLHttpRequest}
- @div(
- id:="div123",
- display:="block",
- height:="200px",
- overflow:="scroll"
- )
- @script("Futures().main0(document.getElementById('div123'))")
+ @example(scrollDiv, "Futures().main0")
@hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle0", "main")
@p
@@ -198,13 +195,7 @@
This solution is basically equivalent to the initial code given in the @sect.ref{Raw Javascript} section of @sect.ref{Interactive Web Pages}, with the additional code necessary for aggregation. As described in @sect.ref{dom.extensions}, we can make use of the @hl.scala{Ajax} object to make it slightly tidier.
@sect{Using dom.extensions.Ajax}
- @div(
- id:="div124",
- display:="block",
- height:="200px",
- overflow:="scroll"
- )
- @script("Futures().main1(document.getElementById('div124'))")
+ @example(scrollDiv, "Futures().main1")
@hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle1", "main")
@p
@@ -213,13 +204,7 @@
However, we still have the messiness inherent in the result aggregation: we don't actually want to perform our action (writing to the @hl.scala{output} div) when one @hl.scala{Future} is complete, but only when @i{all} the @hl.scala{Future}s are complete. Thus we still need to do some amount of manual book-keeping in the @hl.scala{results} buffer.
@sect{Future Combinators}
- @div(
- id:="div125",
- display:="block",
- height:="200px",
- overflow:="scroll"
- )
- @script("Futures().main2(document.getElementById('div125'))")
+ @example(scrollDiv, "Futures().main2")
@hl.ref("examples/demos/src/main/scala/advanced/Futures.scala", "def handle2", "main")
@p
@@ -261,19 +246,18 @@
@p
Let's look at a traditional implementation, using Scala.js but no special features. We'll just use the Javascript @hl.scala{canvas.onmouveXXX} operations directly.
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "// traditional")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @canvas(id:="canvas211", display:="block")
- @script("Async().main0(document.getElementById('canvas211'))")
+ @less
+ @example(canvas, "Async().main0")
@p
- This is a working implementation, and you can play with it on the right. We basically set the three listeners:
+ This is a working implementation, and you can play with it on the right. We basically set the three listeners:
@ul
@li
- @hl.scala{canvas.onmousemove}
+ @hl.scala{canvas.onmousemove}
@li
@hl.scala{canvas.onmousedown}
@li
@@ -295,14 +279,12 @@
@p
Now we've seen what a "traditional" approach looks like, let's look at how we would do this using Scala-Async.
- @div(cls:="pure-g")
- @div(cls:="pure-u-1 pure-u-md-13-24")
+ @split
+ @more
@hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "// async")
- @div(cls:="pure-u-1 pure-u-md-11-24")
- @canvas(id:="canvas222", display:="block")
- @script("Async().main(document.getElementById('canvas222'))")
-
+ @less
+ @example(canvas, "Async().main")
@p
We have an @hl.scala{async} block, which contains a while loop. Each round around the loop, we wait for the @hl.scala{mousedown} channel to start the path, waiting for either @hl.scala{mousedown} or @hl.scala{mousedown} (which continues the path or ends it respectively), fill the shape, and then wait for another @hl.scala{mousedown} before clearing the canvas and going again.
diff --git a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
index 96cbb8b..48c794c 100644
--- a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
+++ b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
@@ -95,7 +95,16 @@
@b{Compilation}: @code{.scala} files to @code{.js} files
@p
- But produced far larger (20mb) and slower executables. This section will explore each stage and we'll learn what these stages do.
+ But produced far larger (20mb) and slower executables. This section will explore each stage and we'll learn what these stages do, starting with a small example program:
+
+ @hl.scala
+ def main() = {
+ var x = 0
+ while(x < 999){
+ x = x + "2".toInt
+ }
+ println(x)
+ }
@sect{Compilation}
@p
@@ -136,8 +145,39 @@
This is the only phase in the Scala.js compilation pipeline that separate compilation is possible: you can compile many different sets of Scala.js @code{.scala} files separately, only to combine them later. This is used e.g. for distributing Scala.js libraries as Maven Jars, which are compiled separately by library authors to be combined into a final executable later.
@sect{Fast Optimization}
+ @hl.javascript
+ ScalaJS.c.LExample$.prototype.main__V = (function() {
+ var x = 0;
+ while ((x < 999)) {
+ x = ((x + new ScalaJS.c.sci_StringOps().init___T(
+ ScalaJS.m.s_Predef().augmentString__T__T("2")
+ ).toInt__I()) | 0)
+ };
+ ScalaJS.m.s_Predef().println__O__V(x)
+ });
+
+ @p
+ This phase is a whole-program optimization of the @code{.sjsir} files, and lives in the @lnk("tools/", "https://github.com/scala-js/scala-js/tree/master/tools") folder of the Scala.js repository. The end result is a rough translation of Scala into the equivalent Javascript (e.g. above):
+
+ @ul
+ @li
+ Scala-style method @hl.scala{def}s become Javascript-style prototype-function-assignment
+ @li
+ Scala @hl.scala{var}s become Javascript @hl.scala{var}s
+ @li
+ Scala @hl.scala{while}s become Javascript @hl.scala{while}s
+ @li
+ Implicits are materialized, hence all the @hl.scala{StringOps} and @hl.scala{augmentString} extensions are present in the output
+ @li
+ Classes and methods are fully-qualified, e.g. @hl.scala{println} becomes @hl.scala{Predef().println}
+ @li
+ Method names are qualified by their types, e.g. @hl.scala{__O__V} means that @hl.scala{println} takes @hl.scala{Object} and returns @hl.scala{void}
+
+ @p
+ This is an incomplete description of the translation, but it should give a good sense of how the translation from Scala to Javascript looks like. In general, the output is verbose but straightforward.
+
@p
- This phase is a whole-program optimization of the @code{.sjsir} files, and lives in the @lnk("tools/", "https://github.com/scala-js/scala-js/tree/master/tools") folder of the Scala.js repository. The rough operations that get performed are:
+ In addition to this superficial translation, the optimizer does a number of things which are more subtle and vary from case to case. The rough operations that get performed are:
@ul
@li
@@ -151,10 +191,24 @@
While the input for this phase is the aggregate @code{.sjsir} files from your project and all your dependencies, the output is executable Javascript. This phase usually runs in less than a second, outputs a Javascript blob in the 700kb-1mb range, and is suitable for repeated use during development. This corresponds to the @code{fastOptJS} command in SBT.
@sect{Full Optimization}
+ @hl.javascript
+ be.prototype.main=function(){
+ for(var a=0;999>a;)
+ a=a+(new de).g(S(L(),"2")).ne()|0;
+ ee(); L();
+ var b=F(fe); ge();
+ a=(new he).g(w(a)); b=bc(0,J(q(b,[a])));
+ ie(bc(L(),J(q(F(fe),[je(ke(ge().Vg),b)]))))
+ }
+
@p
The @lnk("Google Closure Compiler", "https://developers.google.com/closure/compiler/") (GCC) is a set of tools that work with Javascript. It has multiple @lnk("levels of optimization", "https://developers.google.com/closure/compiler/docs/compilation_levels"), doing everything from basic whitespace-removal to heavy optimization. It is a old, relatively mature project that is relied on both inside and outside google to optimize the delivery of Javascript to the browser.
+
+ @p
+ Scala.js uses GCC in its most aggressive mode: @lnk("Advanced Optimization", "https://developers.google.com/closure/compiler/docs/api-tutorial3"). GCC spits out a compressed, minified version of the Javascript (above) that @sect.ref{Fast Optimization} spits out: e.g. in the example above, all identifiers have been renamed to short strings, the @hl.javascript{while}-loop has been replaced by a @hl.javascript{for}-loop, and the @hl.scala{println} function has been inlined.
+
@p
- Scala.js uses GCC in its most aggressive mode: @lnk("Advanced Optimization", "https://developers.google.com/closure/compiler/docs/api-tutorial3"). As described in the linked documentation, this performs optimizations such as:
+ As described in the linked documentation, GCC performs optimizations such as:
@ul
@li
diff --git a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
index da17a4f..2adb0be 100644
--- a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
+++ b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
@@ -2,7 +2,7 @@
Scala.js is a relatively large project, and is the result of both an enormous amount of hard work as well as a number of decisions that craft what it's like to program in Scala.js today. Many of these decisions result in marked differences from the behavior of the same code running on the JVM. This chapter explores the reasoning and rationale behind these decisions.
-@sect("Why No Reflection?")
+@sect{Why No Reflection?}
@p
Scala.js prohibits reflection as it makes dead-code elimination difficult, and the compiler relies heavily on dead-code elimination to generate reasonably-sized executables. The chapter on @sect.ref("The Compilation Pipeline") goes into more detail of why, but a rough estimate of the effect of various optimizations on a small application is:
diff --git a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
index 4cd78bb..cf14057 100644
--- a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
+++ b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
@@ -1,3 +1,4 @@
+@import BookData._
@p
Although Scala.js tries very hard to maintain compatibility with Scala-JVM, there are some parts where the two platforms differs. This can be roughly grouped into two things: differences in the libraries available,and differences in the language itself. This chapter will cover both of these facets.
@@ -10,18 +11,39 @@
@p
Float literals are truncated to their (binary) precision. However, output does not truncate to that precision. This can lead to the following behavior (this works as expected when using doubles):
- @hl.scala
- println(13.345f)
- // Scala: 13.345
- // Scala.js: 13.345000267028809
+ @split
+ @half
+ @hl.scala
+ // Scala-JVM
+ > println(13.345f)
+ 13.345
+ @half
+ @hl.scala
+ // Scala.js
+ > println(13.345f)
+ 13.345000267028809
- @sect{Integer division by 0 is undefined}
+ @sect{Int division by 0 is undefined}
@p
Unlike the JVM where dividing an integer type by 0 throws an exception, in Scala.js integer division by 0 is undefined. This allows for efficient implementation of division. Dividing a Double or Float by 0 yields positive or negative infinity as expected.
+ @split
+ @half
+ @hl.scala
+ // Scala-JVM
+ > 10 / 0
+ java.lang.ArithmeticException: / by zero
+ @half
+ @hl.scala
+ // Scala.js
+ > 10 / 0
+ 0
- @sect{isInstanceOf tests are based on value}
+ @p
+ This is a consequence of the eternal trade-off between performance and correctness, as described in the section @sect.ref{Why does error behavior differ?}
+
+ @sect{Primitive isInstanceOf tests are based on value}
@p
Instance tests (and consequently pattern matching) on any of @hl.scala{Byte}, @hl.scala{Short}, @hl.scala{Int}, @hl.scala{Float}, @hl.scala{Double} are based on the value and not the type they were created with. The following are examples:
@@ -51,24 +73,24 @@
// Scala: 1.0
// Scala.js: 1
@p
- This is due to how numeric values are represented at runtime in Scala.js. Use a formatting interpolator if you always want to show decimals:
+ This is due to how numeric values are represented at runtime in Scala.js: @hl.scala{Float}s and @hl.scala{Double}s are raw Javascript @hl.scala{Number}s, and their @hl.scala{toString} behavior follows from that.
+
+ @p
+ Use a formatting interpolator if you always want to show decimals:
@hl.scala
val x = 1.0
println(f"$x%.1f")
- // Scala: 1.0
+ // Scala-JVM: 1.0
// Scala.js: 1.0
+
@sect{Unit}
@p
scala.Unit is represented using JavaScript's undefined. Therefore, calling @hl.scala{toString()} on @hl.scala{Unit} will return @hl.scala{"undefined"} rather than @hl.scala{"()""}.
- @sect{Strings}
- @p
- JavaScript uses UCS-2 for encoding strings and does not support conversion to or from other character sets. As a result, String constructors taking Byte arrays are not supported by Scala.js.
-
@sect{Reflection}
@p
- Java reflection and, a fortiori, Scala reflection, are not supported. There is limited support for @lnk("java.lang.Class", "https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html"), e.g., obj.getClass.getName will work for any Scala.js object (not for objects that come from JavaScript interop).
+ Java reflection and Scala reflection, are not supported. There is limited support for @lnk("java.lang.Class", "https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html"), e.g., obj.getClass.getName will work for any Scala.js object (not for objects that come from JavaScript interop). Reflection makes it difficult to perform the optimizations that Scala.js heavily relies on. For a more detailed discussion on this topic, take a look at the section @sect.ref{Why No Reflection?}.
@sect{Exceptions}
@p
@@ -89,10 +111,11 @@
This sometimes has an impact on functions in the Scala library that use regular expressions themselves. A list of known functions that are affected is given here:
@ul
@li
- StringLike.split(x: Array[Char]) (see issue #105)
+ @hl.scala{StringLike.split(x: Array[Char])} (see issue #105)
+
@sect{Symbols}
@p
- scala.Symbol is supported, but is a potential source of memory leaks in applications that make heavy use of symbols. The main reason is that JavaScript does not support weak references, causing all symbols created by Scala.js tow remain in memory throughout the lifetime of the application.
+ @hl.scala{scala.Symbol} is supported, but is a potential source of memory leaks in applications that make heavy use of symbols. The main reason is that JavaScript does not support weak references, causing all symbols created by Scala.js tow remain in memory throughout the lifetime of the application.
@sect{Enumerations}
@p
@@ -139,6 +162,7 @@
("JavaScript libraries: chipmunk.js, hand.js, react.js, jquery", "Java ecosystem: guice, junit, apache-commons, log4j"),
("IntelliJ, Eclipse, SBT, Chrome console, firebug", "Scala REPL, Yourkit, VisualVM, JProfiler")
)
+
@p
Scala.js differs from Scala-JVM not just in the corner-cases of the language, but also in the libraries available. Scala-JVM has access to JVM APIs and the wealth of the Java libraries, while Scala.js has access to Javascript APIs and Javascript libraries. It's also possible to write pure-Scala libraries that run on both Scala.js and Scala-JVM, as detailed @a("here").
@p
diff --git a/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala b/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
index ceb7ac9..6686258 100755
--- a/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
+++ b/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
@@ -48,7 +48,6 @@ class ScalaTexPlugin(val global: Global) extends Plugin {
val shim = s"""
$pkgName
- import BookData._
import scalatags.Text.all._
object $objectName{
def apply() = scalatex.twf("${file.getPath}")