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 OptGroup[]|DropdownElement 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 return $this; 69 } 70 71 /** 72 * Get or set the options of the Dropdown 73 * 74 * Options can be given as associative array (value => label) or as an 75 * indexd array (label = value) or as an array of arrays. In the latter 76 * case an element has to look as follows: 77 * option-value => array ( 78 * 'label' => option-label, 79 * 'attrs' => array ( 80 * attr-key => attr-value, ... 81 * ) 82 * ) 83 * 84 * @param null|array $options 85 * @return $this|array 86 */ 87 public function options($options = null) { 88 if ($options === null) { 89 return $this->options->options(); 90 } 91 $this->options = new OptGroup(null, $options); 92 return $this; 93 } 94 95 /** 96 * Gets or sets an attribute 97 * 98 * When no $value is given, the current content of the attribute is returned. 99 * An empty string is returned for unset attributes. 100 * 101 * When a $value is given, the content is set to that value and the Element 102 * itself is returned for easy chaining 103 * 104 * @param string $name Name of the attribute to access 105 * @param null|string $value New value to set 106 * @return string|$this 107 */ 108 public function attr($name, $value = null) { 109 if(strtolower($name) == 'multiple') { 110 throw new \InvalidArgumentException('Sorry, the dropdown element does not support the "multiple" attribute'); 111 } 112 return parent::attr($name, $value); 113 } 114 115 /** 116 * Get or set the current value 117 * 118 * When setting a value that is not defined in the options, the value is ignored 119 * and the first option's value is selected instead 120 * 121 * @param null|string $value The value to set 122 * @return $this|string 123 */ 124 public function val($value = null) { 125 if($value === null) return $this->value; 126 127 $value_exists = $this->setValueInOptGroups($value); 128 129 if($value_exists) { 130 $this->value = $value; 131 } else { 132 // unknown value set, select first option instead 133 $this->value = $this->getFirstOption(); 134 $this->setValueInOptGroups($this->value); 135 } 136 137 return $this; 138 } 139 140 /** 141 * Returns the first options as it will be rendered in HTML 142 * 143 * @return string 144 */ 145 protected function getFirstOption() { 146 $options = $this->options(); 147 if (!empty($options)) { 148 return (string) array_shift(array_keys($options)); 149 } 150 foreach ($this->optGroups as $optGroup) { 151 $options = $optGroup->options(); 152 if (!empty($options)) { 153 return (string) array_shift(array_keys($options)); 154 } 155 } 156 } 157 158 /** 159 * Set the value in the OptGroups, including the optgroup for the options without optgroup. 160 * 161 * @param string $value 162 * @return bool 163 */ 164 protected function setValueInOptGroups($value) { 165 $value_exists = false; 166 $isMultiSelect = $this->attributes['multiple']; 167 /** @var OptGroup $optGroup */ 168 foreach (array_merge(array($this->options), $this->optGroups) as $optGroup) { 169 $value_exists = $optGroup->setValue($value) || $value_exists; 170 if ($value_exists && !$isMultiSelect) { 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 .= $this->options->toHTML(); 187 $html = array_reduce($this->optGroups, function($html, OptGroup $optGroup) {return $html . $optGroup->toHTML();}, $html); 188 $html .= '</select>'; 189 190 return $html; 191 } 192 193} 194