1<?php 2namespace dokuwiki\Form; 3 4/** 5 * Class DropdownElement 6 * 7 * Represents a HTML select. Please note that this does not support multiple selected options! 8 * 9 * @package dokuwiki\Form 10 */ 11class DropdownElement extends InputElement { 12 13 /** @var OptGroup */ 14 protected $options = null; 15 16 /** @var array OptGroup[] */ 17 protected $optGroups = array(); 18 19 protected $value = ''; 20 21 /** 22 * @param string $name The name of this form element 23 * @param string $options The available options 24 * @param string $label The label text for this element (will be autoescaped) 25 */ 26 public function __construct($name, $options, $label = '') { 27 parent::__construct('dropdown', $name, $label); 28 $this->rmattr('type'); 29 $this->options = new OptGroup(null, $options); 30 $this->val(''); 31 } 32 33 /** 34 * @param $label 35 * @param $options 36 * @return OptGroup 37 * @throws \Exception 38 */ 39 public function addOptGroup($label, $options) { 40 if (empty($label)) { 41 throw new \InvalidArgumentException(hsc('<optgroup> must have a label!')); 42 } 43 $this->optGroups[] = new OptGroup($label, $options); 44 return end($this->optGroups); 45 } 46 47 /** 48 * Set or get the optgroups of an Dropdown-Element. 49 * 50 * optgroups have to be given as associative array 51 * * the key being the label of the group 52 * * the value being an array of options as defined in @see OptGroup::options() 53 * 54 * @param null|array $optGroups 55 * @return array 56 */ 57 public function optGroups($optGroups = null) { 58 if($optGroups === null) { 59 return $this->optGroups; 60 } 61 if (!is_array($optGroups)) { 62 throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!')); 63 } 64 $this->optGroups = array(); 65 foreach ($optGroups as $label => $options) { 66 $this->addOptGroup($label, $options); 67 } 68 } 69 70 /** 71 * Get or set the options of the Dropdown 72 * 73 * Options can be given as associative array (value => label) or as an 74 * indexd array (label = value) or as an array of arrays. In the latter 75 * case an element has to look as follows: 76 * option-value => array ( 77 * 'label' => option-label, 78 * 'attrs' => array ( 79 * attr-key => attr-value, ... 80 * ) 81 * ) 82 * 83 * @param null|array $options 84 * @return $this|array 85 */ 86 public function options($options = null) { 87 if ($options === null) { 88 return $this->options->options(); 89 } 90 $this->options = new OptGroup(null, $options); 91 return $this; 92 } 93 94 /** 95 * Gets or sets an attribute 96 * 97 * When no $value is given, the current content of the attribute is returned. 98 * An empty string is returned for unset attributes. 99 * 100 * When a $value is given, the content is set to that value and the Element 101 * itself is returned for easy chaining 102 * 103 * @param string $name Name of the attribute to access 104 * @param null|string $value New value to set 105 * @return string|$this 106 */ 107 public function attr($name, $value = null) { 108 if(strtolower($name) == 'multiple') { 109 throw new \InvalidArgumentException('Sorry, the dropdown element does not support the "multiple" attribute'); 110 } 111 return parent::attr($name, $value); 112 } 113 114 /** 115 * Get or set the current value 116 * 117 * When setting a value that is not defined in the options, the value is ignored 118 * and the first option's value is selected instead 119 * 120 * @param null|string $value The value to set 121 * @return $this|string 122 */ 123 public function val($value = null) { 124 if($value === null) return $this->value; 125 126 $value_exists = $this->setValueInOptGroups($value); 127 128 if($value_exists) { 129 $this->value = $value; 130 } else { 131 // unknown value set, select first option instead 132 $this->value = $this->getFirstOption(); 133 $this->setValueInOptGroups($this->value); 134 } 135 136 return $this; 137 } 138 139 /** 140 * Returns the first options as it will be rendered in HTML 141 * 142 * @return string 143 */ 144 protected function getFirstOption() { 145 $options = $this->options(); 146 if (!empty($options)) { 147 return (string) array_shift(array_keys($options)); 148 } 149 foreach ($this->optGroups as $optGroup) { 150 $options = $optGroup->options(); 151 if (!empty($options)) { 152 return (string) array_shift(array_keys($options)); 153 } 154 } 155 } 156 157 /** 158 * Set the value in the OptGroups, including the optgroup for the options without optgroup. 159 * 160 * @param string $value 161 * @return bool 162 */ 163 protected function setValueInOptGroups($value) { 164 $value_exists = false; 165 $isMultiSelect = $this->attributes['multiple']; 166 /** @var OptGroup $optGroup */ 167 foreach (array_merge(array($this->options), $this->optGroups) as $optGroup) { 168 $value_exists = $optGroup->setValue($value) || $value_exists; 169 if ($value_exists && !$isMultiSelect) { 170 $value = null; 171 } 172 } 173 return $value_exists; 174 } 175 176 /** 177 * Create the HTML for the select it self 178 * 179 * @return string 180 */ 181 protected function mainElementHTML() { 182 if($this->useInput) $this->prefillInput(); 183 184 $html = '<select ' . buildAttributes($this->attrs()) . '>'; 185 $html .= $this->options->toHTML(); 186 $html = array_reduce($this->optGroups, function($html, OptGroup $optGroup) {return $html . $optGroup->toHTML();}, $html); 187 $html .= '</select>'; 188 189 return $html; 190 } 191 192} 193