1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Panel;
6:
7: use Atk4\Core\Factory;
8: use Atk4\Ui\Button;
9: use Atk4\Ui\Js\Jquery;
10: use Atk4\Ui\Js\JsChain;
11: use Atk4\Ui\Js\JsExpressionable;
12: use Atk4\Ui\Modal;
13: use Atk4\Ui\View;
14:
15: /**
16: * Right Panel implementation.
17: * Opening, closing and loading Panel content is managed
18: * via the JS panel service.
19: *
20: * Content is loaded via a LoadableContent View.
21: * This view must implement a callback for content to be added via the callback function.
22: */
23: class Right extends View implements Loadable
24: {
25: public array $class = ['atk-right-panel'];
26: public $defaultTemplate = 'panel/right.html';
27:
28: /** @var Modal|null */
29: public $closeModal;
30: /** @var array Confirmation Modal default */
31: public $defaultModal = [Modal::class, 'class' => ['mini']];
32:
33: /** @var View|null The content to display inside flyout */
34: protected $dynamicContent;
35:
36: /** @var bool can be closed by clicking outside panel. */
37: protected $hasClickAway = true;
38:
39: /** @var bool can be closed via esc key. */
40: protected $hasEscAway = true;
41:
42: /** @var array The default content seed. */
43: public $dynamic = [Content::class];
44:
45: /** @var string The CSS selector on where to add close panel event triggering for closing it. */
46: public $closeSelector = '.atk-panel-close';
47:
48: /** @var string a CSS selector where warning trigger class will be applied. */
49: public $warningSelector = '.atk-panel-warning';
50:
51: /** @var string the CSS class name to apply to element set by warning selector. */
52: public $warningTrigger = 'atk-visible';
53:
54: /** @var string the warning icon class */
55: public $warningIcon = 'exclamation circle';
56:
57: /** @var string the close icon class */
58: public $closeIcon = 'times';
59:
60: #[\Override]
61: protected function init(): void
62: {
63: parent::init();
64:
65: if ($this->dynamic) {
66: $this->addDynamicContent(Factory::factory($this->dynamic));
67: }
68: }
69:
70: #[\Override]
71: public function addDynamicContent(LoadableContent $content): void
72: {
73: $this->dynamicContent = Content::addTo($this, [], ['LoadContent']);
74: }
75:
76: #[\Override]
77: public function getDynamicContent(): LoadableContent
78: {
79: return $this->dynamicContent;
80: }
81:
82: /**
83: * Return JS expression in order to retrieve panelService.
84: */
85: public function service(): JsChain
86: {
87: return new JsChain('atk.panelService');
88: }
89:
90: /**
91: * Return JS expression need to open panel via JS panelService.
92: *
93: * @param array<string, string> $urlArgs the argument to include when dynamic content panel open
94: * @param array $dataAttribute the data attribute name to include in reload from the triggering element
95: * @param string|null $activeCss the CSS class name to apply on triggering element when panel is open
96: * @param JsExpressionable $jsTrigger JS expression that trigger panel to open. Default = $(this).
97: */
98: public function jsOpen(array $urlArgs = [], array $dataAttribute = [], string $activeCss = null, JsExpressionable $jsTrigger = null): JsExpressionable
99: {
100: return $this->service()->openPanel([
101: 'triggered' => $jsTrigger ?? new Jquery(),
102: 'reloadArgs' => $dataAttribute,
103: 'urlArgs' => $urlArgs,
104: 'openId' => $this->name,
105: 'activeCSS' => $activeCss,
106: ]);
107: }
108:
109: /**
110: * Will reload panel passing args as Get param via JS flyoutService.
111: */
112: public function jsPanelReload(array $args = []): JsExpressionable
113: {
114: return $this->service()->reloadPanel($this->name, $args);
115: }
116:
117: /**
118: * Return JS expression need to close panel via JS panelService.
119: */
120: public function jsClose(): JsExpressionable
121: {
122: return $this->service()->closePanel($this->name);
123: }
124:
125: /**
126: * Attach confirmation modal view to display.
127: * JS flyoutService will prevent closing of Flyout if a confirmation modal
128: * is attached to it and flyoutService detect that the current open flyoutContent has warning on.
129: */
130: public function addConfirmation(string $msg, string $title = 'Closing panel!', string $okButton = null, string $cancelButton = null): void
131: {
132: if (!$okButton) {
133: $okButton = (new Button(['Ok']))->addClass('ok');
134: }
135:
136: if (!$cancelButton) {
137: $cancelButton = (new Button(['Cancel']))->addClass('cancel');
138: }
139: $this->closeModal = $this->getApp()->add(array_merge($this->defaultModal, ['title' => $title]));
140: $this->closeModal->add([View::class, $msg, 'element' => 'p']);
141: $this->closeModal->addButtonAction(Factory::factory($okButton));
142: $this->closeModal->addButtonAction(Factory::factory($cancelButton));
143:
144: $this->closeModal->notClosable();
145: }
146:
147: /**
148: * Callback to execute when panel open if dynamic content is set.
149: * Differ the callback execution to the FlyoutContent.
150: *
151: * @param \Closure(object): void $fx
152: */
153: public function onOpen(\Closure $fx): void
154: {
155: $this->getDynamicContent()->onLoad($fx);
156: }
157:
158: /**
159: * Display or not a Warning sign in Panel.
160: *
161: * @return Jquery
162: */
163: public function jsDisplayWarning(bool $state = true): JsExpressionable
164: {
165: $chain = new Jquery('#' . $this->name . ' ' . $this->warningSelector);
166:
167: return $state ? $chain->addClass($this->warningTrigger) : $chain->removeClass($this->warningTrigger);
168: }
169:
170: /**
171: * Toggle warning sign.
172: *
173: * @return Jquery
174: */
175: public function jsToggleWarning(): JsExpressionable
176: {
177: return (new Jquery('#' . $this->name . ' ' . $this->warningSelector))->toggleClass($this->warningTrigger);
178: }
179:
180: public function getPanelOptions(): array
181: {
182: $res = [
183: 'id' => $this->name,
184: 'loader' => ['selector' => '.ui.loader', 'trigger' => 'active'], // the CSS selector and trigger class to activate loader
185: 'modal' => $this->closeModal,
186: 'warning' => ['selector' => $this->warningSelector, 'trigger' => $this->warningTrigger],
187: 'visible' => 'atk-visible', // the triggering CSS class that will make this panel visible
188: 'closeSelector' => $this->closeSelector, // the CSS selector to close this flyout
189: 'hasClickAway' => $this->hasClickAway,
190: 'hasEscAway' => $this->hasEscAway,
191: ];
192:
193: if ($this->dynamicContent) {
194: $res['url'] = $this->getDynamicContent()->getCallbackUrl();
195: $res['clearable'] = $this->getDynamicContent()->getClearSelector();
196: }
197:
198: return $res;
199: }
200:
201: #[\Override]
202: protected function renderView(): void
203: {
204: $this->template->trySet('WarningIcon', $this->warningIcon);
205: $this->template->trySet('CloseIcon', $this->closeIcon);
206:
207: parent::renderView();
208:
209: $this->js(true, $this->service()->addPanel($this->getPanelOptions()));
210: }
211: }
212: