Adam Warski

16 Dec 2014

Supler 0.1.0: complex forms made easier

scala
web

Supler aims to make complex web forms development easier, without tying you to a web framework. Supler provides a server-side DSL for defining forms, generating a JSON form representation, applying values received from the client, validating and running actions. It also provides a front-end renderer for the forms, client-side validation, serialization to json and background refresh support.

For a general introduction, see the previous blog, or just head over to GitHub. Please star the project if you find it interesting and would like to see it developed further!

We also have a live demo if you’d like to see things in action, with the (quite simple) code available here.

Supler 0.1.0 is available now in Sonatype’s OSS repository (backend) and in Bower (frontend).

The Supler Flow …

… is best illustrated by a diagram:

supler diagram

Note that all the details on where the form-backing objects (entities) come from, how they are persisted, how the HTTP endpoints are exposed, what JS framework (if any) you use to handle AJAX requests, are left to you: you are free to do it in any way you like. Supler is only concerned with form handling, nothing else.

What’s new since the initial announcement? Three major features:

Actions

First of all, forms can now contain buttons tied to server-side actions. These actions can modify the backing object, e.g.:

case class Person(name: String)

val personForm = form[Person](f => List(
  f.field(_.name).label("Name"),
  f.action("duplicateName")(p => ActionResult(p.copy(name = s"${p.name} ${p.name}"))
    .label("Duplicate name")
))

Quite commonly, we need to modify the parent object, e.g. to remove or change the order of sub-objects (sub-forms). This is possible by parametrising the form with the action and using parentActions:

case class Address(street: String)
case class Person(name: String, addresses: List[Address]) {
  def removeAddress(a: Address) = this.copy(addresses = this.addresses diff List(a))
}

def addressForm(removeAction: Address => ActionResult[Address]) = form[Address](f => List(
  f.field(_.street).label("Street"),
  f.action("remove")(removeAction).label("Remove")
))

val personForm = form[Person](f => List(
  f.field(_.name).label("Name"),
  f.subform(_.addresses, addressForm(
    f.parentAction((person, index, address) => ActionResult(person.removeAddress(address)))))
    .label("Addresses")
))

Note that the parametrisation is pure Scala: we are defining a method, which returns an immutable form description.

On the frontend, we need to have some way to reload the form when an action is invoked. This is done by providing Supler with a Javascript function that should be invoked on an action, and which receives two parameters: the serialized form and a successful response callback. On the backend, a convenient FormWithObject.reload(JValue): JValue method is provided, which applies the values, runs validation, actions and generates a new JSON.

Further development will include running the actions only when certain form area is valid, or handling action responses which are a custom json (so that you can perform custom naviagation, for example).

Background refresh

Another important feature for dynamic forms is being able to change the form content in response to user input (e.g. change a detail-dropdown when the master-dropdown selection is changed). This is implemented using the same callback as for the actions. Whenever a field is changed, the form is reloaded and re-rendered.

Further development will include making the mechanism configurable by specifying changes to which fields should trigger a background reload.

HTML templates

The last addition is being able to customise the HTML rendering process by providing HTML templates. By default Bootstrap3-compatible markup is generated, but that may not be of course what you want.

You may customise rendering at various levels of granularity: starting from labels, through validation, individual inputs, to overall shape of a form entry. The customisations may affect an individual field, all fields of a given type, or all fields. For example:

<div id="form-container">
  <div supler:fieldTemplate supler:fieldPath="lastName">
    <div class="formGroup">
      <i>{{suplerLabel}} <span style="font-size: xx-small">(extra information)</span></i>
            {{suplerInput}}
            {{suplerValidation}}
          
    </div>
      
  </div>
  
</div>

The full details are here.

Summing up

Please remember that Supler is still in early stages of development. If you would have any design suggestions or ideas, now is the time! Also, if you like the project, please let us know by starring it on GitHub!

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?