File: DropdownElement.php

package info (click to toggle)
dokuwiki 2024-02-06b%2Bdfsg-9
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 24,624 kB
  • sloc: php: 97,851; javascript: 3,724; sh: 599; makefile: 70; xml: 34
file content (199 lines) | stat: -rw-r--r-- 5,859 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<?php

namespace dokuwiki\Form;

/**
 * Class DropdownElement
 *
 * Represents a HTML select. Please not that prefilling with input data only works for single values.
 *
 * @package dokuwiki\Form
 */
class DropdownElement extends InputElement
{
    /** @var array OptGroup[] */
    protected $optGroups = [];

    /** @var string[] the currently set values */
    protected $values = [];

    /**
     * @param string $name The name of this form element
     * @param array $options The available options
     * @param string $label The label text for this element (will be autoescaped)
     */
    public function __construct($name, $options, $label = '')
    {
        parent::__construct('dropdown', $name, $label);
        $this->rmattr('type');
        $this->optGroups[''] = new OptGroup(null, $options);
        $this->val('');
    }

    /**
     * Add an `<optgroup>` and respective options
     *
     * @param string $label
     * @param array $options
     * @return OptGroup a reference to the added optgroup
     * @throws \InvalidArgumentException
     */
    public function addOptGroup($label, $options)
    {
        if (empty($label)) {
            throw new \InvalidArgumentException(hsc('<optgroup> must have a label!'));
        }
        $this->optGroups[$label] = new OptGroup($label, $options);
        return end($this->optGroups);
    }

    /**
     * Set or get the optgroups of an Dropdown-Element.
     *
     * optgroups have to be given as associative array
     *   * the key being the label of the group
     *   * the value being an array of options as defined in @param null|array $optGroups
     * @return OptGroup[]|DropdownElement
     * @see OptGroup::options()
     *
     */
    public function optGroups($optGroups = null)
    {
        if ($optGroups === null) {
            return $this->optGroups;
        }
        if (!is_array($optGroups)) {
            throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!'));
        }
        $this->optGroups = [];
        foreach ($optGroups as $label => $options) {
            $this->addOptGroup($label, $options);
        }
        return $this;
    }

    /**
     * Get or set the options of the Dropdown
     *
     * Options can be given as associative array (value => label) or as an
     * indexd array (label = value) or as an array of arrays. In the latter
     * case an element has to look as follows:
     * option-value => array (
     *                 'label' => option-label,
     *                 'attrs' => array (
     *                                    attr-key => attr-value, ...
     *                                  )
     *                 )
     *
     * @param null|array $options
     * @return $this|array
     */
    public function options($options = null)
    {
        if ($options === null) {
            return $this->optGroups['']->options();
        }
        $this->optGroups[''] = new OptGroup(null, $options);
        return $this;
    }

    /**
     * Get or set the current value
     *
     * When setting a value that is not defined in the options, the value is ignored
     * and the first option's value is selected instead
     *
     * @param null|string|string[] $value The value to set
     * @return $this|string|string[]
     */
    public function val($value = null)
    {
        // getter
        if ($value === null) {
            if (isset($this->attributes['multiple'])) {
                return $this->values;
            } else {
                return $this->values[0];
            }
        }

        // setter
        $this->values = $this->setValuesInOptGroups((array) $value);
        if (!$this->values) {
            // unknown value set, select first option instead
            $this->values = $this->setValuesInOptGroups((array) $this->getFirstOptionKey());
        }

        return $this;
    }

    /**
     * Returns the first option's key
     *
     * @return string
     */
    protected function getFirstOptionKey()
    {
        $options = $this->options();
        if (!empty($options)) {
            $keys = array_keys($options);
            return (string)array_shift($keys);
        }
        foreach ($this->optGroups as $optGroup) {
            $options = $optGroup->options();
            if (!empty($options)) {
                $keys = array_keys($options);
                return (string)array_shift($keys);
            }
        }

        return ''; // should not happen
    }

    /**
     * Set the value in the OptGroups, including the optgroup for the options without optgroup.
     *
     * @param string[] $values The values to be set
     * @return string[] The values actually set
     */
    protected function setValuesInOptGroups($values)
    {
        $valueset = [];

        /** @var OptGroup $optGroup */
        foreach ($this->optGroups as $optGroup) {
            $found = $optGroup->storeValues($values);
            $values = array_diff($values, $found);
            $valueset = array_merge($valueset, $found);
        }

        return $valueset;
    }

    /**
     * Create the HTML for the select it self
     *
     * @return string
     */
    protected function mainElementHTML()
    {
        $attr = $this->attrs();
        if (isset($attr['multiple'])) {
            // use array notation when multiple values are allowed
            $attr['name'] .= '[]';
        } elseif ($this->useInput) {
            // prefilling is only supported for non-multi fields
            $this->prefillInput();
        }

        $html = '<select ' . buildAttributes($attr) . '>';
        $html = array_reduce(
            $this->optGroups,
            static fn($html, OptGroup $optGroup) => $html . $optGroup->toHTML(),
            $html
        );
        $html .= '</select>';

        return $html;
    }
}