1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Form;
6:
7: use Atk4\Core\Factory;
8: use Atk4\Data\Field;
9: use Atk4\Ui\Button;
10: use Atk4\Ui\Header;
11: use Atk4\Ui\HtmlTemplate;
12: use Atk4\Ui\Label;
13: use Atk4\Ui\View;
14:
15: /**
16: * Provides generic layout for a form.
17: */
18: class Layout extends AbstractLayout
19: {
20: public $defaultTemplate = 'form/layout/generic.html';
21:
22: /** @var string Default input template file. */
23: public $defaultInputTemplate = 'form/layout/generic-input.html';
24:
25: /** @var string|null If specified will appear on top of the group. Can be string or Label object. */
26: public $label;
27:
28: /**
29: * Specify width of a group in numerical word e.g. 'width' => 'two' as per
30: * Fomantic-UI grid system.
31: *
32: * @var string
33: */
34: public $width;
35:
36: /** @var bool Set true if you want fields to appear in-line. */
37: public $inline = false;
38:
39: /** @var HtmlTemplate|null Template holding input HTML. */
40: public $inputTemplate;
41:
42: /** @var array Seed for creating input hint View used in this layout. */
43: public $defaultHint = [Label::class, 'class' => ['pointing']];
44:
45: #[\Override]
46: protected function _addControl(Control $control, Field $field): Control
47: {
48: return $this->add($control, ['desired_name' => $field->shortName]);
49: }
50:
51: #[\Override]
52: protected function init(): void
53: {
54: parent::init();
55:
56: if (!$this->inputTemplate) {
57: $this->inputTemplate = $this->getApp()->loadTemplate($this->defaultInputTemplate);
58: }
59: }
60:
61: #[\Override]
62: public function addButton($seed)
63: {
64: return $this->add(Factory::mergeSeeds([Button::class], $seed), 'Buttons');
65: }
66:
67: /**
68: * @param string|array $label
69: *
70: * @return $this
71: */
72: public function addHeader($label)
73: {
74: Header::addTo($this, [$label, 'class.dividing' => true, 'element' => 'h4']);
75:
76: return $this;
77: }
78:
79: /**
80: * Adds field group in form layout.
81: *
82: * @param string|array $label
83: *
84: * @return static
85: */
86: public function addGroup($label = null)
87: {
88: if (!is_array($label)) {
89: $label = ['label' => $label];
90: } elseif (isset($label[0])) {
91: $label['label'] = $label[0];
92: unset($label[0]);
93: }
94:
95: $label['form'] = $this->form;
96:
97: return static::addTo($this, [$label]);
98: }
99:
100: /**
101: * Add a form layout section to this layout.
102: *
103: * Each section may contain other section or group.
104: *
105: * @param mixed $seed
106: * @param bool $addDivider Should we add divider after this section
107: *
108: * @return self
109: */
110: public function addSubLayout($seed = [self::class], $addDivider = true)
111: {
112: $v = $this->add(Factory::factory($seed, ['form' => $this->form]));
113: if ($v instanceof Layout\Section) {
114: $v = $v->addSection();
115: }
116:
117: if ($addDivider) {
118: View::addTo($this, ['ui' => 'hidden divider']);
119: }
120:
121: return $v;
122: }
123:
124: #[\Override]
125: protected function recursiveRender(): void
126: {
127: $labeledControl = $this->inputTemplate->cloneRegion('LabeledControl');
128: $noLabelControl = $this->inputTemplate->cloneRegion('NoLabelControl');
129: $labeledGroup = $this->inputTemplate->cloneRegion('LabeledGroup');
130: $noLabelGroup = $this->inputTemplate->cloneRegion('NoLabelGroup');
131:
132: $this->template->del('Content');
133:
134: foreach ($this->elements as $element) {
135: // buttons go under Button section
136: if ($element instanceof Button) {
137: $this->template->dangerouslyAppendHtml('Buttons', $element->getHtml());
138:
139: continue;
140: }
141:
142: if ($element instanceof self) {
143: if ($element->label && !$element->inline) {
144: $template = $labeledGroup;
145: $template->set('label', $element->label);
146: } else {
147: $template = $noLabelGroup;
148: }
149:
150: if ($element->width) {
151: $template->set('width', $element->width);
152: }
153:
154: if ($element->inline) {
155: $template->set('class', 'inline');
156: }
157: $template->dangerouslySetHtml('Content', $element->getHtml());
158:
159: $this->template->dangerouslyAppendHtml('Content', $template->renderToHtml());
160:
161: continue;
162: }
163:
164: // anything but controls or explicitly defined controls get inserted directly
165: if (!$element instanceof Control || !$element->layoutWrap) {
166: $this->template->dangerouslyAppendHtml('Content', $element->getHtml()); // @phpstan-ignore-line
167:
168: continue;
169: }
170:
171: $template = $element->renderLabel ? $labeledControl : $noLabelControl;
172: $label = $element->caption ?? $element->entityField->getField()->getCaption();
173:
174: // anything but form controls gets inserted directly
175: if ($element instanceof Control\Checkbox) {
176: $template = $noLabelControl;
177: $element->template->set('Content', $label);
178: }
179:
180: if ($this->label && $this->inline) {
181: if ($element instanceof Control\Input) {
182: $element->placeholder = $label;
183: }
184: $label = $this->label;
185: $this->label = null;
186: } elseif ($this->label || $this->inline) {
187: $template = $noLabelControl;
188: if ($element instanceof Control\Input) {
189: $element->placeholder = $label;
190: }
191: }
192:
193: // controls get extra pampering
194: $template->dangerouslySetHtml('Input', $element->getHtml());
195: $template->trySet('label', $label);
196: $template->trySet('labelFor', $element->name . '_input');
197: $template->set('controlClass', $element->controlClass);
198:
199: if ($element->entityField->getField()->required) {
200: $template->append('controlClass', 'required ');
201: }
202:
203: if ($element->width) {
204: $template->append('controlClass', $element->width . ' wide ');
205: }
206:
207: if ($element->hint && $template->hasTag('Hint')) {
208: $hint = Factory::factory($this->defaultHint);
209: $hint->name = $element->name . '_hint';
210: if (is_object($element->hint) || is_array($element->hint)) {
211: $hint->add($element->hint);
212: } else {
213: $hint->set($element->hint);
214: }
215: $hint->setApp($this->getApp());
216: $template->dangerouslySetHtml('Hint', $hint->getHtml());
217: } elseif ($template->hasTag('Hint')) {
218: $template->del('Hint');
219: }
220:
221: if ($this->template->hasTag($element->shortName)) {
222: $this->template->dangerouslySetHtml($element->shortName, $template->renderToHtml());
223: } else {
224: $this->template->dangerouslyAppendHtml('Content', $template->renderToHtml());
225: }
226: }
227:
228: // collect JS from everywhere
229: foreach ($this->elements as $view) {
230: foreach ($view->_jsActions as $when => $actions) { // @phpstan-ignore-line
231: foreach ($actions as $action) {
232: $this->_jsActions[$when][] = $action;
233: }
234: }
235: }
236: }
237: }
238: