Skip to content

Commit 01ad308

Browse files
FormElements: Let the form modify default decorators and add custom decorators
FormElements: - Add property `$elementDecoratorLoaderPaths` to add custom decorators - Add methods `addElementDecoratorLoaderPaths()` and `modifyDefaultDecorators()` - Introduce interface `DefaultFormElementDecoration` These methods can be overridden by the form to add decorator loaders and modify the default decotators.
1 parent b190a1f commit 01ad308

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace ipl\Html\Contract;
4+
5+
interface DefaultFormElementDecoration
6+
{
7+
/**
8+
* Get the default element decorators
9+
*
10+
* @return ?array
11+
*/
12+
public function getDefaultElementDecorators(): ?array;
13+
14+
/**
15+
* Set the default element decorators
16+
*
17+
* Call this method before creating any element OR override the {@see self::$defaultElementDecorators} property,
18+
* to ensure that the default decorators are applied
19+
*
20+
* @param ?array $defaultElementDecorators
21+
*
22+
* @return $this
23+
*/
24+
public function setDefaultElementDecorators(?array $defaultElementDecorators): self;
25+
}

src/Form.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace ipl\Html;
44

5+
use ipl\Html\Contract\DefaultFormElementDecoration;
56
use ipl\Html\Contract\FormElement;
67
use ipl\Html\Contract\FormSubmitElement;
78
use ipl\Html\FormElement\FormElements;
89
use ipl\Stdlib\Messages;
910
use Psr\Http\Message\ServerRequestInterface;
1011
use Throwable;
1112

12-
class Form extends BaseHtmlElement
13+
class Form extends BaseHtmlElement implements DefaultFormElementDecoration
1314
{
1415
use FormElements {
1516
FormElements::remove as private removeElement;
@@ -46,6 +47,18 @@ class Form extends BaseHtmlElement
4647

4748
protected $tag = 'form';
4849

50+
public function __construct()
51+
{
52+
if ($this->getDefaultElementDecorators() === null && ! $this->hasDefaultElementDecorator()) {
53+
$this->setDefaultElementDecorators([
54+
'Fieldset',
55+
'Label',
56+
'Description',
57+
'HtmlTag' => ['tag' => 'div'],
58+
]);
59+
}
60+
}
61+
4962
/**
5063
* Get whether the given value is empty
5164
*

src/FormElement/FieldsetElement.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
use InvalidArgumentException;
66
use ipl\Html\Common\MultipleAttribute;
7+
use ipl\Html\Contract\DefaultFormElementDecoration;
78
use ipl\Html\Contract\FormElement;
89
use ipl\Html\Contract\FormElementDecorator;
910
use ipl\Html\Contract\Wrappable;
1011
use LogicException;
1112

1213
use function ipl\Stdlib\get_php_type;
1314

14-
class FieldsetElement extends BaseFormElement
15+
class FieldsetElement extends BaseFormElement implements DefaultFormElementDecoration
1516
{
1617
use FormElements {
1718
FormElements::getValue as private getElementValue;

src/FormElement/FormElements.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace ipl\Html\FormElement;
44

55
use InvalidArgumentException;
6+
use ipl\Html\Contract\DefaultFormElementDecoration;
67
use ipl\Html\Contract\FormElement;
78
use ipl\Html\Contract\FormElementDecorator;
89
use ipl\Html\Contract\ValueCandidates;
@@ -29,12 +30,53 @@ trait FormElements
2930
/** @var bool Whether the default element loader has been registered */
3031
protected $defaultElementLoaderRegistered = false;
3132

33+
/**
34+
* Custom Element decorator loader paths
35+
*
36+
* @var array<int, array<string|int, string> Override this property to add custom decorator loader paths
37+
*
38+
* Each entry must be an array, where the key is the decorator name postfix (if any) and the value is the path
39+
*/
40+
protected array $elementDecoratorLoaderPaths = [];
41+
42+
/**
43+
* Default element decorators
44+
*
45+
* Override this property to change the default decorators of the elements.
46+
* The default decorators will be applied to all elements that do not have their own decorators.
47+
*
48+
* The order of the decorators is important, as it determines the rendering order.
49+
*
50+
* @var ?array<string|int, string|array<string, string>>
51+
*/
52+
protected ?array $defaultElementDecorators = [];
53+
3254
/** @var FormElement[] */
3355
private $elements = [];
3456

3557
/** @var array<string, array<int, mixed>> */
3658
private $populatedValues = [];
3759

60+
public function getDefaultElementDecorators(): ?array
61+
{
62+
if (empty($this->defaultElementDecorators)) {
63+
return null;
64+
}
65+
66+
return $this->defaultElementDecorators;
67+
}
68+
69+
public function setDefaultElementDecorators(?array $defaultElementDecorators): self
70+
{
71+
if (empty($defaultElementDecorators)) {
72+
$defaultElementDecorators = null;
73+
}
74+
75+
$this->defaultElementDecorators = $defaultElementDecorators;
76+
77+
return $this;
78+
}
79+
3880
/**
3981
* Get all elements
4082
*
@@ -130,6 +172,23 @@ public function addElement($typeOrElement, $name = null, $options = null)
130172
return $this;
131173
}
132174

175+
/**
176+
* Add custom element decorator loader paths for the elements
177+
*
178+
* Call this method before creating any element OR override the {@see self::$elementDecoratorLoaderPaths} property,
179+
* to ensure that your custom decorator loader paths are applied
180+
*
181+
* @param array $loaderPaths
182+
*
183+
* @return $this
184+
*/
185+
public function addElementDecoratorLoaderPaths(array $loaderPaths): self
186+
{
187+
$this->elementDecoratorLoaderPaths = $loaderPaths;
188+
189+
return $this;
190+
}
191+
133192
/**
134193
* Create an element
135194
*
@@ -157,6 +216,33 @@ public function createElement($type, $name, $options = null)
157216
/** @var FormElement $element */
158217
$element = new $class($name);
159218

219+
$customDecoratorPaths = $this->elementDecoratorLoaderPaths;
220+
if (! empty($customDecoratorPaths)) {
221+
if ($element instanceof DefaultFormElementDecoration) {
222+
$element->addElementDecoratorLoaderPaths($customDecoratorPaths);
223+
}
224+
225+
$chain = $element->getDecorators();
226+
foreach ($customDecoratorPaths as $paths) {
227+
foreach ($paths as $postfix => $path) {
228+
if (is_int($postfix)) {
229+
$postfix = '';
230+
}
231+
232+
$chain->addDecoratorLoader($path, $postfix);
233+
}
234+
}
235+
}
236+
237+
$defaultDecorators = $this->getDefaultElementDecorators();
238+
if ($defaultDecorators !== null && ! $this->hasDefaultElementDecorator()) {
239+
if ($element instanceof DefaultFormElementDecoration) {
240+
$element->setDefaultElementDecorators($defaultDecorators);
241+
}
242+
243+
$element->setDecorators($defaultDecorators);
244+
}
245+
160246
if ($options !== null) {
161247
$element->addAttributes($options);
162248
}
@@ -454,6 +540,11 @@ protected function ensureDefaultElementLoaderRegistered()
454540
*/
455541
protected function decorate(FormElement $element)
456542
{
543+
if ($element->hasDecorators()) {
544+
// new decorator implementation in use
545+
return $this;
546+
}
547+
457548
if ($this->hasDefaultElementDecorator()) {
458549
$decorator = $this->getDefaultElementDecorator();
459550

0 commit comments

Comments
 (0)