by David Stibbe.
- Required knwoledge: scala and SBT (minimal)
- Tooling:
- JDK 8
- Scala 2.11.7
- SBT 0.13.8
- Git
- Terminal
- IDE (Eclipse, Intellij)
- Browser
In this first exercise we will create a 'Hello World' app that will run from the command line.
Purpose
- Getting first hands-on experience with scala-js, which will not differ from scala
- Setting up the project structure
Exercise 1
- Open the following file in your editor:
src/main/scala/quintor/exercise1/MainApp
and add the following in the main()
method
println("Hello world")
- Then run from the project root:
cd 1-standalone
sbt run
. This produce the following output:
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[info] Loading project definition from D:\dstibbe\projects\workshop\scalajs\exercises\1-standalone\project
[info] Set current project to standalone (in build file:/D:/dstibbe/projects/workshop/scalajs/exercises/1-standalone/)
[info] Updating {file:/D:/dstibbe/projects/workshop/scalajs/exercises/1-standalone/}root-1-standalone...
[info] Resolving org.eclipse.jetty#jetty-continuation;8.1.16.v20140903 ...
[info] Done updating.
[info] Compiling 1 Scala source to D:\dstibbe\projects\workshop\scalajs\exercises\1-standalone\target\scala-2.11\classes...
[info] Running quintor.exercise1.MainApp
Hello world
[success] Total time: 4 s, completed 3-mrt-2016 14:48:58
As you can see, it prints 'Hello World' to the command line.
Explanation
The app you just modified extends JSApp. The sbt plugin recognizes top-level objects extending JSApp, and runs
their main
method when sbt run
is executed.
The app is actually run in a JavaScript engine (Rhino). You can check this by executing
sbt "last run"
(don't forget the quotes) which shows the logging of the last sbt run command. At the end of the log, you'll see:
...
[info] Running quintor.exercise1.MainApp
[debug] with JSEnv RhinoJSEnv
Indicating that it was run in the Rhino JavaScript environment.
In the target\scala-2.11\classes\quintor\exercise1
directory, we find the compilation results.
There are .sjsir and .class files for each class in your project (just like for Scala JVM).
The .class files are used by the Scala.js compiler for symbol lookup during separate compilation.
Do not use these .class files to run your project on a JVM.
The .sjsir files are a binary representation of (extended) JavaScript code which can be linked to actual JavaScript.
In these exercises we will call the scala.js code from javascript code.
In this exercise, we shall call the same app we created before, but then from JavaScript
Purpose
- Show the user how to compile to javascript
- illustrate that the same code that was compiled to
.class
is now compiled to.js
- Show that the class is accessible from JavaScript
Exercise 2.A
- Open
src/main/resources/hello.html
, it contains the following:
<script type="text/javascript" src="../frontend-fastopt.js"></script>
<script type="text/javascript">
quintor.exercise2.MainApp().main();
</script>
The first script tag includes the javascript that will be generated from the scala.js code. The second script tag calls the main method of the MainApp, which executes
println("Hello World")
- From the project root, run
cd 2A-from_javascript
sbt fastOptJS
This will first execute the compile task, and then build the javascript file from_javascript-fastopt.js
that
is included in the html page.
- Open the html page in
target\scala-2.11\classes\hello.html
This will tell you to check the browser console (usually: F12). In the javascript console, you'll find the message 'hello world!'. - Depending on the browser, you will also see where it is produced.
- Chrome uses the supplied js map and reports that it is produced by
System.scala
, which is the scala.js library that causes println to write to console.log. - Firefox reports it is produced by
from_javascript_2A-fastopt.js
, the generated javascript file.
- Chrome uses the supplied js map and reports that it is produced by
Explanation
The sbt task fastOptJS
generates, based on the compiled sjsir files, javascript code. Its
focus is on speed and meant for development. For production, one should use fullOptJS
,
which is slower, but produces a smaller javascript file.
Try it:
sbt fullOptJS
However, with our application, you won't notice any performance decrease, because it is so small. The size differences however, is noticable, 88KB vs 13KB.
In this exercise we will access the DOM through a scala.js facade. Even though facades will be discussed later, this gives a nice sense of reward
Purpose
- This application will show you how to manipulate the DOM.
Exercise 2.B
To be able to use the standard scala.js dom facade, we've added a dependency on 'scalajs-dom', which you can find in the build.sbt
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.0"
This library provides an interface from scalajs to the JavaScript DOM object.
(The %%%
indicates that the dependency is a webjar (http://www.webjars.org/).)
Now we we'll modify our html, by adding a div where text can be placed:. Add the following to the html page in
src/main/resources/hello.html
, as following
<div id="fillme">I am empty</div>
Change the main app to fill the div with 'hello world' as shown below.
document.getElementById("fillme").innerHTML = "Hello World"
Then build it, and open the hello.html
in the target directory.
You see the message appear in the page.
To make it a little more interesting, try the following scala code:
object MainApp extends JSApp {
override def main() = {
val random = scala.util.Random.nextInt(1000)
val msg = s"Hello World. Now for a random number: $random"
document.getElementById("fillme").innerHTML = msg
println("I fllled the div with hello world")
}
}
This should update with a new number at each refresh.
Explanation
This approach uses the dom.document
facade to access the JavaScript DOM object.
It also stil performs a println after performing the insert.
- When opening
hello.html
in the browser, you'll seeHello World. Now for a random number: {some number}
printed. The random number should change on each request.
Use the dom.window facade to create a blink tag, that changes the css background color of the div each 100ms.
Hints:
window.setInterval
and setAttribute
on the div element.
Exercise 2.C
- In this project we added a dependency to aid in our frontend development, we added the sbt workbench plugin from lihaoyi. This provides a simple Akka-based webserver to make development easier.
The workbench plugin was configured as following:
The plugin was add
For adding the plugin, we added in project/plugins.sbt
:
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
resolvers += "spray repo" at "http://repo.spray.io"
addSbtPlugin("com.lihaoyi" % "workbench" % "0.2.3")
The plugin was configured
For configuring the plugin, we added in ./build.sbt
:
workbenchSettings
bootSnippet := "location.reload();"
The first line loads the settings for the workbench. The second line, tells the workbench how to restart your application through javascript.
Here we enter the script we previously had in hello.html
to start our application.
The plugin is used
To use the workbench we configured a single script tag in hello.html
<script type="text/javascript" src="/workbench.js"></script>
This bootstraps the workbench, including the created fastOptJS javascript, after which it will execute the bootSnippet, configured above.
Run with the plugin
We will then run the application with
sbt ~fastOptJS
The ~
indicates that it will keep a watch on the files and recompile (and run) when they are changed.
Check the result by opening the brower to http://localhost:12345/target/scala-2.11/classes/hello.html
Change some code in hello.html; the text for example. The page will automatically reload (Firefox doesn't ) and show the changes.
Check the developer console what do you see?
It shows console logging from server.
Purpose
- Purpose of this exercise is to show how ScalaJS objects can be exposed to JavaScript
Exercies 3.A
To make Scala.JS methods and classes visible in JavaScript, you add the @JSExport
annotation to each. Not adding this annotation to the class, makes the methods in it, only
visible via an anonymous object.
The class, when exported, is visible with the full qualified name as a method in JavaScript, eg.
var instance = foo.bar.ClassName();
We have a Scala.JS Calculator class and a html page that wants to use it.
- modify the Calculator in such a way that it is visible in JavaScript.
- modify the webpage to use the class.
You can modify the name with which an item is exposed to JavaScript by providing an argument to JsExport, eg.
@JSExport("AnotherName")
Try it yourself, modify the name of Calculator to AbsCalculator. You might notice something.
Another annotation to export elements is @JSExportAll
. It is an annotation on class level that exposes all
fields and methods in a class to JavaScript, but also only that. It does not export the class itself.
Remove the JSExport from the method and replace it by using
@JSExportAll
on class level.
Purpose
- Learn to access a simple value from a javascript object
Exercise 4.A
This exercise provides a Calculator that gets its input by reading two JavaScript objects, called
Input
and Result
(instead of getting it as argument for the method and simply returning the result).
To represent a JavaScript object as a Scala.JS trait, it has to extend (direct, or indirectly) js.Any
(usually it extends js.Object
) and be annotated with @js.native
.
In such a trait, all concrete definitions require =native
as body and are mapped
to their javascript counterparts.
Example of a small part of a facade for the JavaScript window object:
@js.native
trait Window extends js.Object {
var location: String = js.native
...
The var, val and def definitions without parentheses all map to field access in JavaScript, whereas def definitions with parentheses (even empty) map to method calls in JavaScript.
Make this exercise work by implementing the facades for Input and Result.
The ScalaJS website provides a list of how the different data types are mapped from ScalaJS to JavaScript and vice versa.
Note:
The @js.native
annotation is meant for native JavaScript classes. It is also possible
to define Scala.JS defined JavaScript classes:
https://www.scala-js.org/doc/interoperability/sjs-defined-js-classes.html
Purpose
- Learn to access a method from a javascript object )
Exercise 4.B
As mentioned previously, vals and vars map to JavaScript fields. Scala def definitions with parentheses however, map toJavaScript method calls.
In this exercise, make the Calculator work, by using the JavaScript AbsCalculation execute method.
Purpose
- Learn to create a facade on your own ;-)
Exercise 4.C
The site http://www.scala-js-fiddle.com/ shows a very nice oscillator, to entice you to start with scala.js.
Now, we will run this locally.
In this exercise, you find the oscillator code, with some slight adjustments, in Oscillo.scala. Create the facades in such a way , that te oscillator works.
Note: some type-hints are provided in the scala code.
You can reference the scala.js website all you like, suggestions:
- https://www.scala-js.org/doc/interoperability/facade-types.html
- https://www.scala-js.org/doc/interoperability/types.html
In short, Angular is a JavaScript-based MVC framework.
Its main components are
- Templates - the html setup of a page
- Controllers - when the templates want to use funcationality, they call the controllers
- Scopes - in these objects we store scoped data
- Services - these are used for communicating to backend/third-party
- Directives - can be seen as definitions for custom tags and/or arguments
So, that was the extremely short introduction of Angular. We will revisit most of these elements later on in more detail.
Purpose
- Here we will get acquainted with the controller-scope setup in angular
Exercise 5.A
- Add a dependency on the Angular JavaScript library in sb by adding:
jsDependencies ++= Seq(
"org.webjars" % "angularjs" % "1.3.15" / "angular.js" // angular JavaScript library
)
- Add a dependency to the Scala.js angular facade in the build.sbt by adding to
libraryDependencies
"com.greencatsoft" %%% "scalajs-angular" % "0.6" // scala.js interface for angular
-
The Scala.js sbt will bundle the jsDependencies in a
*-jsdeps.js
file which has already been added as source inhello.html
. You can see it above the inclusion of the-fastopt.js
file. -
Our template is
hello.html
. In the body tag you see that two arguments:ng-app="myHelloApp"
- the angular module to use .ng-controller="HelloCtrl"
- the Controller bound to this tag.
In the body tag, we access the corresponding scope in several ways:
- By arguments of
ng-
directives, such asng-click
, e.g.<button ng-click="controller.submit()">
- By
{{...}}
expressions, e.g.{{directMessage}}
The variables and methods you access through such expressions, all come from the scope, with the exception of
controller
, which is a special variable that provides access to the controller, via which you can access the controller variables/methods (usually you only access methods). -
If we open
HelloController
we see that it extendsController
. Each controller in the angular facade should extend this trait.- The type of Controller is
HelloScope
. This binds our scope to this controller. It is injected through@inject
- The controller overwrites the method
initialize
which is called after the injection has been completed. - The controller is configured with
@injectable("HelloCtrl")
, making it injectable within Angular using that name.
- The type of Controller is
-
HelloScope
extends theScope
trait. This is required for scope objects.- The scope is basically is a mapping to the javascript scope object
js.Any
, whose lifecycle is managed by Angular.js
- The scope is basically is a mapping to the javascript scope object
-
In the
HelloApp
the Angular modulemyHelloApp
is registered (loaded in ng-app).- The controller also gets registered there
- The HelloApp itself is designated as the Config object for this module.
-
Run the application:
sbt clean ~fastOptJS
You see that variable directMessage
is mutated immediatly. This is due to the fact, that it mutates
the exact same variable on the scope that you read from.
Expand the controller and html in such a way that submitting causes the scope variable 'submittingMessage' is copied to 'submittedMessage'. Which in turn is shown in the html.
Purpose
- Start the basics of a todo list
Exercise 5.B
We will create, a page showing a list of todo's from the scope using the following steps. Feel free to use internet as a reference.
Create a model of a case class called
Todo
with a fieldlabel
Expand the scope with a
js.Array
of Todo's
Have the controller initialize the scope with a list of todos. Hint:
.toJSArray
Create an ordered list of todo's in the template
using
ng-repeat```
Purpose
- No more print(ln)
Exercise 5.B
Well, also in Scala, using println is not done.
A good logging framework is the slogging framework.
Add
"biz.enef" %%% "slogging" % "0.4.0"
To your dependencies (regular, not jsDependencies).
Now you can start logging by using the LazyLogging
trait. This provides the logger
field, which
can be used to log at various levels (trace, debug, info, warn, error).
To configure your logger, add the following to the construct of the TodoApp:
LoggerConfig.factory = ConsoleLoggerFactory()
LoggerConfig.level = LogLevel.TRACE
This configure it to log at Trace level to Console.
Replace all println's with logging.
Purpose
- Nicer looks
Exercise 5.D
Angular material provides material UI components to angular with little extra cost.
We start by updating build.sbt
First we extend our dependencies. Add the following to jsDependencies:
"org.webjars" % "angularjs" % "1.3.15" / "angular-route.js" dependsOn "angular.js",
"org.webjars" % "angularjs" % "1.3.15" / "angular-animate.js" dependsOn "angular.js",
"org.webjars" % "angularjs" % "1.3.15" / "angular-aria.js" dependsOn "angular.js",
"org.webjars" % "angularjs" % "1.3.15" / "angular-locale_ko.js" dependsOn "angular.js",
"org.webjars" % "angular-material" % "1.0.6" / "angular-material.js" dependsOn "angular.js"
We add a new plugin to plugins.sbt:
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") //for managing the assets in target
Next we add enable the plugin in build.sbt
enablePlugins(SbtWeb) //make resources (other than js) from webjars available
In the resources we find a new ```todos.html`` , copy this to your 5.B exercise. This provides a template that uses for angular material.
Start the app.
It probably doesn't look very nice. What you need is still an Angular dependency of your module on ngMaterial
Modify your module in the TodoApp with the following:
val myModule = Angular.module("theTodoApp", Seq("ngMaterial"))
. This adds the required dependency on material. Now run it again
Purpose
- Ability to test
Continue on Exercise 5.B
First we have to disable the Rhino javascript engine. It somehow causes issues when trying to test.
To disable Rhino, modify the build.sbt as following:
- Add:
scalaJSUseRhino in Global := false
testFrameworks += new TestFramework("utest.runner.Framework")
- Add
RuntimeDOM
tojsDependencies
Then we add phantomJs by copying ```phantomjs.exe` to the root of exercise 5.B from exercie 6.A
Then we add the testing framework utest to our dependencies:
- Add
"com.lihaoyi" %%% "utest" % "0.3.0" % "test" // utest
-
A problem arose when testing with angular however, this only works with
angular.min.js
instead ofangular.js
Update the build.sbt accordingly. -
Create a directory
src/test/scala
where you will place your tests.
Create a simple test to see whether the setup is working, e.g.
object SimpleTest extends TestSuite {
def tests = TestSuite{
'DoesItWork{
assert(0 == 0)
}
}
}
Run the simple test, using
sbt test
Now for something more difficult: create a test that will assert whether your TodoController has been initialized with the proper todos
Hints:
- to compare Arrays:
arrayA.deep == arrayB.deep
- You cannot simply implement a
Scope
object in scala. Suggestion: create a scala based javascript object (using@ScalaJSDefined
) and cast it to TodoScope usingasInstanceOf
Purpose
- Introduce the service interface
Continue on Exercise 5.B
- Create a trait for a TodoService that extends the ```Service````trait, with a method
def getTodos(): Seq[Todo]
-
Add the TodoService to the constructor of our TodoController
-
Make the todo controller use the
getTodos
method of that Service. -
Create a test that tests the usage of the Service.
Purpose
- Get the service to actually do something
Continue on Exercise 5.B
Implement the TodoService extending the trait you made above. Implementing a Service requires you to also create a factory, which should be available under the same injection identifier as the service it self, e.g. :
@injectable("taskService")
class TaskService(http: HttpService) {
...
}
@injectable("taskService")
class TaskServiceFactory(http: HttpService) extends Factory[TaskService] {
override def apply() = new TaskService(http)
}
Note: do not add the @injectable
to the service implementation, instead add it to the service trait that we implemented before.
This way, you are not forced to inject the implementation in the controller, but can instead use the trait.
Also don't forget to register the factory in the module as following:
module.factory[TodoServiceFactory]
- Have this service return a list of todos.
Purpose
- Adding and removing
Continue on Exercise 5.B
-
Implement an add function
add(todo:Todo)
to the service. -
Implement an delete function
delete(todo:Todo)
to the service.
Purpose
- Use mdDialog for popping up an add box. Requires a scala.js facade for mdDialog.
Continue on Exercise 5.B
We will use Angular material's mdDialog to create a popup for adding todos
- Use the sources from 7.A
- Form the TodoController, just use the
showDialog
method - It provides a template, controller and scope for adding a todo. Only the template is complete
- The template todos.html only has one change: the added button that triggers showing the mdDialog
- Form the TodoController, just use the
- Fix the implementation for the mdDialog facade.
- Implement the AddTodoController and AddTodoScope
- When saving, call the TodoService add functionality and add it to the passed todos array
- Register the controller.
Purpose
- Add remove button to remove todo
Continue on Exercise 5.B
Far simpler than 7.1: add for eacht todo a button that will remove that todo from the list.
- Old presentation progress scala.js: http://lampwww.epfl.ch/~doeraene/presentations/scala-js-scaladays2014/#/
- Good hands-on: http://www.lihaoyi.com/hands-on-scala-js/
- Existing facades: http://www.scala-js.org/libraries/facades.html
- JavaScript <-> Scala.JS types: http://www.scala-js.org/doc/interoperability/types.html