Table

Table is the simplest way to output multiple records of structured data. Table only works along with the model, however you can use View::setSource to inject static data (although it is slower than simply using a model). no_data

Using Table

The simplest way to create a table:

$table = $layout->add('Table');
$table->setModel(new Order($db));

The table will be able to automatcally determine all the fields defined in your “Order” model, map them to appropriate column types, implement type-casting and also connect your model with the appropriate data source (database) $db.

To change the order or explicitly specify which columns must appear, you can pass list of columns as a second argument to setModel:

$table = $layout->add('Table');
$table->setModel(new Order($db), ['name', 'price', 'amount', 'status']);

Table will make use of “Only Fields” feature in Agile Data to adjust query for fetching only the necessary columns. See also field_visibility.

Adding Additional Columns

If you feel that you’d like to add several other columns to your table, you need to understand what type of columns they would be.

If your column is designed to carry a value of any type, then it’s much better to define it as a Model Field. A good example of this scenario is adding “total” column to list of your invoice lines that already contain “price” and “amount” values. Start by adding new Field in the model that is associated with your table:

$table = $layout->add('Table');
$order = new Order($db);

$order->addExpression('total', '[price]*[amount]')->type = 'money';

$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);

The type of the Model Field determines the way how value is presented in the table. I’ve specified value to be ‘money’ which makes column align values to the right, format it with 2 decimal signs and possibly add a currency sign.

To learn about value formatting, read documentation on ui_persistence.

Table object does not contain any information about your fields (such as captions) but instead it will consult your Model for the necessary field information. If you are willing to define the type but also specify the caption, you can use code like this:

$table = $layout->add('Table');
$order = new Order($db);

$order->addExpression('total', [
    '[price]*[amount]',
    'type'=>'money',
    'caption'=>'Total Price'
]);

$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);

Column Objects

Table object relies on a separate class: atk4uiTableColumnGeneric to present most of the values. The goals of the column object is to format anything around the actual values. The type = ‘money’ will result in a custom formatting of the value, but will also require column to be right-aligned. To simplify this, type = ‘money’ will use a different column class - TableColumnMoney. There are several others, but first we need to look at the generic column and understand it’s base capabilities:

class phpctrl\ui\TableColumnGeneric

A class resposnible for cell formatting. This class defines 3 main methods that is used by the Table when constructing HTML:

phpctrl\ui\TableColumnGeneric::getHeaderCellHTML(atk4dataField $f)

Must respond with HTML for the header cell (<th>) and an appropriate caption. If necessary will include “sorting” icons or any other controls that go in the header of the table.

The output of this field will automatically encode any values (such as caption), shorten them if necessary and localize them.

phpctrl\ui\TableColumnGeneric::getTotalsCellHTML(atk4dataField $f, $value)

Provided with the field and the value, format the cell for the footer “totals” column. Table can rely on various strategies for calculating totals. See Table::addTotals.

phpctrl\ui\TableColumnGeneric::getDataCellHTML(atk4dataField f)

Provided with a field, this method will respond with HTML template. In order to keep performance of Web Application at the maximum, Table will execute getDataCellHTML for all the fields once. When iterating, a combined template will be used to display the values.

The template must not incorporate field values (simply because related model will not be loaded just yet), but instead should resort to tags and syntax compatible with Template.

A sample template could be:

<td><b>{$name}</b></td>

Note that the “name” here must correspond with the field name inside the Model. You may use multiple field names to format the column:

<td><b>{$year}-{$month}-{$day}</b></td>

The above 3 methods define first argument as a field, however it’s possible to define column without a physical field. This makes sense for situations when column contains multiple field values or if it doesn’t contain any values at all.

Sometimes you do want to inject HTML instead of using row values:

phpctrl\ui\TableColumnGeneric::getHTMLTags($model, $field = null)

Return array of HTML tags that will be injected into the row template. See Injecting HTML for further example.

Advanced Column Denifitions

class phpctrl\ui\Table

Table defines a method columnFactory, which returns Column object which is to be used to display values of specific model Field.

phpctrl\ui\Table::columnFactory(atk4dataField $f)

If the value of the field can be displayed by TableColumnGeneric then Table will respord with object of this class. Since the default column does not contain any customization, then to save memory Table will re-use the same objects for all generic fields.

property phpctrl\ui\Table::$default_column

Protected property that will contain “generic” column that will be used to format all columns, unless a different column type is specified or the Field type will require a use of a different class (e.g. ‘money’). Value will be initialized after first call to Table::addColumn

