summaryrefslogtreecommitdiff
path: root/book
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-10-30 22:37:51 -0700
committerLi Haoyi <haoyi@dropbox.com>2014-10-30 22:37:51 -0700
commit058e9bac2dd8166051bafa16d51f27ee5856929a (patch)
treeb95f12df96a563ea9629912971d3b4f28b255c69 /book
parente8b38f242876f99966c3d13cefae2f5863c5bb9e (diff)
downloadhands-on-scala-js-058e9bac2dd8166051bafa16d51f27ee5856929a.tar.gz
hands-on-scala-js-058e9bac2dd8166051bafa16d51f27ee5856929a.tar.bz2
hands-on-scala-js-058e9bac2dd8166051bafa16d51f27ee5856929a.zip
web pages
Diffstat (limited to 'book')
-rw-r--r--book/canvas-app.tw18
-rw-r--r--book/getting-started.tw2
-rw-r--r--book/index.tw2
-rw-r--r--book/src/main/scala/book/Book.scala1
-rw-r--r--book/web-page.tw132
5 files changed, 144 insertions, 11 deletions
diff --git a/book/canvas-app.tw b/book/canvas-app.tw
index ba440e4..dc1b113 100644
--- a/book/canvas-app.tw
+++ b/book/canvas-app.tw
@@ -20,7 +20,7 @@
@p
To begin with, lets remove all the existing stuff in our @code{.scala} file and leave only the @hli.scala{object} and the @hli.scala{main} method. Let's start off with some necessary boilerplate:
- @hl.ref("examples/src/main/scala/ScratchPad.scala", "/*setup*/", end = "/*code*/")
+ @hl.ref("examples/src/main/scala/canvasapp/ScratchPad.scala", "/*setup*/", end = "/*code*/")
@p
As described earlier, this code uses the @hli.javascript{document.getElementById} function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page.
@@ -30,7 +30,7 @@
@div(cls:="pure-g")
@div(cls:="pure-u-1 pure-u-md-13-24")
- @hl.ref("examples/src/main/scala/ScratchPad.scala", "/*code*/")
+ @hl.ref("examples/src/main/scala/canvasapp/ScratchPad.scala", "/*code*/")
@div(cls:="pure-u-1 pure-u-md-11-24")
@canvas(id:="canvas2", display:="block")
@@ -55,7 +55,7 @@
@p
Again, we need roughly the same boilerplate as just now to set up the canvas:
- @hl.ref("examples/src/main/scala/Clock.scala", "/*setup*/", "/*code*/")
+ @hl.ref("examples/src/main/scala/canvasapp/Clock.scala", "/*setup*/", "/*code*/")
@p
The only thing unusual here is that I'm going to create a @hli.scala{linearGradient} in order to make the stopwatch look pretty. This is by no means necessary, and you could simply make the @hli.scala{fillStyle} @hli.scala{"black"} if you want to keep things simple.
@@ -65,7 +65,7 @@
@div(cls:="pure-g")
@div(cls:="pure-u-1 pure-u-md-13-24")
- @hl.ref("examples/src/main/scala/Clock.scala", "/*code*/")
+ @hl.ref("examples/src/main/scala/canvasapp/Clock.scala", "/*code*/")
@div(cls:="pure-u-1 pure-u-md-11-24")
@canvas(id:="canvas3", display:="block")
@@ -98,7 +98,7 @@
It's a relatively simple game, but there should be enough "business logic" in here that we won't be simply gluing together APIs. Let's start!
@sect{Setting Up the Canvas}
- @hl.ref("examples/src/main/scala/FlappyLine.scala", "/*setup*/", end="/*variables*/")
+ @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "/*setup*/", end="/*variables*/")
@p
This section of the code is peripherally necessary, but not core to the implementation or logic of Flappy Box. We see the same @hli.scala{canvas}/@hli.scala{renderer} logic we've seen in all our examples, along with some logic to make the canvas a reasonable size, and some configuration of how we will render text to the canvas.
@@ -107,7 +107,7 @@
In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in some helpers, so you don't have to mess with this stuff unless you really want to.
@sect{Defining our State}
- @hl.ref("examples/src/main/scala/FlappyLine.scala", "/*variables*/", end="def runDead")
+ @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "/*variables*/", end="def runDead")
@p
This is where we start defining things that are relevant to Flappy Box. There are roughly two groups of values here: immutable constants in the top group, and mutable variables in the bottom. The rough meaning of each variable is documented in the comments, and we'll see exactly how we use them later.
@@ -116,7 +116,7 @@
One notable thing is that we're using a @hli.scala{collection.mutable.Queue} to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js.
@sect{Game Logic}
- @hl.ref("examples/src/main/scala/FlappyLine.scala", "def runLive", "def runDead")
+ @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "def runLive", "def runDead")
@p
The @hli.scala{runLive} function is the meat of Flappy Box. In it, we
@@ -136,13 +136,13 @@
@p
This function basically contains all the game logic, from motion, to collision-detection, to rendering, so it's pretty large. Not that large though! And entirely understandable, even if it takes a few moments to read through.
- @hl.ref("examples/src/main/scala/FlappyLine.scala", "def runDead", end="def run")
+ @hl.ref("examples/src/main/scala/canvasapp/FlappyLine.scala", "def runDead", end="def run")
@p
This is the function that handles what happens when you're dead. Essentially, we reset all the mutable variables to their initial state, and just count down the @hli.scala{dead} counter until it reaches zero and we're considered alive again.
@sect{A Working Product}
- @hl.ref("examples/src/main/scala/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 @hli.scala{run} function to swap between @hli.scala{runLive} and @hli.scala{runDead}, register an @hli.scala{canvas.onclick} handler to make the player jump by tweaking his velocity, and we call @code{setInterval} to run the @hli.scala{run} function every 20 milliseconds.
diff --git a/book/getting-started.tw b/book/getting-started.tw
index 9b6b9c7..b04d6b4 100644
--- a/book/getting-started.tw
+++ b/book/getting-started.tw
@@ -184,7 +184,7 @@
This is the HTML page which our toy app lives in, and the same page that we have so far been using to view the app in the browser. To anyone who has used HTML, most of it is probably familiar. Things of note are the Script tags: @hli.scala{"../example-fastopt.js"} Is the executable blob spat out by the compiler, which we need to include in the HTML page for anything to happen. This is where the results of your compiled Scala code appear. @hli.scala{"workbench.js"} is the client for the Workbench plugin that connects to SBT, reloads the browser and forwards logspam to the browser console.
@p
- The @hli.scala{ScalaJSExample().main()} call is what kicks off the Scala.js application and starts its execution. Scala.js follows Scala semantics in that @hli.scala{object}s are evaluated lazily, with no top-level code allowed. This is in contrast to Javascript, where you can include top-level statements and object-literals in your code which execute immediately. In Scala.js, nothing happens when @code{../example-fastopt.js} is imported! We have to call the main-method first.
+ The @hli.scala{ScalaJSExample().main()} call is what kicks off the Scala.js application and starts its execution. Scala.js follows Scala semantics in that @hli.scala{object}s are evaluated lazily, with no top-level code allowed. This is in contrast to Javascript, where you can include top-level statements and object-literals in your code which execute immediately. In Scala.js, nothing happens when @code{../example-fastopt.js} is imported! We have to call the main-method first. In this case, we're passing the canvas object (attained using @hli.javascript{getElementById}) to it so it knows where to do its thing.
@p
Lastly, only @hli.scala("@JSExport")ed objects and methods can be called from Javascript. Also, although this example only exports the @hli.scala{main} method which is called once, there is nothing stopping you from exporting any number of objects and methods and calling them whenever you need to. In this way, you can easily make a Scala.js "library" which is available to external Javascript as an API.
diff --git a/book/index.tw b/book/index.tw
index 3cf8df3..c25a711 100644
--- a/book/index.tw
+++ b/book/index.tw
@@ -17,7 +17,7 @@
@Book.intro
@Book.gettingStarted
@Book.canvasApp
-
+ @Book.webPage
diff --git a/book/src/main/scala/book/Book.scala b/book/src/main/scala/book/Book.scala
index c41ab15..10116fe 100644
--- a/book/src/main/scala/book/Book.scala
+++ b/book/src/main/scala/book/Book.scala
@@ -15,6 +15,7 @@ object Book {
lazy val intro = sect("Intro to Scala.js")(twf("book/intro.tw"))
lazy val gettingStarted = sect("Getting Started")(twf("book/getting-started.tw"))
lazy val canvasApp = sect("Canvas App")(twf("book/canvas-app.tw"))
+ lazy val webPage = sect("Interactive Web Pages")(twf("book/web-page.tw"))
val txt = twf("book/index.tw")
val contentBar = {
def rec(current: Node, depth: Int): Seq[Frag] = {
diff --git a/book/web-page.tw b/book/web-page.tw
new file mode 100644
index 0000000..6527340
--- /dev/null
+++ b/book/web-page.tw
@@ -0,0 +1,132 @@
+@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 @hli.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 @hli.scala{"dog"}, but if they're user-defined, you may not notice there is a problem until something like @hli.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 @hli.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 @hli.scala{Frag} using functions like @hli.scala{div}, @hli.scala{h1}, etc., and call @hli.scala{.render} on it to turn it into a real @hli.scala{dom.Element}. Different fragments render to different things: e.g. @hli.scala{input.render} gives you a @hli.scala{dom.HTMLInputElement}, @hli.scala{span.render} gives you a @hli.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 @hli.scala{input} element and a @hli.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 @hli.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 @hli.html{<ul>} with @hli.html{<li>}s inside of it if we wanted the list to be unordered. We'll make it a @hli.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 @hli.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 @hli.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, highlight the matched section of each fruit's name, we can modify the @hli.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 @hli.scala{li}, we instead first split off the part which matches the query, and then highlight 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.
+