1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\UserAction;
6:
7: use Atk4\Core\HookTrait;
8: use Atk4\Data\Model;
9: use Atk4\Ui\Button;
10: use Atk4\Ui\Exception;
11: use Atk4\Ui\Header;
12: use Atk4\Ui\Js\JsBlock;
13: use Atk4\Ui\Js\JsExpressionable;
14: use Atk4\Ui\Js\JsToast;
15: use Atk4\Ui\Message;
16: use Atk4\Ui\View;
17:
18: class BasicExecutor extends View implements ExecutorInterface
19: {
20: use HookTrait;
21:
22: public const HOOK_AFTER_EXECUTE = self::class . '@afterExecute';
23:
24: /** @var Model\UserAction|null */
25: public $action;
26:
27: /** @var bool display header or not */
28: public $hasHeader = true;
29:
30: /** @var string|null header description */
31: public $description;
32:
33: /** @var string display message when action is disabled */
34: public $disableMsg = 'Action is disabled and cannot be executed';
35:
36: /** @var Button|array Button that trigger the action. Either as an array seed or object */
37: public $executorButton;
38:
39: /** @var array<string, mixed> */
40: protected $arguments = [];
41:
42: /** @var string display message when missing arguments */
43: public $missingArgsMsg = 'Insufficient arguments';
44:
45: /** @var array list of validated arguments */
46: protected $validArguments = [];
47:
48: /** @var JsExpressionable|\Closure JS expression to return if action was successful, e.g "new JsToast('Thank you')" */
49: protected $jsSuccess;
50:
51: #[\Override]
52: public function getAction(): Model\UserAction
53: {
54: return $this->action;
55: }
56:
57: #[\Override]
58: public function setAction(Model\UserAction $action)
59: {
60: $this->action = $action;
61: if (!$this->executorButton) {
62: $this->executorButton = $this->getExecutorFactory()->createTrigger($action, ExecutorFactory::BASIC_BUTTON);
63: }
64:
65: return $this;
66: }
67:
68: /**
69: * Provide values for named arguments.
70: */
71: public function setArguments(array $arguments): void
72: {
73: // TODO: implement mechanism for validating arguments based on definition
74:
75: $this->arguments = array_merge($this->arguments, $arguments);
76: }
77:
78: #[\Override]
79: protected function recursiveRender(): void
80: {
81: if (!$this->action) {
82: throw new Exception('Action is not set. Use setAction()');
83: }
84:
85: // check action can be called
86: if ($this->action->enabled) {
87: $this->initPreview();
88: } else {
89: Message::addTo($this, ['type' => 'error', $this->disableMsg]);
90:
91: return;
92: }
93:
94: parent::recursiveRender();
95: }
96:
97: /**
98: * Check if all argument values have been provided.
99: */
100: public function hasAllArguments(): bool
101: {
102: foreach ($this->action->args as $key => $val) {
103: if (!isset($this->arguments[$key])) {
104: return false;
105: }
106: }
107:
108: return true;
109: }
110:
111: protected function initPreview(): void
112: {
113: // lets make sure that all arguments are supplied
114: if (!$this->hasAllArguments()) {
115: Message::addTo($this, ['type' => 'error', $this->missingArgsMsg]);
116:
117: return;
118: }
119:
120: $this->addHeader();
121:
122: Button::addToWithCl($this, $this->executorButton)->on('click', function () {
123: return $this->executeModelAction();
124: });
125: }
126:
127: /**
128: * Will call $action->execute() with the correct arguments.
129: */
130: #[\Override]
131: public function executeModelAction(): JsBlock
132: {
133: $args = [];
134: foreach ($this->action->args as $key => $val) {
135: $args[] = $this->arguments[$key];
136: }
137:
138: $return = $this->action->execute(...$args);
139:
140: $success = $this->jsSuccess instanceof \Closure
141: ? ($this->jsSuccess)($this, $this->action->getModel())
142: : $this->jsSuccess;
143:
144: return JsBlock::fromHookResult($this->hook(self::HOOK_AFTER_EXECUTE, [$return]) // @phpstan-ignore-line
145: ?: ($success ?? new JsToast('Success' . (is_string($return) ? (': ' . $return) : ''))));
146: }
147:
148: public function addHeader(): void
149: {
150: if ($this->hasHeader) {
151: Header::addTo($this, [$this->action->getCaption(), 'subHeader' => $this->description ?? $this->action->getDescription()]);
152: }
153: }
154: }
155: