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):
The above code should print:
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
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.
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:
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:
we change the value in user
to "Alice"
, which triggers re-evaluation of helloPrinter
, resulting in:
printed on the standard output. Then, we do:
and it prints:
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:
Output:
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:
Output:
Great! We can also make the code a bit more functional, if we want:
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:
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
:
The following imports will be needed:
The absolutely minimal HTML to test this is:
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:
We define some sample data:
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:
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:
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:
However, for such simple event handlers it’s easier to define them inline.
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:
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.
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:
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:
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:
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:
Now we just need to adjust renderDepositsTable
:
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:
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:
it will be using:
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.