Skip to content

Commit 64bc402

Browse files
author
Christian Leucht
committed
ChoiceList // refactor internal structure to support "disabled"-attribute for checkbox, radio and options and allow in future to add more settings like "help"-text.
View // complete refactor of markup to move away from <table> and be more flexible with styling elements. All elements on output will now have the "type" as modifier on the wrapper. Additionally the <label> and element itself will now also have a CSS class including the type. View/Button // introduce new "reset"- and "button"-types which will render a <button>
1 parent 2bf0792 commit 64bc402

24 files changed

+360
-97
lines changed

docs/03 - create choices.md

+48-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The `ChoiceElement` allows us to add different choices. This package ships 2 imp
66

77
To show the differences for both, here's a short example for a `<select>` which shows posts:
88

9-
**1. ArrayChoiceList**
9+
## `ArrayChoiceList`
1010
```php
1111
<?php
1212
use ChriCo\Fields\Element\ChoiceElement;
@@ -41,7 +41,7 @@ $select = createElement(
4141
);
4242
```
4343

44-
**2. CallbackChoiceList**
44+
## `CallbackChoiceList`
4545
```php
4646
<?php
4747
use ChriCo\Fields\Element\ChoiceElement;
@@ -84,3 +84,49 @@ $select = createElement(
8484
```
8585

8686
The main difference is: The `CallbackChoiceList` only loads the choices, when they are first time accessed in view. The `ArrayChoiceList` has already assigned the complete choice-values in it's constructor.
87+
88+
## Structure of choices
89+
90+
The choices have a new structure since version 2.1 which will be used internally and allows more flexibility.
91+
92+
**Old structure** (still works)
93+
94+
```php
95+
use ChriCo\Fields\Element\ChoiceElement;
96+
use ChriCo\Fields\ChoiceList\ArrayChoiceList;
97+
$data = [
98+
'M' => 'Male',
99+
'F' => 'Female',
100+
'D' => 'Diverse'
101+
];
102+
103+
// normal ArrayChoiceList
104+
$select = (new ChoiceElement('select'))
105+
->withAttributes([ 'type' => 'select' ])
106+
->withChoices(new ArrayChoiceList($data));
107+
```
108+
109+
**New structure**
110+
```php
111+
use ChriCo\Fields\Element\ChoiceElement;
112+
use ChriCo\Fields\ChoiceList\ArrayChoiceList;
113+
$data = [
114+
'M' => [
115+
'label' => 'Male',
116+
'disabled' => true,
117+
],
118+
'F' => [
119+
'label' => 'Female',
120+
]
121+
'D' => [
122+
'label' => 'Diverse'
123+
]
124+
];
125+
126+
// normal ArrayChoiceList
127+
$select = (new ChoiceElement('select'))
128+
->withAttributes([ 'type' => 'select' ])
129+
->withChoices(new ArrayChoiceList($data));
130+
```
131+
132+
As you can see we now have instead of `array<string|int, string>` mapping a more complex structure `array<string|int, array<{ label: string, disabled?:boolean }>`, which will allow us to additionally set `disabled`. The result is the same, but the "old structure" will be internally converted to "new structure".

src/AbstractFactory.php

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ abstract class AbstractFactory
5757

5858
'textarea' => View\Textarea::class,
5959
'progress' => View\Progress::class,
60+
61+
'button' => View\Button::class,
62+
'reset' => View\Button::class,
6063
];
6164

