Skip to content

Commit

Permalink
ChoiceList // refactor internal structure to support "disabled"-attri…
Browse files Browse the repository at this point in the history
…bute 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>
  • Loading branch information
Chrico committed Jun 27, 2023
1 parent 2bf0792 commit 64bc402
Show file tree
Hide file tree
Showing 24 changed files with 360 additions and 97 deletions.
50 changes: 48 additions & 2 deletions docs/03 - create choices.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The `ChoiceElement` allows us to add different choices. This package ships 2 imp

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

**1. ArrayChoiceList**
## `ArrayChoiceList`
```php
<?php
use ChriCo\Fields\Element\ChoiceElement;
Expand Down Expand Up @@ -41,7 +41,7 @@ $select = createElement(
);
```

**2. CallbackChoiceList**
## `CallbackChoiceList`
```php
<?php
use ChriCo\Fields\Element\ChoiceElement;
Expand Down Expand Up @@ -84,3 +84,49 @@ $select = createElement(
```

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.

## Structure of choices

The choices have a new structure since version 2.1 which will be used internally and allows more flexibility.

**Old structure** (still works)

```php
use ChriCo\Fields\Element\ChoiceElement;
use ChriCo\Fields\ChoiceList\ArrayChoiceList;
$data = [
'M' => 'Male',
'F' => 'Female',
'D' => 'Diverse'
];

// normal ArrayChoiceList
$select = (new ChoiceElement('select'))
->withAttributes([ 'type' => 'select' ])
->withChoices(new ArrayChoiceList($data));
```

**New structure**
```php
use ChriCo\Fields\Element\ChoiceElement;
use ChriCo\Fields\ChoiceList\ArrayChoiceList;
$data = [
'M' => [
'label' => 'Male',
'disabled' => true,
],
'F' => [
'label' => 'Female',
]
'D' => [
'label' => 'Diverse'
]
];

// normal ArrayChoiceList
$select = (new ChoiceElement('select'))
->withAttributes([ 'type' => 'select' ])
->withChoices(new ArrayChoiceList($data));
```

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".
5 changes: 5 additions & 0 deletions src/AbstractFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ abstract class AbstractFactory

'textarea' => View\Textarea::class,
'progress' => View\Progress::class,

'button' => View\Button::class,
'reset' => View\Button::class,
];

