From aa8a745d3c40d71c52bbc79ef27063483ee37611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sat, 23 Nov 2024 22:14:42 +0100 Subject: [PATCH] Adjust code for removed UserAction::$modifier property (#2231) --- README.md | 2 +- demos/collection/card-deck.php | 2 +- demos/data-action/actions.php | 2 +- demos/data-action/jsactions.php | 4 +- demos/data-action/jsactionscrud.php | 2 +- demos/tutorial/actions.php | 4 +- docs/callbacks.md | 2 +- docs/dataexecutor.md | 6 +-- docs/form-control.md | 2 +- docs/grid.md | 2 +- docs/multiline.md | 2 +- docs/view.md | 2 +- docs/virtualpage.md | 2 +- src/App.php | 2 +- src/Behat/MinkSeleniumDriver.php | 2 +- src/CardDeck.php | 4 +- src/Crud.php | 54 +++++++++++++------------- src/UserAction/CommonExecutorTrait.php | 2 +- src/UserAction/JsCallbackExecutor.php | 2 +- src/UserAction/StepExecutorTrait.php | 6 ++- 20 files changed, 56 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 76e6aab969..6a991c6bc4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Agile UI is the quickest way for building back-end UI, admin interfaces, data ma - Agile UI offers out-of-the-box components, you don't need front-end development experience. - Agile UI is interactive, making it very easy to trigger PHP code on JS events. - Agile UI is compact - single file, several lines of code - that's all it takes. -- Agile UI is extensible - integrates VueJS for custom components and interactive behaviours. +- Agile UI is extensible - integrates VueJS for custom components and interactive behaviors. [![Build](https://github.com/atk4/ui/actions/workflows/test-unit.yml/badge.svg?branch=develop)](https://github.com/atk4/ui/actions?query=branch:develop) [![CodeCov](https://codecov.io/gh/atk4/ui/branch/develop/graph/badge.svg)](https://codecov.io/gh/atk4/ui) diff --git a/demos/collection/card-deck.php b/demos/collection/card-deck.php index fc4f822de8..0e96648f78 100644 --- a/demos/collection/card-deck.php +++ b/demos/collection/card-deck.php @@ -40,7 +40,7 @@ 'callback' => static function (Country $country, $email) { return 'Your request for information was sent to email: ' . $email; }, - 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORD, ]); $infoAction->args = [ diff --git a/demos/data-action/actions.php b/demos/data-action/actions.php index 75003b43c9..9d198b0e33 100644 --- a/demos/data-action/actions.php +++ b/demos/data-action/actions.php @@ -39,7 +39,7 @@ 'args' => [ 'path' => ['type' => 'string', 'required' => true], ], - 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORD, ]); Header::addTo($app, [ diff --git a/demos/data-action/jsactions.php b/demos/data-action/jsactions.php index 4bd31183f4..8507e88918 100644 --- a/demos/data-action/jsactions.php +++ b/demos/data-action/jsactions.php @@ -24,7 +24,7 @@ $country = new Country($app->db); $sendEmailAction = $country->addUserAction('Email', [ - 'confirmation' => 'Are you sure you wish to send an email?', + 'confirmation' => 'Are you sure to send an email?', 'callback' => static function (Country $country) { return 'Email to Kristy in ' . $country->name . ' has been sent!'; }, @@ -42,7 +42,7 @@ // note here that we explicitly required a JsCallbackExecutor for the greet action $country->addUserAction('greet', [ - 'appliesTo' => UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => UserAction::APPLIES_TO_NO_RECORD, 'args' => [ 'name' => [ 'type' => 'string', diff --git a/demos/data-action/jsactionscrud.php b/demos/data-action/jsactionscrud.php index 4caac20d72..b1345c9adf 100644 --- a/demos/data-action/jsactionscrud.php +++ b/demos/data-action/jsactionscrud.php @@ -29,7 +29,7 @@ 'args' => [ 'path' => ['type' => 'string', 'required' => true], ], - 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORD, ]); $files->addUserAction('download', static function (Model $entity) { diff --git a/demos/tutorial/actions.php b/demos/tutorial/actions.php index 1936b79240..0de3d923d2 100644 --- a/demos/tutorial/actions.php +++ b/demos/tutorial/actions.php @@ -118,7 +118,7 @@ $model = new Model($owner->getApp()->db, ['table' => 'test']); $model->addUserAction('greet', [ - 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORD, 'args' => [ 'name' => [ 'type' => 'string', @@ -130,7 +130,7 @@ ]); $model->addUserAction('ask_age', [ - 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORDS, + 'appliesTo' => Model\UserAction::APPLIES_TO_NO_RECORD, 'args' => [ 'age' => [ 'type' => 'integer', diff --git a/docs/callbacks.md b/docs/callbacks.md index 233ef7ec06..e453e2ed3c 100644 --- a/docs/callbacks.md +++ b/docs/callbacks.md @@ -89,7 +89,7 @@ another request to the server: - the {php:meth}`Callback::set()` will notice this argument and execute "terminate()" - terminate() will exit app execution and output 'in callback' back to user. -Calling {php:meth}`App::terminate()` will prevent the default behaviour (of rendering UI) and will +Calling {php:meth}`App::terminate()` will prevent the default behavior (of rendering UI) and will output specified string instead, stopping further execution of your application. # Return value of set() diff --git a/docs/dataexecutor.md b/docs/dataexecutor.md index a82f04eddf..4d791a78c4 100644 --- a/docs/dataexecutor.md +++ b/docs/dataexecutor.md @@ -123,9 +123,9 @@ Executors can use the HOOK_AFTER_EXECUTE hook in order to return javascript acti executing. It is use in Crud for example in order to display users of successful model UserAction execution. Either by displaying Toast messages or removing a row within a Crud table. -Some Ui View component, like Crud for example, will also set javascript action to return based on the UserAction::modifier property. -For example it the modifier property is set to MODIFIER_DELETE then Crud will know it has to delete a table row on the -other hand, if MODIFIER_UPDATE is set, then Table needs to be reloaded. +Some Ui View component, like Crud for example, will also set javascript action to return based on the UserAction behavior. +For example if the action deleted an entity then Crud will delete a table row. If the action updated an entity +then Table needs to be reloaded. ## The Executor Factory diff --git a/docs/form-control.md b/docs/form-control.md index b5e53f5312..6eea70dc1c 100644 --- a/docs/form-control.md +++ b/docs/form-control.md @@ -471,7 +471,7 @@ function (string $value, $key) { ### Dropdown Settings -There's a bunch of settings to influence Dropdown behaviour. +There's a bunch of settings to influence Dropdown behavior. :::{php:attr} empty ::: diff --git a/docs/grid.md b/docs/grid.md index 5d5ba85b1a..7e541ba49e 100644 --- a/docs/grid.md +++ b/docs/grid.md @@ -193,7 +193,7 @@ $grid->menu->addItem('show selection') ::: When grid is associated with a model that supports order, it will automatically make itself sortable. You can -override this behaviour by setting $sortable property to `true` or `false`. +override this behavior by setting $sortable property to `true` or `false`. You can also set $sortable property for each table column decorator. That way you can enable/disable sorting of particular columns. diff --git a/docs/multiline.md b/docs/multiline.md index 58f47d2f66..e98a230c6c 100644 --- a/docs/multiline.md +++ b/docs/multiline.md @@ -200,7 +200,7 @@ You will find a list of Vue component associated with each field type within the :::{php:attr} fieldMapToComponent ::: -Each control being a Vue component means that they accept 'Props' that may change their look or behaviour. +Each control being a Vue component means that they accept 'Props' that may change their look or behavior. Props on each component may be applied globally, i.e. to all control within Multiline that use that control, or per component. diff --git a/docs/view.md b/docs/view.md index 6e964904b2..d062901b3a 100644 --- a/docs/view.md +++ b/docs/view.md @@ -388,7 +388,7 @@ View::addTo($app, ['defaultTemplate' => __DIR__ . '/../templates/mytpl.httml']); Agile UI does not currently provide advanced search path for templates, by default the template is loaded from folder `vendor/atk4/ui/template`. To change this -behaviour, see {php:meth}`App::loadTemplate()`. +behavior, see {php:meth}`App::loadTemplate()`. :::{php:attr} region ::: diff --git a/docs/virtualpage.md b/docs/virtualpage.md index d26270c9f1..527172b2cd 100644 --- a/docs/virtualpage.md +++ b/docs/virtualpage.md @@ -203,7 +203,7 @@ Returns JS action which will trigger loading. The action will be carried out in ::: If you have NOT invoked jsLoad in your code, Loader will automatically assign it do DOM Ready(). If the automatic -behaviour does not work, you should set value for $loadEvent: +behavior does not work, you should set value for $loadEvent: - null = load on DOM ready unless you have invoked jsLoad() in the code. - true = load on DOM ready diff --git a/src/App.php b/src/App.php index 811d41163b..ccf6e39ace 100644 --- a/src/App.php +++ b/src/App.php @@ -1030,7 +1030,7 @@ public function getTag(string $tag, array $attr = [], $value = null): string } // see https://mathiasbynens.be/notes/etago $result[] = preg_replace('~(?<=<)(?=/\s*' . preg_quote($tag, '~') . '|!--)~', '\\\\', $v); - } elseif (is_array($value)) { // todo, remove later and fix wrong usages, this is the original behaviour, only directly passed strings were escaped + } elseif (is_array($value)) { // todo, remove later and fix wrong usages, this is the original behavior, only directly passed strings were escaped $result[] = $v; } else { $result[] = $this->encodeHtml($v); diff --git a/src/Behat/MinkSeleniumDriver.php b/src/Behat/MinkSeleniumDriver.php index 8f095e582e..de234181b2 100644 --- a/src/Behat/MinkSeleniumDriver.php +++ b/src/Behat/MinkSeleniumDriver.php @@ -53,7 +53,7 @@ protected function mouseOverElement(WebDriverElement $element): void { // move the element into the viewport // needed at least for Firefox as Selenium moveto does move the mouse cursor only - $this->executeScript('arguments[0].scrollIntoView({ behaviour: \'instant\', block: \'center\', inline: \'center\' })', [$element]); + $this->executeScript('arguments[0].scrollIntoView({ behavior: \'instant\', block: \'center\', inline: \'center\' })', [$element]); $this->getWebDriverSession()->moveto(['element' => $element->getID()]); } diff --git a/src/CardDeck.php b/src/CardDeck.php index 24cbdf80c4..6c5fa4450e 100644 --- a/src/CardDeck.php +++ b/src/CardDeck.php @@ -164,7 +164,7 @@ public function setModel(Model $model, ?array $fields = null, ?array $extra = nu // add no record scope action to menu if ($this->useAction && $this->menu) { - foreach ($this->getModelActions(Model\UserAction::APPLIES_TO_NO_RECORDS) as $k => $action) { + foreach ($this->getModelActions(Model\UserAction::APPLIES_TO_NO_RECORD) as $k => $action) { $executor = $this->initActionExecutor($action); $this->menuActions[$k]['button'] = $this->menu->addItem( $this->getExecutorFactory()->createTrigger($action, ExecutorFactory::MENU_ITEM) @@ -287,7 +287,7 @@ private function getModelActions(string $appliesTo): array $this->singleScopeActions, array_map(fn ($v) => $this->model->getUserAction($v), $this->singleScopeActions) ); - } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORDS && $this->noRecordScopeActions !== []) { + } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORD && $this->noRecordScopeActions !== []) { $actions = array_combine( $this->noRecordScopeActions, array_map(fn ($v) => $this->model->getUserAction($v), $this->noRecordScopeActions) diff --git a/src/Crud.php b/src/Crud.php index 05bb693cac..be5ff42dd7 100644 --- a/src/Crud.php +++ b/src/Crud.php @@ -32,7 +32,7 @@ class Crud extends Grid /** @var bool|null Should we use table column drop-down menu to display user actions? */ public $useMenuActions; - /** @var array Collection of APPLIES_TO_NO_RECORDS Scope Model action menu item */ + /** @var array Collection of APPLIES_TO_NO_RECORD Scope Model action menu item */ private array $menuItems = []; /** @var list Model single scope action to include in table action column. Will include all single scope actions if empty. */ @@ -51,7 +51,10 @@ class Crud extends Grid public $defaultMsg = 'Done!'; /** @var list> Callback containers for model action. */ - public $onActions = []; + public array $onActions = []; + + /** @var mixed Recently created/updated record ID. */ + private $updatedId; /** @var mixed Recently deleted record ID. */ private $deletedId; @@ -96,9 +99,14 @@ public function setModel(Model $model, ?array $fields = null): void parent::setModel($model, $this->displayFields); - // grab model ID when using delete - // must be set before delete action execute + $this->model->onHook(Model::HOOK_BEFORE_SAVE, function (Model $entity) { + $this->updatedId = $entity->getId(); + }); + $this->model->onHook(Model::HOOK_AFTER_SAVE, function (Model $entity) { + $this->updatedId = $entity->getId(); + }); $this->model->onHook(Model::HOOK_AFTER_DELETE, function (Model $entity) { + $this->updatedId = null; $this->deletedId = $entity->getId(); }); @@ -116,7 +124,7 @@ public function setModel(Model $model, ?array $fields = null): void } if ($this->menu) { - foreach ($this->_getModelActions(Model\UserAction::APPLIES_TO_NO_RECORDS) as $k => $action) { + foreach ($this->_getModelActions(Model\UserAction::APPLIES_TO_NO_RECORD) as $k => $action) { if ($action->enabled) { $executor = $this->initActionExecutor($action); $this->menuItems[$k]['item'] = $this->menu->addItem( @@ -176,13 +184,13 @@ protected function jsExecute($return, Model\UserAction $action): JsBlock $res->addStatement($jsAction); } - // display msg return by action or depending on action modifier + // display msg return by action or depending on action behavior if (is_string($return)) { $res->addStatement($this->jsCreateNotifier($return)); } else { - if ($action->modifier === Model\UserAction::MODIFIER_CREATE || $action->modifier === Model\UserAction::MODIFIER_UPDATE) { + if ($this->updatedId !== null) { $res->addStatement($this->jsCreateNotifier($this->saveMsg)); - } elseif ($action->modifier === Model\UserAction::MODIFIER_DELETE) { + } elseif ($this->deletedId !== null) { $res->addStatement($this->jsCreateNotifier($this->deleteMsg)); } else { $res->addStatement($this->jsCreateNotifier($this->defaultMsg)); @@ -193,26 +201,20 @@ protected function jsExecute($return, Model\UserAction $action): JsBlock } /** - * Return proper JS actions depending on action modifier type. + * Return proper JS actions depending on action behavior. */ protected function getJsGridAction(Model\UserAction $action): ?JsExpressionable { - switch ($action->modifier) { - case Model\UserAction::MODIFIER_UPDATE: - case Model\UserAction::MODIFIER_CREATE: - $js = $this->container->jsReload($this->_getReloadArgs()); - - break; - case Model\UserAction::MODIFIER_DELETE: - // use deleted record ID to remove row, fallback to closest tr if ID is not available - $js = $this->deletedId - ? $this->js(false, null, 'tr[data-id="' . $this->getApp()->uiPersistence->typecastAttributeSaveField($this->model->getIdField(), $this->deletedId) . '"]') - : (new Jquery())->closest('tr'); - $js = $js->transition('fade left', new JsFunction([], [new JsExpression('this.remove()')])); - - break; - default: - $js = null; + if ($this->updatedId !== null) { + $js = $this->container->jsReload($this->_getReloadArgs()); + } elseif ($this->deletedId !== null) { + // use deleted record ID to remove row, fallback to closest tr if ID is not available + $js = $this->deletedId + ? $this->js(false, null, 'tr[data-id="' . $this->getApp()->uiPersistence->typecastAttributeSaveField($this->model->getIdField(), $this->deletedId) . '"]') + : (new Jquery())->closest('tr'); + $js = $js->transition('fade left', new JsFunction([], [new JsExpression('this.remove()')])); + } else { + $js = null; } return $js; @@ -292,7 +294,7 @@ private function _getModelActions(string $appliesTo): array $this->singleScopeActions, array_map(fn ($v) => $this->model->getUserAction($v), $this->singleScopeActions) ); - } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORDS && $this->noRecordScopeActions !== []) { + } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORD && $this->noRecordScopeActions !== []) { $actions = array_combine( $this->noRecordScopeActions, array_map(fn ($v) => $this->model->getUserAction($v), $this->noRecordScopeActions) diff --git a/src/UserAction/CommonExecutorTrait.php b/src/UserAction/CommonExecutorTrait.php index bdc1e754c7..5127339a68 100644 --- a/src/UserAction/CommonExecutorTrait.php +++ b/src/UserAction/CommonExecutorTrait.php @@ -23,7 +23,7 @@ protected function executeModelActionLoad(Model\UserAction $action): Model\UserA } else { $action = $action->getActionForEntity($model->load($id)); } - } elseif (!$action->isOwnerEntity() && in_array($action->appliesTo, [Model\UserAction::APPLIES_TO_NO_RECORDS, Model\UserAction::APPLIES_TO_SINGLE_RECORD], true)) { + } elseif (!$action->isOwnerEntity() && in_array($action->appliesTo, [Model\UserAction::APPLIES_TO_NO_RECORD, Model\UserAction::APPLIES_TO_SINGLE_RECORD], true)) { $action = $action->getActionForEntity($model->createEntity()); } diff --git a/src/UserAction/JsCallbackExecutor.php b/src/UserAction/JsCallbackExecutor.php index 2faed7eedc..0f5da5d9de 100644 --- a/src/UserAction/JsCallbackExecutor.php +++ b/src/UserAction/JsCallbackExecutor.php @@ -96,7 +96,7 @@ private function executeModelActionLoad(Model\UserAction $action): Model\UserAct } else { $action = $action->getActionForEntity($model->load($id)); } - } elseif (!$action->isOwnerEntity() && in_array($action->appliesTo, [Model\UserAction::APPLIES_TO_NO_RECORDS, Model\UserAction::APPLIES_TO_SINGLE_RECORD], true)) { + } elseif (!$action->isOwnerEntity() && in_array($action->appliesTo, [Model\UserAction::APPLIES_TO_NO_RECORD, Model\UserAction::APPLIES_TO_SINGLE_RECORD], true)) { $action = $action->getActionForEntity($model->createEntity()); } diff --git a/src/UserAction/StepExecutorTrait.php b/src/UserAction/StepExecutorTrait.php index 5d3642ad3d..7b5845668b 100644 --- a/src/UserAction/StepExecutorTrait.php +++ b/src/UserAction/StepExecutorTrait.php @@ -54,7 +54,11 @@ trait StepExecutorTrait public $previewType = 'html'; /** @var array> View seed for displaying title for each step. */ - protected $stepTitle = ['args' => [], 'fields' => [], 'preview' => []]; + protected array $stepTitle = [ + 'args' => [], + 'fields' => [], + 'preview' => [], + ]; /** @var string */ public $finalMsg = 'Complete!';