raquo / scala-dom-builder

Low-level Scala / Scala.js library for building and manipulating DOM trees

Github

Scala DOM Builder

Scala DOM Builder is a low level, unopinionated library for building and manipulating DOM trees (objects representing HTML tags and their attributes, properties and styles).

"com.raquo" %%% "dombuilder" % "0.4"

This library can be used in two ways: 1) directly for simple things, and 2) as a flexible foundation for a more opinionated UI library.

1. Simple / Direct Usage

If all you want is a no-hassle, type-safe way to create and manipulate some DOM nodes / trees in Scala.js, use the provided jsdom.simple package, it lets you build DOM nodes using syntax similar to ScalaTags:

val scalaNode = div( // Create a Scala representation of a DOM node. Javascript DOM node gets created automatically at the same time in this example
  "Hello, ",
  a(href := "http://example.com", "World"),
  button(onClick := doSomething, "Do Something!")
)
 
// Scala Nodes map one-to-one to real Javascript DOM nodes 
val javascriptDomNode: org.scalajs.dom.Div = scalaNode.ref // This is how you get a reference to the real Javascript DOM node if you need it
  
mount(org.scalajs.dom.document.body, scalaNode) // Add the Javascript DOM Node to the rendered page

You can even build simple components using this low-level API:

class Counter {    // This doesn't need to be a class, all you need is to build a `SimpleElement` somehow
 
  private var count = 0    // Declare this component's internal state
 
  private val captionNode: SimpleText = count.toString    // Create node to represent the caption that shows the current count
                                                          // Uses string-to-textnode implicit conversion that you need to import
 
  private val incButton: SimpleElement = button(    // Create a node to represent the "increment" button
    onClick := increment _,                         // Add event listener to the button node
    "[ + ]"                                         // Add a child node (which happens to be a text node) to the button node
  )
  
  val element: SimpleElement = div(    // Create a node that will be either mounted as a root node or added as a child to another node.
    className := "CounterClassBlah",   // Add a CSS class name to this node (not used here, just an example)
    display.inlineBlock,               // Set CSS display property to "inline-block" (just because)
    h1("Counter"),                     // Create an h1 HTML node and add it as a child
    incButton,                         // Add the "increment" button as a child node
    captionNode                        // Add the caption as a child node
  )
 
  def increment(): Unit = {                // Callback that will fire on every button click
    count += 1                             // Update internal component state
    captionNode.setText(count.toString)    // Update the DOM
  }
}

See example directory for more examples.

This kind of design works great for small things, but if you're building rich, interactive web applications there's a better way – use (or make!) a higher level, more opinionated library on top of Scala DOM Builder. Read on for how to do that.

2. As a Foundation For Other UI Libraries

If you are building a DOM manipulation library in Scala, say you're inventing your own React, you will most probably want to represent either the DOM tree itself or the Component tree, whatever a Component might be in your library. Scala DOM Builder's API was designed specifically for this use case – to serve as a low level foundation for other libraries.

This is similar to how Snabbdom is the foundation for many popular Javascript libraries, except Snabbdom gives you a virtual DOM solution. Scala DOM Builder is not a virtual DOM out of the box. We do not keep track of the DOM state other than the parent/child relationship of nodes. You could certainly implement a type safe virtual DOM library on top of Scala DOM Builder though!

Scala DOM Builder is specifically designed to serve as a foundation for more advanced, more opinionated libraries. By itself it is very flexible, it doesn't get in your way with its own opinions. It essentially lets you represent the mutable state of the DOM – your fancy functional reactive / monadic library will have to deal with that mutable state either way, but at least with Scala DOM Builder you can do it from a more convenient, type-safe API.

For an example on how to build your own library on top of Scala DOM Builder, check out the code of my library Laminar which lets you build UI components in a functional reactive way. It was originally based on Snabbdom, and had a lot of unnecessary complexity in its code to deal with the impedance mismatch between the reactive streams API that it provides and the virtual DOM paradigm that it had to be aware of. It is now based on Scala DOM Builder, and its code is considerably smaller and faster as a result.

Server Side Rendering

Even though Javascript DOM and the Scala.js interface to it are not available on the JVM, almost all Scala DOM Builder components work just fine on the JVM if you implement a simple JS DOM API backend (see domapi traits). So you can certainly build JVM things on top of Scala DOM Builder, but because the library is fairly new, this is uncharted territory right now.

One thing that I definitely want to implement on the JVM is rendering to static HTML code, perhaps even extensible enough to support React-style DOM preloading.

My Related Projects

  • Scala DOM Types – Type definitions that we use for all the HTML tags, attributes, properties, and styles
  • Scala DOM TestUtils – Test that your Javascript DOM nodes match your expectations
  • Laminar – Reactive UI library based on Scala DOM Builder
  • Snabbdom.scala – Scala.js interface to a popular JS virtual DOM library
  • XStream.scala – Scala.js interface to a simple JS reactive streams library
  • Cycle.scala – Scala.js interface to a popular JS functional reactive library

Author

Nikita Gazarov – raquo.com

License

Scala DOM Builder is provided under the MIT license.