• Stars
    star
    1,579
  • Rank 28,440 (Top 0.6 %)
  • Language
    Scala
  • License
    MIT License
  • Created over 8 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Reactive data-binding for Scala

Binding.scala

Production Ready Extremely Lightweight

Join the chat at https://gitter.im/ThoughtWorksInc/Binding.scala StackOverflow Scala CI Scaladoc Latest version

Binding.scala is a data-binding library for Scala, running on both JVM and Scala.js.

Binding.scala can be used as the basis of UI frameworks, however latest Binding.scala 12.x does not contain any build-in UI frameworks any more. For creating reactive HTML UI, you may want to check out html.scala, which is an UI framework based on Binding.scala, and it is also the successor of previously built-in dom library. See also React / Binding.scala / html.scala Interoperability for using existing React components with Binding.scala,

See Binding.scala • TodoMVC or ScalaFiddle DEMOs as examples for common tasks when working with Binding.scala.

Comparison to other reactive web frameworks

Binding.scala and html.scala has more features and less concepts than other reactive web frameworks like ReactJS.

Binding.scala ReactJS
Support HTML literal? Yes Partially supported. Regular HTML does not compile, unless developers manually replaces class and for attributes to className and htmlFor, and manually converts inline styles from CSS syntax to JSON syntax.
Algorithm to update DOM Precise data-binding, which is faster than virtual DOM Virtual DOM differentiation, which requires manually managed key attributes for complicated DOM.
Lifecycle management for data-binding expressions Automatically N/A
Statically type checking Yes, even for HTML tags and attribues No
Learning curve Always easy Easy to start. Requires much more efforts to understand its corner cases.

See Design section for more information.

Getting started

We will build an Binding.scala web page during the following steps.

Step 0: Setup a Sbt Scala.js project

See http://www.scala-js.org/tutorial/basic/ for information about how to setup such a project.

Step 1: Add html.scala dependencies into your build.sbt:

// Enable macro annotations by setting scalac flags for Scala 2.13
scalacOptions ++= {
  import Ordering.Implicits._
  if (VersionNumber(scalaVersion.value).numbers >= Seq(3L)) {
    Nil
  } if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
    Seq("-Ymacro-annotations")
  } else {
    Nil
  }
}

// Enable macro annotations by adding compiler plugins for Scala 2.12
libraryDependencies ++= {
  import Ordering.Implicits._
  if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
    Nil
  } else {
    Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
  }
}

libraryDependencies += "com.yang-bo" %%% "html" % "latest.release"

Step 2: Create a data field, which contains some Var and Vars as data source for your data-binding expressions

case class Contact(name: Var[String], email: Var[String])

val data = Vars.empty[Contact]

A Var represents a bindable variable, which also implements Binding trait, hence a Var can be seen as a binding expression as well. If another expression depends on a Var, the value of the expression changes whenever value of the Var changes.

A Vars represents a sequence of bindable variables, which also implements BindingSeq trait, hence a Vars can be seen as a binding expression of a sequence as well. If another comprehension expression depends on a Vars, the value of the expression changes whenever value of the Vars changes.

Step 3: Create a @html method that contains data-binding expressions

// For Scala 3
def table: Binding[HTMLTableElement] = {
  html"""<table border="1" cellPadding="5">
    <thead>
      <tr>
        <th>Name</th>
        <th>E-mail</th>
      </tr>
    </thead>
    <tbody>
      ${
        for (contact <- data) yield {
          html"""<tr>
            <td>
              ${contact.name.bind}
            </td>
            <td>
              ${contact.email.bind}
            </td>
          </tr>"""
        }
      }
    </tbody>
  </table>"""
}
// For Scala 2
@html
def table: Binding[HTMLTableElement] = {
  <table border="1" cellPadding="5">
    <thead>
      <tr>
        <th>Name</th>
        <th>E-mail</th>
      </tr>
    </thead>
    <tbody>
      {
        for (contact <- data) yield {
          <tr>
            <td>
              {contact.name.bind}
            </td>
            <td>
              {contact.email.bind}
            </td>
          </tr>
        }
      }
    </tbody>
  </table>
}