property phpctrl\ui\Table::$columns

Contains array of defined columns.

phpctrl\ui\Table::addColumn([$name, ]TableColumnGeneric $column = null, atk4uiDataField = null)

Adds a new column to the table. This method has several usages. The most basic one is:

$table->setModel(new Order($db), ['name', 'price', 'total']);
$table->addColumn(new \phpctrl\ui\TableColumn\Delete());

The above code will add a new extra column that will only contain ‘delete’ icon. When clicked it will automatically delete the corresponding record.

You have probably noticed, that I have omitted the name for this column. If name is not specified (null) then the Column object will not be associated with any model field in TableColumnGeneric::getHeaderCellHTML, TableColumnGeneric::getTotalsCellHTML and TableColumnGeneric::getDataCellHTML.

Some columns require name, such as TableColumnGeneric will not be able to cope with this situations, but many other column types are perfectly fine with this.

Some column classes will be able to take some information from a specified column, but will work just fine if column is not passed.

If you do specify a string as a $name for addColumn, but no such field exist in the model, the method will rely on 3rd argument to create a new field for you. Here is example that calculates the “total” column value (as above) but using PHP math instead of doing it inside database:

$table = $layout->add('Table');
$order = new Order($db);

$table->setModel($order, ['name', 'price', 'amount', 'status']);
$table->addColumn('total', new \phpctrl\data\Field\Calculated(
    function($row) {
        return $row['price'] * $row['amount'];
    }));

If you execute this code, you’ll notice that the “total” column is now displayed last. If you wish to position it before status, you can use the final format of addColumn():

$table = $layout->add('Table');
$order = new Order($db);

$table->setModel($order, ['name', 'price', 'amount']);
$table->addColumn('total', new \phpctrl\data\Field\Calculated(
    function($row) {
        return $row['price'] * $row['amount'];
    }));
$table->addColumn('status');

This way we don’t populate the column through setModel() and instead populate it manually later through addColumn(). This will use an identical logic (see Table::columnFactory). For your convenience there is a way to add multiple columns efficiently.

addColumns($names);

Here, names can be an array of strings ([‘status’, ‘price’]) or contain array that will be passed as argument sto the addColumn method ([[‘total’, $field_def], [‘delete’, $delete_column]);

As a final note in this section - you can re-use column objects multiple times:

$c_gap = new \phpctrl\ui\TableColumn\Template('<td> ... <td>');

$table->addColumn($c_gap);
$table->setModel(new Order($db), ['name', 'price', 'amount']);
$table->addColumn($c_gap);
$table->addColumns(['total','status'])
$table->addColumn($c_gap);

This will result in 3 gap columns rendered to the left, middle and right of your Table.

Table sorting

property phpctrl\ui\Table::$sortable
property phpctrl\ui\Table::$sort_by
property phpctrl\ui\Table::$sort_order

Table does not support an interractive sorting on it’s own, (but Grid does), however you can designade columns to display headers as if table were sorted:

$table->sortable = true;
$table->sort_by = 'name';
$table->sort_order = 'ascending';

This will highlight the column “name” header and will also display a sorting indicator as per sort order.

JavaScript Sorting

You can make your table sortable through JavaScript inside your browser. This won’t work well if your data is paginated, because only the current page will be sorted:

$table->app->includeJS('http://semantic-ui.com/javascript/library/tablesort.js');
$table->js(true)->tablesort();

For more information see https://github.com/kylefox/jquery-tablesort

Injecting HTML

The tag will override model value. Here is example usage of TableColumnGeneric::getHTMLTags:

class ExpiredColumn extends \phpctrl\ui\TableColumn\Generic
    public function getDataCellHTML()
    {
        return '{$_expired}';
    }

    function getHTMLTags($model)
    {
        return ['_expired'=>
            $model['date'] < new \DateTime() ?
            '<td class="danger">EXPIRED</td>' :
            '<td></td>'
        ];
    }
}

Your column now can be added to any table:

$table->addColumn(new ExpiredColumn());

IMPORTANT: HTML injection will work unless Table::use_html_tags property is disabled (for performance).

Talbe Data Handling

Table is very similar to Lister in the way how it loads and displays data. To control which data Table will be displaying you need to properly specify the model and persistence. The following two examples will show you how to display list of “files” inside your Dropbox folder and how to display list of issues from your Github repository:

// Show contents of dropbox
$dropbox = \phpctrl\dropbox\Persistence($db_config);
$files = new \phpctrl\dropbox\Model\File($dropbox);

$layout->add('Table')->setModel($files);


// Show contents of dropbox
$github = \phpctrl\github\Persistence_Issues($github_api_config);
$issues = new \phpctrl\github\Model\Issue($github);

$layout->add('Table')->setModel($issues);

This example demonstrates that by selecting a 3rd party persistence implementation, you can access virtually any API, Database or SQL resource and it will always take care of formatting for you as well as handle field types.

I must also note that by simply adding ‘Delete’ column (as in example above) will allow your app users to delete files from dropbox or issues from GitHub.

Table follows a “universal data design” principles established by PHPControls to make it compatible with all the different data persitences. (see universal_data_access)

For most applications, however, you would be probably using internally defined models that rely on data stored inside your own database. Either way, several principles apply to the way how Table works.

Table Rendering Steps

Once model is specified to the Table it will keep the object until render process will begin. Table columns can be defined anytime and will be stored in the Table::columns property. Columns without defined name will have a numeric index. It’s also possible to define multiple columns per key in which case we call them “formatters”.

During the render process (see View::renderView) Table will perform the following actions:

  1. Generate header row.

  2. Generate template for data rows.

  3. Iterate through rows

    3.1 Current row data is accessible through $table->model property. 3.2 Update Totals if Table::addTotals was used. 3.3 Insert row values into Table::t_row

    3.3.1 Template relies on ui_persistence for formatting values

    3.4 Collect HTML tags from ‘getHTMLTags’ hook. 3.5 Collect getHTMLTags() from columns objects 3.6 Inject HTML into Table::t_row template 3.7 Render and append row template to Table Body ({$Body}) 3.8 Clear HTML tag values from template.

  4. If no rows were displayed, then “empty message” will be shown (see Table::t_empty).

  5. If addTotals was used, append totals row to table footer.

Dealing with Multiple formatters

You can add column several times like this:

$table->addColumn('salary', new \phpctrl\ui\TableColumn\Money());
$table->addColumn('salary', new \phpctrl\ui\TableColumn\Link(['page2']));

In this case system needs to format the output as a currency and subsequently format it as a link. Formattrers are always applied in the same orders they are defined. Remember that setModel() will typically set a Generic fromatter for all columns.

There are a few things to note:

  1. calling addColumn multiple time will convert Table::columns value for that column into array containing all column objects
  2. formatting is always applied in same order as defined - in example above Money first, Link after.
  3. output of the ‘Money’ formatter is used into Link formatter as if it would be value of cell.

TableColumnMoney::getDataCellTemplate is called, which returns ONLY the HTML value, without the <td> cell itself. Subsequently TableColumnLink::getDataCellTemplate is called and the ‘{$salary}’ tag from this link is replaced by output from Money column resulting in this template:

<a href="{$c_name_link}">£ {$salary}</a>

To calculate which tag should be used, a different approach is done. Attributes for <td> tag from Money are collected then merged with attributes of a Link class. The money column wishes to add class “right aligned single line” to the <td> tag but sometimes it may also use class “negative”. The way how it’s done is by defining class=”{$f_name_money}” as one of the TD properties.

The link does add any TD properties so the resulting “td” tag would be:

['class' => ['{$f_name_money}'] ]

// would produce <td class="{$f_name_money}"> .. </td>

Combined with the field template generated above it provides us with a full cell template:

<td class="{$f_name_money}"><a href="{$c_name_link}">£ {$salary}</a></td>

Which is concatinated with other table columns just before rendering starts. The actual template is formed by calling. This may be too much detail, so if you need to make a note on how template caching works then,

  • values are encapsulated for named fields.
  • values are concatinated by anonymous fields.
  • <td> properties are stacked
  • last formatter will convert array with td properties into an actual tag.

Redefining

If you are defining your own column, you may want to re-define getDataCellTemplate. The getDataCellHTML can be left as-is and will be handled correctly. If you have overriden getDataCellHTML only, then your column will still work OK provided that it’s used as a last formatter.

Advanced Usage

Table is a very flexible object and can be extended through various means. This chapter will focus on various requirements and will provide a way how to achieve that.

Toolbar, Quick-search and Paginator

See Grid

Column attributes and classes

By default Table will include ID for each row: <tr data-id=”123”>. The following code example demonstrates how various standard column types are relying on this property:

