Skip to content

Commit

Permalink
Fix row click demo with php callback (#2224)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored Nov 21, 2024
1 parent 6184ea3 commit 1c6f3a4
Show file tree
Hide file tree
Showing 16 changed files with 75 additions and 47 deletions.
4 changes: 3 additions & 1 deletion demos/collection/grid.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
$grid->addActionButton('Say HI', static function (Jquery $j, $id) use ($grid) {
$model = Country::assertInstanceOf($grid->model);

return 'Loaded "' . $model->load($id)->name . '" from ID=' . $id;
$id = $grid->getApp()->uiPersistence->typecastAttributeLoadField($grid->model->getIdField(), $id); // TODO fix asap, this line must not be needed!

return new JsToast('Loaded "' . $model->load($id)->name . '" from ID=' . $id->getId());
});

$grid->addModalAction(['icon' => 'external'], 'Modal Test', static function (View $p, $id) {
Expand Down
3 changes: 2 additions & 1 deletion demos/data-action/jsactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
$card->addDescription('Kristy is a friend of Mully.');

$s = $card->addSection('Country');
$s->addFields($entity = $country->loadAny(), [$country->fieldName()->name, $country->fieldName()->iso]);
$entity = $country->loadAny();
$s->addFields($entity, [$country->fieldName()->name, $country->fieldName()->iso]);

// pass the model action to the Card::addClickAction() method
$card->addClickAction($sendEmailAction, null, ['id' => $entity->getId()]);
3 changes: 2 additions & 1 deletion demos/data-action/jsactionsgrid.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
if (in_array($action->shortName, ['add', 'edit', 'delete'], true)) {
continue;
}
$grid->addExecutorMenuItem($executor = $app->getExecutorFactory()->createExecutor($action, $grid));

$grid->addExecutorMenuItem($app->getExecutorFactory()->createExecutor($action, $grid));
}

$grid->ipp = 10;
4 changes: 2 additions & 2 deletions demos/interactive/scroll-grid-container.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
$g1->addModalAction(['icon' => 'cogs'], 'Details', static function (View $p, $id) use ($g1) {
Card::addTo($p)->setModel($g1->model->load($id));
});
$g1->addActionButton('red', static function (Jquery $js) {
return $js->closest('tr')->css('color', 'red');
$g1->addActionButton('red', static function (Jquery $js, $id) {
return $js->find('tr[data-id=' . $id . ']')->css('color', 'red');
});
// THIS SHOULD GO AFTER YOU CALL Grid::addActionButton()
$g1->addJsPaginatorInContainer(30, 350);
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ parameters:
-
path: 'src/Form/Control/Calendar.php'
identifier: method.childParameterType
message: '~^Parameter #1 \$expr \(Atk4\\Ui\\Js\\JsExpressionable\) of method Atk4\\Ui\\Form\\Control\\Calendar::onChange\(\) should be contravariant with parameter \$expr \(array\{Closure\(Atk4\\Ui\\Js\\Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed\): \(Atk4\\Ui\\Js\\JsExpressionable\|Atk4\\Ui\\View\|string\|void\)\}\|Atk4\\Ui\\Js\\JsExpressionable\|\(Closure\(Atk4\\Ui\\Js\\Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed\): \(Atk4\\Ui\\Js\\JsExpressionable\|Atk4\\Ui\\View\|string\|void\)\)\) of method Atk4\\Ui\\Form\\Control::onChange\(\)$~'
message: '~^Parameter #1 \$expr \(Atk4\\Ui\\Js\\JsExpressionable\) of method Atk4\\Ui\\Form\\Control\\Calendar::onChange\(\) should be contravariant with parameter \$expr \(array\{Closure\(Atk4\\Ui\\Js\\Jquery, mixed\): \(Atk4\\Ui\\Js\\JsExpressionable\|Atk4\\Ui\\View\|string\|void\)\}\|Atk4\\Ui\\Js\\JsExpressionable\|\(Closure\(Atk4\\Ui\\Js\\Jquery, mixed\): \(Atk4\\Ui\\Js\\JsExpressionable\|Atk4\\Ui\\View\|string\|void\)\)\) of method Atk4\\Ui\\Form\\Control::onChange\(\)$~'
count: 1
-
path: 'src/Form/Control/Upload.php'
Expand Down
6 changes: 4 additions & 2 deletions src/Behat/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -763,10 +763,12 @@ public function iScrollToBottom(): void
}

/**
* @Then Toast display should contain text :arg1
* @Then ~^Toast display should contain text "((?:[^"]|\\")*)"$~
*/
public function toastDisplayShouldContainText(string $text): void
{
$text = $this->unquoteStepArgument($text);

$toastContainer = $this->findElement(null, '.ui.toast-container');
$toastText = $this->findElement($toastContainer, '.content')->getText();
if (!str_contains($toastText, $text)) {
Expand Down Expand Up @@ -845,7 +847,7 @@ public function elementAttributeShouldContainText(string $selector, string $attr
$element = $this->findElement(null, $selector);
$attr = $element->getAttribute($attribute);
if (!str_contains($attr, $text)) {
throw new \Exception('Element " . $selector . " attribute "' . $attribute . '" does not contain "' . $text . '"');
throw new \Exception('Element "' . $selector . '" attribute "' . $attribute . '" does not contain "' . $text . '"');
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Form/Control.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* Provides generic functionality for a form control.
*
* @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
* @phpstan-type JsCallbackSetWithValueClosure \Closure(Jquery, mixed): (JsExpressionable|View|string|void)
*/
class Control extends View
{
Expand Down Expand Up @@ -129,8 +129,8 @@ protected function renderTemplateToHtml(): string
* $control->onChange(new JsExpression('console.log(\'changed\')'));
* $control->onChange(new JsExpression('$(this).parents(\'.form\').form(\'submit\')'));
*
* @param JsExpressionable|JsCallbackSetClosure|array{JsCallbackSetClosure} $expr
* @param array<int|string, mixed>|bool $defaults
* @param JsExpressionable|JsCallbackSetWithValueClosure|array{JsCallbackSetWithValueClosure} $expr
* @param array<int|string, mixed>|bool $defaults
*/
public function onChange($expr, $defaults = []): void
{
Expand Down
4 changes: 3 additions & 1 deletion src/Form/Control/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ protected function prepareRenderButton($button, $spot)
: $button;
$button = $this->add($this->getExecutorFactory()->createTrigger($executor->getAction()), $spot);
if ($executor->getAction()->args) {
$button->on('click', $executor, ['args' => [array_key_first($executor->getAction()->args) => $this->jsInput()->val()]]);
$button->on('click', $executor, ['args' => [
array_key_first($executor->getAction()->args) => $this->jsInput()->val(),
]]);
} else {
$button->on('click', $executor);
}
Expand Down
1 change: 0 additions & 1 deletion src/Form/Control/Upload.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class Upload extends Input
* The uploaded file ID.
* This ID is return on form submit.
* If not set, will default to file name.
* file ID is also sent with onDelete Callback.
*
* @var string|null
*/
Expand Down
14 changes: 7 additions & 7 deletions src/Grid.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Atk4\Ui\UserAction\ExecutorInterface;

/**
* @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
* @phpstan-type JsCallbackSetWithRowIdClosure \Closure(Jquery, mixed): (JsExpressionable|View|string|void)
*/
class Grid extends View
{
Expand Down Expand Up @@ -345,9 +345,9 @@ public function jsReload($args = [], $afterSuccess = null, array $apiConfig = []
* Adds a new button into the action column on the right. For Crud this
* column will already contain "delete" and "edit" buttons.
*
* @param string|array<mixed>|View $button Label text, object or seed for the Button
* @param JsExpressionable|JsCallbackSetClosure $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
* @param string|array<mixed>|View $button Label text, object or seed for the Button
* @param JsExpressionable|JsCallbackSetWithRowIdClosure $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
*
* @return View
*/
Expand Down Expand Up @@ -393,9 +393,9 @@ private function getActionButtons(): Table\Column\ActionButtons
* Similar to addActionButton. Will add Button that when click will display
* a Dropdown menu.
*
* @param View|string $view
* @param JsExpressionable|JsCallbackSetClosure $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
* @param View|string $view
* @param JsExpressionable|JsCallbackSetWithRowIdClosure $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
*
* @return View
*/
Expand Down
2 changes: 1 addition & 1 deletion src/JsCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
class JsCallback extends Callback
{
/** @var array<string, string|JsExpressionable> Holds information about arguments passed in to the callback. */
public $args = [];
public array $args = [];

/** @var string Text to display as a confirmation. Set with setConfirm(..). */
public $confirm;
Expand Down
8 changes: 4 additions & 4 deletions src/Table/Column/ActionButtons.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* Formatting action buttons column.
*
* @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
* @phpstan-type JsCallbackSetWithRowIdClosure \Closure(Jquery, mixed): (JsExpressionable|View|string|void)
*/
class ActionButtons extends Table\Column
{
Expand All @@ -39,9 +39,9 @@ protected function init(): void
/**
* Adds a new button which will execute $action when clicked.
*
* @param string|array<mixed>|View $button
* @param JsExpressionable|JsCallbackSetClosure|ExecutorInterface $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
* @param string|array<mixed>|View $button
* @param JsExpressionable|JsCallbackSetWithRowIdClosure|ExecutorInterface $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
*
* @return View
*/
Expand Down
8 changes: 4 additions & 4 deletions src/Table/Column/ActionMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Table column action menu.
* Will create a dropdown menu within table column.
*
* @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
* @phpstan-type JsCallbackSetWithRowIdClosure \Closure(Jquery, mixed): (JsExpressionable|View|string|void)
*/
class ActionMenu extends Table\Column
{
Expand Down Expand Up @@ -53,9 +53,9 @@ public function getTag(string $position, $attr, $value): string
/**
* Add a menu item in Dropdown.
*
* @param View|string $item
* @param JsExpressionable|JsCallbackSetClosure|ExecutorInterface $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
* @param View|string $item
* @param JsExpressionable|JsCallbackSetWithRowIdClosure|ExecutorInterface $action
* @param bool|\Closure<T of Model>(T): bool $isDisabled
*
* @return View
*/
Expand Down
4 changes: 1 addition & 3 deletions src/Table/Column/Checkbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ class Checkbox extends Table\Column
public $class;

/**
* Return action which will calculate and return array of all Checkbox IDs, e.g.
*
* [3, 5, 20]
* Return action which will calculate and return array of all checkbox IDs, e.g. [3, 5, 20].
*/
public function jsChecked(): JsExpressionable
{
Expand Down
40 changes: 25 additions & 15 deletions src/UserAction/JsCallbackExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,29 +81,39 @@ public function jsExecute(array $urlArgs = []): JsBlock
}, $urlArgs);
}

private function executeModelActionLoad(Model\UserAction $action): Model\UserAction
{
$model = $action->getModel();

$id = $this->getApp()->uiPersistence->typecastAttributeLoadField(
$model->getIdField(),
$this->getApp()->tryGetRequestPostParam($this->name)
);

if ($id && $action->appliesTo === Model\UserAction::APPLIES_TO_SINGLE_RECORD) {
if ($action->isOwnerEntity() && $action->getEntity()->getId()) {
$action->getEntity()->setId($id); // assert ID is the same
} 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)) {
$action = $action->getActionForEntity($model->createEntity());
}

return $action;
}

#[\Override]
public function executeModelAction(): void
{
$this->invokeFxWithUrlArgs(function () { // backup/restore $this->args mutated in https://github.com/atk4/ui/blob/8926412a31/src/JsCallback.php#L71
$this->set(function (Jquery $j, ...$values) {
$id = $this->getApp()->uiPersistence->typecastAttributeLoadField(
$this->action->getModel()->getIdField(),
$this->getApp()->tryGetRequestPostParam($this->name)
);
if ($id && $this->action->appliesTo === Model\UserAction::APPLIES_TO_SINGLE_RECORD) {
if ($this->action->isOwnerEntity() && $this->action->getEntity()->getId()) {
$this->action->getEntity()->setId($id); // assert ID is the same
} else {
$this->action = $this->action->getActionForEntity($this->action->getModel()->load($id));
}
} elseif (!$this->action->isOwnerEntity()
&& in_array($this->action->appliesTo, [Model\UserAction::APPLIES_TO_NO_RECORDS, Model\UserAction::APPLIES_TO_SINGLE_RECORD], true)
) {
$this->action = $this->action->getActionForEntity($this->action->getModel()->createEntity());
}
$this->action = $this->executeModelActionLoad($this->action);

$return = $this->action->execute(...$values);

$id = $this->action->getEntity()->getId();

$success = $this->jsSuccess instanceof \Closure
? ($this->jsSuccess)($this, $this->action->getModel(), $id, $return)
: $this->jsSuccess;
Expand Down
13 changes: 13 additions & 0 deletions tests-behat/grid.feature
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ Feature: Grid
Then I should see "China"
Then I should see "Zambia"

Scenario: Row remote action - load record
Given I am on "collection/grid.php"
Then I should not see "Bahamas"
Then I click paginator page "2"
Then I should see "Bahamas"
When I click using selector "//tr[td[text()='Bahamas']]//div.ui.button[text()='Say HI']"
Then Toast display should contain text "Loaded \"Bahamas\" from ID=16"

Scenario: Row remote action - change row CSS
Given I am on "interactive/scroll-grid-container.php"
When I click using selector "//tr[td[text()='Algeria']]//div.ui.button[text()='red']"
Then Element "//tr[td[text()='Algeria'] and .//div.ui.button[text()='red']]" attribute "style" should contain text "color: red;"

Scenario: Bulk action
Given I am on "collection/grid.php"
Then I press button "Show selected"
Expand Down

0 comments on commit 1c6f3a4

Please sign in to comment.