1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Form;
6:
7: use Atk4\Core\WarnDynamicPropertyTrait;
8: use Atk4\Data\Field;
9: use Atk4\Data\Model;
10: use Atk4\Ui\Button;
11: use Atk4\Ui\Exception;
12: use Atk4\Ui\Form;
13: use Atk4\Ui\View;
14:
15: /**
16: * Custom Layout for a form.
17: */
18: abstract class AbstractLayout extends View
19: {
20: use WarnDynamicPropertyTrait;
21:
22: /** Links layout to owner Form. */
23: public Form $form;
24:
25: protected function _addControl(Control $control, Field $field): Control
26: {
27: return $this->add($control, $this->template->hasTag($field->shortName) ? $field->shortName : null);
28: }
29:
30: /**
31: * Places element inside a layout somewhere. Should be called
32: * through $form->addControl().
33: *
34: * @param array<mixed>|Control $control
35: * @param array<mixed> $fieldSeed
36: */
37: public function addControl(string $name, $control = [], array $fieldSeed = []): Control
38: {
39: if ($this->form->model === null) {
40: $this->form->model = (new \Atk4\Ui\Misc\ProxyModel())->createEntity();
41: }
42: $model = $this->form->model->getModel();
43:
44: // TODO this class should not refer to any specific form control
45: $controlClass = is_object($control)
46: ? get_class($control)
47: : ($control[0] ?? (($fieldSeed['ui'] ?? [])['form'][0] ?? null));
48: if (is_a($controlClass, Control\Checkbox::class, true)) {
49: $fieldSeed['type'] = 'boolean';
50: } elseif (is_a($controlClass, Control\Calendar::class, true)) {
51: $calendarType = $control instanceof Control\Calendar ? $control->type : ($control['type'] ?? null);
52: if ($calendarType !== null) {
53: $fieldSeed['type'] = $calendarType;
54: }
55: }
56:
57: try {
58: if ($model->hasField($name)) {
59: $field = $model->getField($name)->setDefaults($fieldSeed); // TODO assert same defaults only
60: } else {
61: $field = $model->addField($name, $fieldSeed);
62: }
63:
64: $control = $this->form->controlFactory($field, $control);
65: } catch (\Exception $e) {
66: if ($e instanceof \ErrorException) {
67: throw $e;
68: }
69:
70: throw (new Exception('Unable to create form control', 0, $e))
71: ->addMoreInfo('name', $name)
72: ->addMoreInfo('control' . (!is_object($control) ? 'Seed' : ''), $control)
73: ->addMoreInfo('fieldSeed', $fieldSeed);
74: }
75:
76: return $this->_addControl($control, $field);
77: }
78:
79: /**
80: * Returns array of names of fields to automatically include them in form.
81: * This includes all editable or visible fields of the model.
82: *
83: * @return array
84: */
85: protected function getModelFields(Model $model)
86: {
87: return array_keys($model->getFields('editable'));
88: }
89:
90: /**
91: * Sets form model and adds form controls.
92: *
93: * @param array<int, string>|null $fields
94: */
95: #[\Override]
96: public function setModel(Model $entity, array $fields = null): void
97: {
98: $entity->assertIsEntity();
99:
100: parent::setModel($entity);
101:
102: if ($fields === null) {
103: $fields = $this->getModelFields($entity);
104: }
105:
106: // add controls - check if fields are editable or read-only/disabled
107: foreach ($fields as $fieldName) {
108: $field = $entity->getField($fieldName);
109:
110: $controlSeed = null;
111: if ($field->isEditable()) {
112: $controlSeed = [];
113: } elseif ($field->isVisible()) {
114: $controlSeed = ['readOnly' => true];
115: }
116:
117: if ($controlSeed !== null) {
118: $this->addControl($field->shortName, $controlSeed);
119: }
120: }
121: }
122:
123: /**
124: * Return Field decorator associated with the form's field.
125: */
126: public function getControl(string $name): Control
127: {
128: return $this->form->getControl($name);
129: }
130:
131: /**
132: * Adds Button into form layout.
133: *
134: * @param Button|array $seed
135: *
136: * @return Button
137: */
138: abstract public function addButton($seed);
139: }
140: