summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-11-02 00:30:33 -0700
committerLi Haoyi <haoyi@dropbox.com>2014-11-02 00:30:33 -0700
commitd00a2e25a9ee527fa39feb04c3137715501e9fda (patch)
tree250e6272708f3f346e2e91ef2f0b76eebfc953b3
parent00746e5c8d1d731d0a0cb581f3bddadde31d0d31 (diff)
downloadhands-on-scala-js-d00a2e25a9ee527fa39feb04c3137715501e9fda.tar.gz
hands-on-scala-js-d00a2e25a9ee527fa39feb04c3137715501e9fda.tar.bz2
hands-on-scala-js-d00a2e25a9ee527fa39feb04c3137715501e9fda.zip
Moved things into folders, sketched out a broad skeleton of the rest of the book
-rw-r--r--book/src/main/scala/book/Utils.scala3
-rw-r--r--book/src/main/scalatex/book/Index.scalatex35
-rw-r--r--book/src/main/scalatex/book/Intro.scalatex9
-rw-r--r--book/src/main/scalatex/book/WebPage.scalatex135
-rw-r--r--book/src/main/scalatex/book/handson/CanvasApp.scalatex (renamed from book/src/main/scalatex/book/CanvasApp.scalatex)2
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex1
-rw-r--r--book/src/main/scalatex/book/handson/CrossModules.scalatex1
-rw-r--r--book/src/main/scalatex/book/handson/GettingStarted.scalatex (renamed from book/src/main/scalatex/book/GettingStarted.scalatex)0
-rw-r--r--book/src/main/scalatex/book/handson/WebPage.scalatex277
-rw-r--r--book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex1
-rw-r--r--book/src/main/scalatex/book/indepth/DesignSpace.scalatex12
-rw-r--r--book/src/main/scalatex/book/indepth/JavascriptInterop.scalatex9
-rw-r--r--book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex1
-rw-r--r--build.sbt2
-rw-r--r--examples/src/main/resources/webpage/weather.js17
-rw-r--r--examples/src/main/scala/webpage/Weather0.scala27
-rw-r--r--examples/src/main/scala/webpage/Weather1.scala30
-rw-r--r--examples/src/main/scala/webpage/Weather2.scala35
-rw-r--r--examples/src/main/scala/webpage/Weather3.scala55
-rw-r--r--examples/src/main/scala/webpage/WeatherSearch.scala90
-rwxr-xr-xscalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala21
21 files changed, 606 insertions, 157 deletions
diff --git a/book/src/main/scala/book/Utils.scala b/book/src/main/scala/book/Utils.scala
index a991ca4..690cee1 100644
--- a/book/src/main/scala/book/Utils.scala
+++ b/book/src/main/scala/book/Utils.scala
@@ -18,7 +18,8 @@ object Utils{
"css/grids-responsive-min.css",
"css/layouts/side-menu.css",
"js/ui.js",
- "example-fastopt.js"
+ "example-fastopt.js",
+ "webpage/weather.js"
)
val manualResources = Seq(
diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex
index af17e0e..a3c77f6 100644
--- a/book/src/main/scalatex/book/Index.scalatex
+++ b/book/src/main/scalatex/book/Index.scalatex
@@ -16,15 +16,32 @@
@sect("Intro to Scala.js")
@Intro.template
-
- @sect("Getting Started")
- @GettingStarted.template
-
- @sect("Making a Canvas App")
- @CanvasApp.template
-
- @sect("Interactive Web Pages")
- @WebPage.template
+ @sect("Hands On")
+ @sect("Getting Started")
+ @handson.GettingStarted.template
+ @sect("Making a Canvas App")
+ @handson.CanvasApp.template
+ @sect("Interactive Web Pages")
+ @handson.WebPage.template
+
+ @sect("Cross-platform Modules")
+ @handson.CrossModules.template
+
+ @sect("Client-Server Integration")
+ @handson.ClientServer.template
+
+ @sect("Scala.js in Depth")
+ @sect("Javascript Interoperability")
+ @indepth.JavascriptInterop.template
+
+ @sect("Scala.js/Scala-JVM Differences")
+ @indepth.SemanticDifferences.template
+
+ @sect("The Scala.js Compilation Pipeline")
+ @indepth.CompilationPipeline.template
+
+ @sect("Scala.js' Design Space")
+ @indepth.DesignSpace.template \ No newline at end of file
diff --git a/book/src/main/scalatex/book/Intro.scalatex b/book/src/main/scalatex/book/Intro.scalatex
index f2cc17f..b7c5268 100644
--- a/book/src/main/scalatex/book/Intro.scalatex
+++ b/book/src/main/scalatex/book/Intro.scalatex
@@ -171,15 +171,8 @@
@p
In general, this section of the book will go much deeper into Scala.js, much more than is necessary to get your first applications built. We will talk about the small number of semantic differences between Scala.js and Scala, details of the foreign-function-interface with Javascript, the various optimization levels and what they do. Nothing pressing or urgently needed, but all very interesting, and worth reading if you want to really understand Scala.js in depth.
- @sect{Why}
@p
- Why should you care about Scala.js in the first place? Why not just write Javascript? As a new, barely-a-year-old project, what does it provide that other more mature compile-to-javascript langauges don't?
-
- @p
- In this section of the book, we will examine the reasons which got me first interested in Scala.js. We'll talk at length about the deficiencies of Javascript, the ubiquity of the browser as an application platform, and the technical characteristics that make this the case.
-
- @p
- Lastly, we will explore the solution space. Scala.js isn't the only game in town, and there are hundreds of over attempts at covering the same ground the Scala.js now attempts to cover. We'll compare and contrast the different approaches to see what Scala.js can bring to the table that other approaches cannot.
+ Lastly, we will explore the solution space. Scala.js isn't the only game in town, and there are hundreds of over attempts at covering the same ground the Scala.js now attempts to cover. We'll compare and contrast the different approaches to see what Scala.js can bring to the table that other approaches cannot. Many decisions have been made that shape what Scala.js is today; why did we make them?
@hr
diff --git a/book/src/main/scalatex/book/WebPage.scalatex b/book/src/main/scalatex/book/WebPage.scalatex
deleted file mode 100644
index cbc665c..0000000
--- a/book/src/main/scalatex/book/WebPage.scalatex
+++ /dev/null
@@ -1,135 +0,0 @@
-
-
-@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.
-
-@p
- At this point, you are already competent at using Scala.js to make basic, self-contained canvas applications. In this chapter, we will cover how to use Scala.js to build the sort of interactive-web-pages that make up the bulk of the modern-day internet. We'll cover how to use powerful libraries that turn front-end development form the typical fragile-mess into a structured, robust piece of software.
-
-@sect{Hello World: HTML}
-
- @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")
- @hl.ref("examples/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'))")
-
-
- @p
- This approach works, as the above example shows, but has a couple of disadvantages:
-
- @ul
- @li
- It is untyped: it is easy to accidentally mistype something, and result in malformed HTML. A typo such as @hl.html{<dvi>} would go un-noticed at build-time. Depending on where the typo happens, it could go un-noticed until the application is deployed, causing subtle bugs that only get resolved much later.
- @li
- It is insecure: @a("Cross-site Scripting", href:="http://en.wikipedia.org/wiki/Cross-site_scripting") is a real thing, and it is easy to forget to escape the values you are putting into your HTML strings. Above they're constants like @hl.scala{"dog"}, but if they're user-defined, you may not notice there is a problem until something like @hl.scala{"<script>...</script>"} sneaks through and your users' accounts & data is compromised.
-
- @p
- There are more, but we won't go deep into the intricacies of these problems. Suffice to say it makes mistakes easy to make and hard to catch, and we have something better...
-
-@sect{Scalatags}
- @p
- @a("Scalatags", href:="https://github.com/lihaoyi/scalatags") is a cross-platform Scala.js/Scala-JVM library that is designed to generate HTML. To use Scalatags, you need to add it as a dependency to your Scala.js SBT project, in the @code{build.sbt} file:
-
- @hl.ref("examples/build.sbt", "com.scalatags")
-
- @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")
- @hl.ref("examples/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'))")
-
- @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 @a("Scalatags Readme", href:="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.
-
- @p
- The Scalatags github page has @a("comprehensive documentation", href:="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")
- @hl.ref("examples/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'))")
-
- @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 @hl.scala{dom.Element}. Different fragments render to different things: e.g. @hl.scala{input.render} gives you a @hl.scala{dom.HTMLInputElement}, @hl.scala{span.render} gives you a @hl.scala{dom.HTMLSpanElement}. You can then access the properties of these elements: adding callbacks, checking their value, anything you want.
-
- @p
- In this example, we render and @hl.scala{input} element and a @hl.scala{span}, wire up the input to set the value of the span whenever you press a key in the input, and then stuff both of them into a larger HTML fragment that forms the contents of our @hl.scala{target} element.
-
- @sect{Re-rendering}
- @p
- Let's look at a slightly longer example. While above we spliced small snippets of text into the DOM, here we are going to re-render entire sections of HTML! The goal of this little exercise is to make a filtering search-box: starting from a default list of items, narrow it down as the user enters text into the box.
-
- @p
- To begin with, let's define our list of items: Fruits!
-
- @hl.ref("examples/src/main/scala/webpage/Search0.scala", "val listings", "def")
-
- @p
- Next, let's think about how we want to render these fruits. One natural way would be as a list, which in HTML is represented by a @hl.html{<ul>} with @hl.html{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hl.scala{def}, because we know up-front we're going to need to re-render this listing as the search query changes. Lastly, we know we want 1 list item for each fruit, but only if the fruit starts with the search query.
-
- @hl.ref("examples/src/main/scala/webpage/Search0.scala", "def renderListings", "lazy val")
-
- @p
- Using a @hl.scala{for}-loop with a filter inside the Scalatags fragment is just normal Scala, since you can nest arbitrary Scala expressions inside a Scalatags snippet. In this case, we're converting both the fruit and the search query to lower case so we can compare them case-insensitively.
-
- @p
- Lastly, we just need to define the input box and output-container (as we did earlier), set the @hl.scala{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")
- @hl.ref("examples/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'))")
-
- @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")
- @hl.ref("examples/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'))")
-
- @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!
-
- @hr
-
- @p
- Hopefully this has given you a good overview of how to do things using Scala.js and Scalatags. I won't go too deep into the various ways you can use Scalatags: the @a("documentation", href:="https://github.com/lihaoyi/scalatags") should cover most of it. Now that you've gone through this experience, it's worth re-iterating a few things you've probably already noticed about Scalatags
-
- @ul
- @li
- It's safe! If you make a trivial syntactic mistake, the compiler will catch it, because Scalatags is plain Scala
- @li
- It's composable! You can easily define fragments and assign them to variables, to be used later. You can break apart large Scalatags fragments the same way you break apart normal code, avoiding the huge monolithic HTML templates that are common in other templating systems.
- @li
- It's Scala! You have the full power of the Scala language to write your fragments. No need to learn special syntax/cases for conditionals or repetitions: you can use plain-old-scala if-elses, for-loops, etc.
-
- @p
- Now that you've gotten a quick overview of the kinds of things you can do with Scalatags, let's move on to the next section of our hands-on tutorial...
-
-@sect{Using Web Services}
-
- @p
- 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.
-
diff --git a/book/src/main/scalatex/book/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
index 2e05682..ba76493 100644
--- a/book/src/main/scalatex/book/CanvasApp.scalatex
+++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex
@@ -143,7 +143,7 @@
This is the function that handles what happens when you're dead. Essentially, we reset all the mutable variables to their initial state, and just count down the @hl.scala{dead} counter until it reaches zero and we're considered alive again.
@sect{A Working Product}
- @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "def run")
+ @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "def run()")
@p
And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @hl.scala{canvas.onclick} handler to make the player jump by tweaking his velocity, and we call @code{setInterval} to run the @hl.scala{run} function every 20 milliseconds.
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -0,0 +1 @@
+TODO \ No newline at end of file
diff --git a/book/src/main/scalatex/book/handson/CrossModules.scalatex b/book/src/main/scalatex/book/handson/CrossModules.scalatex
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/book/src/main/scalatex/book/handson/CrossModules.scalatex
@@ -0,0 +1 @@
+TODO \ No newline at end of file
diff --git a/book/src/main/scalatex/book/GettingStarted.scalatex b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
index ac484a8..ac484a8 100644
--- a/book/src/main/scalatex/book/GettingStarted.scalatex
+++ b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
diff --git a/book/src/main/scalatex/book/handson/WebPage.scalatex b/book/src/main/scalatex/book/handson/WebPage.scalatex
new file mode 100644
index 0000000..1e8cf3f
--- /dev/null
+++ b/book/src/main/scalatex/book/handson/WebPage.scalatex
@@ -0,0 +1,277 @@
+
+
+@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.
+
+@p
+ At this point, you are already competent at using Scala.js to make basic, self-contained canvas applications. In this chapter, we will cover how to use Scala.js to build the sort of interactive-web-pages that make up the bulk of the modern-day internet. We'll cover how to use powerful libraries that turn front-end development form the typical fragile-mess into a structured, robust piece of software.
+
+@sect{Hello World: HTML}
+
+ @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")
+ @hl.ref("examples/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'))")
+
+
+ @p
+ This approach works, as the above example shows, but has a couple of disadvantages:
+
+ @ul
+ @li
+ It is untyped: it is easy to accidentally mistype something, and result in malformed HTML. A typo such as @hl.html{<dvi>} would go un-noticed at build-time. Depending on where the typo happens, it could go un-noticed until the application is deployed, causing subtle bugs that only get resolved much later.
+ @li
+ It is insecure: @a("Cross-site Scripting", href:="http://en.wikipedia.org/wiki/Cross-site_scripting") is a real thing, and it is easy to forget to escape the values you are putting into your HTML strings. Above they're constants like @hl.scala{"dog"}, but if they're user-defined, you may not notice there is a problem until something like @hl.scala{"<script>...</script>"} sneaks through and your users' accounts & data is compromised.
+
+ @p
+ There are more, but we won't go deep into the intricacies of these problems. Suffice to say it makes mistakes easy to make and hard to catch, and we have something better...
+
+@sect{Scalatags}
+ @p
+ @a("Scalatags", href:="https://github.com/lihaoyi/scalatags") is a cross-platform Scala.js/Scala-JVM library that is designed to generate HTML. To use Scalatags, you need to add it as a dependency to your Scala.js SBT project, in the @code{build.sbt} file:
+
+ @hl.ref("examples/build.sbt", "com.scalatags")
+
+ @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")
+ @hl.ref("examples/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'))")
+
+ @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 @a("Scalatags Readme", href:="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.
+
+ @p
+ The Scalatags github page has @a("comprehensive documentation", href:="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")
+ @hl.ref("examples/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'))")
+
+ @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 @hl.scala{dom.Element}. Different fragments render to different things: e.g. @hl.scala{input.render} gives you a @hl.scala{dom.HTMLInputElement}, @hl.scala{span.render} gives you a @hl.scala{dom.HTMLSpanElement}. You can then access the properties of these elements: adding callbacks, checking their value, anything you want.
+
+ @p
+ In this example, we render and @hl.scala{input} element and a @hl.scala{span}, wire up the input to set the value of the span whenever you press a key in the input, and then stuff both of them into a larger HTML fragment that forms the contents of our @hl.scala{target} element.
+
+ @sect{Re-rendering}
+ @p
+ Let's look at a slightly longer example. While above we spliced small snippets of text into the DOM, here we are going to re-render entire sections of HTML! The goal of this little exercise is to make a filtering search-box: starting from a default list of items, narrow it down as the user enters text into the box.
+
+ @p
+ To begin with, let's define our list of items: Fruits!
+
+ @hl.ref("examples/src/main/scala/webpage/Search0.scala", "val listings", "def")
+
+ @p
+ Next, let's think about how we want to render these fruits. One natural way would be as a list, which in HTML is represented by a @hl.html{<ul>} with @hl.html{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hl.scala{def}, because we know up-front we're going to need to re-render this listing as the search query changes. Lastly, we know we want 1 list item for each fruit, but only if the fruit starts with the search query.
+
+ @hl.ref("examples/src/main/scala/webpage/Search0.scala", "def renderListings", "lazy val")
+
+ @p
+ Using a @hl.scala{for}-loop with a filter inside the Scalatags fragment is just normal Scala, since you can nest arbitrary Scala expressions inside a Scalatags snippet. In this case, we're converting both the fruit and the search query to lower case so we can compare them case-insensitively.
+
+ @p
+ Lastly, we just need to define the input box and output-container (as we did earlier), set the @hl.scala{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")
+ @hl.ref("examples/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'))")
+
+ @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")
+ @hl.ref("examples/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'))")
+
+ @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!
+
+ @hr
+
+ @p
+ Hopefully this has given you a good overview of how to do things using Scala.js and Scalatags. I won't go too deep into the various ways you can use Scalatags: the @a("documentation", href:="https://github.com/lihaoyi/scalatags") should cover most of it. Now that you've gone through this experience, it's worth re-iterating a few things you've probably already noticed about Scalatags
+
+ @ul
+ @li
+ It's safe! If you make a trivial syntactic mistake, the compiler will catch it, because Scalatags is plain Scala
+ @li
+ It's composable! You can easily define fragments and assign them to variables, to be used later. You can break apart large Scalatags fragments the same way you break apart normal code, avoiding the huge monolithic HTML templates that are common in other templating systems.
+ @li
+ It's Scala! You have the full power of the Scala language to write your fragments. No need to learn special syntax/cases for conditionals or repetitions: you can use plain-old-scala if-elses, for-loops, etc.
+
+ @p
+ Now that you've gotten a quick overview of the kinds of things you can do with Scalatags, let's move on to the next section of our hands-on tutorial...
+
+@sect{Using Web Services}
+
+ @p
+ 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")
+ @hl.ref("examples/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'))")
+ @p
+ The above snippet of code uses the raw Javascript Ajax API in order to make a request to @a("openweathermap.org", href:="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 @a("here", href:="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")
+ @hl.ref("examples/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'))")
+
+ @p
+ The primary syntactic differences are:
+
+ @ul
+ @li
+ @hl.scala{val}s for immutable data v.s. mutable @hl.javascript{var}s.
+ @li
+ @hl.scala("=>") v.s. @hl.javascript{function} to define the callback.
+ @li
+ Scalatags' @hl.scala{pre} v.s. @hl.javascript{document.createElement}
+
+ @p
+ Overall, they're pretty close, which is a common theme in Scala.js: using Javascript APIs in Scala.js is often as seamless and easy as using them in Javascript itself, and it often looks almost identical.
+
+ @sect{dom.extensions}
+ @p
+ Although the Javascript XMLHttpRequest API is workable, it's kind of awkward and clunky compared to what you're used to in Scala. We create a half-baked object, set some magic properties, and call a magic function, which all has to be done in the correct order or it won't work.
+
+ @p
+ With Scala.js, we provide a simpler API that is more clearly functional. First, you need to import some things into scope:
+
+ @hl.ref("examples/src/main/scala/webpage/Weather1.scala", "import dom", "val url =")
+
+ @p
+ The first import brings in Scala adapters to several DOM APIs, which allow you to use them more idiomatically from Scala. The second brings in an implicit @hl.scala{ExecutionContext} that we'll need to run our asynchronous operations.
+
+ @p
+ Then we need the code itself:
+
+ @div(cls:="pure-g")
+ @div(cls:="pure-u-1 pure-u-md-13-24")
+ @hl.ref("examples/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'))")
+
+ @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 @a("Scala Async", href:="https://github.com/scala/async"), or however else we can use normal @hl.scala{Future}s
+
+ @sect{Parsing the Data}
+ @p
+ We've taken the data-dump from OpenWeatherMap in three different ways, but there's still something missing: we need to actually parse the JSON data to make use of it! Most people don't use their JSON data as strings but as structured documents, querying and extracting only the bits we need.
+
+ @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")
+ @hl.ref("examples/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'))")
+
+ @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.
+
+ @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")
+ @hl.ref("examples/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'))")
+
+ @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.
+
+ @p
+ Calls on @hl.scala{js.Dynamic} resolve directly to javascript property/method references, and will fail at run-time with an exception if used wrongly. This is also why we need to call @hl.scala{.toString} or @hl.scala{.asInstanceOf}on the values before use: without these casts, the compiler can't be sure what kind of value is underneath the @hl.scala{js.Dynamic} type, and so we have to provide it the guarantee that it is what it needs.
+
+@sect{Tying it together: Weather Search}
+ @p
+ At this point we've made a small app that allows us to search from a pre-populated list of words, as well as a small app that lets us query a remote web-service to find the weather in Singapore. The natural thing to do is to put these things together to make a app that will let us search from a list of countries and query the weather in any country we desire. Let's start!
+
+ @hl.ref("examples/src/main/scala/webpage/WeatherSearch.scala", "lazy val box", "def fetchWeather")
+
+ @p
+ This sets up the basics: an input box, an output div, and sets an @hl.scala{onkeyup} that fetches the weather data each time you hit a key. It then renders all these components and sticks them into the @hl.scala{target} div. This is basically the same stuff we saw in the early examples, with minor tweaks e.g. adding a @hl.scala{maxHeight} and @hl.scala{overflowY:="scroll"} to the @hl.scala{output} box in case the output is too large. Whenever we enter something in the box, we call the function @hl.scala{fetchWeather}, which is defined as:
+
+ @hl.ref("examples/src/main/scala/webpage/WeatherSearch.scala", "def fetchWeather", "def showResults")
+
+ @p
+ This is where the actual data fetching happens. It's relatively straightforward: we make an @hl.scala{Ajax.get} request, @hl.scala{JSON.parse} the response, and feed it into the callback function. We're using a slightly different API from earlier: we now have the @hl.scala{"type=like"} flag, which is documented in the @a("OpenWeatherMap API docs", href:="http://openweathermap.org/current#other") to return multiple results for each city whose name matches your query.
+
+ @p
+ Notably, before we re-render the results, we check whether the @hl.scala{query} that was passed in is the same value that's in the @hl.scala{box}. This is to prevent a particularly slow ajax call from finishing out-of-order, potentially stomping over the results of more recent searches. We also check whether the @hl.scala{.list: js.Dynamic} property we want is an instance of @hl.scala{js.Array}: if it isn't, it means we don't have any results to show, and we can skip the whole render-output step.
+
+ @hl.ref("examples/src/main/scala/webpage/WeatherSearch.scala", "def showResults")
+
+ @p
+ Here is the meat and potatoes of this program: every time it gets called with an array of weather-data, we iterate over the cities in that array. It then does a similar sort of data-extraction that we did earlier, putting the results into the @hl.scala{output} div we defined above, including highlighting.
+
+ @div(id:="div11")
+ @script("WeatherSearch().main(document.getElementById('div11'))")
+
+ @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.
+
+@sect{Interactive Web Pages Recap}
+ @p
+ In this chapter, we've explored the basics of how you can use Scala.js to build interactive web pages. The two main contributions are using Scalatags to render HTML in a concise, safe way, and making Ajax calls to external web services. We combined these two capabilities in a small weather-search app that let a user interactively search for the weather in different cities around the world.
+ @p
+ Some things you may have noticed in the process:
+
+ @ul
+ @li
+ Using Scalatags to render HTML fragments, and managing them at runtime with callbacks and getting/setting properties, is really quite nice
+ @li
+ Using @hl.scala{new dom.XMLHttpRequest} to make web requests feels just like the Javascript code to do so
+ @li
+ Using @hl.scala{Ajax.get(...)} and working with the resultant : @hl.scala{Future} feels a lot cleaner than directly using the Javascript API
+
+ @p
+ You're at this point reasonably pro
diff --git a/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/book/src/main/scalatex/book/indepth/CompilationPipeline.scalatex
@@ -0,0 +1 @@
+TODO \ No newline at end of file
diff --git a/book/src/main/scalatex/book/indepth/DesignSpace.scalatex b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
new file mode 100644
index 0000000..121301e
--- /dev/null
+++ b/book/src/main/scalatex/book/indepth/DesignSpace.scalatex
@@ -0,0 +1,12 @@
+@p
+ 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?")
+ TODO
+
+@sect("Why No inline-Javascript?")
+ TODO
+
+@sect("Why does error behavior differ?")
+ TODO \ No newline at end of file
diff --git a/book/src/main/scalatex/book/indepth/JavascriptInterop.scalatex b/book/src/main/scalatex/book/indepth/JavascriptInterop.scalatex
new file mode 100644
index 0000000..5266784
--- /dev/null
+++ b/book/src/main/scalatex/book/indepth/JavascriptInterop.scalatex
@@ -0,0 +1,9 @@
+@sect("Calling Javascript from Scala.js")
+ @sect("js.Dynamic")
+ TODO
+ @sect("Typed Facades")
+ TODO
+@sect("Calling Scala.js from Javascript")
+ TODO
+@sect("Scala.js <-> Javascript Type Mappings")
+ TODO \ No newline at end of file
diff --git a/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/book/src/main/scalatex/book/indepth/SemanticDifferences.scalatex
@@ -0,0 +1 @@
+TODO \ No newline at end of file
diff --git a/build.sbt b/build.sbt
index cb6b97d..d7994be 100644
--- a/build.sbt
+++ b/build.sbt
@@ -40,6 +40,8 @@ lazy val book = Project(
(fastOptJS in (examples, Compile)).value
(artifactPath in (examples, Compile, fastOptJS)).value
},
+ (unmanagedResourceDirectories in Compile) ++=
+ (unmanagedResourceDirectories in (examples, Compile)).value,
scalacOptions in Compile ++= {
val jar = (Keys.`package` in (scalatexPlugin, Compile)).value
val addPlugin = "-Xplugin:" + jar.getAbsolutePath
diff --git a/examples/src/main/resources/webpage/weather.js b/examples/src/main/resources/webpage/weather.js
new file mode 100644
index 0000000..8e95305
--- /dev/null
+++ b/examples/src/main/resources/webpage/weather.js
@@ -0,0 +1,17 @@
+function WeatherJs(target) {
+ var xhr = new XMLHttpRequest()
+
+ xhr.open("GET",
+ "http://api.openweathermap.org/data/" +
+ "2.5/weather?q=Singapore"
+ );
+
+ xhr.onload = function (e) {
+ if (xhr.status == 200) {
+ var pre = document.createElement("pre");
+ pre.textContent = xhr.responseText;
+ target.appendChild(pre);
+ }
+ };
+ xhr.send();
+} \ No newline at end of file
diff --git a/examples/src/main/scala/webpage/Weather0.scala b/examples/src/main/scala/webpage/Weather0.scala
new file mode 100644
index 0000000..4a2c6f0
--- /dev/null
+++ b/examples/src/main/scala/webpage/Weather0.scala
@@ -0,0 +1,27 @@
+package webpage
+
+import org.scalajs.dom
+import org.scalajs.dom.{Node, Element}
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import scalatags.JsDom.all._
+
+@JSExport
+object Weather0 extends{
+ @JSExport
+ def main(target: dom.HTMLDivElement) = {
+ val xhr = new dom.XMLHttpRequest()
+ xhr.open("GET",
+ "http://api.openweathermap.org/" +
+ "data/2.5/weather?q=Singapore"
+ )
+ xhr.onload = (e: dom.Event) => {
+ if (xhr.status == 200) {
+ target.appendChild(
+ pre(xhr.responseText).render
+ )
+ }
+ }
+ xhr.send()
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/scala/webpage/Weather1.scala b/examples/src/main/scala/webpage/Weather1.scala
new file mode 100644
index 0000000..c509cb5
--- /dev/null
+++ b/examples/src/main/scala/webpage/Weather1.scala
@@ -0,0 +1,30 @@
+package webpage
+
+import org.scalajs.dom
+import org.scalajs.dom.{Node, Element}
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import scalatags.JsDom.all._
+
+@JSExport
+object Weather1 extends{
+ @JSExport
+ def main(target: dom.HTMLDivElement) = {
+ import dom.extensions._
+ import scala.scalajs
+ .concurrent
+ .JSExecutionContext
+ .Implicits
+ .runNow
+
+ val url =
+ "http://api.openweathermap.org/" +
+ "data/2.5/weather?q=Singapore"
+
+ Ajax.get(url).onSuccess{ case xhr =>
+ target.appendChild(
+ pre(xhr.responseText).render
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/scala/webpage/Weather2.scala b/examples/src/main/scala/webpage/Weather2.scala
new file mode 100644
index 0000000..aa04059
--- /dev/null
+++ b/examples/src/main/scala/webpage/Weather2.scala
@@ -0,0 +1,35 @@
+package webpage
+
+import org.scalajs.dom
+import org.scalajs.dom.{Node, Element}
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import scalatags.JsDom.all._
+
+@JSExport
+object Weather2 extends{
+ @JSExport
+ def main(target: dom.HTMLDivElement) = {
+ import dom.extensions._
+ import scala.scalajs
+ .concurrent
+ .JSExecutionContext
+ .Implicits
+ .runNow
+
+ val url =
+ "http://api.openweathermap.org/" +
+ "data/2.5/weather?q=Singapore"
+
+ Ajax.get(url).onSuccess{ case xhr =>
+ target.appendChild(
+ pre(
+ js.JSON.stringify(
+ js.JSON.parse(xhr.responseText),
+ space=4
+ )
+ ).render
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/scala/webpage/Weather3.scala b/examples/src/main/scala/webpage/Weather3.scala
new file mode 100644
index 0000000..4dadf94
--- /dev/null
+++ b/examples/src/main/scala/webpage/Weather3.scala
@@ -0,0 +1,55 @@
+package webpage
+
+import org.scalajs.dom
+import org.scalajs.dom.{Node, Element}
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import scalatags.JsDom.all._
+
+@JSExport
+object Weather3 extends{
+ @JSExport
+ def main(target: dom.HTMLDivElement) = {
+ import dom.extensions._
+ import scala.scalajs
+ .concurrent
+ .JSExecutionContext
+ .Implicits
+ .runNow
+
+ val url =
+ "http://api.openweathermap.org/" +
+ "data/2.5/weather?q=Singapore"
+
+ Ajax.get(url).onSuccess{ case xhr =>
+ if (xhr.status == 200) {
+ val json = js.JSON.parse(
+ xhr.responseText
+ )
+ val name = json.name.toString
+ val weather = json.weather
+ .pop()
+ .main
+ .toString
+
+ def celsius(kelvins: js.Dynamic) = {
+ kelvins.asInstanceOf[Double] - 273.15
+ }.toInt
+ val min = celsius(json.main.temp_min)
+ val max = celsius(json.main.temp_max)
+ val humid = json.main.humidity.toString
+ target.appendChild(
+ div(
+ b("Weather in Singapore:"),
+ ul(
+ li(b("Country "), name),
+ li(b("Weather "), weather),
+ li(b("Temp "), min, " - ", max),
+ li(b("Humidity "), humid, "%")
+ )
+ ).render
+ )
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/examples/src/main/scala/webpage/WeatherSearch.scala b/examples/src/main/scala/webpage/WeatherSearch.scala
new file mode 100644
index 0000000..0c8acc9
--- /dev/null
+++ b/examples/src/main/scala/webpage/WeatherSearch.scala
@@ -0,0 +1,90 @@
+package webpage
+
+import org.scalajs.dom
+import dom.extensions._
+import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+import scalatags.JsDom.all._
+
+@JSExport
+object WeatherSearch extends{
+ @JSExport
+ def main(target: dom.HTMLDivElement) = {
+
+ lazy val box = input(
+ `type`:="text",
+ placeholder:="Type here!"
+ ).render
+
+ lazy val output = div(
+ height:="400px",
+ overflowY:="scroll"
+ ).render
+
+ box.onkeyup = (e: dom.Event) => {
+ output.innerHTML = "Loading..."
+ fetchWeather(box.value)
+ }
+
+ target.appendChild(
+ div(
+ h1("Weather Search"),
+ p(
+ "Enter the name of a city to pull the ",
+ "latest weather data from api.openweathermap.com!"
+ ),
+ p(box),
+ hr, output, hr
+ ).render
+ )
+
+ def fetchWeather(query: String) = {
+ val searchUrl =
+ "http://api.openweathermap.org/data/" +
+ "2.5/find?type=like&mode=json&q=" +
+ query
+
+ for{
+ xhr <- Ajax.get(searchUrl)
+ if query == box.value
+ } js.JSON.parse(xhr.responseText).list match{
+ case jsonlist: js.Array[js.Dynamic] =>
+ output.innerHTML = ""
+ showResults(jsonlist, query)
+ case _ =>
+ output.innerHTML = "No Results"
+ }
+ }
+
+ def showResults(jsonlist: js.Array[js.Dynamic], query: String) = {
+ for (json <- jsonlist) {
+ val name = json.name.toString
+ val country = json.sys.country.toString
+ val weather = json.weather.pop().main.toString
+
+ def celsius(kelvins: js.Dynamic) = {
+ kelvins.asInstanceOf[Double] - 273.15
+ }.toInt
+
+ val min = celsius(json.main.temp_min)
+ val max = celsius(json.main.temp_max)
+ val humid = json.main.humidity.toString
+ val (first, last) = name.splitAt(query.length)
+ output.appendChild(
+ div(
+ b(span(first, backgroundColor:="yellow"), last, ", ", country),
+ ul(
+ li(b("Weather "), weather),
+ li(b("Temp "), min, " - ", max),
+ li(b("Humidity "), humid, "%")
+ )
+ ).render
+ )
+ }
+
+ }
+
+
+ }
+} \ No newline at end of file
diff --git a/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala b/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
index 4808661..910639f 100755
--- a/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
+++ b/scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala
@@ -1,5 +1,7 @@
package scalatex
+import java.nio.file.Paths
+
import scala.reflect.internal.util.BatchSourceFile
import scala.reflect.io.VirtualFile
import scala.tools.nsc.{ Global, Phase }
@@ -19,10 +21,15 @@ class ScalaTexPlugin(val global: Global) extends Plugin {
override val runsBefore = List("namer")
val phaseName = "Demo"
-
+ val scalatexRoot = "book/src/main/scalatex"
override def newPhase(prev: Phase) = new GlobalPhase(prev) {
override def run() = {
- for (file <- new java.io.File("book/src/main/scalatex/book").listFiles()) {
+ def recursiveListFiles(f: java.io.File): Array[java.io.File] = {
+ val these = f.listFiles
+ val (dirs, files) = f.listFiles().partition(_.isDirectory)
+ files ++ dirs.flatMap(recursiveListFiles)
+ }
+ for (file <- recursiveListFiles(new java.io.File(scalatexRoot))) {
val txt = io.Source.fromFile(file).mkString
val fakeJfile = new java.io.File(file.getName)
val virtualFile = new VirtualFile(file.getName) {
@@ -32,8 +39,16 @@ class ScalaTexPlugin(val global: Global) extends Plugin {
val unit = new CompilationUnit(sourceFile)
val name = file.getName
val objectName = name.slice(name.lastIndexOf('/'), name.lastIndexOf('.'))
+ val pkgName =
+ Paths.get(scalatexRoot)
+ .relativize(file.getParentFile.toPath)
+ .toString
+ .split("/")
+ .map(s => s"package $s")
+ .mkString("\n")
+
val shim = s"""
- package book
+ $pkgName
import Book._
import Utils.sect
import scalatags.Text.all._