Skip to content
Ethan Bell edited this page May 20, 2020 · 15 revisions

Binding.scala 11 supports FXML and JavaFX.

You can create JavaFX GUI or any other JavaBeans, in FXML syntax, along with embedded Scala code with data-binding abilitiy.

import javafx.application.Application
import javafx.stage._
import javafx.scene._
import javafx.scene.control._
import com.thoughtworks.binding._

final class FxmlSample extends Application {

  @fxml override def start(primaryStage: Stage): Unit = {
    val scene: Binding[Scene] = <Scene><Label>Hello, World!</Label></Scene>
    fxml.show(primaryStage, scene)
  }

}

object FxmlSample {
  def main(args: Array[String]): Unit = {
    Application.launch(classOf[FxmlSample], args: _*)
  }
}

Very simple!

What's different from FXMLLoader

Statically type checking

Unlike FXML dynamically loaded from FXMLLoader, Binding.scala compiles FXML into monadic expressions, which are statically type checked. Therefore, you can get rid of implicit conversion between XML text and other types, and use typed Scala expression instead.

@fxml val gridPane = {
  <GridPane>
    <Label text="My Label">
      
      <!-- OK, as 2 is an Int -->
      <GridPane.rowIndex>{2}</GridPane.rowIndex>
      
      <!-- Compilation error because "not a number" is a string, not an Int -->
      <GridPane.columnIndex>not a number</GridPane.columnIndex>

    </Label>
  </GridPane>
}

Arbitrary data-binding expression

FXMLs loaded from FXMLLoader support very limited data-binding expressions via ${...} syntax. On the other hand, Binding.scala supports arbitrary Scala code as data-binding expressions.

@fxml def vbox(buttonText: BindingSeq[String]) = {
  <VBox>
    <Button text="first button"/>{
      for (t <- buttonTexts) yield {
        <Button text={t}/>
      }
    }<Button text="last button"/>
  </VBox>
}

You can even create repeated UI elements in a for/yield block, whereby Buttons will be added and removed automatically whenever buttonTexts changes.

FXML for non-JavaFX classes

@fxml supports any JavaBean on both JVM and Scala.js. For example you can create custom JavaBeans in Scala.js:

@fxml val date = {
  <?import scala.scalajs.js.Date?>
  <Date date={2}>
    <milliseconds>{42}</milliseconds>
  </Date>
}

What's different from @dom

If you have used Binding.scala with Scala.js, you may already very familiar with @dom.

@dom creates a data-binding expression with XHTML support. For example:

@dom val i: Binding[Int] = 1
@dom def plusI(n: Binding[Int]): Binding[Int] = n.bind + i.bind

@dom val myDiv: Binding[HTMLDivElement] = {
  val node: HTMLDivElement = <div></div>
  myDiv
}

@dom val myDivs: Binding[BindingSeq[HTMLDivElement]] = {
  val nodes: BindingSeq[HTMLDivElement] = <div></div><div></div>
  nodes
}

Unlike @dom, @fxml itself does not create a root data-binding expression for the annotated method, it merely converts FXML literals into Binding or BindingSeq.

@fxml val i: Int = 1 // No magic here, i is not bindable

// Compilation error because n.bind is not inside a data-binding context
// @fxml def plusI(n: Binding[Int]): Int = n.bind + i

// Manually create a Binding block in order to use `.bind` syntax.
@fxml def plusI(n: Binding[Int]): Binding[Int] = Binding { n.bind + i }

@fxml val myButton: Binding[Button] = {
  val node: Binding[Button] = <Button/>
  node
}

@fxml val myButtons: BindingSeq[Button] = {
  val nodes: BindingSeq[Button] = <Button/><Button/>
  nodes
}