Skip to content
Snippets Groups Projects
Commit 238a072b authored by Michael Grosse's avatar Michael Grosse
Browse files

Add support for optgroup-tags for select-fields

In more complex selects we may want to group options by some criteria.
HTML has the <optgroup>-tag for that purpose.

However in Order to not duplicate code, I've to move the handling of
options to the OptGroup-Class completely.

Known Issues:
-------------
* We may have more than one option with the same value and therefore
more than one option may be marked as selected.
parent 693978b1
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,11 @@ namespace dokuwiki\Form;
*/
class DropdownElement extends InputElement {
protected $options = array();
/** @var OptGroup */
protected $options = null;
/** @var array OptGroup[] */
protected $optGroups = array();
protected $value = '';
......@@ -22,7 +26,45 @@ class DropdownElement extends InputElement {
public function __construct($name, $options, $label = '') {
parent::__construct('dropdown', $name, $label);
$this->rmattr('type');
$this->options($options);
$this->options = new OptGroup(null, $options);
$this->val('');
}
/**
* @param $label
* @param $options
* @return mixed
* @throws \Exception
*/
public function addOptGroup($label, $options) {
if (empty($label)) {
throw new \InvalidArgumentException(hsc('<optgroup> must have a label!'));
}
$this->optGroups[] = new OptGroup($label, $options);
return end($this->optGroups);
}
/**
* Set or get the optgroups of an Dropdown-Element.
*
* optgroups have to be given as associative array
* * the key being the label of the group
* * the value being an array of options as defined in @see OptGroup::options()
*
* @param null|array $optGroups
* @return array
*/
public function optGroups($optGroups = null) {
if($optGroups === null) {
return $this->optGroups;
}
if (!is_array($optGroups)) {
throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!'));
}
$this->optGroups = array();
foreach ($optGroups as $label => $options) {
$this->addOptGroup($label, $options);
}
}
/**
......@@ -42,21 +84,10 @@ class DropdownElement extends InputElement {
* @return $this|array
*/
public function options($options = null) {
if($options === null) return $this->options;
if(!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
$this->options = array();
foreach($options as $key => $val) {
if(is_int($key)) {
$this->options[$val] = array('label' => (string) $val);
} elseif (!is_array($val)) {
$this->options[$key] = array('label' => (string) $val);
} else {
if (!key_exists('label', $val)) throw new \InvalidArgumentException('If option is given as array, it has to have a "label"-key!');
$this->options[$key] = $val;
}
if ($options === null) {
return $this->options->options();
}
$this->val(''); // set default value (empty or first)
$this->options = new OptGroup(null, $options);
return $this;
}
......@@ -92,17 +123,51 @@ class DropdownElement extends InputElement {
public function val($value = null) {
if($value === null) return $this->value;
if(isset($this->options[$value])) {
$value_exists = $this->setValueInOptGroups($value);
if($value_exists) {
$this->value = $value;
} else {
// unknown value set, select first option instead
$keys = array_keys($this->options);
$this->value = (string) array_shift($keys);
$this->value = $this->getFirstOption();
$this->setValueInOptGroups($this->value);
}
return $this;
}
/**
* Returns the first options as it will be rendered in HTML
*
* @return string
*/
protected function getFirstOption() {
$options = $this->options();
if (!empty($options)) {
return (string) array_shift(array_keys($options));
}
foreach ($this->optGroups as $optGroup) {
$options = $optGroup->options();
if (!empty($options)) {
return (string) array_shift(array_keys($options));
}
}
}
/**
* Set the value in the OptGroups, including the optgroup for the options without optgroup.
*
* @param string $value
* @return bool
*/
protected function setValueInOptGroups($value) {
$value_exists = $this->options->setValue($value);
foreach ($this->optGroups as $optGroup) {
$value_exists = $optGroup->setValue($value) || $value_exists;
}
return $value_exists;
}
/**
* Create the HTML for the select it self
*
......@@ -112,15 +177,8 @@ class DropdownElement extends InputElement {
if($this->useInput) $this->prefillInput();
$html = '<select ' . buildAttributes($this->attrs()) . '>';
foreach($this->options as $key => $val) {
$selected = ($key == $this->value) ? ' selected="selected"' : '';
$attrs = '';
if (is_array($val['attrs'])) {
array_walk($val['attrs'],function (&$aval, $akey){$aval = hsc($akey).'="'.hsc($aval).'"';});
$attrs = join(' ', $val['attrs']);
}
$html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>' . hsc($val['label']) . '</option>';
}
$html .= $this->options->toHTML();
$html = array_reduce($this->optGroups, function($html, OptGroup $optGroup) {return $html . $optGroup->toHTML();}, $html);
$html .= '</select>';
return $html;
......
<?php
namespace dokuwiki\Form;
class OptGroup extends Element {
protected $options = array();
protected $value;
/**
* @param string $name The name of this form element
* @param string $options The available options
* @param string $label The label text for this element (will be autoescaped)
*/
public function __construct($name, $options) {
parent::__construct('optGroup', array('label' => $name));
$this->options($options);
}
/**
* Set the element's value
*
* This is intended to be only called from within @see DropdownElement::val()
*
* @param string $value
* @return bool
*/
public function setValue($value) {
$this->value = $value;
return isset($this->options[$value]);
}
/**
* Get or set the options of the optgroup
*
* Options can be given as associative array (value => label) or as an
* indexd array (label = value) or as an array of arrays. In the latter
* case an element has to look as follows:
* option-value => array (
* 'label' => option-label,
* 'attrs' => array (
* attr-key => attr-value, ...
* )
* )
*
* @param null|array $options
* @return $this|array
*/
public function options($options = null) {
if($options === null) return $this->options;
if(!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
$this->options = array();
foreach($options as $key => $val) {
if(is_int($key)) {
$this->options[$val] = array('label' => (string) $val);
} elseif (!is_array($val)) {
$this->options[$key] = array('label' => (string) $val);
} else {
if (!key_exists('label', $val)) throw new \InvalidArgumentException('If option is given as array, it has to have a "label"-key!');
$this->options[$key] = $val;
}
}
return $this;
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
if ($this->attributes['label'] === null) {
return $this->renderOptions();
}
$html = '<optgroup '. buildAttributes($this->attrs()) . '>';
$html .= $this->renderOptions();
$html .= '</optgroup>';
return $html;
}
/**
* @return string
*/
protected function renderOptions() {
$html = '';
foreach($this->options as $key => $val) {
$selected = ($key == $this->value) ? ' selected="selected"' : '';
$attrs = '';
if (is_array($val['attrs'])) {
array_walk($val['attrs'],function (&$aval, $akey){$aval = hsc($akey).'="'.hsc($aval).'"';});
$attrs = join(' ', $val['attrs']);
}
$html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>' . hsc($val['label']) . '</option>';
}
return $html;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment