Tessell

Functional* Reactive Framework for GWT

http://www.tessell.org

Two goals

  1. MVP Automation
  2. Reactive Data Binding

MVP: Purpose

  1. Unit test busines logic
  2. Quickly, e.g. no browser

public void testNameIsRequired() {
  v.submitButton().click();
  assertThat(v.nameErrors(), hasError("Required"));
}
  

public void testServerResponse() {
  v.submitButton().click();
  doSubmitResult("some error from server");
  assertThat(v.nameErrors(), hasError("some error..."));
}
  

MVP: Manual Workflow

1. Change somePage.ui.xml

<gwt:TextBox ui:field="foo" />

2. Change IsSomePageView


public interface IsSomePageView {
  HasText getFoo();
}
    

3. Change GwtSomePageImpl


@UiField
TextBox foo;

public HasText getFoo() {
  return this.foo;
}
    

4. Update presenter

MVP: Tessell Workflow

1. Change somePage.ui.xml

<gwt:TextBox ui:field="foo" />

2. Change IsSomePageView interface


public interface IsSomePageView {
  IsTextBox getFoo();
}
    

3. Change GwtSomePageView implementation


@UiField
GwtTextBox foo;

public IsTextBox getFoo() {
  return this.foo;
}
    

4. Update presenter

MVP: Result

Fast, no-boilerplate workflow

Demo

Reactive Databinding

  1. Two-way binding between view and model
  2. Support for collections, derived values
  3. Functional*
    • Caveat: need lambdas to be "functional"

Databinding: Swing Approach

Handler spaghetti code


textBox.addChangeHandler(new ChangeHandler() {
  public void onChange() {
    model.setName(textBox.getValue());
  }
});
  

model.addChangeHandler(new ChangeHandler() {
  public void onChange() {
    textBox.setValue(model.getName());
  }
});
  

Databinding: Swing with Lambdas


textBox.addChangeHandler(e -> model.setName(textBox.getValue()));
  

model.addChangeHandler(e -> textBox.setValue(model.getName()));
  

Databinding: Tessell

Think declaratively


public void onBind() {
  binder.bind(model.name).to(view.textBox());
}
  

With collections


binder.bind(model.children).to(view.panel(), new ListViewFactory() {
  public IsWidget create(ChildDto child) {
    return new ChildPresenter(child);
  }
}));
  

Conditional logic


    binder.when(editing).is(true).set(css.editing()).on(view.li());
  

On user input


    binder.onKeyDown(view.editBox(), KEY_ESCAPE).set(editing).to(false);
  

Caveat: We need properties

Given a DTO:


class ChildDto {
  public String name;
  public Dollars salary;
}
  

How would this work?


ChildDto c = new ChildDto();
binder.bind(c.name).to(view.textBox()); // ??
  

When ChildDto.name changes?

Caveat: We need properties

Build models object:


class ChildModel {
  public StringProperty name = stringProperty("name");
  public DollarsProperty salary = dollarsProperty("salary");
}
  

Now we can see when name changes:


ChildModel c = new ChildModel();
c.name.addPropertyChangedHandler() {
  public void onChange() {
    view.textBox().setValue(c.name.get());
  }
}

// Or, shorter way
binder.bind(c.name).to(view.textBox());
  

Caveat: We need models

Sounds like more boilerplate?

Answer: Code generation

dtonator (http://www.dtonator.org)


ChildDto:
  domain: Child
  properties: id, name
  tessellModel: true
  

Outputs:


ChildDto dto = new ChildDto(1, "c1");
ChildModel model = new ChildModel(dto);
model.name.get(); // returns c1
  

Reactive

Derived values


StringProperty name = stringProperty("name", "n1");
IntegerProperty max = integerProperty("max", 10);
Property<String> shortName = new DerivedProperty<String>() {
  public String getDerivedValue() {
    return name.get().substring(0, max.get());
  }
}
binder.bind(shortName).to(view.shortName());
  

And soon:


binder.bind(() -> {
  name.get().substring(0, max.get())
}).to(view.shortName());
  

Reactive

Demo

Minimal Places


public class MyPresenter {
  @GenPlace(name = "myPlace", async = false)
  public static void onRequest(MyPlaceRequest req, AppContext c) {
    c.show(new MyPresenter(...));
  }
  

Command Pattern Code Generation


@GenDispatch
public class SaveClientSpec {
  @In(1)
  ClientDto dto;
  @Out(1)
  String error;
}
  

Generates:


async.execute(new SaveClientAction(dto), SaveClientResult r -> {
  r.getError(); // logic
});
  

Interfaces and Stubs for widgets

Out of the box:

  • IsTextBox, StubTextBox
  • IsListBox, StubListBox
  • IsFlowPanel, StubFlowPanel

Tessell Overview: Benefits

  1. Mature, stable code (4+ years, 6800 commits, 361 releases)
  2. All core functionality (data binding, etc.) is Java, so presenters can be unit tested and it will exercise all binding/etc. logic
  3. First-class support for common idioms
    • All properties have validation rules
    • All properties have "touched" state

Tessell Overview: Weaknesses

  1. No bootstrap/archetype to get started
    • Day one setup is going to suck
    • Day two on is going to be fun
  2. Does not take "Rails"/batteries-included approach
    • Has gwt-dispatch-style RPC, optional
    • Has pre-Activities & Places places, optional

Done