Skip to content

Commit ae687f2

Browse files
committed
making totals more usable, extended doc, work in progress
1 parent 370f4f1 commit ae687f2

File tree

4 files changed

+149
-56
lines changed

4 files changed

+149
-56
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.rst

Lines changed: 128 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -88,52 +88,8 @@ You can also add individual column to your table::
8888
When invoking addColumn, you have a great control over the field properties and decoration. The format
8989
of addColumn() is very similar to :php:meth:`Form::addField`.
9090

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

13894
Table defines a method `columnFactory`, which returns Column object which is to be used to
13995
display values of specific model Field.
@@ -226,6 +182,66 @@ As a final note in this section - you can re-use column objects multiple times::
226182

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

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

@@ -321,7 +337,7 @@ For most applications, however, you would be probably using internally defined m
321337
data stored inside your own database. Either way, several principles apply to the way how Table works.
322338

323339
Table Rendering Steps
324-
--------------------
340+
---------------------
325341

326342
Once model is specified to the Table it will keep the object until render process will begin. Table
327343
columns can be defined anytime and will be stored in the :php:attr:`Table::columns` property. Columns
@@ -430,6 +446,71 @@ getDataCellHTML can be left as-is and will be handled correctly. If you have ove
430446
getDataCellHTML only, then your column will still work OK provided that it's used as a
431447
last decorator.
432448

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

docs/tablecolumn.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

2-
.. _tablecolumn:
32

43
.. php:namespace:: atk4\ui
54
5+
.. _tablecolumn:
6+
67
=======================
78
Table Column Decorators
89
=======================

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)