Copyright © Cay S. Horstmann 2015
This work is licensed under a Creative Commons Attribution 4.0 International License
@JSExport object ScalaJSExample { @JSExport def main(canvas: html.Canvas) { val ctx = canvas.getContext("2d") .asInstanceOf[dom.CanvasRenderingContext2D] ctx.fillRect(0, 0, 100, 100) } }
cxt.fillRect
are typesafe, can use IDE autocompletion@JSExport
This follows the excellent tutorial by Li Haoyi
git clone https://github.com/lihaoyi/workbench-example-app
, or if for whatever reason, you don't have git on your computer, download and unzip this zip file.In a command shell, type
cd workbench-example-app ../activator-1.3.2/activator ~fastOptJS(adjusting the directory paths and version number if necessary)
sbt
. The activator is a superset of the sbt tool.
1. Waiting for source changes... (press enter to interrupt)point your browser to http://localhost:12345/target/scala-2.11/classes/index-dev.html. What happens?
workbench-example-app/project/build.sbt
and add a line
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")to the end. Run
../activator-1.3.2/activator eclipseThen run the Scala IDE and import the project at
workbench-example-app
.Run
../activator-1.3.2/activator ~fastOptJSagain. In the IDE, find the file
src/main/scala/example/ScalaJSExample
. You'll find the source code for the fractal, which isn't very illuminating. Let's do something simpler instead. Change the run
method to
def run = { count += 1 ctx.fillStyle = "green" ctx.fillRect(0, 0, count, 100) }Refresh your browser window. What happens? Why?
for/yield
anywhere. Let's fix that. We want to draw a bar chart, like this:val data = List(2, 3, 5, 7, 11, 13)For now, comment out the line
dom.setInterval(() => run, 50)Add this class
case class Rect(x: Double, y: Double, width: Double, height: Double) { def fill(ctx: CanvasRenderingContext2D) { ctx.fillRect(x, y, width, height)} }and transform
data
to a list of Rect
, using for/yield
. Put the code outside the run
method. What is your code?rects foreach { _.fill(ctx) }and refresh the browser. Did it work?
println(rects). Where do you think the printout shows up? (Hint: It's JavaScript—look into the console of your browser dev tools...)
def main(target: html.Div) = { var p = document.createElement("p") val t = document.createTextNode("Hello, World!") p.appendChild(t) target.appendChild(n) }
jQuery
instead of $
:
target.append(jQuery("<p/>").text("Hello, World"))
Ajax.get(url).onSuccess { case xhr => val str = xhr.responseText ... }
JSON.parse
turns response string into an object of type scalajs.js.Dynamic
val response = JSON.parse(xhr.responseText) // has type Dynamic val tasks = response.tasks // Works if JSON has field with name "tasks"
val tasksAsArray = tasks.asInstanceOf[scalajs.js.Array[String]] // Works if it was a JavaScript array of strings
ScalaJSExample.scala
file so that it contains
package example import org.scalajs.dom import dom.html import scalajs.js.annotation.JSExport @JSExport object ScalaJSExample { @JSExport def main(target: html.Div) ={ target.innerHTML = s""" <div> <h1>Hello World!</h1> </div> """ } }In
resources/index-dev.html
, change the div
containing the canvas into
<div id='div'></div>and in the call to
getElementById
, change canvas
to div
. Reload the page. What happens? main
:
import dom.document val items = List("Hello", "World") var n1 = document.createElement("ul") for (i <- items) { val t = document.createTextNode(i) val n = document.createElement("li") n.appendChild(t) n1.appendChild(n) } target.appendChild(n1)What happens? Why?
resources/index-dev.html
, add
<script type="text/javascript" src="http://cdn.jsdelivr.net/jquery/2.1.3/jquery.js"></script>inside the
head
element. In workbench-example-app/build.sbt
(not the project/build.sbt
file!), add
, "be.doeraene" %%% "scalajs-jquery" % "0.8.0"to the
Seq
of library dependencies. Then
import
to the top of ScalaJSExample.scala
:
import org.scalajs.jquery._You should get an error message about an unknown import.
../activator-1.3.2/activator
and type
reload eclipse ~fastOptJS
main
with
val items = List("Hello", "World") var n1 = jQuery("<ol/>").appendTo(jQuery("#div")) for (i <- items) n1.append(jQuery("<li/>").text(i))Reload. What happens?
div
in index-dev.html
to
<h1>Capital Box!</h1> <p id='cap'>Type below and have it capitalized!</p> <input id='box' type="text" value='Type here'>Change the contents of
main
to
val box = jQuery("#box") box.keyup((ev: JQueryEventObject) => { val t = box.value() jQuery("#cap").text(t.toString().toUpperCase()) })
box
? What is the type of t
?toString
necessary?JQueryEventObject
parameter type be inferred?unit15
directory (or whereever you had it), and pointing a browser to http://localhost:9000. Add a few tasks. package controllers import play.api._ import play.api.mvc._ import play.api.libs.json._ import models.Task object Api extends Controller { def tasks = Action { Ok(Json.toJson(Json.obj("tasks" -> Task.all().map(t => Json.obj("id" -> t.id, "label" -> t.label))))) } }and a route
GET /api/tasks controllers.Api.tasksPoint a browser window to
http://localhost:9000/api/tasks
. What do you get? Why?div
to
<div id='div'> <ul id='tasks'> </ul> </div>and the
main
method to
val tasks = jQuery("#tasks") val prefix = "http://localhost:9000/api/" val url = prefix + "tasks" def updateTasks() { tasks.empty() Ajax.get(url).onSuccess { case xhr => JSON.parse(xhr.responseText).tasks.asInstanceOf[scalajs.js.Array[scalajs.js.Dynamic]] map { t => val task = jQuery("<li/>") task.text(t.label.asInstanceOf[String]) tasks.append(task) } } } updateTasks()Add this to the top of the Scala file:
import dom.ext._ import scalajs.js.JSON import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow
--disable-web-security
. Point it to http://localhost:12345/target/scala-2.11/classes/index-dev.html. What happens now?val button = jQuery("<input type='button' value='Delete' style='margin: 0.2em'/>") task.append(button) button.click((ev: JQueryEventObject) => { Ajax.post(prefix + "tasks/" + t.id + "/delete", "").onSuccess { case xhr => println(xhr.responseText) } })Where do you put it?
POST /api/tasks/:id/delete controllers.Api.deleteTask(id: Long)and a method
def deleteTask(id: Long) = Action { Task.delete(id) Ok("deleted " + id) }Now what happens when you delete a task?
updateTasks
.)<p>Label <input id='label' type="text"/> <input id='create' type="button" value="Create"/></p>And add a button handler:
jQuery("#create").click((ev: JQueryEventObject) => { val label = jQuery("#label").value().toString Ajax.post(prefix + "tasks/" + label + "/create", "").onSuccess { case xhr => println(xhr.responseText) updateTasks() } })What will happen when you click the button?
newTask
from Unit 15. (It's a bit wrong to put the label into the URL—it really should go with the POST data, but that's not so easy due to the immaturity of the libraries, so we'll skip it.) What did you do to make creating new tasks possible?This was a cutting-edge lab, and there were two weak points. The transfer of data between the client and server are not really satisfactory. As already mentioned, that's something that can be fixed with some libraries.
But what is less satisfactory is running two projects and hacking the browser to allow connections to two ports. We just didn't have the time in the lab to do this right. Your homework assignment is to fix it.
Follow this or this post and deliver a single project that includes both the Play app and the Scala JS client. Make it so that connecting to localhost:9000 serves up the single page of the app.