1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui;
6:
7: use Atk4\Data\Model;
8: use Atk4\Ui\Js\Jquery;
9: use Atk4\Ui\Js\JsBlock;
10: use Atk4\Ui\Js\JsExpressionable;
11:
12: class Menu extends View
13: {
14: public $ui = 'menu';
15:
16: /**
17: * If you set this to false, then upon clicking on the item, it won't
18: * be highlighted as "active". This is useful if you have action on your
19: * menu and page does not actually reload.
20: *
21: * @var bool
22: */
23: public $activateOnClick = true;
24:
25: public $defaultTemplate = 'menu.html';
26:
27: /**
28: * Will be set to true, when Menu is used as a part of a dropdown.
29: *
30: * @internal
31: *
32: * @var bool
33: */
34: public $inDropdown = false;
35:
36: /**
37: * $seed can also be name here.
38: *
39: * @param string|array|MenuItem $item
40: * @param string|array<0|string, string|int|false>|JsExpressionable|Model\UserAction $action
41: *
42: * @return MenuItem
43: */
44: public function addItem($item = null, $action = null)
45: {
46: if (!is_object($item)) {
47: if (!is_array($item)) {
48: $item = [$item];
49: }
50:
51: array_unshift($item, MenuItem::class);
52: }
53:
54: /** @var MenuItem */
55: $item = $this->add($item);
56:
57: if (is_string($action) || is_array($action)) {
58: $url = $this->url($action);
59: $item->link($url);
60: } elseif ($action) {
61: $item->on('click', null, $action);
62: }
63:
64: return $item;
65: }
66:
67: /**
68: * Adds header.
69: *
70: * @param string $name
71: *
72: * @return MenuItem
73: */
74: public function addHeader($name)
75: {
76: return MenuItem::addTo($this, [$name])->addClass('header');
77: }
78:
79: /**
80: * Adds sub-menu.
81: *
82: * @param string|array $name
83: *
84: * @return Menu
85: */
86: public function addMenu($name)
87: {
88: $subMenu = (self::class)::addTo($this, ['defaultTemplate' => 'submenu.html', 'ui' => 'dropdown', 'inDropdown' => true]);
89:
90: if (!is_array($name)) {
91: $name = [$name];
92: }
93:
94: $label = $name['title'] ?? $name['text'] ?? $name['name'] ?? $name[0] ?? null;
95:
96: if ($label !== null) {
97: $subMenu->template->set('label', $label);
98: }
99:
100: if (isset($name['icon'])) {
101: Icon::addTo($subMenu, [$name['icon']], ['Icon'])->removeClass('item');
102: }
103:
104: if (!$this->inDropdown) {
105: $subMenu->js(true)->dropdown(['on' => 'hover', 'action' => 'hide']);
106: }
107:
108: return $subMenu;
109: }
110:
111: /**
112: * Adds menu group.
113: *
114: * @param string|array $name
115: *
116: * @return Menu
117: */
118: public function addGroup($name, string $template = 'menugroup.html')
119: {
120: $group = (self::class)::addTo($this, ['defaultTemplate' => $template, 'ui' => false]);
121:
122: if (!is_array($name)) {
123: $name = [$name];
124: }
125:
126: $title = $name['title'] ?? $name['text'] ?? $name['name'] ?? $name[0] ?? null;
127:
128: if ($title !== null) {
129: $group->template->set('title', $title);
130: }
131:
132: if (isset($name['icon'])) {
133: Icon::addTo($group, [$name['icon']], ['Icon'])->removeClass('item');
134: }
135:
136: return $group;
137: }
138:
139: /**
140: * Add right positioned menu.
141: *
142: * @return Menu
143: */
144: public function addMenuRight()
145: {
146: return (self::class)::addTo($this, ['ui' => false], ['RightMenu'])->removeClass('item')->addClass('right menu');
147: }
148:
149: #[\Override]
150: public function add($seed, $region = null): AbstractView
151: {
152: return parent::add($seed, $region)->addClass('item');
153: }
154:
155: /**
156: * Adds divider.
157: *
158: * @return View
159: */
160: public function addDivider()
161: {
162: return parent::add([View::class, 'class' => ['divider']]);
163: }
164:
165: #[\Override]
166: public function getHtml(): string
167: {
168: // if menu don't have a single element or content, then destroy it
169: if ($this->elements === [] && !$this->content) {
170: $this->destroy();
171:
172: return '';
173: }
174:
175: return parent::getHtml();
176: }
177:
178: #[\Override]
179: protected function renderView(): void
180: {
181: if ($this->activateOnClick && $this->ui === 'menu') {
182: // Fomantic-UI need some JS magic
183: $this->on('click', 'a.item', new JsBlock([
184: $this->js()->find('.active')->removeClass('active'),
185: (new Jquery())->addClass('active'),
186: ]), ['preventDefault' => false, 'stopPropagation' => false]);
187: }
188:
189: if ($this->content) {
190: $this->addClass($this->content);
191: $this->content = null;
192: }
193:
194: parent::renderView();
195: }
196: }
197: