Skip to content

Commit fe9214c

Browse files
committed
making totals more usable, extended doc, work in progress
1 parent 98e0f5c commit fe9214c

File tree

3 files changed

+151
-55
lines changed

3 files changed

+151
-55
lines changed

demos/collection/table.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@
3535
'name' => 'Total {$_row_count} rows:',
3636
'surname'=> [
3737
// longest surname
38-
function ($total, $value, $model) {
38+
'compare'=> function ($total, $value, $model) {
3939
return strlen($value) > strlen($total) ? $value : $total;
4040
},
41+
'title'=> function ($total, $model) {
42+
return 'Shortes is: '.$total;
43+
},
44+
],
45+
'salary' => [
46+
'init' => '123',
47+
'update'=> null,
4148
],
42-
'salary' => ['sum'],
4349
]);
4450

4551
// 2nd table
@@ -51,8 +57,15 @@ function ($total, $value, $model) {
5157
];
5258

5359
$table = $app->add('Table');
60+
5461
$table->setSource($my_array, ['name']);
5562

63+
$table->addColumn('no');
64+
65+
$table->addHook('beforeRow', function ($t) {
66+
$t->model['no'] = @++$t->npk;
67+
});
68+
5669
//$table->addColumn('name');
5770
$table->addColumn('surname', ['Link', 'url' => 'details.php?surname={$surname}']);
5871
$table->addColumn('birthdate', null, ['type' => 'date']);

docs/table.md

Lines changed: 132 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -98,53 +98,7 @@ $table->addColumn('price');
9898
When invoking addColumn, you have a great control over the field properties and decoration. The format
9999
of addColumn() is very similar to {php:meth}`Form::addControl`.
100100

