Community contributed extensions

Tabula Rasa

Tabula Rasa provides support for user-customisable tables, allowing each user to have a custom view of any table in your application. Client-side support is provided using the excellent DataTables jQuery plugin.

Database table definitions

The structure of the table definitions in the database is pretty straightforward. A TableOwner has multiple TableModels, and each TableModel has 0 or more TableColumns. The tricky part about this is JPA’s problem with interfaces, so in order to make your users table-owners, you’ll need to add a TableOwner field to your user entity – this is a straightforward one-to-one mapping.

@OneToOne(cascade = CascadeType.ALL)
public TableOwner tableOwner;

Once you’ve done this, you can create table models for the table owner.

The table model

A table model represents a table owner’s configuration for a specific table in your application. It’s essentially an aggregation of table columns plus key information – the view ID, the table ID and the table owner.

View IDs

View IDs are linked to a single screen which may contain one or more tables. Think of a view ID as a namespace, allowing tables to reuse table IDs when you feel like it.

Table IDs

Table IDs identify individual tables within a view. Each table within a view has a unique ID, allowing it to have a unique configuration.

Table owners

A table owner can have multiple table models, but the combination of table owner plus view ID plus table ID is unique.

Table models are not necessary!

Despite everything written above, it’s perfectly possible to use Tabula Rasa without table models. Personalized table models are only useful if you’re creating a web app with identifiable users – if you’re not doing this, you can still use Tabula Rasa to give you integration with DataTables so that, for example, AJAX delivery of content is simplified.

Table columns

A table column represents a single column in your table model. It has a key, a position, a flag to indicate if it’s visible and another to indicate if it’s mandatory.

Creating table models

Assuming you’ve already integrated TableOwner into your user entity, you’re now ready to create personalized tables. In your controller, you need to define between one and three String arrays in order to create the factory-default view of the table- the visible columns, all the columns and the mandatory columns.

Visible columns

Visible columns are required, because they define what the user sees the very first time the table is used. The contents of the array define the column keys and their intial order.

private static final String[] VISIBLE_COLUMNS = {"name", "location"};

A table model initialised with this array would contain two columns – both would be visible, neither would be mandatory and the order would be “name” and “location”.

To retrieve a table mode, and create a one if it doesn’t exist, you can use the TableController:

TableModel tableModel = TableController.getTableModel(user.tableOwner,
                                                      VIEW_ID,
                                                      TABLE_ID,
                                                      VISIBLE_COLUMNS);

Using this method, VISIBLE_COLUMNS is taken to define ALL_COLUMNS too.

h3.All columns

It’s possible that your table contains so many columns that it would be a UI nightmare to show them all at once. In this scenario, you can define all your columns, and then define the ones your want to be visible:

private static final String[] VISIBLE_COLUMNS = {"location", "foo"};
private static final String[] ALL_COLUMNS = {"name", "location", "foo", "bar"};

And then create the table model:

TableModel tableModel = TableController.getTableModel(user.tableOwner,
                                                      VIEW_ID,
                                                      TABLE_ID,
                                                      VISIBLE_COLUMNS,
                                                      ALL_COLUMNS);

A table model initialised with these arrays would contain four columns – the union of ALL_COLUMNS and VISIBLE_COLUMNS would be visible, none would be mandatory and the order would be “name”, “location”, “foo” and “bar”.

Mandatory columns

If there is a case where you want to mark a column as always visible, you can set the mandatory flag in the same manner as above.

private static final String[] VISIBLE_COLUMNS = {"location", "foo"};
private static final String[] ALL_COLUMNS = {"name", "location", "foo", "bar"};
private static final String[] MANDATORY_COLUMNS = {"name", "foo"};

A table model initialised with these arrays would contain four columns – the union of ALL_COLUMNS and VISIBLE_COLUMNS would be visible, any column marked as mandatory would be flagged as both mandatory and visible – even if it didn’t appear in VISIBLE_COLUMNS, and the order would be “name”, “location”, “foo” and “bar”.

Displaying the table

Rendering with data at request time

The standard Play mechanism of passing objects to be rendered into the render() method is used here, with the table model also passed in.

List<Foo> foo = Foo.findAll();
render(tableModel,
       foo);

In the view, you first need to initialize the table.

#{set 'moreScripts'}
    #{tabularasa.init addScriptTag:true, tableId:tableModel.tableId, tableModel:tableModel /}
#{/set}

Note the addScriptTag parameter – this will render a javascript block with a jQuery $(document).ready() block around the script. If you exclude this parameter, you will need to put the tag into a javascript block yourself.

This will set up the DataTable parameters and convert the HTML table to a DataTable when the document is ready.

To render the rows of the table, you need to use the tabularasa.table tag. This tag will create the table, the table header, etc, so you only need to add tr elements.

#{tabularasa.table tableModel:tableModel, cssClass:'display'}
    #{list items:foo, as:'fooInstance'}
        <tr>
            #{list items:tableModel.tableColumns, as:'column'}
                #{if column.visible}
                    <td>${controllers.FooController.getByColumnKey(fooInstance, column.columnKey)}</td>
                #{/if}
            #{/list}
        </tr>
    #{/list}
#{/tabularasa.table}

FooController.getByColumnKey is a static utility method that retrieves a value from the target object based on the column key. See the section below on mapping values for more on this.

Adding more data to the AJAX request

If you want to add parameters to the AJAX request, the easiest way to do this is to add a method called addRequestParameters to your page script. This method
takes two parameters – a map containing the request parameters, and the table ID. To add the parameters, just push them into the parameters object.

function addRequestParameters(parameters, tableId) {
    parameters.push( { "name": "manuallyInsertedParameter",
                       "value": "bar" } );
    parameters.push( { "name": "foo.x",
                       "value": "steve" } );
    parameters.push( { "name": "foo.y",
                       "value": true } );
    parameters.push( { "name": "foo.z",
                       "value": 5 } );
}

The usual Play binding magic works, so if your controller method takes a Foo parameter then foo.x, foo.y and foo.z will be bound to the matched fields.

Mapping values based on the column key

The brute force approach

You can use brute force to map values, for example

public static Object getByColumnKey(Foo foo, String columnKey)
{
    Object value = null;
    if ("name".equals(columnKey))
    {
        value = foo.name;
    }
    else if ("location".equals(columnKey))
    {
        value = foo.location;
    }
    return value;
}

ObjectValueMappers

You can encapsulate the logic for mapping column keys to object values into ObjectValueMappers. When rendering data via AJAX, a mapper can be passed to the renderJSON method or you can registered a mapper against a class globally within the TableController. If a mapper for the class can’t be found, or isn’t passed in, a fallback mapper is used which uses objectify-led properties to identify the mapping.

The name

Tabula Rasa is designed to help you work with tables, so I named it after a Buffy the Vampire Slayer that may have featured a table at some point.