6265
/**
@@ -71,6 +74,7 @@ abstract class AbstractFactory
7174

7275
'collection' => CollectionElement::class,
7376

77+
'button' => Element::class,
7478
'col' => Element::class,
7579
'color' => Element::class,
7680
'date' => Element::class,
@@ -87,6 +91,7 @@ abstract class AbstractFactory
8791
'range' => Element::class,
8892
'search' => Element::class,
8993
'submit' => Element::class,
94+
'reset' => Element::class,
9095
'tel' => Element::class,
9196
'text' => Element::class,
9297
'textarea' => Element::class,

src/ChoiceList/ArrayChoiceList.php

+42-11
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,72 @@
66
* Class ArrayChoiceList
77
*
88
* @package ChriCo\Fields\ChoiceList
9+
*
10+
* @psalm-type SelectOption = array{
11+
* label: string,
12+
* value: string|int,
13+
* disabled: boolean,
14+
* }
915
*/
1016
class ArrayChoiceList implements ChoiceListInterface
1117
{
12-
1318
/**
14-
* @var array
19+
* @var array<string|int, SelectOption>
1520
*/
1621
protected array $choices;
1722

1823
/**
19-
* ArrayChoiceList constructor.
20-
*
2124
* @param array $choices
2225
*/
2326
public function __construct(array $choices = [])
2427
{
25-
$this->choices = $choices;
28+
$this->choices = $this->prepareChoices($choices);
2629
}
2730

28-
/**
29-
* {@inheritdoc}
30-
*/
3131
public function values(): array
3232
{
3333
return array_map('strval', array_keys($this->choices()));
3434
}
3535

36-
/**
37-
* {@inheritdoc}
38-
*/
3936
public function choices(): array
4037
{
4138
return $this->choices;
4239
}
4340

41+
/**
42+
* Internal function which migrates choices from [ $value => $name ]
43+
* into the new [ $value => 'value' => $value, 'label' => $name, 'disabled' => false ]
44+
* structure to allow more configuration and flexibility in future.
45+
*
46+
* @param array $choices
47+
*
48+
* @return array
49+
*/
50+
protected function prepareChoices(array $choices): array
51+
{
52+
$prepared = [];
53+
foreach ($choices as $value => $nameOrChoice) {
54+
if (is_string($nameOrChoice)) {
55+
$prepared[$value] = [
56+
'value' => $value,
57+
'label' => trim($nameOrChoice),
58+
'disabled' => false,
59+
];
60+
} elseif (is_array($nameOrChoice)) {
61+
$prepared[$value] = [
62+
'value' => $value,
63+
'label' => trim($nameOrChoice['label'] ?? ''),
64+
'disabled' => (bool) ($nameOrChoice['disabled'] ?? false),
65+
];
66+
}
67+
}
68+
69+
return array_filter(
70+
$prepared,
71+
static fn(array $choice) => $choice['label'] !== ''
72+
);
73+
}
74+
4475
/**
4576
* @param array $values
4677
*

src/ChoiceList/CallbackChoiceList.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
*/
1010
class CallbackChoiceList extends ArrayChoiceList
1111
{
12-
1312
/**
1413
* @var bool
1514
*/
@@ -49,9 +48,9 @@ public function choices(): array
4948
*/
5049
private function maybeLoadChoices(): bool
5150
{
52-
if (! $this->isLoaded()) {
51+
if (!$this->isLoaded()) {
5352
$callback = $this->callback;
54-
$this->choices = $callback();
53+
$this->choices = $this->prepareChoices($callback());
5554
$this->isLoaded = true;
5655

5756
return true;

src/ChoiceList/ChoiceListInterface.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function choices(): array;
2020
/**
2121
* Returns a unique list of all values for the choices.
2222
*
23-
* @return string[]
23+
* @return string[]|int[]
2424
*/
2525
public function values(): array;
2626

src/Element/CollectionElement.php

+6-9
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,13 @@ public function withErrors(array $errors = []): CollectionElement
136136
{
137137
$this->allErrors = $errors;
138138

139-
array_walk(
140-
$this->elements,
141-
static function (ElementInterface $element) use ($errors): void {
142-
$name = $element->name();
143-
if (isset($errors[$name]) && $element instanceof ErrorAwareInterface) {
144-
$element->withErrors($errors);
145-
unset($errors[$name]);
146-
}
139+
foreach ($this->elements as $element) {
140+
$name = $element->name();
141+
if (isset($errors[$name]) && $element instanceof ErrorAwareInterface) {
142+
$element->withErrors((array) $errors[$name]);
143+
unset($errors[$name]);
147144
}
148-
);
145+
}
149146

150147
// assign errors without matches to the collection itself.
151148
$this->errors = $errors;

src/View/AttributeFormatterTrait.php

+25-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace ChriCo\Fields\View;
44

5+
use ChriCo\Fields\Element\ElementInterface;
6+
57
/**
68
* Trait AttributeFormatterTrait
79
*
810
* @package ChriCo\Fields\View
911
*/
1012
trait AttributeFormatterTrait
1113
{
12-
1314
/**
1415
* @var array
1516
*/
@@ -94,4 +95,27 @@ public function escapeHtml($value): string
9495
{
9596
return esc_html((string) $value);
9697
}
98+
99+
/**
100+
* Internal function to add additional selectors for type, type+elementName, type+elementType.
101+
*
102+
* @param array $attributes
103+
* @param string $type
104+
* @param ElementInterface $element
105+
*
106+
* @return array $attributes
107+
*/
108+
public function buildCssClasses(array $attributes, string $type, ElementInterface $element): array
109+
{
110+
$classes = (array) ($attributes['class'] ?? []);
111+
112+
$classes[] = "form-{$type}";
113+
if ($element->type() !== $type) {
114+
$classes[] = "form-{$type}--{$element->type()}";
115+
}
116+
117+
$attributes['class'] = implode(' ', $classes);
118+
119+
return $attributes;
120+
}
97121
}

src/View/Button.php

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types=1); # -*- coding: utf-8 -*-
2+
3+
namespace ChriCo\Fields\View;
4+
5+
use ChriCo\Fields\Element\ElementInterface;
6+
use ChriCo\Fields\Element\LabelAwareInterface;
7+
8+
/**
9+
* Class Input
10+
*
11+
* @package ChriCo\Fields\View
12+
*/
13+
class Button implements RenderableElementInterface
14+
{
15+
use AttributeFormatterTrait;
16+
use AssertElementInstanceOfTrait;
17+
18+
/**
19+
* @param ElementInterface|LabelAwareInterface $element
20+
*
21+
* @return string
22+
*/
23+
public function render(ElementInterface $element): string
24+
{
25+
// Every button should have a label (text).
26+
$this->assertElementIsInstanceOf($element, LabelAwareInterface::class);
27+
28+
$attributes = $element->attributes();
29+
$attributes = $this->buildCssClasses($attributes, 'element', $element);
30+
31+
return sprintf(
32+
'<button %1$s>%2$s</button>',
33+
$this->attributesToString($attributes),
34+
$element->label(),
35+
);
36+
}
37+
}

src/View/Checkbox.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,20 @@ public function render(ElementInterface $element): string
4444
$isMultiChoice = true;
4545
}
4646

47-
foreach ($choices as $key => $name) {
47+
foreach ($choices as $key => $choice) {
4848
$elementAttr = $attributes;
4949
$elementAttr['value'] = $key;
5050
$elementAttr['checked'] = isset($selected[$key]);
51+
$elementAttr['disabled'] = $choice['disabled'];
5152
$elementAttr['id'] = $isMultiChoice
5253
? $element->id() . '_' . $key
5354
: $element->id();
55+
$elementAttr = $this->buildCssClasses($elementAttr, 'element', $element);
5456

5557
$label = sprintf(
5658
'<label for="%s">%s</label>',
5759
$this->escapeAttribute($elementAttr['id']),
58-
$this->escapeHtml($name)
60+
$this->escapeHtml($choice['label'])
5961
);
6062

6163
$html[] = sprintf(

0 commit comments

Comments
 (0)