1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Table\Column;
6:
7: use Atk4\Core\Factory;
8: use Atk4\Data\Field;
9: use Atk4\Data\Model;
10: use Atk4\Ui\Js\Jquery;
11: use Atk4\Ui\Js\JsChain;
12: use Atk4\Ui\Js\JsExpressionable;
13: use Atk4\Ui\Table;
14: use Atk4\Ui\UserAction\ExecutorInterface;
15: use Atk4\Ui\View;
16:
17: /**
18: * Table column action menu.
19: * Will create a dropdown menu within table column.
20: *
21: * @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
22: */
23: class ActionMenu extends Table\Column
24: {
25: /** @var array Menu items collections. */
26: protected $items = [];
27:
28: /** @var array<string, \Closure(Model): bool> Callbacks as defined in UserAction->enabled for evaluating row-specific if an action is enabled. */
29: protected $callbacks = [];
30:
31: /**
32: * Dropdown label.
33: * Note: In Grid::class, this value is set by ActionMenuDecorator property.
34: *
35: * @var string
36: */
37: public $label;
38:
39: /** @var string Dropdown module CSS class name as per Formantic-UI. */
40: public $ui = 'small dropdown button';
41:
42: /** @var array The dropdown module option setting as per Fomantic-UI. */
43: public $options = ['action' => 'hide'];
44:
45: /** @var string Button icon to use for display dropdown. */
46: public $icon = 'dropdown';
47:
48: #[\Override]
49: public function getTag(string $position, $value, $attr = []): string
50: {
51: if ($this->table->hasCollapsingCssActionColumn && $position === 'body') {
52: $attr['class'][] = 'collapsing';
53: }
54:
55: return parent::getTag($position, $value, $attr);
56: }
57:
58: /**
59: * Add a menu item in Dropdown.
60: *
61: * @param View|string $item
62: * @param JsExpressionable|JsCallbackSetClosure|ExecutorInterface $action
63: * @param bool|\Closure(Model): bool $isDisabled
64: *
65: * @return View
66: */
67: public function addActionMenuItem($item, $action = null, string $confirmMsg = '', $isDisabled = false)
68: {
69: $name = $this->name . '_action_' . (count($this->items) + 1);
70:
71: if (!is_object($item)) {
72: $item = Factory::factory([View::class], ['name' => false, 'ui' => 'item', 'content' => $item]);
73: }
74:
75: $item->setApp($this->getApp());
76: $this->items[] = $item;
77:
78: $item->addClass('{$_' . $name . '_disabled} i_' . $name);
79:
80: if ($isDisabled === true) {
81: $item->addClass('disabled');
82: } elseif ($isDisabled !== false) {
83: $this->callbacks[$name] = $isDisabled;
84: }
85:
86: if ($action !== null) {
87: // set executor context
88: $context = (new Jquery())->closest('.ui.button');
89:
90: $this->table->on('click', '.i_' . $name, $action, [
91: $this->table->jsRow()->data('id'),
92: 'confirm' => $confirmMsg,
93: 'apiConfig' => ['stateContext' => $context],
94: ]);
95: }
96:
97: return $item;
98: }
99:
100: #[\Override]
101: public function getHeaderCellHtml(Field $field = null, $value = null): string
102: {
103: $this->table->js(true)->find('.atk-action-menu')->dropdown(
104: array_merge(
105: $this->options,
106: [
107: 'direction' => 'auto', // direction needs to be "auto"
108: 'transition' => 'none', // no transition
109: 'onShow' => (new JsChain('atk.tableDropdownHelper.onShow')),
110: 'onHide' => (new JsChain('atk.tableDropdownHelper.onHide')),
111: ]
112: )
113: );
114:
115: return parent::getHeaderCellHtml($field, $value);
116: }
117:
118: #[\Override]
119: public function getDataCellTemplate(Field $field = null): string
120: {
121: if (!$this->items) {
122: return '';
123: }
124:
125: // render our menus
126: $outputHtml = '';
127: foreach ($this->items as $item) {
128: $outputHtml .= $item->getHtml();
129: }
130:
131: $res = $this->getApp()->getTag('div', ['class' => 'ui ' . $this->ui . ' atk-action-menu'], [
132: ['div', ['class' => 'text'], $this->label],
133: $this->icon ? $this->getApp()->getTag('i', ['class' => $this->icon . ' icon'], '') : '',
134: ['div', ['class' => 'menu'], [$outputHtml]],
135: ]);
136:
137: return $res;
138: }
139:
140: #[\Override]
141: public function getHtmlTags(Model $row, ?Field $field): array
142: {
143: $tags = [];
144: foreach ($this->callbacks as $name => $callback) {
145: // if action is enabled then do not set disabled class
146: if ($callback($row)) {
147: continue;
148: }
149:
150: $tags['_' . $name . '_disabled'] = 'disabled';
151: }
152:
153: return $tags;
154: }
155: }
156: