MobX Updating a decorated object inside a decorated array property doesn't emit reaction - mobx

I wrote a React prototype for a pay rates field that I'm working on, and now I'm trying to lift state up to a domain store for it. Right now, I'm just trying to test for reactions in the console, but for some reason, reactions aren't working when using an action to change a field of the observable array for that store.
Code
I have the following :
data object
class PayRate {
/**
* #param {number} taskId The id of the task. (-1 for all)
* #param {Date} effectiveDate The date the payrate goes in effect.
* #param {number} rate The rate of pay.
*/
constructor(taskId, effectiveDate, rate) {
this.TaskId = taskId || -1;
this.EffectiveDate = effectiveDate ? new Date(effectiveDate) : new Date();
this.Rate = rate || 0.00;
this.OriginalObject = Object.assign({}, this);
}
/**
* Gets a readable version of the effective date.
* #returns {string} A -m/dd/yyyy representation of the effective date.
*/
GetReadableDate() {
return this.EffectiveDate.toLocaleDateString("en-US");
}
/**
* Gets a one line description of the pay rate.
* #returns {string} A string in the form of (date) - payrate.
*/
GetDescription() {
return `(${this.GetReadableDate()}) - $${this.Rate.toFixed(2)}`;
}
/**
* Gets a one line description of the pay rate.
* Does the exact same as GetDescription(), but is overload of Object.prototype.toString, which allows for stringification of Objects
* #returns {string} A string in the form of (date) - payrate.
*/
toString() {
return `(${this.GetReadableDate()}) - $${this.Rate.toFixed(2)}`;
}
/**
* Tells whether a pay rate was changed or not.
* #returns {boolean} A boolean saying whether or not the pay rate was changed.
*/
IsChanged() {
if (this.EffectiveDate.getTime() !== this.OriginalObject.EffectiveDate.getTime()) {
return true;
}
if (this.Rate != this.OriginalObject.Rate) {
return true;
}
if (this._deleted) {
return true;
}
return false;
}
/**
* Reverts the changes back to the original.
*/
RevertChanges() {
Object.assign(this, this.OriginalObject);
}
}
// mobx decorations
mobx.decorate(PayRate, {
TaskId : mobx.observable,
EffectiveDate : mobx.observable,
Rate : mobx.observable,
})
Domain store
class PayRatesStore {
constructor() {
this.payRates = [];
this.payRateIndex = -1;
this._dateString = '';
this.payRateIndicesToDelete = [];
mobx.autorun(() => {
console.log(this.payRates)
console.log(this.currentPayRate)
})
}
// getters
get currentPayRate() {
if ((this.payRates) && (this.payRates.length)) {
return this.payRates[this.payRateIndex];
}
return new PayRate();
}
get dateString() {
if (!this._dateString) {
if (this.currentPayRate) {
return this.currentPayRate.GetReadableDate()
}
return "";
}
return this._dateString;
}
set dateString(str) {
this._dateString = str;
}
updateCurrentPayRateDate(dateString) {
// update _dateString
this._dateString = dateString
this.payRates[this.payRateIndex].EffectiveDate = new Date(dateString)
}
}
mobx.decorate(PayRatesStore, {
payRates : mobx.observable,
payRateIndex : mobx.observable,
_dateString : mobx.observable,
payRateIndicesToDelete : mobx.observable,
currentPayRate : mobx.computed,
dateString : mobx.computed,
updateCurrentPayRateDate : mobx.action.bound
})
Setting up the PayRatesStore causes reactions as we expect:
let payRate = new PayRate(1, "03/25/2019", 15.5),
payRates = [ payRate ]
// create PayRatesStore
let payRatesStore = new PayRatesStore()
console.log("Passing data to pay rates store....")
payRatesStore.payRates = payRates
payRatesStore.payRateIndex = 0
causes the following to be console.logged:
However, when I try:
payRatesStore.updateCurrentPayRateDate('4/1/2019')
Nothing happens; // MobX doesn't react!!
I don't know what I can do to get MobX to react to this, as it was lifted up from the React Component.

It seems you misunderstand how autorun works.
autorun subscribes to changes in the values of the variables that are referenced in the function.
What is happening
Here, your autorun function is observing payRates and currentPayRate.
The first time, you modify both payRates and currentPayRate (because you change payRateIndex, it updates the computed property currentPayRate).
-> autorun reacted.
The second time, you modify an element property in the payRates array, not the value of this.payRates itself, it is still the same array. So, payRates did not change, nor currentPayRate.
-> autorun does not react.
More about autorun
Autorun won't subscribe to "nested" array elements. For more details, you could read this: Watch for nested object/array changes

Related

Create different objects based on multiple parameters

I have a REST API. I need to create presentation (DTO) object, but the construction of this object depends on request - it differs in 15%.
I wonder what pattern should I use.
My case:
//presentation-DTO
class Item {
private $name;
private $price;
private $tags;
private $liked; //is Liked by logged user
...
public function __construct(Item $item, bool $liked, ...)
{
$this->name = $item->getName();
$this->price = $item->getPrice();
$this->tags = $item->getTags();
$this->liked = $liked;
...
}
}
When user is not logged in - I don't need $liked
When showing list of items - I don't need $tags
And there are more attributes that works as above.
My first idea was to use Builder principle.
$itemBuilder = new ItemBuilder();
$itemBuilder->setItem($item);
...
if($user) {
$itemBuilder->setUserLiked($userLiked);
...
}
return $itemBuilder->build();
It solves my problem with too many parameters in constructor.
But still, I also don't need all parameters to be constructed - eg. I don't need tags (on lists). As I use lazy load, I don't want my dto constructor to call them.
So I thought, maybe Factory.. but then my problem with too many (and optional) parameters is returning.
How will you solve this?
Sorry I don't have required points to make a comment hence an answer.
What are you trying to do with the Item class. Your class is Item and first parameter is also of type Item. I cannot visualizes how its going to work.
I will prefer to keep business login to set proper properties in a separate class:
/**
* A class for business logic to set the proper properties
*/
class ItemProperties {
private $item;
public $isLogin = false;
public $showList = false;
.....
public function __construct(Item &$item) {
// set all properties;
}
public function getProperties() {
$retVal = [];
if($this->isLogin == true) {
$retVal['liked'] = true;
}
if($this->showList == true) {
$retVal['tags'] = $this->item->getTags();
}
if(....) {
$retVal['...'] = $this->item->.....();
}
return $retVal;
}
}
/**
* DTO
*/
class Item {
public function __construct(ItemProperties $itemProps) {
$this->setItemProps($itemProps);
}
// If you prefer lazy loading here...maybe make it public
// and remove call from constructor.
private function setItemProps(&$itemProps) {
$properties = $itemProps->getProperties();
foreach($properties AS $propName => $propValue) {
$this->$propName = $propValue;
}
}
}
// Usage:
$itemProps = new ItemProperties($Item);
// set other properties if you need to...
$itemProps->isLogin = false;
$item = new Item($itemProps);

Confused about prestashop PDF generated process. Where is the code to handle the logic?

I want to deep understand prestahop inside structure and amend some parts. I am stoped in the PDF. I want can't find the controller used to handle the AdminPdf and generateDeliverySlipPDF
{if $order->delivery_number}
<a class="btn btn-default _blank" href="{$link->getAdminLink('AdminPdf')|escape:'html':'UTF-8'}&submitAction=generateDeliverySlipPDF&id_order={$order->id}">
<i class="icon-truck"></i>
</a>
{/if}
Who can help me figure out the inside processes? I can't find the methods to handle generateDeliverySlipPDF.
AdminPdfController is located at /controllers/admin/AdminPdfController.php.
The submitAction=generateDeliverySlipPDF part of the url will call the method processGenerateDeliverySlipPDF() inside this controller.
Here is this method:
public function processGenerateDeliverySlipPDF()
{
if (Tools::isSubmit('id_order')) {
$this->generateDeliverySlipPDFByIdOrder((int)Tools::getValue('id_order'));
} elseif (Tools::isSubmit('id_order_invoice')) {
$this->generateDeliverySlipPDFByIdOrderInvoice((int)Tools::getValue('id_order_invoice'));
} elseif (Tools::isSubmit('id_delivery')) {
$order = Order::getByDelivery((int)Tools::getValue('id_delivery'));
$this->generateDeliverySlipPDFByIdOrder((int)$order->id);
} else {
die(Tools::displayError('The order ID -- or the invoice order ID -- is missing.'));
}
}
In this Controller you'll find other methods as this one to generate Invoices, Order, ... and other PDFs.
Feel free to ask if you need more informations.
EDIT:
If you want to change format in a proper way you'll have to override those classes:
/override/classes/pdf/PDFGenerator.php:
<?php
/**
* #since 1.5
*/
class PDFGenerator extends PDFGeneratorCore
{
/**
* #param bool $use_cache
* #param string $orientation
* #param string $format
*/
public function __construct($use_cache = false, $orientation = 'P', $format = 'A4')
{
TCPDF::__construct($orientation, 'mm', $format, true, 'UTF-8', $use_cache, false);
$this->setRTL(Context::getContext()->language->is_rtl);
}
}
/override/classes/pdf/PDF.php:
<?php
/**
* #since 1.5
*/
class PDF extends PDFCore
{
/**
* #param $objects
* #param $template
* #param $smarty
* #param string $orientation
*/
public function __construct($objects, $template, $smarty, $orientation = 'P', $format = 'A4')
{
parent::__construct($objects, $template, $smarty, $orientation);
$this->pdf_renderer = new PDFGenerator((bool)Configuration::get('PS_PDF_USE_CACHE'), $orientation, $format);
}
}
/override/controllers/admin/AdminPdfController.php:
<?php
class AdminPdfController extends AdminPdfControllerCore
{
public function generatePDF($object, $template)
{
switch($template) {
case PDF::TEMPLATE_DELIVERY_SLIP:
$format = array(210, 50000); // Replace with your desired size
break;
default:
$format = 'A4';
}
$pdf = new PDF($object, $template, Context::getContext()->smarty, 'P', $format);
$pdf->render();
}
}
Now you can specify the format for each PDF.
You will find informations about $format at this place
This code has not been tested but should work as expected. Let me know if you encounter any issue.
You will need to delete /cache/class_index.php after adding those overrides to clear Prestashop internal classes path cache.
thank you for this topic,
I would like to ask you for the case of generating pdf in front?
i.e
this is a part of history.tpl
`{if $order.details.invoice_url}
<i class="material-icons"></i>`
i found $order.details.invoice_url is define in OrderPresenter.php
and OrderPresenter use HistoryController to get the invoice_url.
So i take a look in historyController
if ((bool) Configuration::get('PS_INVOICE') && OrderState::invoiceAvailable($order->current_state) && count($order->getInvoicesCollection())) {
$url_to_invoice = $context->link->getPageLink('pdf-invoice', true, null, 'id_order='.$order->id);
if ($context->cookie->is_guest) {
$url_to_invoice .= '&secure_key='.$order->secure_key;
}
}
getPageLink use "pdf-invoice" ,i take a look for getPageLink Method and see that "pdf-invoice" is a controller...
the trouble is i don't know where is this controller?
help please

phalcon - Relationships not defined when converting resultset to array?