html"""...""" interpolation in Scala 3 (or @html annotated methods in Scala 3) represents an reactive XHTML template, which supports HTML literal. The type of HTML interpolation/literal is a specific subtype of com.thoughtworks.binding.Binding[org.scalajs.dom.Node] or com.thoughtworks.binding.Binding.BindingSeq[org.scalajs.dom.Node], instead of scala.xml.Node or scala.xml.NodeSeq. So we could have def node: Binding[HTMLBRElement] = html"""<br/>""" and def node: BindingSeq[HTMLBRElement] = html"""<br/><br/>""".

A HTML interpolation/literal method is composed with other data-binding expressions in two ways:

  1. You could use bind method in an interpolation to get the value of another Binding.
  2. You could use for / yield expression in a @html method to map a BindingSeq to another.

You can nest Node or BindingSeq[Node] in other HTML element literals via { ... } interpolation syntax.

Step 4: Render the data-binding expressions to DOM in the main method

@JSExport
def main(): Unit = {
  html.render(document.body, table)
}

Step 5: Invoke the main method in a HTML page

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="js-fastopt.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      SampleMain().main()
    </script>
  </body>
</html>

Now you will see a table that just contains a header, because data is empty at the moment.

Step 6: Add some <button> to fill data for the table

def table: BindingSeq[Node] = {
  html"""<div>
    <button
      onclick=${ event: Event =>
        data.value += Contact(Var("Yang Bo"), Var("[email protected]"))
      }
    >
      Add a contact
    </button>
  </div>
  <table border="1" cellPadding="5">
    <thead>
      <tr>
        <th>Name</th>
        <th>E-mail</th>
        <th>Operation</th>
      </tr>
    </thead>
    <tbody>
      ${
        for (contact <- data) yield {
          <tr>
            <td>
              ${contact.name.bind}
            </td>
            <td>
              ${contact.email.bind}
            </td>
            <td>
              <button
                onclick=${ event: Event =>
                  contact.name.value = "Modified Name"
                }
              >
                Modify the name
              </button>
            </td>
          </tr>
        }
      }
    </tbody>
  </table>"""
}

When you click the "Add a contact" button, it appends a new Contact into data, then, Binding.scala knows the relationship between DOM and data, so it decides to append a new <tr> corresponding to the newly appended Contact.

And when you click the "Modify the name", the name field on contact changes, then, Binding.scala decides to change the content of the corresponding tr to new value of name field.

Design

Precise data-binding

ReactJS requires users to provide a render function for each component. The render function should map props and state to a ReactJS's virtual DOM, then ReactJS framework creates a DOM with the same structure as the virtual DOM.

When state changes, ReactJS framework invokes render function to get a new virtual DOM. Unfortunately, ReactJS framework does not precisely know what the state changing is. ReactJS framework has to compare the new virtual DOM and the original virtual DOM, and guess the changeset between the two virtual DOM, then apply the guessed changeset to the real DOM as well.

For example, after you prepend a table row <tr> into an existing <tbody> in a <table>, ReactJS may think you also changed the content of every existing <tr> of the <tbody>.

The reason for this is that the render function for ReactJS does not describe the relationship between state and DOM. Instead, it describes the process to create a virtual DOM. As a result, the render function does not provide any information about the purpose of the state changing, although a data-binding framework should need the information.

Unlike ReactJS, a Binding.scala @html method is NOT a regular function. It is a template that describes the relationship between data source and the DOM. When part of the data source changes, Binding.scala knows about the exact corresponding partial DOM affected by the change, thus only re-evaluating that part of the @html method to reflect the change in the DOM.

