Making UI easily with Binding.scala
Binding.scala is a one-way data-binding library written in Scala. It lets you create reactive user interfaces by writing concise Scala code. In this post, I’ll guide you step by step through the basics, as well as creating a simple dynamic web page. On top of that, I will discuss some undocumented issues I faced, which might be interesting for you if you decide to give Binding.scala a chance in your next project.
Why Binding.scala?
There are many one-way data-binding (or “reactive”) libraries nowadays. In the JavaScript world, a well-known one is React, while in the Scala world, there’s Scala.Rx. Why would you want to use Binding.scala, then? There are many reasons, including:
- It’s written in Scala (obviously), so if you want to write your code in Scala as well, you don’t need to worry about compatibility
- Targets both JS and JVM, so you can use it in non-web projects too
- Supports statically type-checked HTML literals (not kidding!)
- Properly manages the life-cycle of allocated objects, so no memory leaks
- Uses precise data-binding instead of virtual DOM, so it’s potentially faster
- It’s easy to learn
The biggest downside I’ve found so far is that the documentation is somewhat lacking, and that’s why I decided to write this tutorial.
Hello, World!
Let’s start with a minimal example (ScalaFiddle):
https://gist.github.com/anonymous/75cdb6c36e2d97cee3dcce1083b6a4bd
The above code should print:
https://gist.github.com/anonymous/78a1ab7de89124c631c372f5e43e18fd
You’re probably already starting to grasp the idea behind this code, but we’ll analyze it in detail now, just in case.
Step-by-step analysis
https://gist.github.com/anonymous/e90503b3ddec2eb05da232b66200bc41
Here, user
is a Var[String]
and it’s being initialized with the value "Anonymous"
. Var
is a wrapper which lets you change the value inside, so in practice you can think of user
as a var user: String
. “Ugh, mutability?!” you exclaimed in disgust, quite likely. Don’t worry, it’s a relatively safe and tame mutability, as we’ll see in a minute. Not the wild kind you know and fear, and rightfully so.
https://gist.github.com/anonymous/0f583d1a27a54f1557f802f9eb959745
Binding
, on the other hand, wraps some code which depends on some Var
s. In this case helloPrinter
depends on user
, which it concatenates with some String
literals and prints the result. So helloPrinter
is of type Binding[Unit]
(because println
return type is Unit
).
Have you noticed how we used user.bind
instead of user
directly? This is how you extract values from Var
s. But it doesn’t simply get the value out and return: it also informs the library that helloPrinter
depends on user
, which is very important, as we’ll see soon.
Up till now, the above code does nothing out of the ordinary. It simply prints Hello, Anonymous!
. However, here is where the magic begins:
https://gist.github.com/anonymous/3f50c027d95b1212b1d3543f01e18ab7
This line informs Binding.scala that it should start watching for changes in the Var
s on which helloPrinter
depends. So each time user
changes value, helloPrinter
gets re-evaluated. Thus, when we do:
https://gist.github.com/anonymous/f8b03e79d3bfee5a8ce07139789125f3
we change the value in user
to "Alice"
, which triggers re-evaluation of helloPrinter
, resulting in:
https://gist.github.com/anonymous/83af1759d6736aafb3ba8c7856630765
printed on the standard output. Then, we do:
https://gist.github.com/anonymous/b90a073a7ed1c9433a12d6cf746ad4b2
and it prints:
https://gist.github.com/anonymous/02ba29bb4e5ed9d423eaeed1c04b7273
The important thing to note here is that we must use :=
instead of =
to update Var
s and that it results in immediate re-evaluation of all dependent Binding
s.
Var
is a Binding
too
OK, not very useful so far, I know. The real power of Binding.scala comes from the fact that you can easily compose Binding
s:
https://gist.github.com/anonymous/3bbddde238a404ccfcc7dc4978f086b3
Output:
https://gist.github.com/anonymous/a3be6db57418f4b89fe775fdbf0573a9
As you can see, we can use the value of one Binding
(area
in this case) when computing another (areaPrinter
). That’s because bind
is a method of trait Binding
, and yes, Var
is a Binding
as well.
Another thing you may have noticed is that areaPrinter
gets re-evaluated whenever we change either width
or height
. This may cause a “glitch”: a temporary state inconsistency.
For example, we may want to calculate the area of a 5x4
rectangle first, then a 4x6
rectangle. However, since we update width
and height
independently, the system will produce a state where the area of a 4x4
rectangle is shown, because height
hasn’t been updated yet.
How can we avoid such glitches? Some libraries let you suspend observers, do the updates and resume normal operation after you’re sure that the input state is consistent. But what to do if the library we chose has no such functionality, as in the case of Binding.scala? Well, no one said you can only use simple Int
s and String
s as Var
s:
https://gist.github.com/anonymous/02ac1fd91c61e2f381ae75a6a595578a
Output:
https://gist.github.com/anonymous/5779d7a28daf9858f4c68e2b5db5dceb
Great! We can also make the code a bit more functional, if we want:
https://gist.github.com/anonymous/0ca4d30911f6101d70053d2f6ac86d57
We just replaced the hardcoded val area
with a function taking the rectangle as a parameter and we did the same with areaPrinter
. You may be wondering why we specified Binding[Rectangle]
as the parameter type for area
, when our rectangle
is really a Var[Rectangle]
.
Short answer: why not? We don’t intend to modify this Var
within this function. The only method we need is bind
, which is provided by the Binding
trait. This way, we could easily do the following, if we’d like:
https://gist.github.com/anonymous/c79b062a9b5c732fe70aba5e0485ef41
However, there’s a more important reason: by not exposing Var
we ensure that no-one will modify the value from within another part of the code (not without type-casting, at least). If we enforce a rule that each Var
can be assigned only in a single place, we don’t risk losing our sanity.
OK, now that you know the basics, we can move on to creating some real user interface with Scala.js.
Binding.scala + Scala.js = ♥
The thing that distinguishes Binding.scala from other data-binding libraries is that it supports Scala’s XML literals, so you can dynamically build your HTML DOM within your Scala code very easily. We’ll explore this by creating a very simple web application for calculating time deposits.
We could stick to the tradition and implement yet another TodoMVC, but there’s already at least onewritten with Binding.scala. To make things even simpler, we won’t concern ourselves with things such as clean architecture, because that’s not the point of this article.
We will create this:
I used Materialize to make the page pretty, but in the code snippets within this article I omitted any appearance-specific things.
Don’t worry, there will be a link to the complete project at the end of this article.
Setting up the project
First, we need to create a Scala.js project. You can follow the official documentation, or use this Giter8 template for Scala.js projects. Then, add the Binding.scala dependency to your build.sbt
:
https://gist.github.com/anonymous/ef1252384454a4fd2a6c19280c2f2da4
The following imports will be needed:
https://gist.github.com/anonymous/a73251e720d2b1ceec28bc7a79346a64
The absolutely minimal HTML to test this is:
https://gist.github.com/anonymous/4db4ca0c7f8059ffe625acf391226180
Of course, you’ll need to customize the path to your target JavaScript file.
Model
Our business logic is very simple: we just take an amount
of money to invest, a duration
(in months) of the investment and a list of Deposit
types available (each characterized by period
in months and interest
rate), then we calculate the gain
for each deposit:
https://gist.github.com/anonymous/f83b9bbf48b76bb40e23bf11a84afb3b
We define some sample data:
https://gist.github.com/anonymous/2f859330f09c7710d9038f16d230a31b
The first two lines should be pretty self-explanatory by now, but what is Vars
? It’s similar to Var
, but it holds a mutable list of values. We’ll see how to operate on this list in a moment.
User Interface
We have the data, so it’s time to build the user interface:
https://gist.github.com/anonymous/08fa5f0f304b30e8fddb02cfec0c10b8
This function will render the main <div>
containing our whole UI: input fields for amount
and duration
, as well as the table of deposits
. If you’re not familiar with XML literals in Scala, they’re almost normal XML with Scala code embedded between braces ({ }
).
The most important thing to note here is that we aren’t wrapping the code with Binding
. Instead, we are using a @dom
macro annotation on the function, which does the same thing, but it also does some HTML-specific magic behind the scenes, like converting the XML to Scala.js DOM and making sure that only the relevant portion of the DOM gets updated when some Binding
changes value.
We’ll implement the render*
functions next:
https://gist.github.com/anonymous/71919771f0d5cd51261363221e3b583b
Here we’re rendering the input field for amount
. Since Binding.scala is statically typed, we need the toString
: the attribute value
expects a String
, not a Double
. This may seem annoying, but may save you many hours of debugging in case you accidentally assign wrong value to an attribute.
The onchange
listener may be not obvious at a first glance. As all event listeners in the DOM, it has to be a function with a single parameter: Event
. It may return something (e.g. false
to stop event propagation), but it doesn’t have to. Here we’re ignoring the parameter and simply assign the value of the input field to the amount
variable.
But wait, where’s amountInput
coming from? Well, another “magical” thing that the @dom
macro does is exposing the DOM elements as local symbols named after their id
and having the proper type (HTMLInputElement
, in this case). Sounds like true magic, but it’s very handy when writing more complex user interfaces.
Of course, we could also define the function somewhere else and reference it here, like so:
https://gist.github.com/anonymous/2cb490d276920675fad657777a0d4fca
However, for such simple event handlers it’s easier to define them inline.
https://gist.github.com/anonymous/aad0b7169b91586b797fa84f09f79f31
The input field for duration
is very similar to the one for amount
, so we’ll skip the explanation.
Now, we want to display the table of deposits:
https://gist.github.com/anonymous/cd335ddbd9a589136c669aa425595b60
One of the useful methods provided by Vars
is map
: it’s just like the normal map
you know from Scala collections such as Seq
. Here, it’s iterating the elements and calling renderDeposit
for each Deposit
(passed using the anonymous argument _
). Since renderDeposit
is a @dom
function itself, we need to bind
its value afterwards.
https://gist.github.com/anonymous/020b28f779d2bd6e8203ee4d6b4ff5af
Nothing new here. We are just creating a row with three columns: period
, interest
(formatted as percentage) and the calculated gain
(formatted with two decimal places).
Finally, we bind the function rendering our main container to the element #mainContainer
from our HTML:
https://gist.github.com/anonymous/f8ec6185f2da213349e37b3dd413dbc2
Instead of calling renderMainContainer.watch()
(which would do nothing, because renderMainContainer
has no side-effects) we’re using Binding.scala’s dom.render
function, which takes the DOM structure produced by renderMainContent
and automatically inserts it at the specified place in the document
.
The application should be usable by now: you can change the amount
or the duration
and the gain
s should re-calculate automatically. Easy, right? Let’s make it possible to add/remove Deposit
types now.
You can find the full source code we wrote so far in this commit.
Modifying Vars
To be able to add deposits, we need to make a simple form:
https://gist.github.com/anonymous/99e2422d30655807b8b690d437f175ca
Again, it’s almost the same as with normal (mutable) collections: we use the operator +=
to add an element. However, we need to call get
on the deposits
first. Yes, it’s inconsistent with the :=
notation, but it’s been fixed in Binding.scala v11.0.0-M2. For now we have to live with this inconsistency (unless you’re from a future in which 11.0.0
got released already).
We’ll put this form at the end of the deposits table:
https://gist.github.com/anonymous/9224fbd04f949e6d81c7825b1421b984
So far so good.
Removing deposits is a bit tricky, however: the delete
button should be rendered in renderDeposit
, but we can’t access deposits
there. We could, of course, just add deposits: Vars[Deposit]
to the function’s parameters, but it feels wrong. Why would a function rendering a single deposit need access to the list of all deposits?
A much cleaner solution is passing a callback:
https://gist.github.com/anonymous/7c6888cbe9bac57f83aab23eca0407eb
Now we just need to adjust renderDepositsTable
:
https://gist.github.com/anonymous/56f39e7e1dd5053be2e371a6e175b5da
I reformatted the map
call a little and added onDelete
as a named argument to make it more readable. The actual callback is very straightforward: we just use -=
instead of +=
this time, because we want to remove an element.
And that’s all: our application should be fully functional (i.e. working) now. Maybe not the cleanest code you saw in your life, but I wanted to include many different use cases, so that you can choose yourself what suits you best.
Links to the complete project
- GitHub repository
- Online demo
- Simpler version without Materialize
- ScalaFiddle – if you want to experiment with the code inside your browser
Things to keep in mind
Before you go (and start writing that awesome web app you wanted to do for a long time but felt the frontend is too much work), I’d like to mention a few other things you’ll probably encounter while using Binding.scala.
FutureBinding
Yes, I know, every example should use some REST API. I skipped that not because it’s impossible or difficult with Binding.scala – on the contrary:
https://gist.github.com/anonymous/88dc10dc2d4aa2455f196197027e615b
You can wrap any Future
with FutureBinding
, not only AJAX calls.
Changes in v11.0
As mentioned earlier, version 11.0 (not released yet at the time of writing this article) will deprecate the :=
operator and get
method for Var
, replacing them with value
for consistency. So instead of:
https://gist.github.com/anonymous/177f59dfe3241aa5e187324c6f0ebf4e
it will be using:
https://gist.github.com/anonymous/7000fabfc7a8f02010f182b6d8b9f0dc
Beyond HTML DOM
Despite having a very good HTML support, Binding.scala isn’t limited to HTML. Right now, there’s also a support library for JavaFX’s FXML and nothing stops you from integrating Binding.scala with another framework/library of your choice.
Conclusion
We saw how easy Binding.scala is, especially compared to other data-binding libraries. It’s written in Scala, works with JVM and JS targets, has good support for HTML and avoids memory leaks. On the downside, it’s not as mature as some other libraries and it’s still lacking detailed documentation.
Would I use it for a large, serious project now? Probably not, but I hope it gets to that stage someday. Would I use it for hobby projects and prototypes? Definitely yes.
However, another interesting library showed up recently: monadic-html. It seems to combine most advantages of Binding.scala and Scala.rx, but that’s probably a topic for another post…
Links
- GitHub repository for this example project
- Giter8 template for Scala.js projects
- More Binding.scala examples on ScalaFiddle
- monadic-html
Do you like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.