<?php
declare(strict_types=1);
namespace Arcanedev\Html\Elements;
use Arcanedev\Html\Contracts\Selectable;
use Arcanedev\Html\Elements\Concerns\HasAutofocusAttribute;
use Arcanedev\Html\Elements\Concerns\HasDisabledAttribute;
use Arcanedev\Html\Elements\Concerns\HasNameAttribute;
use Arcanedev\Html\Elements\Concerns\HasReadonlyAttribute;
use Arcanedev\Html\Elements\Concerns\HasRequiredAttribute;
use Illuminate\Support\{Collection, Str};
/**
* Class Select
*
* @author ARCANEDEV <arcanedev.maroc@gmail.com>
*/
class Select extends HtmlElement
{
/* -----------------------------------------------------------------
| Traits
| -----------------------------------------------------------------
*/
use HasAutofocusAttribute,
HasDisabledAttribute,
HasNameAttribute,
HasRequiredAttribute,
HasReadonlyAttribute;
/* -----------------------------------------------------------------
| Properties
| -----------------------------------------------------------------
*/
/** @var string */
protected $tag = 'select';
/** @var array */
protected $options = [];
/** @var string|iterable */
protected $value = '';
/* -----------------------------------------------------------------
| Main Methods
| -----------------------------------------------------------------
*/
/**
* Set the select input as multiple.
*
* @return $this
*/
public function multiple()
{
/** @var self $elt */
$elt = with(clone $this)->attribute('multiple');
$name = $elt->getAttribute('name');
return $elt->if($name && ! Str::endsWith($name->value(), '[]'), function (self $elt) use ($name) {
return $elt->name($name->value().'[]');
})->applyValueToOptions();
}
/**
* Add options.
*
* @param iterable $options
* @param array $attributes
* @param array $groupAttributes
*
* @return $this
*/
public function options($options, array $attributes = [], array $groupAttributes = [])
{
return $this->children($options, function ($text, $value) use ($attributes, $groupAttributes) {
return is_array($text)
? $this->makeOptionsGroup($value, $text, $attributes, $groupAttributes[$value] ?? [])
: $this->makeOption($value, $text, $attributes[$value] ?? []);
});
}
/**
* Add a placeholder option.
*
* @param string $text
* @param mixed|null $value
* @param bool $disabled
*
* @return $this
*/
public function placeholder($text, $value = null, $disabled = false)
{
return $this->prependChild(
$this->makeOption($value, $text)
->selectedUnless($this->hasSelection())
->disabled($disabled)
);
}
/**
* Set the value.
*
* @param string|iterable|null $value
*
* @return $this
*/
public function value($value = null)
{
return tap(clone $this, function ($element) use ($value) {
$element->value = $value;
})->applyValueToOptions();
}
/* -----------------------------------------------------------------
| Other Methods
| -----------------------------------------------------------------
*/
/**
* Check if has a selected option.
*
* @return bool
*/
protected function hasSelection()
{
return $this->getChildren()->contains(function (HtmlElement $child) {
return $child->hasAttribute('selected');
});
}
/**
* Make an option tag.
*
* @param string $value
* @param string $text
* @param array $attributes
*
* @return \Arcanedev\Html\Elements\Option
*/
protected function makeOption($value, $text = null, array $attributes = [])
{
return Option::make()
->value($value)
->text($text ?: $value, false)
->selectedIf($value === $this->value)
->attributes($attributes);
}
/**
* Make an options group.
*
* @param string $label
* @param array $options
* @param array $attributes
* @param array $groupAttributes
*
* @return \Arcanedev\Html\Elements\Optgroup
*/
protected function makeOptionsGroup($label, array $options, array $attributes = [], array $groupAttributes = [])
{
return Optgroup::make()
->label($label)
->attributes($groupAttributes)
->children($options, function ($optionText, $optionValue) use ($attributes) {
return $this->makeOption($optionValue, $optionText, $attributes[$optionValue] ?? []);
});
}
/**
* Apply the selected value to the options.
*
* @return static
*/
protected function applyValueToOptions()
{
$value = Collection::make($this->value);
if ( ! $this->hasAttribute('multiple'))
$value = $value->take(1);
return $this->setNewChildren(
static::applyValueToElements($value, $this->getChildren())
);
}
/**
* Apply the selected value to the options.
*
* @param \Illuminate\Support\Collection $value
* @param \Illuminate\Support\Collection $children
*
* @return \Illuminate\Support\Collection
*/
protected static function applyValueToElements(Collection $value, Collection $children)
{
return $children->map(function (HtmlElement $child) use ($value) {
if ($child instanceof Optgroup)
return $child->setNewChildren(static::applyValueToElements($value, $child->getChildren()));
if ($child instanceof Selectable)
return $child->selectedIf(
$value->contains($child->getAttribute('value')->value())
);
return $child;
});
}
}