I have tested it with 2 methods:
The first:
class ProjectsController extends ControllerBase
{
public function indexAction()
{
$row = array();
$projects = Projects::find();
foreach ($projects as $project) {
foreach($project->employees as $employee){
echo "Employee: " . $employee->name;
}
}
exit;
}
}
Output:
Employee: Admin
The second:
class ProjectsController extends ControllerBase
{
public function indexAction()
{
$row = array();
$projects = Projects::find();
$projects = $projects->toArray();
foreach ($projects as $project) {
foreach($project["employees"] as $employee){
echo $employee->name;
}
}
exit;
}
}
Output:
Notice: Undefined index: employees in app/controllers/ProjectsController.php on line 10
When converting the resultset to array the relationships aren't added to the array, is there a workaround to add it to the array?
The reason I converted the resultset to an array is to edit results for example calculating progress or something like that, without saving it to the database.
Things like this:
foreach($projects as &$project){
//count all the todos.
$todos = Todos::find("project_id = '".$project["id"]."'");
$numberOfTodos = $todos->count();
//count all the todos that are done.
$todos = Todos::find("project_id = '".$project["id"]."' AND status_id = 9");
$numberOfDoneTodos = $todos->count();
$project["percentageDone"] = ($numberOfDoneTodos / $numberOfTodos) * 100;
var_dump($row);exit;
}
$this->view->setVar("projects",$projects);
So I don't have to do calculations on the view side and only have to output it
Yes, when you convert a result set to an array only scalar values are converted.
But for adding a calculated property to your model there's no need to convert it to an array, you can change or create new properties as you wish and it will only be saved to the database when you call for example $project->save() and just properties that match a column name will be stored in the database.
For adding calculated properties I'd recommend you to use the event afterFetch that gets fired for each model retrieved from the database:
class Projects extends \Phalcon\Mvc\Model
{
...
public function afterFetch()
{
//Adds a calculated property when a project is retrieved from the database
$totalTodos = Todos::count("project_id = $this->id");
$completeTodos = Todos::count("project_id = $this->id AND status_id = 9");
$this->percentageDone = round(($completeTodos / $totalTodos) * 100, 2);
}
}

How to extend Illuminate\Database\Query\Builder