/**
Expand All @@ -71,6 +74,7 @@ abstract class AbstractFactory

'collection' => CollectionElement::class,

'button' => Element::class,
'col' => Element::class,
'color' => Element::class,
'date' => Element::class,
Expand All @@ -87,6 +91,7 @@ abstract class AbstractFactory
'range' => Element::class,
'search' => Element::class,
'submit' => Element::class,
'reset' => Element::class,
'tel' => Element::class,
'text' => Element::class,
'textarea' => Element::class,
Expand Down
53 changes: 42 additions & 11 deletions src/ChoiceList/ArrayChoiceList.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,72 @@
* Class ArrayChoiceList
*
* @package ChriCo\Fields\ChoiceList
*
* @psalm-type SelectOption = array{
* label: string,
* value: string|int,
* disabled: boolean,
* }
*/
class ArrayChoiceList implements ChoiceListInterface
{

/**
* @var array
* @var array<string|int, SelectOption>
*/
protected array $choices;

/**
* ArrayChoiceList constructor.
*
* @param array $choices
*/
public function __construct(array $choices = [])
{
$this->choices = $choices;
$this->choices = $this->prepareChoices($choices);
}

/**
* {@inheritdoc}
*/
public function values(): array
{
return array_map('strval', array_keys($this->choices()));
}

/**
* {@inheritdoc}
*/
public function choices(): array
{
return $this->choices;
}

/**
* Internal function which migrates choices from [ $value => $name ]
* into the new [ $value => 'value' => $value, 'label' => $name, 'disabled' => false ]
* structure to allow more configuration and flexibility in future.
*
* @param array $choices
*
* @return array
*/
protected function prepareChoices(array $choices): array
{
$prepared = [];
foreach ($choices as $value => $nameOrChoice) {
if (is_string($nameOrChoice)) {
$prepared[$value] = [
'value' => $value,
'label' => trim($nameOrChoice),
'disabled' => false,
];
} elseif (is_array($nameOrChoice)) {
$prepared[$value] = [
'value' => $value,
'label' => trim($nameOrChoice['label'] ?? ''),
'disabled' => (bool) ($nameOrChoice['disabled'] ?? false),
];
}
}

return array_filter(
$prepared,
static fn(array $choice) => $choice['label'] !== ''
);
}

/**
* @param array $values
*
Expand Down
5 changes: 2 additions & 3 deletions src/ChoiceList/CallbackChoiceList.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/
class CallbackChoiceList extends ArrayChoiceList
{

/**
* @var bool
*/
Expand Down Expand Up @@ -49,9 +48,9 @@ public function choices(): array
*/
private function maybeLoadChoices(): bool
{
if (! $this->isLoaded()) {
if (!$this->isLoaded()) {
$callback = $this->callback;
$this->choices = $callback();
$this->choices = $this->prepareChoices($callback());
$this->isLoaded = true;

return true;
Expand Down
2 changes: 1 addition & 1 deletion src/ChoiceList/ChoiceListInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function choices(): array;
/**
* Returns a unique list of all values for the choices.
*
* @return string[]
* @return string[]|int[]
*/
public function values(): array;

Expand Down
15 changes: 6 additions & 9 deletions src/Element/CollectionElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,13 @@ public function withErrors(array $errors = []): CollectionElement
{
$this->allErrors = $errors;

array_walk(
$this->elements,
static function (ElementInterface $element) use ($errors): void {
$name = $element->name();
if (isset($errors[$name]) && $element instanceof ErrorAwareInterface) {
$element->withErrors($errors);
unset($errors[$name]);
}
foreach ($this->elements as $element) {
$name = $element->name();
if (isset($errors[$name]) && $element instanceof ErrorAwareInterface) {
$element->withErrors((array) $errors[$name]);
unset($errors[$name]);
}
);
}

// assign errors without matches to the collection itself.
$this->errors = $errors;
Expand Down
26 changes: 25 additions & 1 deletion src/View/AttributeFormatterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace ChriCo\Fields\View;

use ChriCo\Fields\Element\ElementInterface;

/**
* Trait AttributeFormatterTrait
*
* @package ChriCo\Fields\View
*/
trait AttributeFormatterTrait
{

/**
* @var array
*/
Expand Down Expand Up @@ -94,4 +95,27 @@ public function escapeHtml($value): string
{
return esc_html((string) $value);
}

/**
* Internal function to add additional selectors for type, type+elementName, type+elementType.
*
* @param array $attributes
* @param string $type
* @param ElementInterface $element
*
* @return array $attributes
*/
public function buildCssClasses(array $attributes, string $type, ElementInterface $element): array
{
$classes = (array) ($attributes['class'] ?? []);

$classes[] = "form-{$type}";
if ($element->type() !== $type) {
$classes[] = "form-{$type}--{$element->type()}";
}

$attributes['class'] = implode(' ', $classes);

return $attributes;
}
}
37 changes: 37 additions & 0 deletions src/View/Button.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1); # -*- coding: utf-8 -*-

namespace ChriCo\Fields\View;

use ChriCo\Fields\Element\ElementInterface;
use ChriCo\Fields\Element\LabelAwareInterface;

/**
* Class Input
*
* @package ChriCo\Fields\View
*/
class Button implements RenderableElementInterface
{
use AttributeFormatterTrait;
use AssertElementInstanceOfTrait;

/**
* @param ElementInterface|LabelAwareInterface $element
*
* @return string
*/
public function render(ElementInterface $element): string
{
// Every button should have a label (text).
$this->assertElementIsInstanceOf($element, LabelAwareInterface::class);

$attributes = $element->attributes();
$attributes = $this->buildCssClasses($attributes, 'element', $element);

return sprintf(
'<button %1$s>%2$s</button>',
$this->attributesToString($attributes),
$element->label(),
);
}
}
6 changes: 4 additions & 2 deletions src/View/Checkbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,20 @@ public function render(ElementInterface $element): string
$isMultiChoice = true;
}

foreach ($choices as $key => $name) {
foreach ($choices as $key => $choice) {
$elementAttr = $attributes;
$elementAttr['value'] = $key;
$elementAttr['checked'] = isset($selected[$key]);
$elementAttr['disabled'] = $choice['disabled'];
$elementAttr['id'] = $isMultiChoice
? $element->id() . '_' . $key
: $element->id();
$elementAttr = $this->buildCssClasses($elementAttr, 'element', $element);

$label = sprintf(
'<label for="%s">%s</label>',
$this->escapeAttribute($elementAttr['id']),
$this->escapeHtml($name)
$this->escapeHtml($choice['label'])
);

$html[] = sprintf(
Expand Down
Loading

0 comments on commit 64bc402

Please sign in to comment.