With the help of the ability of precise data-binding provided by Binding.scala, you can get rid of concepts for hinting ReactJS's guessing algorithm, like key attribute, shouldComponentUpdate method, componentDidUpdate method or componentWillUpdate method.

Composability

The smallest composable unit in ReactJS is a component. It is fair to say that a React component is lighter than an AngularJS controller, while Binding.scala is better than that.

The smallest composable unit in Binding.scala is a @html method. Every @html method is able to compose other @html methods via .bind.

case class Contact(name: Var[String], email: Var[String])

def bindingButton(contact: Contact) = {
  html"""<button
    onclick=${ event: Event =>
      contact.name.value = "Modified Name"
    }
  >
   Modify the name
  </button>"""
}

def bindingTr(contact: Contact) = {
  html"""<tr>
    <td>${ contact.name.bind }</td>
    <td>${ contact.email.bind }</td>
    <td>${ bindingButton(contact).bind }</td>
  </tr>"""
}

def bindingTable(contacts: BindingSeq[Contact]) = {
  html"""<table>
    <tbody>
      ${
        for (contact <- contacts) yield {
          bindingTr(contact)
        }
      }
    </tbody>
  </table>"""
}

@JSExport
def main(): Unit = {
  val data = Vars(Contact(Var("Yang Bo"), Var("[email protected]")))
  dom.render(document.body, bindingTable(data))
}

You may find out this approach is much simpler than ReactJS, as:

  • Instead of passing props in ReactJS, you just simply provide parameters for Binding.scala.
  • Instead of specifying propTypes in ReactJS, you just simply define the types of parameters in Binding.scala.
  • Instead of raising a run-time error when types of props do not match in ReactJS, you just check the types at compile-time.

Lifecycle management for data-binding expressions

The ability of precise data-binding in Binding.scala requires listener registrations on the data source. Other reactive frameworks that have the ability ask users manage the lifecycle of data-binding expressions.

For example, MetaRx provides a dispose method to unregister the listeners created when building data-binding expressions. The users of MetaRx have the responsibility to call dispose method for every map and flatMap call after the expression changes, otherwise MetaRx leaks memory. Unfortunately, manually disposeing everything is too hard to be right for complicated binding expressions.

Another reactive web framework Widok did not provide any mechanism to manage lifecycle of of data-binding expressions. As a result, it simply always leaks memory.

In Binding.scala, unlike MetaRx or Widok, all data-binding expressions are pure functional, with no side-effects. Binding.scala does not register any listeners when users create individual expressions, thus users do not need to manually unregister listeners for a single expression like MetaRx.

Instead, Binding.scala creates all internal listeners together, when the user calls dom.render or Binding.watch on the root expression. Note that dom.render or Binding.watch manages listeners on all upstream expressions, not only the direct listeners of the root expression.

In brief, Binding.scala separates functionality in two kinds:

  • User-defined @html methods, which produce pure functional expressions with no side-effects.
  • Calls to dom.render or Binding.watch, which manage all side-effects automatically.

HTML literal and statically type checking

As you see, you can embed HTML literals in @html methods in Scala source files. You can also embed Scala expressions in braces in content or attribute values of the HTML literal.

def notificationBox(message: String): Binding[Div] = {
  html"""<div class="notification" title=${ s"Tooltip: $message" }>
    {
      message
    }
  </div>"""
}

Despite the similar syntax of HTML literal between Binding.scala and ReactJS, Binding.scala creates real DOM instead of ReactJS's virtual DOM.

In the above example, <div>...</div> creates a DOM element with the type of org.scalajs.dom.html.Div. Then, the magic @html lets the method wrap the result as a Binding.

You can even assign the HTMLDivElement to a local variable and invoke native DOM methods on the variable:

def notificationBox(message: String): Binding[HTMLDivElement] = {
  val result: Binding.Stable[HTMLDivElement] = html"""<div class="notification" title=${ s"Tooltip: $message" }>
    ${
      message
    }
  </div>"""

  result.value.scrollIntoView()

  result
}

