xref: /dokuwiki/inc/Form/DropdownElement.php (revision 068abb2b1d5268e745ce71674b56502de562ec25)
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