Making UI easily with Binding.scala

Making UI easily with Binding.scala

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 Vars. 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 Vars. 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 Vars 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 Vars and that it results in immediate re-evaluation of all dependent Bindings.

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 Bindings:

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 Ints and Strings as Vars:

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:

binding scala example

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: periodinterest (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 gains 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

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

Do you like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.

Download e-book:

Scalac Case Study Book

Download now

Authors

Paweł Bartkiewicz

I have a broad experience with programming languages and technologies, I choose Scala as my preferred tool nowadays. I take security very seriously due to my background in developing military-grade appliances. I am the best at back-end development, but I am not afraid of any programming-related tasks. I love types. In my spare time, I contribute to open source, play role-playing games (pen & paper), do some gardening with my wife, read Lovecraft, tinker with electronics, build synthesizer, sometimes brew beer.

Latest Blogposts

14.03.2024 / By  Dawid Jóźwiak

Implementing cloud VPN solution using AWS, Linux and WireGuard

Implementing cloud VPN solution using AWS, Linux and WireGuard

What is a VPN, and why is it important? A Virtual Private Network, or VPN in short, is a tunnel which handles all the internet data sent and received between Point A (typically an end-user) and Point B (application, server, or another end-user). This is done with security and privacy in mind, because it effectively […]

07.03.2024 / By  Bartosz Puszczyk

Building application with AI: from concept to prototype

Blogpost About Building an application with the power of AI.

Introduction – Artificial Intelligence in Application Development When a few years ago the technological world was taken over by the blockchain trend, I must admit that I didn’t hop on that train. I couldn’t see the real value that this technology could bring to someone designing application interfaces. However, when the general public got to […]

28.02.2024 / By  Matylda Kamińska

Scalendar March 2024

scalendar march 2024

Event-driven Newsletter In the rapidly evolving world of software development, staying in line with the latest trends, technologies, and community gatherings is crucial for professionals seeking to enhance their skills and network. Scalendar serves as your comprehensive guide to navigate events scheduled worldwide, from specialized Scala conferences in March 2024 to broader gatherings on software […]

software product development

Need a successful project?

Estimate project