<?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; }); } }