Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormElement: Add RadioElement #68

Merged
merged 1 commit into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/FormElement/RadioElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

namespace ipl\Html\FormElement;

use InvalidArgumentException;
use ipl\Html\Attributes;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Validator\DeferredInArrayValidator;
use ipl\Validator\ValidatorChain;

class RadioElement extends BaseFormElement
{
use Translation;

/** @var string The element type */
protected $type = 'radio';
sukhwinder33445 marked this conversation as resolved.
Show resolved Hide resolved

/** @var RadioOption[] Radio options */
protected $options = [];

/** @var array Disabled radio options */
protected $disabledOptions = [];

/**
* Set the options
*
* @param array $options
*
* @return $this
*/
public function setOptions(array $options): self
{
$this->options = [];
foreach ($options as $value => $label) {
$option = (new RadioOption($value, $label))
->setDisabled(
in_array($value, $this->disabledOptions, ! is_int($value))
|| ($value === '' && in_array(null, $this->disabledOptions, true))
);

$this->options[$value] = $option;
}

$this->disabledOptions = [];

return $this;
}

/**
* Get the option with specified value
*
* @param string|int $value
*
* @return RadioOption
*
* @throws InvalidArgumentException If no option with the specified value exists
*/
public function getOption($value): RadioOption
{
if (! isset($this->options[$value])) {
throw new InvalidArgumentException(sprintf('There is no such option "%s"', $value));
}

return $this->options[$value];
}

/**
* Set the specified options as disable
*
* @param array $disabledOptions
*
* @return $this
*/
public function setDisabledOptions(array $disabledOptions): self
{
if (! empty($this->options)) {
foreach ($this->options as $value => $option) {
$option->setDisabled(
in_array($value, $disabledOptions, ! is_int($value))
|| ($value === '' && in_array(null, $disabledOptions, true))
);
}

$this->disabledOptions = [];
} else {
$this->disabledOptions = $disabledOptions;
}

return $this;
}

public function renderUnwrapped()
{
// Parent::renderUnwrapped() requires $tag and the content should be empty. However, since we are wrapping
// each button in a label, the call to parent cannot work here and must be overridden.
return HtmlDocument::renderUnwrapped();
}

protected function assemble()
{
foreach ($this->options as $option) {
$radio = (new InputElement($this->getName()))
->setType($this->type)
->setValue($option->getValue());

// Only add the non-callback attributes to all options
foreach ($this->getAttributes() as $attribute) {
$radio->getAttributes()->addAttribute(clone $attribute);
}

$radio->getAttributes()
->merge($option->getAttributes())
->registerAttributeCallback(
'checked',
function () use ($option) {
$optionValue = $option->getValue();

return ! is_int($optionValue)
? $this->getValue() === $optionValue
: $this->getValue() == $optionValue;
}
)
->registerAttributeCallback(
'disabled',
function () use ($option) {
return $this->getAttributes()->get('disabled')->getValue() || $option->isDisabled();
}
);

$label = new HtmlElement(
'label',
new Attributes(['class' => $option->getLabelCssClass()]),
$radio,
Text::create($option->getLabel())
);

$this->addHtml($label);
}
}

protected function addDefaultValidators(ValidatorChain $chain): void
{
$chain->add(new DeferredInArrayValidator(function (): array {
$possibleValues = [];

foreach ($this->options as $option) {
if ($option->isDisabled()) {
continue;
}

$possibleValues[] = $option->getValue();
}

return $possibleValues;
}));
}

protected function registerAttributeCallbacks(Attributes $attributes)
{
parent::registerAttributeCallbacks($attributes);

$this->getAttributes()->registerAttributeCallback(
'options',
null,
[$this, 'setOptions']
);

$this->getAttributes()->registerAttributeCallback(
'disabledOptions',
null,
[$this, 'setDisabledOptions']
);
}
}
148 changes: 148 additions & 0 deletions src/FormElement/RadioOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace ipl\Html\FormElement;

use ipl\Html\Attributes;

class RadioOption
nilmerg marked this conversation as resolved.
Show resolved Hide resolved
{
/** @var string The default label class */
public const LABEL_CLASS = 'radio-label';

/** @var string|int|null Value of the option */
protected $value;

/** @var string Label of the option */
protected $label;

/** @var mixed Css class of the option's label */
protected $labelCssClass = self::LABEL_CLASS;
sukhwinder33445 marked this conversation as resolved.
Show resolved Hide resolved

/** @var bool Whether the radio option is disabled */
protected $disabled = false;

/** @var Attributes */
protected $attributes;

/**
* RadioOption constructor.
*
* @param string|int|null $value
* @param string $label
*/
public function __construct($value, string $label)
{
$this->value = $value === '' ? null : $value;
$this->label = $label;
}

/**
* Set the label of the option
*
* @param string $label
*
* @return $this
*/
public function setLabel(string $label): self
{
$this->label = $label;

return $this;
}

/**
* Get the label of the option
*
* @return string
*/
public function getLabel(): string
{
return $this->label;
}

/**
* Get the value of the option
*
* @return string|int|null
*/
public function getValue()
{
return $this->value;
}

/**
* Set css class to the option label
*
* @param string|string[] $labelCssClass
*
* @return $this
*/
public function setLabelCssClass($labelCssClass): self
{
$this->labelCssClass = $labelCssClass;

return $this;
}

/**
* Get css class of the option label
*
* @return string|string[]
*/
public function getLabelCssClass()
{
return $this->labelCssClass;
}

/**
* Set whether to disable the option
*
* @param bool $disabled
*
* @return $this
*/
public function setDisabled(bool $disabled = true): self
{
$this->disabled = $disabled;

return $this;
}

/**
* Get whether the option is disabled
*
* @return bool
*/
public function isDisabled(): bool
{
return $this->disabled;
}

/**
* Add the attributes
*
* @param Attributes $attributes
*
* @return $this
*/
public function addAttributes(Attributes $attributes): self
{
$this->attributes = $attributes;

return $this;
}

/**
* Get the attributes
*
* @return Attributes
*/
public function getAttributes(): Attributes
{
if ($this->attributes === null) {
$this->attributes = new Attributes();
}

return $this->attributes;
}
}
Loading