How can i show calculated values on Grid/Crud


#1

I have a Job class form data\Model and it has a table in DB, Grid/Crud works great.
I want to have a progress field in this model, so that it will appear in Grid. But the DB table does not have this value. Progress needs to be calculated, using other queries, and values. If it was a simple count or sum, i would have used aggregate, but that is not the case.

I thought about extending a custom Field class, like ProgressField from \atk4\data\Field, set it to read_only, never_persist, and override the get() to return the calculated value.

But i’m not sure if it is possible, and how to do it. Or is there a better way to do this. Like maybe hooking up a callback somewhere, in the Model domain, or in the UI domain?


#2

Can you make your database table use a calculated field and/or subqueries?


#3

Good suggestion. If DBMS supported something like, on xxxx updates, run this query and update this field kind of a thing, which mysql might be doing already, that would probably a good enough way. Although not the most efficient way (since calculation would then be done even when no one is asking for it), it would be good, because it off loads the work to DBMS, and it would be using an available solution. I should definitely look into mysql’s capability on that. Thanks.

What i ended up doing was, i looked through the \atk4\data\Model class and saw a little function named get( $field ) . This guy was returning the requested fields value, and if given null, it was returning all the field values as an array. My Job class was already extended from Model, so overriding get(), solved the problem. I added a field which is ‘never_persist’=>‘true’ and ‘read_only’=>‘true’.

My overridden get() checked if the request was related with my special field, ‘jobProgress’ in this case, and supplied the value in the right way (single value or array) so that components expecting this functionality wouldn’t be effected. This worked. CRUD/Grid started displaying my values. I also enabled this functionality checking a flag first, so that when disabled, it would just return parent::get($field).

However, when i checked later i saw that a CRUD was calling the get() multiple times for each row of data. With a null parameter, and then once for each Field. So i for each row, my implementation had to do the somewhat heavy calculation 2 times, which is not unnecessary.

I abandoned this solution, and looked for other. What i’m happy with now uses the afterLoad hook on Model. I checked the docs and the hook itself, and saw that afterload was being called for each row of data loaded. (or at least it worked like that when accessed from CRUD). So i kept my field the same, read_only, never_persist. And i initialized this field, if progressCalculation was enebled, at afterLoad. This way, if my Model has progressCalculation enabled, then for each row of data loaded, this is calculated once, and put into the field value. The value can then be accessed many times without recalculation.

I wonder this could be done at reference, like if it was a simple aggregated field, like a sum, or a count. But i needed 2 separate sql queries which took a summed a column, and then do a simple percentage math, so i’m not sure if it could be done at reference level using aggregated fields, and expressions and such.


#4

There is this hook after_load which you can use to manually calculate the value.

There is also a field type - Callback. Try this:

$model->addField('progress', ['Callback', function($m) { 
    return $m['test'] * 123;
}]);

#5

Thanks a lot!, thats exactly what i was looking for, i looked everywhere for that. How did i miss it. (i ended up finding a lot of other useful things, going through the code though)