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 array OptGroup[] */ 14 protected $optGroups = array(); 15 16 /** 17 * @param string $name The name of this form element 18 * @param array $options The available options 19 * @param string $label The label text for this element (will be autoescaped) 20 */ 21 public function __construct($name, $options, $label = '') { 22 parent::__construct('dropdown', $name, $label); 23 $this->rmattr('type'); 24 $this->optGroups[''] = new OptGroup(null, $options); 25 $this->val(''); 26 } 27 28 /** 29 * Add an `<optgroup>` and respective options 30 * 31 * @param string $label 32 * @param array $options 33 * @return OptGroup a reference to the added optgroup 34 * @throws \Exception 35 */ 36 public function addOptGroup($label, $options) { 37 if (empty($label)) { 38 throw new \InvalidArgumentException(hsc('<optgroup> must have a label!')); 39 } 40 $this->optGroups[$label] = new OptGroup($label, $options); 41 return end($this->optGroups); 42 } 43 44 /** 45 * Set or get the optgroups of an Dropdown-Element. 46 * 47 * optgroups have to be given as associative array 48 * * the key being the label of the group 49 * * the value being an array of options as defined in @see OptGroup::options() 50 * 51 * @param null|array $optGroups 52 * @return OptGroup[]|DropdownElement 53 */ 54 public function optGroups($optGroups = null) { 55 if($optGroups === null) { 56 return $this->optGroups; 57 } 58 if (!is_array($optGroups)) { 59 throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!')); 60 } 61 $this->optGroups = array(); 62 foreach ($optGroups as $label => $options) { 63 $this->addOptGroup($label, $options); 64 } 65 return $this; 66 } 67 68 /** 69 * Get or set the options of the Dropdown 70 * 71 * Options can be given as associative array (value => label) or as an 72 * indexd array (label = value) or as an array of arrays. In the latter 73 * case an element has to look as follows: 74 * option-value => array ( 75 * 'label' => option-label, 76 * 'attrs' => array ( 77 * attr-key => attr-value, ... 78 * ) 79 * ) 80 * 81 * @param null|array $options 82 * @return $this|array 83 */ 84 public function options($options = null) { 85 if ($options === null) { 86 return $this->optGroups['']->options(); 87 } 88 $this->optGroups[''] = new OptGroup(null, $options); 89 return $this; 90 } 91 92 /** 93 * Gets or sets an attribute 94 * 95 * When no $value is given, the current content of the attribute is returned. 96 * An empty string is returned for unset attributes. 97 * 98 * When a $value is given, the content is set to that value and the Element 99 * itself is returned for easy chaining 100 * 101 * @param string $name Name of the attribute to access 102 * @param null|string $value New value to set 103 * @return string|$this 104 */ 105 public function attr($name, $value = null) { 106 if(strtolower($name) == 'multiple') { 107 throw new \InvalidArgumentException( 108 'Sorry, the dropdown element does not support the "multiple" attribute' 109 ); 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 $keys = array_keys($options); 148 return (string) array_shift($keys); 149 } 150 foreach ($this->optGroups as $optGroup) { 151 $options = $optGroup->options(); 152 if (!empty($options)) { 153 $keys = array_keys($options); 154 return (string) array_shift($keys); 155 } 156 } 157 } 158 159 /** 160 * Set the value in the OptGroups, including the optgroup for the options without optgroup. 161 * 162 * @param string $value 163 * @return bool 164 */ 165 protected function setValueInOptGroups($value) { 166 $value_exists = false; 167 /** @var OptGroup $optGroup */ 168 foreach ($this->optGroups as $optGroup) { 169 $value_exists = $optGroup->storeValue($value) || $value_exists; 170 if ($value_exists) { 171 $value = null; 172 } 173 } 174 return $value_exists; 175 } 176 177 /** 178 * Create the HTML for the select it self 179 * 180 * @return string 181 */ 182 protected function mainElementHTML() { 183 if($this->useInput) $this->prefillInput(); 184 185 $html = '<select ' . buildAttributes($this->attrs()) . '>'; 186 $html = array_reduce( 187 $this->optGroups, 188 function ($html, OptGroup $optGroup) { 189 return $html . $optGroup->toHTML(); 190 }, 191 $html 192 ); 193 $html .= '</select>'; 194 195 return $html; 196 } 197 198} 199