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