scrollIntoView method will be invoked when the HTMLDivElement is created. If you invoke another method not defined in HTMLDivElement, the Scala compiler will report a compile-time error instead of bringing the failure to run-time, because Scala is a statically typed language and the Scala compiler understands the type of Div.

You may also notice class and title. They are DOM properties or HTML attributes on Div. They are type-checked by Scala compiler as well.

For example, given the following typo method:

def typo = {
  val myDiv = html"""<div typoProperty="xx">content</div>"""
  myDiv.value.typoMethod()
  myDiv
}

The Scala compiler will report errors like this:

typo.scala:23: typoProperty is neither a valid property nor a valid attribute for <DIV>
        val myDiv = html"""<div typoProperty="xx">content</div>"""
                            ^
typo.scala:24: value typoMethod is not a member of org.scalajs.dom.HTMLDivElement
        myDiv.value.typoMethod()
                    ^

With the help of the static type system, @html methods can be much more robust than ReactJS components.

You can find a complete list of supported properties and methods on scaladoc of scalajs-dom or MDN

Showcases

(Feel free to add your project here)

Modules

Binding.scala has an extremely tiny code base. The source files are split into few libraries, one file per library.

Core data-binding expressions (Binding.scala)

This module is available for both JVM and Scala.js. You could add it in your build.sbt.

// For JVM projects
libraryDependencies += "com.thoughtworks.binding" %% "binding" % "latest.release"
// For Scala.js projects, or JS/JVM cross projects
libraryDependencies += "com.thoughtworks.binding" %%% "binding" % "latest.release"

HTML DOM integration (html.scala)

This is the new HTML templating library based on Name Based XML Literals, the module is only available for Scala.js, and the Scala version must between 2.12 and 2.13. You could add it in your build.sbt.

// Enable macro annotations by setting scalac flags for Scala 2.13
scalacOptions ++= {
  import Ordering.Implicits._
  if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
    Seq("-Ymacro-annotations")
  } else {
    Nil
  }
}

// Enable macro annotations by adding compiler plugins for Scala 2.12
libraryDependencies ++= {
  import Ordering.Implicits._
  if (VersionNumber(scalaVersion.value).numbers >= Seq(2L, 13L)) {
    Nil
  } else {
    Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
  }
}

// For Scala.js projects (Scala 2.12 - 2.13)
libraryDependencies += "com.yang-bo" %%% "html" % "latest.release"

See html.scala for more information.

Remote data-binding for scala.concurrent.Future (FutureBinding.scala)

This module is available for both JVM and Scala.js. You could add it in your build.sbt.

// For JVM projects
libraryDependencies += "com.thoughtworks.binding" %% "futurebinding" % "latest.release"
// For Scala.js projects, or JS/JVM cross projects
libraryDependencies += "com.thoughtworks.binding" %%% "futurebinding" % "latest.release"

See FutureBinding for more information.

Remote data-binding for ECMAScript 2015's Promise (JsPromiseBinding.scala)

This module is only available for Scala.js. You could add it in your build.sbt.

// For Scala.js projects
libraryDependencies += "com.thoughtworks.binding" %%% "jspromisebinding" % "latest.release"

See FutureBinding for more information.

Requirements

Due to collection API changes, Binding.scala 12.x only works on Scala 2.13, targeting JVM, Scala.js 0.6 and Scala.js 1.x.

For Scala 2.10, 2.11 and 2.12 on JVM or Scala.js 0.6, use Binding.scala 11.x instead.

Related projects

Other links

More Repositories

1

DeepLearning.scala

A simple library for creating complex neural networks
Scala
763
star
2

DeepDarkFantasy

A Programming Language for Deep Learning
Haskell
465
star
3

cd4ml-workshop

Repository with sample code and instructions for "Continuous Intelligence" and "Continuous Delivery for Machine Learning: CD4ML" workshops
Jupyter Notebook
312
star
4