$table->on('click', 'td', new jsExpression(
    'document.location=page.php?id=[]',
    [(new jQuery())->closest('tr')->data('id')]
));

See also JavaScript Mapping.

Static Attributes and classes

class phpctrl\ui\TableColumnGeneric
addClass($class, $scope = 'body');
setAttr($attribute, $value, $scope = 'body');

The following code will make sure that contens of the column appear on a single line by adding class “single line” to all body cells:

$table->addColumn('name', (new \phpctrl\ui\TableColumn\Generic()->addClass('single line')));

If you wish to add a class to ‘head’ or ‘foot’ or ‘all’ cells, you can pass 2nd argument to addClass:

$table->addColumn('name', (new \phpctrl\ui\TableColumn\Generic()->addClass('right aligned', 'all')));

There are several ways to make your code more readable:

$table->addColumn('name', new \phpctrl\ui\TableColumn\Generic())
    ->addClass('right aligned', 'all');

Or if you wish to use factory, the syntax is:

$table->addColumn('name', 'Generic')
    ->addClass('right aligned', 'all');

For setting an attribute you can use setAttr() method:

$table->addColumn('name', 'Generic')
    ->setAttr('colspan', 2, 'all');

Setting a new value to the attribute will override previous value.

Please note that if you are redefining TableColumnGeneric::getHeaderCellHTML, TableColumnGeneric::getTotalsCellHTML or TableColumnGeneric::getDataCellHTML and you wish to preserve functionality of setting custom attributes and classes, you should generate your TD/TH tag through getTag method.

getTag($tag, $position, $value);

Will apply cell-based attributes or classes then use App::getTag to generate HTML tag and encode it’s content.

Columns without fields

You can add column to a table that does not link with field:

$cb = $table->addColumn('Checkbox');

Using dynamic values

Body attributes will be embedded into the template by the default TableColumnGeneric::getDataCellHTML, but if you specify attribute (or class) value as a tag, then it will be auto-filled with row value or injected HTML.

For further examples of and advanced usage, see implementation of TableColumnStatus.

Standard Column Types

In addition to TableColumnGeneric, PHPControls includes several column implementations.

Money

class phpctrl\ui\TableColumnMoney

Helps formatting monetary values. Will align value to the right and if value is less than zero will also use red text. The money cells are not wrapped.

For the actual number formatting, see ui_persistence

Status

class phpctrl\ui\TableColumnStatus

Allow you to set highlight class and icon based on column value. This is most suitable for columns that contain pre-defined values.

If your column “status” can be one of the following “pending”, “declined”, “archived” and “paid” and you would like to use different icons and colors to emphasise status:

$states = [ 'positive'=>['paid', 'archived'], 'negative'=>['declined'] ];

$table->addColumn('status', new \phpctrl\ui\TableColumn\Status($states));

Current list of states supported:

  • positive (icon checkmark)
  • negative (icon close)
  • and the default/unspecified state (icon question)

(list of states may be expanded furteher)

Template

class phpctrl\ui\TableColumnTemplate

This column is suitable if you wish to have custom cell formatting but do not wish to go through the trouble of setting up your own class.

If you wish to display movie rating “4 out of 10” based around the column “rating”, you can use:

$table->addColumn('rating', new \phpctrl\ui\TableColumn\Template('{$rating} out of 10'));

Template may incorporate values from multiple fields in a data row, but current implementation will only work if you asign it to a primary column (by passing 1st argument to addColumn).

(In the future it may be optional with the ability to specify caption).

Checkbox

class phpctrl\ui\TableColumnCheckbox
phpctrl\ui\TableColumnCheckbox::jsChecked()

Adding this column will render checkbox for each row. This column must not be used on a field. Checkbox column provides you with a handy jsChecked() method, which you can use to reference current item selection. The next code will allow you to select the checkboxes, and when you click on the button, it will reload $segment component while passing all the id’s:

$box = $table->addColumn(new \phpctrl\ui\TableColumn\Checkbox());

$button->on('click', new jsReload($segment, ['ids'=>$box->jsChecked()]));

jsChecked expression represents a JavaScript string which you can place inside a form field, use as argument etc.

Actions

class phpctrl\ui\TableColumnActions

This column can have number of buttons (or similar views) inside a column. This would allow you to interract with each row directly.

The basic usage format is:

$act = $table->addColumn(new \phpctrl\ui\TableColumn\Actions());