All Projects → ThoughtWorksInc → Binding.scala

ThoughtWorksInc / Binding.scala

Licence: mit
Reactive data-binding for Scala

Programming Languages

scala
5932 projects
Roff
2310 projects

Projects that are alternatives of or similar to Binding.scala

Slim.js
Fast & Robust Front-End Micro-framework based on modern standards
Stars: ✭ 789 (-48.73%)
Mutual labels:  data-binding, dom, front-end
Amplesdk
Ample SDK - JavaScript UI Framework
Stars: ✭ 169 (-89.02%)
Mutual labels:  xml, gui, dom
Fn Fx
A Functional API around JavaFX / OpenJFX.
Stars: ✭ 373 (-75.76%)
Mutual labels:  javafx, gui, dom
Xml
XML without worries
Stars: ✭ 35 (-97.73%)
Mutual labels:  xml, dom
Etree
parse and generate XML easily in go
Stars: ✭ 763 (-50.42%)
Mutual labels:  xml, dom
Drab
Remote controlled frontend framework for Phoenix.
Stars: ✭ 833 (-45.87%)
Mutual labels:  web-framework, dom
Prettyzoo
😉 Pretty nice Zookeeper GUI, Support Win / Mac / Linux Platform
Stars: ✭ 671 (-56.4%)
Mutual labels:  javafx, gui
Tokamak
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
Stars: ✭ 1,083 (-29.63%)
Mutual labels:  data-binding, dom
Onthefly
🔗 Generate TinySVG, HTML and CSS on the fly
Stars: ✭ 37 (-97.6%)
Mutual labels:  xml, dom
Skin.refocus
reFocus, a skin for Kodi
Stars: ✭ 72 (-95.32%)
Mutual labels:  xml, gui
Ofbiz Plugins
Apache OFBiz is an open source product for the automation of enterprise processes. It includes framework components and business applications for ERP, CRM, E-Business/E-Commerce, Supply Chain Management and Manufacturing Resource Planning. OFBiz provides a foundation and starting point for reliable, secure and scalable enterprise solutions.
Stars: ✭ 83 (-94.61%)
Mutual labels:  xml, web-framework
Ofbiz
Apache OFBiz - Main development has moved to the ofbiz-frameworks repository.
Stars: ✭ 719 (-53.28%)
Mutual labels:  xml, web-framework
Patternfly Design
Use this repo to file all new feature or design change requests for the PatternFly project
Stars: ✭ 82 (-94.67%)
Mutual labels:  gui, front-end
Develnext
JavaFX and IDE for JPHP (only russian localization, english - in progress)
Stars: ✭ 89 (-94.22%)
Mutual labels:  javafx, gui
Easy Dom
EASYDOM-可能是最适合你的 DOM 课程
Stars: ✭ 21 (-98.64%)
Mutual labels:  dom, front-end
Dom4j
flexible XML framework for Java
Stars: ✭ 689 (-55.23%)
Mutual labels:  xml, dom
Wordagam
A fun & interactive word game 🍄
Stars: ✭ 55 (-96.43%)
Mutual labels:  javafx, gui
Easyfxml
A collection of tools and libraries for easier development on the JavaFX platform!
Stars: ✭ 102 (-93.37%)
Mutual labels:  javafx, gui
Displayjs
A simple JavaScript framework for building ambitious UIs 😊
Stars: ✭ 590 (-61.66%)
Mutual labels:  dom, front-end
Jsoup
jsoup: the Java HTML parser, built for HTML editing, cleaning, scraping, and XSS safety.
Stars: ✭ 9,184 (+496.75%)
Mutual labels:  dom, xml

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 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(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 += "org.lrng.binding" %%% "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

@html
def table: Binding[Table] = {
  <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>
}

A @html method represents an reactive XHTML template, which supports HTML literal. Unlike normal XML literal in a normal Scala method, the types of HTML literal are specific subtypes of org.scalajs.dom.raw.Node or com.thoughtworks.binding.BindingSeq[org.scalajs.dom.raw.Node], instead of scala.xml.Node or scala.xml.NodeSeq. So we could have @html def node: Binding[org.scalajs.dom.raw.HTMLBRElement] = <br/> and @html def node: Binding[BindingSeq[org.scalajs.dom.raw.HTMLBRElement]] = <br/><br/>.

A @html method is composed with other data-binding expressions in two ways:

  1. You could use bind method in a @html method to get 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

@html
def table: BindingSeq[Node] = {
  <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])

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

@html
def bindingTr(contact: Contact): Binding[TableRow] = {
  <tr>
    <td>{ contact.name.bind }</td>
    <td>{ contact.email.bind }</td>
    <td>{ bindingButton(contact).bind }</td>
  </tr>
}

@html
def bindingTable(contacts: BindingSeq[Contact]): Binding[Table] = {
  <table>
    <tbody>
      {
        for (contact <- contacts) yield {
          bindingTr(contact).bind
        }
      }
    </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.

@html
def notificationBox(message: String): Binding[Div] = {
  <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 Div to a local variable and invoke native DOM methods on the variable:

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

  result.scrollIntoView()

  result
}

scrollIntoView method will be invoked when the Div is created. If you invoke another method not defined in Div, 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:

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

The Scala compiler will report errors like this:

typo.scala:23: value typoProperty is not a member of org.scalajs.dom.html.Div
        val myDiv = <div typoProperty="xx">content</div>
                     ^
typo.scala:24: value typoMethod is not a member of org.scalajs.dom.html.Div
        myDiv.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

Custom attributes

If you want to suppress the static type checking of attributes, add a data: prefix to the attribute:

@html def myCustomDiv = {
  val myDiv = <div data:customAttributeName="attributeValue"></div>
  assert(myDiv.getAttribute("customAttributeName") == "attributeValue")
  myDiv
}

The Scala compiler will not report errors now.

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 += "org.lrng.binding" %%% "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.

Links

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].