Dsl.scala

A framework to create embedded Domain-Specific Languages in Scala
Scala
255
star
5

each

A macro library that converts native imperative syntax to scalaz's monadic expressions
Scala
253
star
6

guia-de-desenvolvimento-tecnico

JavaScript
207
star
7

Compute.scala

Scientific computing with N-dimensional arrays
Scala
199
star
8

CD4ML-Scenarios

Repository with sample code and instructions for "Continuous Intelligence" and "Continuous Delivery for Machine Learning: CD4ML" workshops
Python
136
star
9

microbuilder

A toolset that helps you build system across multiple micro-services and multiple languages.
HTML
93
star
10

sbt-api-mappings

An Sbt plugin that fills apiMappings for common Scala libraries.
Scala
88
star
11

enableIf.scala

A library that toggles Scala code at compile-time, like #if in C/C++
Scala
65
star
12

todo

Binding.scala • TodoMVC
Scala
60
star
13

sinais

🔣 Desenvolvimento passo a passo do exemplo `sinais` em Go.
Go
60
star
14

sbt-best-practice

Configure common build settings for a Scala project
Scala
56
star
15

TWU101-TDDIntro

Java
46
star
16

template.scala

C++ Flavored Template Metaprogramming in Scala
Scala
40
star
17

future.scala

Stack-safe asynchronous programming
Scala
39
star
18

ml-app-template

An ML project template with sensible defaults
Python
37
star
19

sbt-scala-js-map

A Sbt plugin that configures source mapping for Scala.js projects hosted on Github
Scala
36
star
20

aws_role_credentials

Generates AWS credentials for roles using STS
Python
34
star
21

transervicos

Ruby
33
star
22

RAII.scala

Resource Acquisition Is Initialization
Scala
32
star
23

sbt-example

Run Scaladoc as unit tests
Scala
31
star
24

feature.scala

Access Scala language features on the type-level
Scala
31
star
25

sbt-ammonite-classpath

Export the classpath for Ammonite and Almond
Scala
27
star
26

Import.scala

A Scala compiler plugin for magic imports
Scala
26
star
27

JS-Monthly-Chengdu

CSS
23
star
28

infra-problem

resources for the infrastructure as code practical assessment
Clojure
23
star
29

bindable.scala

User-friendly Binding.scala components
Scala
23
star
30

ml-cd-starter-kit

Set up cross-cutting services (e.g. CI server, monitoring) for ML projects using kubernetes and helm
Smarty
23
star
31

implicit-dependent-type

Scala
22
star
32

Extractor.scala

Make PartialFunction and extractors composable
Scala
22
star
33

objective8

For the most up to date version of this project, see https://github.com/d-cent/objective8
Clojure
21
star
34

oktaauth

Module and CLI client to handle Okta authentication
Python
20
star
35

js-test-project

JavaScript
18
star
36

tryt.scala

Monad transformers for exception handling
Scala
18
star
37

lein-s3-static-deploy

Lein task to deploy static website to s3 bucket.
Clojure
17
star
38

DesignPattern.scala

Functional Programming Design Patterns
Scala
17
star
39

TWTraining

Open source ThoughtWorks training materials
HTML
15
star
40

dsl-domains-cats

Scala
12
star
41

Q.scala

Convert any value to code
Scala
12
star
42

Constructor.scala

Mixin classes and traits dynamically
Scala
10
star
43

dataclouds

Blog for dataclouds@thoughtworks.
CSS
10
star
44

tf-image-interpreter

Object detection and text spotting from images of any size. Based on TensorFlow.
Python
10
star
45

WorkingEffectivelyWithLegacyCode

Java
10
star
46

ZeroCost.scala

Zero-cost Abstractions in Scala
Scala
9
star
47

Binding.scala-website

Scala
9
star
48

voter-service