101-
## Calculations
102-
103-
Apart from adding columns that reflect current values of your database, there are several ways
104-
how you can calculate additional values. You must know the capabilities of your database server
105-
if you want to execute some calculation there. (See https://atk4-data.readthedocs.io/en/develop/expressions.html)
106-
107-
It's always a good idea to calculate column inside database. Lets create "total" column which will
108-
multiply "price" and "amount" values. Use `addExpression` to provide in-line definition for this
109-
field if it's not already defined in `Order::init()`:
110-
111-
```
112-
$table = Table::addTo($app);
113-
$order = new Order($db);
114-
115-
$order->addExpression('total', '[price] * [amount]')->type = 'atk4_money';
116-
117-
$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);
118-
```
119-
120-
The type of the Model Field determines the way how value is presented in the table. I've specified
121-
value to be 'atk4_money' which makes column align values to the right, format it with 2 decimal signs
122-
and possibly add a currency sign.
123-
124-
To learn about value formatting, read documentation on {ref}`uiPersistence`.
125-
126-
Table object does not contain any information about your fields (such as captions) but instead it will
127-
consult your Model for the necessary field information. If you are willing to define the type but also
128-
specify the caption, you can use code like this:
129-
130-
```
131-
$table = Table::addTo($app);
132-
$order = new Order($db);
133-
134-
$order->addExpression('total', [
135-
'[price]*[amount]',
136-
'type' => 'atk4_money',
137-
'caption' => 'Total Price',
138-
]);
139-
140-
$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);
141-
```
142-
143-
### Column Objects
144-
145-
To read more about column objects, see {ref}`tablecolumn`
146-
147-
### Advanced Column Denifitions
101+
### Column Denifition
148102

149103
Table defines a method `columnFactory`, which returns Column object which is to be used to
150104
display values of specific model Field.
@@ -235,6 +189,68 @@ $table->addColumn($colGap);
235189

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

192+
## Calculated Fields
193+
194+
Apart from adding columns that reflect current values of your database, there are several ways
195+
how you can calculate additional values. You must know the capabilities of your database server
196+
if you want to execute some calculation there. (See https://atk4-data.readthedocs.io/en/develop/expressions.html)
197+
198+
### In the database - best performance
199+
200+
It's always a good idea to calculate column inside database. Lets create "total" column which will
201+
multiply "price" and "amount" values. Use `addExpression` to provide in-line definition for this
202+
field if it's not alrady defined in `Order::init()`:
203+
204+
```
205+
$table = $app->add('Table');
206+
$order = new Order($db);
207+
208+
$order->addExpression('total', [
209+
'[price] * [amount]',
210+
'type' => 'money',
211+
]);
212+
213+
$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);
214+
```
215+
216+
### In the model - best compatibility
217+
218+
If your database does not have the capacity to perform calculations, e.g. you are using NoSQL with
219+
no support for expressions, the solution is to calculate value in the PHP:
220+
221+
```
222+
$table = $app->add('Table');
223+
$order = new Order($db);
224+
225+
$model->addField('total', [
226+
'Callback',
227+
function ($m) {
228+
return $m['price'] * $m['amount'];
229+
},
230+
'type' => 'money',
231+
]);
232+
233+
$table->setModel($order, ['name', 'price', 'amount', 'total', 'status']);
234+
```
235+
236+
### Alternative approaches
237+
238+
:::{warning} Those alternatives are for special cases, when you are unable to perform calculation
239+
in the database or in the model. Please use with caution.
240+
::
241+
242+
You can add add a custom code that performs formatting within the table through a hook:
243+
244+
```
245+
$table->addField('no');
246+
247+
$table->addHook('beforeRow', function($table)) {
248+
$table->model['no'] = @++$t->npk;
249+
}
250+
```
251+
252+
To read more about column objects, see {ref}`tablecolumn`
253+
238254
## Table sorting
239255

240256
:::{php:attr} sortable
@@ -453,6 +469,75 @@ getDataCellHtml can be left as-is and will be handled correctly. If you have ove
453469
getDataCellHtml only, then your column will still work OK provided that it's used as a
454470
last decorator.
455471

472+
## Table Totals
473+
474+
:::{php:attr} totals_plan
475+
:::
476+
:::{php:attr} totals
477+
:::
478+
:::{php:method} addTotals()
479+
:::
480+
481+
482+
Table implements a built-in handling for the "Totals" row. Simple usage would be:
483+
484+
```
485+
$table->addTotals();
486+
```
487+
488+
but here is what actually happens:
489+
490+
1. when calling addTotals() - you define a total calculation plan.
491+
2. while iterating through table, totals are accumulated / modified according to plan.
492+
3. when table finishes with the rows, it adds yet another row with the accumulated values.
493+
494+
:::{important} addTotals() will only calculate based on rendered rows. If your table has limit
495+
or uses {php:class}`Paginator` you should calculate totals differently. See {php:meth}`Grid::addGrandTotals`
496+
::
497+
498+
### Definition of a "totals plan"
499+
500+
Each column may have a different plan, which consists of stages:
501+
502+
- init: defines the initial value
503+
- update: defines how value is updated
504+
- format: defines how value is formatted before outputting
505+
506+
Here is the plan to calculate number of records:
507+
508+
```
509+
$table->addTotals(['client' => [
510+
'init' => 0,
511+
'update' => 'increment',
512+
'format' => 'Totals for {$client} client(s)',
513+
]);
514+
```
515+
516+
To make things easier to define, each stage has a reasonable default:
517+
518+
- init: set to 0
519+
- update: 'sum' for numeric/money type and 'increment' otherwise
520+
- format: will output '-' by default
521+
522+
Also when calling addTotals() the column plan value does not have to be array, in which case the 'format'
523+
is set. The above example can therefore be shortened:
524+
525+
```
526+
$table->addTotals(['client' => 'Totals for {$clients} client(s)']);
527+
```
528+
529+
### Possible values for total plan stages
530+
531+
`init` value is typically a number, but can be any value, especially if you are going to control it's
532+
increment / formatting.
533+
534+
`update` can be string - either "sum" or "increment". Other values are not supported. You can also pass
535+
a callable which gives you an option to perform update yourself.
536+
537+
`format` uses a template in a format suitable for {php:class}`Template`. Within the template you can
538+
reference other fields too. A benefit for using template is that type formatting is done automatically
539+
for you.
540+
456541
## Advanced Usage
457542

458543
Table is a very flexible object and can be extended through various means. This chapter will focus

src/Table.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,6 @@ public function addTotals($plan = [], $plan_id = null)
319319
$plan_id = max(array_filter(array_keys($this->totals_plan), 'is_int'));
320320
}
321321

322-
//var_dump($this->totals_plan);
323-
324322
return $plan_id;
325323
}
326324

@@ -698,15 +696,15 @@ public function getTotalsRowHTML($plan_id)
698696
$title = '';
699697
if (is_string($plan[$name]['title'])) {
700698
$title = $plan[$name]['title'];
699+
$title = new Template($title);
700+
$title = $title->set($totals)->render();
701701
} elseif (is_callable($plan[$name]['title'])) {
702-
$title = call_user_func_array($plan[$name]['title'], [$totals, $this->model]);
702+
$title = call_user_func_array($plan[$name]['title'], [isset($totals[$name]) ? $totals[$name] : null, $totals, $this->model]);
703703
}
704704

705705
// title can be defined as template and we fill in other total values if needed
706-
$title = new Template($title);
707-
$title->set($totals);
708706

709-
$output[] = $column->getTotalsCellHTML($field, $title->render(), false);
707+
$output[] = $column->getTotalsCellHTML($field, $title, false);
710708
}
711709

712710
return implode('', $output);

0 commit comments

Comments
 (0)