Tessell

Cell Table

GWT 2.1’s CellTable widget is one of two primary ways to do tables in Tessell The other is RowTable.

Update/Disclaimer May 2014: I personally don’t use cell tables, so this page is likely out of date. The example also uses bindgen integration, which was also removed from Tessell because it was not actively used.

Benefits

CellTable is most appropriate when:

Drawbacks

I generally find RowTable easier to use unless your tables are really simple or have lots of data.

Approach

While CellTable is an out-of-the-box GWT widget, Tessell has a nice way of using it to facilitate easier setup and testing.

First, Tessell treats Cell implementations as inherently DOM-coupled. Besides producing raw HTML, cells respond to browser events and generally do very DOM-focused things. Much like widgets.

However, for pure-JUnit presenter tests, the goal is to test your presenter, not the Cell’s implementation/DOM integration. So Tessell makes an IsXxx interface for each cell, just like it does for each widget. TextCell has IsTextCell, CheckboxCell has IsCheckboxCell, etc. During testing, stubs are used, which are not DOM-coupled and enable fake clicking/etc.

Second, normally with CellTable, you extend Column to provide each column’s value. However, this is verbose (a lot of inner classes) and also DOM-coupled because the Column base class itself has low-level browser event dispatching code in it.

Tessell splits up Column’s concerns into two things: IsColumn which is an abstraction of the DOM-coupled behavior, and ColumnValue, which is a simple interface for just getting/setting the column’s value.

For pure-JUnit presenter tests, the goal is to test your ColumnValue implementation–that your presenter transforms the rows into values appropriately. After that, it is assumed the Cell implementation does the correct thing with the value at runtime.

Putting these together, using CellTable in Tessell, you’ll usually have:

The example should show how this works out well, both for setup and testing.

Simple Example

For example, given a page with an employees table, we’d have Page.ui.xml:

<ui:UiBinder ...>
  <gwt:HTMLPanel>
    ... various html ...
    <gwt:SimplePanel ui:field="employeeTablePanel"/>
  </gwt:HTMLPanel>
</ui:UiBinder>

The PagePresenter fetches the table data.

public class PagePresenter extends AbstractPresenter<IsPageView> {

  private final IsCellTable<EmployeeDto> table = newCellTable();

  private void onBind() {
    super.onBind();
    // logic to get data from server ...
    async.execute(new GetDataAction(...), ...);
    setupTable();
  }

  private void setupTable() {
    // drop the table into its panel
    view.employeeTablePanel().add(table);

    // create a binding (generated by bindgen)
    EmployeeDtoBinding eb = new EmployeeDtoBinding();

    // add the name column
    table.addColumn(newColumn(
      boundValue(eb.name()), newTextCell()));

    // add the description column
    table.addColumn(newColumn(
      boundValue(eb.description()), newTextCell()));
  }

  // call when we get data from the server
  private void onData(ArrayList<EmployeeDto> employees) {
    table.setRowData(employees);
  }
}

EmployeeDtoBinding is a class generated by the bindgen data binding framework. The eb.name() and eb.description() calls return Binding instances that can get/set the name/description of each EmployeeDto. boundValue is a Tessell static method that adapts a Binding to a ColumnValue.

The combination of bindgen-generated Binding classes and boundValue allow you to quickly setup multiple columns in a type-safe manner, without any subclasses.

newColumn is a Tessell static method that takes a ColumnValue + Cell of matching types and turns them into an IsColumn (GwtColumn at GWT-time, StubColumn at test-time).

Testing

Testing CellTable involves getting the stub cells to assert they’ve been given the right values and to fire fake events on them.

This will exercise the business-logic side of your presenters–providing the right column values and responding to their events, without using the DOM-based cell implementations.

public class PagePresenterTest {
  final PagePresenter p = bind(new PagePresenter(registry));
  final StubPageView v = (StubPageView) p.getView();
  final StubCellTable<EmployeeDto> table =
    (StubCellTable<EmployeeDto>) v.employeeTablePanel.getIsWidget(0);

  @Test
  public void nameIsSet() {
    doDataResult();
    assertThat(nameCell().getValue(0), is("Employee 1"));
    assertThat(nameCell().getValue(1), is("Employee 2"));
  }

  private StubTextCell nameCell() {
    return (StubTextCell) table.getCells(0);
  }

  private StubTextCell descriptionCell() {
    return (StubTextCell) table.getCells(1);
  }

  private void doDataResult() {
    // do async callback/something that results in
    // PagePresenter.onData being called
  }
}

Caveats

  1. StubCellTable is minimally implemented right now–it needs to be updated to reuse GWT’s HasDataPresenter for it’s internal row logic
  2. Not all built-in cells have IsXxx interfaces yet as I create them on an as-needed basis