In my first Laminar tutorial I showed how to set up a Scala sbt project to use Laminar, and then showed a “static” example — i.e., there were no moving parts. Please see that tutorial first if you’ve never used Laminar.
BUT, because Laminar is meant for writing reactive applications with observables and observers, this tutorial begins to show its reactive concepts.
Please note that this example is almost 100% the same as the Laminar “Hello, world” example, except that I add a few minor things to it.
Just like my first tutorial, I also provide an sbt project for this tutorial to help you get up and running asap:
Clone that project if you want to follow along with the text below.
A Laminar reactive “Hello, world” example
Given that introduction, this is what this example looks like in a browser:
What happens in this example is that you type into the input field, and then whatever you type is reactively sent to the two output fields that are below the input field. The second field transforms whatever you type into uppercase.
The Laminar/Scala/Scala.js code
Here’s the source code that makes this example work:
import com.raquo.laminar.api.L._
import org.scalajs.dom
object Laminar101Reactive {
def main(args: Array[String]): Unit = {
val nameVar = Var(initial = "world")
// an outer “wrapper” div
val rootElement = div(
padding("2em"),
div(
backgroundColor("#e0e0e0"),
label("Your name: "),
input(
onMountFocus,
placeholder := "Enter your name here",
// as the user types into this 'input' field, send the contents
// of the input field to nameVar. nameVar is a reactive variable
// (observable), and it’s used to update the two DIVs below.
inContext { thisNode => onInput.map(_ => thisNode.ref.value) --> nameVar }
),
),
// a div for the first output area
div(
backgroundColor("#d9d9d9"),
"Reactive 1: ",
// any time nameVar is updated, it sends us a signal, and this
// field is updated:
child.text <-- nameVar.signal
),
// a div for the second output area
div(
backgroundColor("#d0d0d0"),
// any time nameVar is updated, it sends us a signal, and this
// field is updated. note in this case that the value is map’d:
"Reactive 2: ",
child.text <-- nameVar.signal.map(_.toUpperCase)
)
)
// `#root` must match the `id` in index.html
val containerNode = dom.document.querySelector("#root")
// render the element in the container
render(containerNode, rootElement)
}
}
As you can see, the code consists of several div
elements, with different content in each element. Hopefully the HTML-ish elements make sense, so I won’t describe them. But here are a few notes about the unfamiliar parts:
- From the Laminar docs,
onMountFocus
“focuses the element when it's mounted into the DOM. It’s like the HTML autoFocus attribute that actually works for dynamic content.” inContext
is a way to get a reference to the node that you’re inside. In this example,inContext
refers to theinput
field. Its code may be a little easier to read like this:
inContext { thisNode =>
onInput.map(_ => thisNode.ref.value) --> nameVar
}
Discussion
As I write in my book, Functional Programming, Simplified, inContext
is either (a) a class that takes a function parameter, or (b) a function that takes a by-name parameter. In this case, the truth is that I don’t know what inContext
is — I haven’t taken the time to look it up.
But what this code does is that whenever a user types something into the input field, an onInput
event is triggered, and that causes the map
method to be run. Inside map
the value we get from onInput
is ignored with the _
character, and we just yield thisNode.ref.value
, which is the value in the input field. That value is then sent “to” the nameVar
reactive variable.
Because nameVar
is an observable variable and it’s observed by these two lines of code:
child.text <-- nameVar.signal
child.text <-- nameVar.signal.map(_.toUpperCase)
those two lines are immediately updated.
That’s the way observables and observers work,
and Laminar is 100% based on this principle and
style of coding.
Here’s another way of looking at how that code works:
An awesome thing about Laminar is that you can write Scala code that’s compiled to JavaScript (using Scala.js and sbt), letting you write single-page applications using Scala!
A note about Scala
One thing to mention is that in this line of code, where it looks like child.text
is receiving a signal:
child.text <-- nameVar.signal
the <--
thing is just a method. So that line of code is the equivalent of this:
child.text.<--(nameVar.signal)
But because of Scala’s mojo, you can use the first approach.
Other notes
As you’ll see in the Github project:
- The build.sbt file is almost the same as my first tutorial
- The index.html file is almost the same as my first tutorial
- You compile your Scala code to JavaScript using this command in sbt:
sbt:Laminar101> ~fastOptJS
All of that is explained in my first tutorial.
One thing that isn’t mentioned in that tutorial is that whenever you update your Scala code and it’s recompiled into JavaScript, you need to refresh your browser so it picks up the newly-generated JavaScript file.
Experiment with the examples
Now that you’ve seen this example and have access to my Github project, you can also copy and paste the other Laminar examples into this project to experiment with them.
The next tutorial
When you’re ready for the next step, I just added my third Laminar tutorial, A small Laminar reactive routing example.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Resources
If you go forward in working with Laminar, here are some helpful resources:
- Laminar documentation
- Laminar examples
- For other tech support, see the Laminar Gitter page
Laminar depends on the following three projects, and if you go forward, their docs will also be helpful: