summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-11-26 03:31:52 -0800
committerLi Haoyi <haoyi@dropbox.com>2014-11-26 03:31:52 -0800
commit0ae3b5b47a7e1a3a8ae31817bc7b10ca9c054f54 (patch)
tree8f05bf9c9be4640ec4ea005538f2340cae5ee3fc
parentca124817c53d50dc81c7817d37f3c5fc08e0c565 (diff)
downloadhands-on-scala-js-0ae3b5b47a7e1a3a8ae31817bc7b10ca9c054f54.tar.gz
hands-on-scala-js-0ae3b5b47a7e1a3a8ae31817bc7b10ca9c054f54.tar.bz2
hands-on-scala-js-0ae3b5b47a7e1a3a8ae31817bc7b10ca9c054f54.zip
fix all the problems
-rw-r--r--book/src/main/scalatex/book/handson/CommandLine.scalatex8
-rw-r--r--book/src/main/scalatex/book/handson/GettingStarted.scalatex7
-rw-r--r--book/src/main/scalatex/book/handson/PublishingModules.scalatex2
-rw-r--r--book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex2
-rw-r--r--build.sbt38
-rw-r--r--examples/demos/src/main/scala/advanced/Async.scala8
-rw-r--r--examples/demos/src/main/scala/scrollmenu/Controller.scala89
-rw-r--r--examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala18
8 files changed, 106 insertions, 66 deletions
diff --git a/book/src/main/scalatex/book/handson/CommandLine.scalatex b/book/src/main/scalatex/book/handson/CommandLine.scalatex
index 439804c..478292b 100644
--- a/book/src/main/scalatex/book/handson/CommandLine.scalatex
+++ b/book/src/main/scalatex/book/handson/CommandLine.scalatex
@@ -106,7 +106,7 @@
object RunMe extends scala.scalajs.js.JSApp{
def main(): Unit = {
println("Hello World!")
- println("In Scala.js, 1/0 is ${1/0}!")
+ println("In Scala.js, (1.0).toString is ${(1.0).toString}!")
}
}
@@ -115,7 +115,7 @@
@hl.bash
Hello World!
- In Scala.js, 1/0 is 0!
+ In Scala.js, (1.0).toString is 0!
@p
This exhibits the weirdness of integer divide-by-zero in Scala.js, which is one of the few ways in which @sect.ref("Deviations from Scala-JVM", "Scala.js deviates from Scala-JVM"). This also shows us we're really running on Scala.js: on Scala-JVM, integer divide-by-zero throws an exception rather than returning zero!
@@ -129,7 +129,7 @@
@li
@b{Node.js} using @code{sbt fastOptStage::run} or @code{sbt fullOptStage::run}, having installed Node.js separately
@li
- @b{PhantomJS} using @code{sbt fastOptStage::run} or @code{sbt fullOptStage::run}, having installed PhantomJS separately, and turned on @hl.scala{requiresDOM := true} in SBT
+ @b{PhantomJS} using @code{sbt fastOptStage::run} or @code{sbt fullOptStage::run}, having installed PhantomJS separately, and turned on @hl.scala{jsDependencies += RuntimeDOM} in SBT
@p
Typically, the best way to get started is using Rhino and @code{sbt run}, since it's setup-free, and setting up Node.js or PhantomJS later as necessary. The next two sections elaborate on the differences between these ways of running your code. Check out the later sections on @sect.ref{Headless Runtimes} and @sect.ref{Run Configurations} to learn more about the other settings and why you would want to use them.
@@ -152,7 +152,7 @@
@li
@lnk.misc.Nodejs, a relatively new Javascript runtime based on Google's V8 Javascript engine, Node.js lets you run your Scala.js application from the command line much faster than in Rhino, with performance that matches that of modern browsers. However, you need to separately @lnk("install Node.js", "http://nodejs.org/download/") in order to use it. Like Rhino, it comes with a bare-minimal runtime environment, with no DOM or browser-related functionality. You need to run @code{sbt fastOptStage::run} to run using Node.js.
@li
- @lnk.misc.PhantomJS is a headless Webkit browser. This means that unlike Node.js or Rhino, PhantomJS provides you with a full DOM and all its APIs to use in your tests, if you wish to e.g. test interactions with the HTML of the web page. On the other hand, it is somewhat slower than Node.js, though still much faster than Rhino. Like Node.js, it needs to be installed separately. You need to run You need to run @code{sbt fastOptStage::run}, as well as setting the @hl.scala{requiresDOM := true} flag in your SBT configuration, to use PhantomJS.
+ @lnk.misc.PhantomJS is a headless Webkit browser. This means that unlike Node.js or Rhino, PhantomJS provides you with a full DOM and all its APIs to use in your tests, if you wish to e.g. test interactions with the HTML of the web page. On the other hand, it is somewhat slower than Node.js, though still much faster than Rhino. Like Node.js, it needs to be installed separately. You need to run You need to run @code{sbt fastOptStage::run}, as well as setting the @hl.scala{jsDependencies += RuntimeDOM} flag in your SBT configuration, to use PhantomJS.
@p
These are your three options to run your Scala.js code via the command-line. Generally, it's easiest to get started with Rhino since it's the default and requires no setup, though you may find it worthwhile to setup Node or Phantom if you need additional speed or DOM-integration in your runs.
diff --git a/book/src/main/scalatex/book/handson/GettingStarted.scalatex b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
index 571876f..28d04c3 100644
--- a/book/src/main/scalatex/book/handson/GettingStarted.scalatex
+++ b/book/src/main/scalatex/book/handson/GettingStarted.scalatex
@@ -123,9 +123,6 @@
Here we are retrieving a handle to the canvas we will draw on using @hl.scala{document.getElementById}, and from it we can get a @lnk.dom.CanvasRenderingContext2D which we actually use to draw on it.
@p
- @hl.scala{document.getElementById} is the exact same API that's used in normal Javascript, as documented @lnk("here", "https://developer.mozilla.org/en-US/docs/Web/API/document.getElementById"). In fact, the entire @hl.scala{org.scalajs.dom} namespace (imported at the top of the file) comprises statically typed facades for the javascript APIs provided by the browser.
-
- @p
We need to perform the @hl.scala{asInstanceOf} call because depending on what you pass to @hl.scala{getElementById} and @hl.scala{getContext}, you could be returned elements and contexts of different types. Hence we need to tell the compiler explicitly that we're expecting a @lnk.dom.HTMLCanvasElement and @lnk.dom.CanvasRenderingContext2D back from these methods for the strings we passed in.
@hl.ref(cloneRoot + "workbench-example-app/src/main/scala/example/ScalaJSExample.scala", "def run", "dom.setInterval")
@@ -187,6 +184,10 @@
@p
The @hl.scala{ScalaJSExample().main()} call is what kicks off the Scala.js application and starts its execution. Scala.js follows Scala semantics in that @hl.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 @hl.javascript{getElementById}) to it so it knows where to do its thing.
+
+ @p
+ @hl.scala{document.getElementById} is the exact same API that's used in normal Javascript, as documented @lnk("here", "https://developer.mozilla.org/en-US/docs/Web/API/document.getElementById"). In fact, the entire @hl.scala{org.scalajs.dom} namespace (imported at the top of the file) comprises statically typed facades for the javascript APIs provided by the browser.
+
@p
Lastly, only @hl.scala("@JSExport")ed objects and methods can be called from Javascript. Also, although this example only exports the @hl.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/src/main/scalatex/book/handson/PublishingModules.scalatex b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
index 9e742e2..b5a0786 100644
--- a/book/src/main/scalatex/book/handson/PublishingModules.scalatex
+++ b/book/src/main/scalatex/book/handson/PublishingModules.scalatex
@@ -109,7 +109,7 @@
Publish it! Both @code{sbt publishLocal} and @code{sbt publishSigned} work on this module, for publishing either locally, Maven Central via Sonatype, or Bintray. Running the command bare should be sufficient to publish both the @code{js} or @code{jvm} projects, or you can also specify which one e.g. @code{jvm/publishLocal} to publish only one subproject.
@p
- This @code{jvm} project works identically to any other Scala-JVM project, and the @code{js} project works identically to the Command Line API described earlier. Thus you can do things like @code{fastOptStage::run} to run the code on Node.js, setting @hl.scala{requiresDOM := true}, run @code{fullOptStage::run} to run the code with full, aggressive optimizations. And of course, things that work in both Scala.js and Scala-JVM can be run on both, basic commands such as @code{run} or @code{test}.
+ This @code{jvm} project works identically to any other Scala-JVM project, and the @code{js} project works identically to the Command Line API described earlier. Thus you can do things like @code{fastOptStage::run} to run the code on Node.js, setting @hl.scala{jsDependencies += RuntimeDOM}, run @code{fullOptStage::run} to run the code with full, aggressive optimizations. And of course, things that work in both Scala.js and Scala-JVM can be run on both, basic commands such as @code{run} or @code{test}.
@p
You can also run tests using this code, if you have a testing library set up. The next section will go into detail as to how to set that up.
diff --git a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
index 171b2d3..24fc2bd 100644
--- a/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
+++ b/book/src/main/scalatex/book/indepth/AdvancedTechniques.scalatex
@@ -294,7 +294,7 @@
@p
You may be wondering what these @hl.scala{Channel} things are, and where they are coming from. Although these are not provided by Scala, they are pretty straightforward to define ourselves:
- @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "case class Channel")
+ @hl.ref("examples/demos/src/main/scala/advanced/Async.scala", "class Channel")
@p
The point of @hl.scala{Channel} is to allow us to turn event-callbacks (like those provided by the DOM's @hl.scala{onmouseXXX} properties) into some kind of event-stream, that we can listen to asynchronously (via @hl.scala{apply} that returns a @hl.scala{Future}) or merge via @hl.scala{|}. This is a minimal implementation for what we need now, but it would be easy to provide more functionality (filter, map, etc.) as necessary.
diff --git a/build.sbt b/build.sbt
index ba3aa84..d0a2054 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,8 +1,14 @@
+import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.merge.MergeStrategy
+import org.eclipse.jgit.transport.{UsernamePasswordCredentialsProvider, RefSpec}
+
import scala.scalajs.sbtplugin.ScalaJSPlugin._
import ScalaJSKeys._
val cloneRepos = taskKey[Unit]("Clone stuff from github")
+val pushGithub = taskKey[Unit]("Push stuff to github")
val sharedSettings = Seq(
scalaVersion := "2.11.4",
@@ -79,7 +85,7 @@ lazy val book = Project(
localPath.delete()
for ((user, repo) <- paths){
println(s"Cloning $repo...")
- org.eclipse.jgit.api.Git.cloneRepository()
+ Git.cloneRepository()
.setURI(s"https://github.com/$user/$repo")
.setDirectory(localPath / repo)
.call()
@@ -93,6 +99,36 @@ lazy val book = Project(
initialize := {
System.setProperty("clone.root", target.value.getAbsolutePath + "/clones")
System.setProperty("output.root", target.value.getAbsolutePath + "/output")
+ },
+ pushGithub := {
+ val outputRoot = target.value.getAbsolutePath + "/output"
+ val repo = Git.init().setDirectory(new File(outputRoot)).call()
+ val remoteUrl = "https://github.com/lihaoyi/hands-on-scala-js"
+
+ val creds = new UsernamePasswordCredentialsProvider(
+ System.console.readLine("username>"),
+ System.console.readPassword("password>")
+ )
+ repo.add()
+ .addFilepattern(".")
+ .call()
+
+ repo.commit()
+ .setAll(true)
+ .setMessage(".")
+ .call()
+
+ repo.rebase().setUpstream("gh-pages").call()
+ repo.fetch()
+ .setRemote(remoteUrl)
+ .setRefSpecs(new RefSpec("refs/heads/gh-pages:refs/heads/gh-pages"))
+ .call()
+ repo.push()
+ .setRemote(remoteUrl)
+ .setCredentialsProvider(creds)
+ .setRefSpecs(new RefSpec("refs/heads/master:refs/heads/gh-pages"))
+ .call()
+ streams.value.log("Pushing to Github Pages complete!")
}
)
diff --git a/examples/demos/src/main/scala/advanced/Async.scala b/examples/demos/src/main/scala/advanced/Async.scala
index 481e80e..c4d7366 100644
--- a/examples/demos/src/main/scala/advanced/Async.scala
+++ b/examples/demos/src/main/scala/advanced/Async.scala
@@ -29,11 +29,11 @@ object Async {
type ME = dom.MouseEvent
val mousemove =
- Channel[ME](canvas.onmousemove = _)
+ new Channel[ME](canvas.onmousemove = _)
val mouseup =
- Channel[ME](canvas.onmouseup = _)
+ new Channel[ME](canvas.onmouseup = _)
val mousedown =
- Channel[ME](canvas.onmousedown = _)
+ new Channel[ME](canvas.onmousedown = _)
async{
while(true){
@@ -99,7 +99,7 @@ object Async {
}
}
-case class Channel[T](init: (T => Unit) => Unit){
+class Channel[T](init: (T => Unit) => Unit){
init(update)
private[this] var value: Promise[T] = null
def apply(): Future[T] = {
diff --git a/examples/demos/src/main/scala/scrollmenu/Controller.scala b/examples/demos/src/main/scala/scrollmenu/Controller.scala
index 4480445..7fd8c90 100644
--- a/examples/demos/src/main/scala/scrollmenu/Controller.scala
+++ b/examples/demos/src/main/scala/scrollmenu/Controller.scala
@@ -28,53 +28,52 @@ object Controller{
val snippets = dom.document.getElementsByClassName("highlight-me")
snippets.foreach(js.Dynamic.global.hljs.highlightBlock(_))
- def rest() = {
- val scrollSpy = new ScrollSpy(structure, main)
- val list = ul(cls := "menu-item-list collapsed")(
- scrollSpy.domTrees.map(_.value.frag)
+
+ val scrollSpy = new ScrollSpy(structure, main)
+ val list = ul(cls := "menu-item-list collapsed")(
+ scrollSpy.domTrees.map(_.value.frag)
+ ).render
+
+ def updateScroll() = scrollSpy()
+ val expandIcon = i(cls := "fa fa-caret-down").render
+ val expandLink =
+ a(
+ expandIcon,
+ href := "javascript:",
+ marginLeft := "0px",
+ paddingLeft := "15px",
+ paddingRight := "15px",
+ position.absolute,
+ top := "0px",
+ right := "0px",
+ cls := "pure-menu-selected",
+ onclick := { (e: dom.Event) =>
+ expandIcon.classList.toggle("fa-caret-down")
+ expandIcon.classList.toggle("fa-caret-up")
+ list.classList.toggle("collapsed")
+ scrollSpy.clean = !scrollSpy.clean
+ updateScroll()
+ }
+ ).render
+
+
+ menu.appendChild(
+ div(cls := "pure-menu pure-menu-open")(
+ a(cls := "pure-menu-heading")(
+ "Contents", expandLink
+ ),
+ list
).render
+ )
- def updateScroll() = scrollSpy()
- val expandIcon = i(cls := "fa fa-caret-down").render
- val expandLink =
- a(
- expandIcon,
- href := "javascript:",
- marginLeft := "0px",
- paddingLeft := "15px",
- paddingRight := "15px",
- position.absolute,
- top := "0px",
- right := "0px",
- cls := "pure-menu-selected",
- onclick := { (e: dom.Event) =>
- expandIcon.classList.toggle("fa-caret-down")
- expandIcon.classList.toggle("fa-caret-up")
- list.classList.toggle("collapsed")
- scrollSpy.clean = !scrollSpy.clean
- updateScroll()
- }
- ).render
-
-
- menu.appendChild(
- div(cls := "pure-menu pure-menu-open")(
- a(cls := "pure-menu-heading")(
- "Contents", expandLink
- ),
- list
- ).render
- )
-
- menuLink.onclick = (e: dom.MouseEvent) => {
- layout.classList.toggle("active")
- menu.classList.toggle("active")
- menuLink.classList.toggle("active")
- }
-
- main.onscroll = (e: dom.UIEvent) => updateScroll()
- updateScroll()
+ menuLink.onclick = (e: dom.MouseEvent) => {
+ layout.classList.toggle("active")
+ menu.classList.toggle("active")
+ menuLink.classList.toggle("active")
}
- dom.setTimeout(rest _, 10)
+
+ main.onscroll = (e: dom.UIEvent) => updateScroll()
+ updateScroll()
+
}
}
diff --git a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
index 9ce9a2b..7d718e2 100644
--- a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
+++ b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
@@ -61,13 +61,15 @@ class ScrollSpy(structure: Tree[String],
}
- private[this] var scrolling = -1
+ private[this] var scrolling = false
def apply() = {
- dom.clearTimeout(scrolling)
- scrolling = dom.setTimeout(() => start(), 200)
+ if (!scrolling) {
+ scrolling = true
+ dom.requestAnimationFrame((d: Double) => start())
+ }
}
private[this] def start() = {
-// scrolling = false
+ scrolling = false
def scroll(el: dom.Element) = {
val rect = el.getBoundingClientRect()
if (rect.top <= 0)
@@ -96,10 +98,12 @@ class ScrollSpy(structure: Tree[String],
// This means it's the leaf element, because it won but there
// aren't any children which won, so it must be the actual leaf
tree.children.foreach(_.value.frag.classList.remove("selected"))
- if (dom.location.hash != itemId)
- dom.history.pushState(null, null, "#"+itemId)
- scroll(menuItem.children(0))
+ val top = main.scrollTop
+ dom.location.hash = itemId
+ main.scrollTop = top
+
+ scroll(menuItem.children(0))
}
menuItem.children(0).classList.add("pure-menu-selected")
}else{