I'm planning to have a function that will store the sql statement on the Cache using the given second parameter on remember() as the key and whenever the sql statement changes it will run against the database again and overwrite the stored sql, also the cached result, and if not it will take the default cached result by the remember() function.
So I am planning to have something like this on Illuminate\Database\Query\Builder
/**
* Execute the query based on the cached query
*
* #param array $columns
* #return array|static[]
*/
public function getCacheByQuery($columns = array('*'))
{
if ( ! is_null($this->cacheMinutes))
{
list($key, $minutes) = $this->getCacheInfo();
// if the stored sql is the same with the new one then get the cached
// if not, remove the cached query before calling the getCached
$oldSql = self::flag($key);
$newSql = $this->toSql().implode(',', $this->bindings);
if ($newSql!==$oldSql)
{
// remove the cache
\Cache::forget($key);
// update the stored sql
self::updateFlag($key, $newSql);
}
return $this->getCached($columns);
}
return $this->getFresh($columns);
}
public static function updateFlag($flag, $value)
{
$flags = \Cache::get(t().'databaseFlags', []);
$flags[$flag] = $value;
\Cache::put(t().'databaseFlags', $flags, USER_SESSION_EXPIRATION);
}
public static function flag($flag)
{
$flags = \Cache::get(t().'databaseFlags', []);
return #$flags[$flag] ?: false;
}
But the thing is, I don't want to put this directly on Illuminate\Database\Query\Builder since it is just my need for the current application I am working. I'm trying to extend Illuminate\Database\Query\Builder, but the problem is it does not detect the my extension class.
Call to undefined method Illuminate\Database\Query\Builder::getCachedByQuery()
My Extension Class
<?php namespace Lukaserat\Traits;
class QueryBuilder extends \Illuminate\Database\Query\Builder {
/**
* Execute the query based on the caced query
*
* #param array $columns
* #return array|static[]
*/
public function getCachedByQuery($columns = array('*'))
{
if ( ! is_null($this->cacheMinutes))
{
list($key, $minutes) = $this->getCacheInfo();
// if the stored sql is the same with the new one then get the cached
// if not, remove the cached query before calling the getCached
$oldSql = self::flag($key);
$newSql = $this->toSql().implode(',', $this->bindings);
if ($newSql!==$oldSql)
{
// remove the cache
\Cache::forget($key);
// update the stored sql
self::updateFlag($key, $newSql);
}
return $this->getCached($columns);
}
return $this->getFresh($columns);
}
public static function updateFlag($flag, $value)
{
$flags = \Cache::get(t().'databaseFlags', []);
$flags[$flag] = $value;
\Cache::put(t().'databaseFlags', $flags, USER_SESSION_EXPIRATION);
}
public static function flag($flag)
{
$flags = \Cache::get(t().'databaseFlags', []);
return #$flags[$flag] ?: false;
}
}
Implementing on..
<?php
use LaravelBook\Ardent\Ardent;
use Lukaserat\Traits\DataTable;
use Lukaserat\Traits\QueryBuilder as QueryBuilder;
use Illuminate\Support\MessageBag as MessageBag;
class ArdentBase extends Ardent implements InterfaceArdentBase{
use DataTable;
Am I missing something?
Is it correct that I overwrite the get() method on the Illuminate\Database\Query\Builder by renaming the function I made in my extension class from getCachedByQuery to get since I just extending the routine of the get.
I changed
public function getCachedByQuery($columns = array('*'))
to
public function get()
on my Lukaserat\Traits\QueryBuilder
and it is now working as I expected..

Extra Attribute Disappears after it is set successfully

I am trying to get all records that are tied to a parent object through a lookup table, and insert them directly into the model. I have an object, Role, that hasMany() RoleEndpoints. RoleEndpoints belongs to Role and hasMany() Endpoints. All the data is being retrieved exactly as I expect, however, it seems to disappear after I set it.
<?php
class ACL {
private $_di;
public function __construct($di) {
$this->_di = $di;
}
public function createACL() {
if(!$this->_acl) {
$this->_acl = new stdClass();
$roles = $possibleRoles = Roles::find();
/**
* Check if there is at least one role out there
*/
if($roles->count() > 0) {
/**
* Iterate over all of the records
*/
while($roles->valid()) {
$endpoints = array();
/**
* Grab each role's endpoints through the relationship
*/
foreach($roles->current()->getRoleEndpoints() as $roleEndpoint) {
$endpoints[] = Endpoints::findFirst($roleEndpoint->endpoint_id);
}
/**
* At this point, the endpoints exist in the current Role model;
I tried several different approaches; this seemed the best
*/
$roles->current()->endpoints = $endpoints;
}
/**
* Set object to loop through from the beginning
*/
$roles->rewind();
/**
* Here is where my issue lies.
*
* The endpoints attribute, which is set as a public attribute in the model class
* gets unset for some reason
*/
while($roles->valid()) {
echo '<pre>';
var_dump($roles->current());
exit;
}
As the comments say, during the second iteration of the result set, the endpoints attribute drops becomes null for some reason. Am I doing something wrong here? Am I missing a step?
Any help would be appreciated. Thank you!
There is a missing next() in the iterator traversing:
while ($roles->valid()) {
$endpoints = array();
/**
* Grab each role's endpoints through the relationship
*/
foreach ($roles->current()->getRoleEndpoints() as $roleEndpoint) {
$endpoints[] = Endpoints::findFirst($roleEndpoint->endpoint_id);
}
/**
* At this point, the endpoints exist in the current Role model;
* I tried several different approaches; this seemed the best
*/
$roles->current()->endpoints = $endpoints;
//Missing next
$roles->next();
}
Also, you don't need to iterate the cursor in that way, just a foreach is easy to read and maintain:
$roles = Roles::find();
$roleEndpoints = array();
if (count($roles)) {
foreach ($roles as $role) {
$endpoints = array();
/**
* Grab each role's endpoints through the relationship
*/
foreach ($role->getRoleEndpoints() as $roleEndpoint) {
$endpoints[] = Endpoints::findFirst($roleEndpoint->endpoint_id);
}
/**
* At this point, the endpoints exist in the current Role model;
* I tried several different approaches; this seemed the best
*/
$roleEndpoints[$role->id] = $endpoints;
}
}
//Get the endpoints
foreach ($roleEndpoints as $roleId => $endpoint) {
//...
}
Also, If this is a common task you can add a method to your model to reuse that logic:
class Roles extends Phalcon\Mvc\Model
{
public function getEndpoints()
{
$endpoints = array();
foreach ($this->getRoleEndpoints() as $roleEndpoint) {
$endpoints[] = Endpoints::findFirst($roleEndpoint->endpoint_id);
}
return $endpoints;
}
public function initialize()
{
//...
}
}
So you can get your endpoints:
$roles = Roles::find();
if (count($roles)) {
foreach ($roles as $role) {
$endpoints = $role->getEndpoints();
}
}