Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

and we now have the following screen (live):

Data binding works two ways. Let's add a button to the screen and change the data inside the Invoice instance with the following code added at the end of createContent:

Code Block
add(new DefaultButton("Clear amount", a -> {
   m_invoice.setTotal(BigDecimal.ZERO);
}));

The screen now looks like (live):

Press the button, and you will see that the screen amount is set to zero even though we did not touch the component. So we manipulate only the model, and the view adapts itself. This means that business logic does not need to know much about the screens.

How does it work?

DomUI data binding works on the request/response cycle. When you add a data binding using the bind().to() construct the binding is stored inside the component. Now, every time that a new request from the browser enters the server DomUI executes the moveControlToModel() code: it accepts all of the data that the browser sends as the current value for all of the controls there, and then asks each (changed) control to move their data along their bindings. This means that the value that is inside the control is converted (if needed) and then sent to the invoice instance's property.

When the logic inside the server has finished DomUI runs the reverse operation: it now executes moveModelToControl(). This will again ask all controls for their bindings, and will then move the data from all instance properties into the control's value. After that the controls are rendered back to the browser.

Binding to other things than the value of a component

We can apply bindings to a lot of things, and using bindings makes a screen very dynamic without much work. Take the following example:

Code Block
final public class BindingTut3 extends UrlPage {
   @Override public void createContent() throws Exception {
      List<CssColor> cssColors = CssColor.calculateColors(16);
      List<String> list = cssColors.stream().map(c -> c.toString()).collect(Collectors.toList());

      ComboLookup2<String> colorC = new ComboLookup2<>(list);
      add(colorC);

      VerticalSpacer vs = new VerticalSpacer(50);
      add(vs);
      vs.setBackgroundColor("#000000");

      colorC.bind().to(vs, "backgroundColor");
      colorC.immediate();
   }
}

This shows the following (live):

By selecting the color in the combobox the VerticalSpacer below it changes color immediately, because the background property of the VerticalSpacer is bound to the value of the combobox with:

Code Block
colorC.bind().to(vs, "backgroundColor");

We also see something else: the colorC.immediate() method. We need this because we want the binding to be executed as soon as the combobox changes value. Usually DomUI only sends the values to the server when some action is needed, like a click on a button. The immediate() method on a control forces updates to be sent as soon as the control value changes.

Another example of binding to other values: handling disabled buttons with ease

Say we have a screen like this, where we want both values to be selected before the user can press the "Send Info" button:

The code for the page is this:

Code Block
final public class BindingTut4 extends UrlPage {
   @Override public void createContent() throws Exception {
      LookupInput2<Artist> artistC = new LookupInput2<Artist>(QCriteria.create(Artist.class));
      add(new Div()).add(artistC);

      LookupInput2<Customer> customerC = new LookupInput2<Customer>(QCriteria.create(Customer.class));
      add(new Div()).add(customerC);

      DefaultButton btn = new DefaultButton("Send info", a -> {
         MsgBox.info(this, "E-mailing " + customerC.getValue() + " with info on " + artistC.getValue());
      });
      add(btn);
   }
}

Problem here is that pressing the "send info" button now would have an unwanted effect:

Image Added

The button should be disabled as long as the user has not made both choices. This can be easily done with binding. We change the code as follows:


Code Block
final public class BindingTut5 extends UrlPage {
   private Artist m_artist;

   private Customer m_customer;

   @Override public void createContent() throws Exception {
      LookupInput2<Artist> artistC = new LookupInput2<Artist>(QCriteria.create(Artist.class));
      add(new Div()).add(artistC);
      artistC.bind().to(this, "artist");

      LookupInput2<Customer> customerC = new LookupInput2<Customer>(QCriteria.create(Customer.class));
      add(new Div()).add(customerC);
      customerC.bind().to(this, "customer");

      DefaultButton btn = new DefaultButton("Send info", a -> {
         MsgBox.info(this, "E-mailing " + customerC.getValue() + " with info on " + artistC.getValue());
      });
      add(btn);
      btn.bind("disabled").to(this, "buttonDisabled");
   }

   private boolean isButtonDisabled() {
      return m_artist == null || m_customer == null;
   }

   private Artist getArtist() {
      return m_artist;
   }

   private void setArtist(Artist artist) {
      m_artist = artist;
   }

   private Customer getCustomer() {
      return m_customer;
   }

   private void setCustomer(Customer customer) {
      m_customer = customer;
   }
}
  • We added properties for the customer and artist, and bound these properties to the controls
  • We added the isButtonDisabled() method, this returns TRUE as long as either of the properties is NULL
  • And we bound the disabled property of the button to this method.

This is an important thing: the bind() call binds to the value property (actually the bindValue property of the control if that exists), but by adding a control's property as parameter to bind you can use data binding to control the control (wink)

And now the button works as desired: