Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 144 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
HTMLSelectOrOtherField | |
0.00% |
0 / 143 |
|
0.00% |
0 / 9 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getInputHTML | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
90 | |||
shouldInfuseOOUI | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOOUIModules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInputOOUI | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
72 | |||
getInputWidget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInputCodex | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
90 | |||
loadDataFromRequest | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getFieldClasses | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\HTMLForm\Field; |
4 | |
5 | use MediaWiki\Html\Html; |
6 | use MediaWiki\HTMLForm\HTMLFormField; |
7 | use MediaWiki\Request\WebRequest; |
8 | use MediaWiki\Xml\XmlSelect; |
9 | |
10 | /** |
11 | * Select dropdown field, with an additional "other" textbox. |
12 | * |
13 | * HTMLComboboxField implements the same functionality using a single form field |
14 | * and should be used instead. |
15 | * |
16 | * @stable to extend |
17 | */ |
18 | class HTMLSelectOrOtherField extends HTMLTextField { |
19 | private const FIELD_CLASS = 'mw-htmlform-select-or-other'; |
20 | |
21 | /** |
22 | * @stable to call |
23 | * @inheritDoc |
24 | */ |
25 | public function __construct( $params ) { |
26 | parent::__construct( $params ); |
27 | $this->getOptions(); |
28 | if ( !in_array( 'other', $this->mOptions, true ) ) { |
29 | $msg = |
30 | $params['other'] ?? wfMessage( 'htmlform-selectorother-other' )->text(); |
31 | // Have 'other' always as first element |
32 | $this->mOptions = [ $msg => 'other' ] + $this->mOptions; |
33 | } |
34 | } |
35 | |
36 | public function getInputHTML( $value ) { |
37 | $valInSelect = false; |
38 | |
39 | if ( $value !== false ) { |
40 | $value = strval( $value ); |
41 | $valInSelect = in_array( |
42 | $value, HTMLFormField::flattenOptions( $this->getOptions() ), true |
43 | ); |
44 | } |
45 | |
46 | $selected = $valInSelect ? $value : 'other'; |
47 | |
48 | $select = new XmlSelect( $this->mName, false, $selected ); |
49 | $select->addOptions( $this->getOptions() ); |
50 | |
51 | $tbAttribs = [ 'size' => $this->getSize() ]; |
52 | |
53 | if ( !empty( $this->mParams['disabled'] ) ) { |
54 | $select->setAttribute( 'disabled', 'disabled' ); |
55 | $tbAttribs['disabled'] = 'disabled'; |
56 | } |
57 | |
58 | if ( isset( $this->mParams['tabindex'] ) ) { |
59 | $select->setAttribute( 'tabindex', $this->mParams['tabindex'] ); |
60 | $tbAttribs['tabindex'] = $this->mParams['tabindex']; |
61 | } |
62 | |
63 | $select = $select->getHTML(); |
64 | |
65 | if ( isset( $this->mParams['maxlength'] ) ) { |
66 | $tbAttribs['maxlength'] = $this->mParams['maxlength']; |
67 | } |
68 | |
69 | if ( isset( $this->mParams['minlength'] ) ) { |
70 | $tbAttribs['minlength'] = $this->mParams['minlength']; |
71 | } |
72 | |
73 | $textbox = Html::input( $this->mName . '-other', $valInSelect ? '' : $value, 'text', $tbAttribs ); |
74 | |
75 | $wrapperAttribs = [ |
76 | 'id' => $this->mID, |
77 | 'class' => $this->getFieldClasses() |
78 | ]; |
79 | if ( $this->mClass !== '' ) { |
80 | $wrapperAttribs['class'][] = $this->mClass; |
81 | } |
82 | return Html::rawElement( |
83 | 'div', |
84 | $wrapperAttribs, |
85 | "$select<br />\n$textbox" |
86 | ); |
87 | } |
88 | |
89 | protected function shouldInfuseOOUI() { |
90 | return true; |
91 | } |
92 | |
93 | protected function getOOUIModules() { |
94 | return [ 'mediawiki.widgets.SelectWithInputWidget' ]; |
95 | } |
96 | |
97 | public function getInputOOUI( $value ) { |
98 | $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' ); |
99 | |
100 | $valInSelect = false; |
101 | if ( $value !== false ) { |
102 | $value = strval( $value ); |
103 | $valInSelect = in_array( |
104 | $value, HTMLFormField::flattenOptions( $this->getOptions() ), true |
105 | ); |
106 | } |
107 | |
108 | # DropdownInput |
109 | $dropdownAttribs = [ |
110 | 'name' => $this->mName, |
111 | 'options' => $this->getOptionsOOUI(), |
112 | 'value' => $valInSelect ? $value : 'other', |
113 | ]; |
114 | |
115 | $allowedParams = [ |
116 | 'disabled', |
117 | 'tabindex', |
118 | ]; |
119 | |
120 | $dropdownAttribs += \OOUI\Element::configFromHtmlAttributes( |
121 | $this->getAttributes( $allowedParams ) |
122 | ); |
123 | |
124 | # TextInput |
125 | $textAttribs = [ |
126 | 'name' => $this->mName . '-other', |
127 | 'size' => $this->getSize(), |
128 | 'value' => $valInSelect ? '' : $value, |
129 | ]; |
130 | |
131 | $allowedParams = [ |
132 | 'required', |
133 | 'autofocus', |
134 | 'multiple', |
135 | 'disabled', |
136 | 'tabindex', |
137 | 'maxlength', |
138 | 'minlength', |
139 | ]; |
140 | |
141 | $textAttribs += \OOUI\Element::configFromHtmlAttributes( |
142 | $this->getAttributes( $allowedParams ) |
143 | ); |
144 | |
145 | if ( $this->mPlaceholder !== '' ) { |
146 | $textAttribs['placeholder'] = $this->mPlaceholder; |
147 | } |
148 | |
149 | $disabled = false; |
150 | if ( isset( $this->mParams[ 'disabled' ] ) && $this->mParams[ 'disabled' ] ) { |
151 | $disabled = true; |
152 | } |
153 | |
154 | $inputClasses = $this->getFieldClasses(); |
155 | if ( $this->mClass !== '' ) { |
156 | $inputClasses = array_merge( $inputClasses, explode( ' ', $this->mClass ) ); |
157 | } |
158 | return $this->getInputWidget( [ |
159 | 'id' => $this->mID, |
160 | 'classes' => $inputClasses, |
161 | 'disabled' => $disabled, |
162 | 'textinput' => $textAttribs, |
163 | 'dropdowninput' => $dropdownAttribs, |
164 | 'required' => $this->mParams[ 'required' ] ?? false, |
165 | 'or' => true, |
166 | ] ); |
167 | } |
168 | |
169 | public function getInputWidget( $params ) { |
170 | return new \MediaWiki\Widget\SelectWithInputWidget( $params ); |
171 | } |
172 | |
173 | public function getInputCodex( $value, $hasErrors ) { |
174 | // Figure out the value of the select. |
175 | $valInSelect = false; |
176 | if ( $value !== false ) { |
177 | $value = strval( $value ); |
178 | $valInSelect = in_array( |
179 | $value, HTMLFormField::flattenOptions( $this->getOptions() ), true |
180 | ); |
181 | } |
182 | $selected = $valInSelect ? $value : 'other'; |
183 | |
184 | // Create the <select> element. |
185 | $select = new XmlSelect( $this->mName, false, $selected ); |
186 | // TODO: Add support for error class once it's implemented in the Codex CSS-only Select. |
187 | $select->setAttribute( 'class', 'cdx-select' ); |
188 | $select->addOptions( $this->getOptions() ); |
189 | |
190 | // Set up attributes for the select and the text input. |
191 | $textInputAttribs = [ 'size' => $this->getSize() ]; |
192 | $textInputAttribs['name'] = $this->mName . '-other'; |
193 | |
194 | if ( !empty( $this->mParams['disabled'] ) ) { |
195 | $select->setAttribute( 'disabled', 'disabled' ); |
196 | $textInputAttribs['disabled'] = 'disabled'; |
197 | } |
198 | |
199 | if ( isset( $this->mParams['tabindex'] ) ) { |
200 | $select->setAttribute( 'tabindex', $this->mParams['tabindex'] ); |
201 | $textInputAttribs['tabindex'] = $this->mParams['tabindex']; |
202 | } |
203 | |
204 | if ( isset( $this->mParams['maxlength'] ) ) { |
205 | $textInputAttribs['maxlength'] = $this->mParams['maxlength']; |
206 | } |
207 | |
208 | if ( isset( $this->mParams['minlength'] ) ) { |
209 | $textInputAttribs['minlength'] = $this->mParams['minlength']; |
210 | } |
211 | |
212 | // Get HTML of the select and text input. |
213 | $select = $select->getHTML(); |
214 | $textInput = parent::buildCodexComponent( |
215 | $valInSelect ? '' : $value, |
216 | $hasErrors, |
217 | 'text', |
218 | $this->mName . '-other', |
219 | $textInputAttribs |
220 | ); |
221 | |
222 | // Set up the wrapper element and return the entire component. |
223 | $wrapperAttribs = [ |
224 | 'id' => $this->mID, |
225 | 'class' => $this->getFieldClasses() |
226 | ]; |
227 | if ( $this->mClass !== '' ) { |
228 | $wrapperAttribs['class'][] = $this->mClass; |
229 | } |
230 | return Html::rawElement( |
231 | 'div', |
232 | $wrapperAttribs, |
233 | "$select\n$textInput" |
234 | ); |
235 | } |
236 | |
237 | /** |
238 | * @param WebRequest $request |
239 | * |
240 | * @return string |
241 | */ |
242 | public function loadDataFromRequest( $request ) { |
243 | if ( $request->getCheck( $this->mName ) ) { |
244 | $val = $request->getText( $this->mName ); |
245 | |
246 | if ( $val === 'other' ) { |
247 | $val = $request->getText( $this->mName . '-other' ); |
248 | } |
249 | |
250 | return $val; |
251 | } else { |
252 | return $this->getDefault(); |
253 | } |
254 | } |
255 | |
256 | /** |
257 | * Returns a list of classes that should be applied to the widget itself. Unfortunately, we can't use |
258 | * $this->mClass or the 'cssclass' config option, because they're also added to the outer field wrapper |
259 | * (which includes the label). This method exists a temporary workaround until HTMLFormField will have |
260 | * a stable way for subclasses to specify additional classes for the widget itself. |
261 | * @internal Should only be used in HTMLTimezoneField |
262 | * @return string[] |
263 | */ |
264 | protected function getFieldClasses(): array { |
265 | return [ self::FIELD_CLASS ]; |
266 | } |
267 | } |
268 | |
269 | /** @deprecated class alias since 1.42 */ |
270 | class_alias( HTMLSelectOrOtherField::class, 'HTMLSelectOrOtherField' ); |