The Voter Spring Boot RESTful Web Service, backed by MongoDB, is used for DevOps-related training and testing.
Java
9
star
49

OpenStack-EC2-Driver

OpenStack-EC2-Driver
9
star
50

stonecutter

[Main repo found at https://github.com/d-cent/stonecutter] A D-CENT project: an easily deployable oauth server for small organisations.
Clojure
9
star
51

streaming-data-pipeline

Streaming pipeline repo for data engineering training program
Scala
9
star
52

JavaBootcamp

Java
8
star
53

infra-code-workshop

TechRadar Academy em PoA - Cloud
7
star
54

java-test-project

Java
7
star
55

twseleniumworkshop

Workshop Selenium Belo Horizonte - Setembro 2014
Java
7
star
56

DeepLearning.scala-website

The website of DeepLearning.scala
CSS
7
star
57

AS101-4-workshop

Python
7
star
58

skadoosh

Here we have the building blocks of a virtual entity in the making (in crude words, a chat bot - but don't call it that. It gets offended).
Python
7
star
59

json-stream-core

Universal Serialization Framework for JSON
Haxe
6
star
60

ScaleWorks_YUMChina

Ruby
6
star
61

lein-filegen

A leiningen plugin to generate files
Clojure
6
star
62

microbuilder-core

Haxe
6
star
63

clj-http-s3

Middleware to allow cli-http to authenticate with s3
Clojure
6
star
64

expend-rs

Internal application to submit certain expenses to ThoughtWorks' system
Rust
6
star
65

LatestEvent.scala

bidirectional data-binding and routing for Scala.js
Scala
6
star
66

sbt-jdeps

an sbt plugin to run JDeps
Scala
5
star
67

modularizer

Scala
5
star
68

twu-toolkit

Calendar generator for TWU
Ruby
5
star
69

twcss

CSS Coding Guidelines
5
star
70

loans-lah-tdd-workshop

JavaScript
5
star
71

Binding.scala-play-template

Scala
5
star
72

TypeOf.scala

Create types from expressions
Scala
4
star
73

SG-ObjectBootcamp

Java
4
star
74

sbt-delombok

an sbt plug-in to delombok Java sources files that contain Lombok annotations
Scala
4
star
75

zeratul

a wrapper for JPA
Java
4
star
76

CSharpTestProject

C#
4
star
77

HashRoute.scala

Scala
4
star
78

Tensor.scala

A totally functional DSL for general purpose GPU programming
4
star
79

akka-http-rpc

Turn akka-http to a RPC server
Scala
4
star
80

monadic-deep-learning

TeX
4
star
81

wxapp-workshop

3
star
82

sde

Scala
3
star
83

clojuregoat

A goat, in Clojure
Clojure
3
star
84

sonic

React UI Components
JavaScript
3
star
85

go-maven-poller

Go plugin that polls Maven (Nexus) repositories
3
star
86

mooncake

A D-CENT project: Secure notifications combined with w3 activity streams
Clojure
3
star
87

Binding.scala-activator-template

Scala
3
star
88

akka-http-webjars

Serve static assets from WebJars
Scala
3
star
89

scala-project-template

3
star
90

offnet

The Unified Neural Network
Jupyter Notebook
3
star
91

aem-training-2016

Code repo for AEM training in August 2016.
Java
3
star
92

hackerbrasileiro

Java
3
star
93

helsinki

[Main repo found at https://github.com/d-cent/decisionsproto] Spike for indexing data from the Open Ahjo API in elasticsearch
Python
3
star
94

FallbackLookupStrategy.java

Java
2
star
95

cep-conference

Core Engineering Practices "Conference" exercise
Java
2
star
96

Cifar10.scala

Scala
2
star
97

Kaleidoscopez

JavaScript
2
star
98

infra-code-devopslabs01

TechRadar Academy - Cloud Workshop - IaC
Python
2
star
99

FunctionalPattern

Scala
2
star
100

android-test-project

Kotlin
2
star