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('Sorry, the dropdown element does not support the "multiple" attribute'); 108 } 109 return parent::attr($name, $value); 110 } 111 112 /** 113 * Get or set the current value 114 * 115 * When setting a value that is not defined in the options, the value is ignored 116 * and the first option's value is selected instead 117 * 118 * @param null|string $value The value to set 119 * @return $this|string 120 */ 121 public function val($value = null) { 122 if($value === null) return $this->value; 123 124 $value_exists = $this->setValueInOptGroups($value); 125 126 if($value_exists) { 127 $this->value = $value; 128 } else { 129 // unknown value set, select first option instead 130 $this->value = $this->getFirstOption(); 131 $this->setValueInOptGroups($this->value); 132 } 133 134 return $this; 135 } 136 137 /** 138 * Returns the first options as it will be rendered in HTML 139 * 140 * @return string 141 */ 142 protected function getFirstOption() { 143 $options = $this->options(); 144 if (!empty($options)) { 145 $keys = array_keys($options); 146 return (string) array_shift($keys); 147 } 148 foreach ($this->optGroups as $optGroup) { 149 $options = $optGroup->options(); 150 if (!empty($options)) { 151 $keys = array_keys($options); 152 return (string) array_shift($keys); 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 /** @var OptGroup $optGroup */ 166 foreach ($this->optGroups as $optGroup) { 167 $value_exists = $optGroup->storeValue($value) || $value_exists; 168 if ($value_exists) { 169 $value = null; 170 } 171 } 172 return $value_exists; 173 } 174 175 /** 176 * Create the HTML for the select it self 177 * 178 * @return string 179 */ 180 protected function mainElementHTML() { 181 if($this->useInput) $this->prefillInput(); 182 183 $html = '<select ' . buildAttributes($this->attrs()) . '>'; 184 $html = array_reduce($this->optGroups, function($html, OptGroup $optGroup) {return $html . $optGroup->toHTML();}, $html); 185 $html .= '</select>'; 186 187 return $html; 188 } 189 190} 191