2 * HTMLForm enhancements:
3 * Set up 'hide-if' behaviors for form fields that have them.
8 * Helper function for hide-if to find the nearby form field.
10 * Find the closest match for the given name, "closest" being the minimum
11 * level of parents to go to find a form field matching the given name or
12 * ending in array keys matching the given name (e.g. "baz" matches
18 * @param {string} name
19 * @return {jQuery|OO.ui.Widget|null}
21 function hideIfGetField( $el
, name
) {
22 var $found
, $p
, $widget
,
23 suffix
= name
.replace( /^([^\[]+)/, '[$1]' );
25 function nameFilter() {
26 return this.name
=== name
||
27 ( this.name
=== ( 'wp' + name
) ) ||
28 this.name
.slice( -suffix
.length
) === suffix
;
31 for ( $p
= $el
.parent(); $p
.length
> 0; $p
= $p
.parent() ) {
32 $found
= $p
.find( '[name]' ).filter( nameFilter
);
33 if ( $found
.length
) {
34 $widget
= $found
.closest( '.oo-ui-widget[data-ooui]' );
35 if ( $widget
.length
) {
36 return OO
.ui
.Widget
.static.infuse( $widget
);
45 * Helper function for hide-if to return a test function and list of
46 * dependent fields for a hide-if specification.
53 * @return {Array} return.0 Dependent fields, array of jQuery objects or OO.ui.Widgets
54 * @return {Function} return.1 Test function
56 function hideIfParse( $el
, spec
) {
57 var op
, i
, l
, v
, field
, $field
, fields
, func
, funcs
, getVal
;
68 for ( i
= 1; i
< l
; i
++ ) {
69 if ( !$.isArray( spec
[ i
] ) ) {
70 throw new Error( op
+ ' parameters must be arrays' );
72 v
= hideIfParse( $el
, spec
[ i
] );
73 fields
= fields
.concat( v
[ 0 ] );
82 for ( i
= 0; i
< l
; i
++ ) {
83 if ( !funcs
[ i
]() ) {
94 for ( i
= 0; i
< l
; i
++ ) {
106 for ( i
= 0; i
< l
; i
++ ) {
107 if ( !funcs
[ i
]() ) {
118 for ( i
= 0; i
< l
; i
++ ) {
119 if ( funcs
[ i
]() ) {
128 return [ fields
, func
];
132 throw new Error( 'NOT takes exactly one parameter' );
134 if ( !$.isArray( spec
[ 1 ] ) ) {
135 throw new Error( 'NOT parameters must be arrays' );
137 v
= hideIfParse( $el
, spec
[ 1 ] );
140 return [ fields
, function () {
147 throw new Error( op
+ ' takes exactly two parameters' );
149 field
= hideIfGetField( $el
, spec
[ 1 ] );
151 return [ [], function () {
157 if ( !( field
instanceof jQuery
) ) {
158 // field is a OO.ui.Widget
159 if ( field
.supports( 'isSelected' ) ) {
160 getVal = function () {
161 var selected
= field
.isSelected();
162 return selected
? field
.getValue() : '';
165 getVal = function () {
166 return field
.getValue();
171 if ( $field
.prop( 'type' ) === 'radio' || $field
.prop( 'type' ) === 'checkbox' ) {
172 getVal = function () {
173 var $selected
= $field
.filter( ':checked' );
174 return $selected
.length
? $selected
.val() : '';
177 getVal = function () {
186 return getVal() === v
;
191 return getVal() !== v
;
196 return [ [ field
], func
];
199 throw new Error( 'Unrecognized operation \'' + op
+ '\'' );
203 mw
.hook( 'htmlform.enhance' ).add( function ( $root
) {
205 $fields
= $root
.find( '.mw-htmlform-hide-if' ),
206 $oouiFields
= $fields
.filter( '[data-ooui]' ),
209 if ( $oouiFields
.length
) {
210 modules
.push( 'mediawiki.htmlform.ooui' );
211 $oouiFields
.each( function () {
212 var data
, extraModules
,
215 data
= $el
.data( 'mw-modules' );
217 // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
218 extraModules
= data
.split( ',' );
219 modules
.push
.apply( modules
, extraModules
);
224 mw
.loader
.using( modules
).done( function () {
225 $fields
.each( function () {
226 var v
, i
, fields
, test
, func
, spec
, self
,
229 if ( $el
.is( '[data-ooui]' ) ) {
230 // self should be a FieldLayout that mixes in mw.htmlform.Element
231 self
= OO
.ui
.FieldLayout
.static.infuse( $el
);
233 // The original element has been replaced with infused one
237 spec
= $el
.data( 'hideIf' );
244 v
= hideIfParse( $el
, spec
);
247 // The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
249 var shouldHide
= test();
250 self
.toggle( !shouldHide
);
252 // It is impossible to submit a form with hidden fields failing validation, e.g. one that
253 // is required. However, validity is not checked for disabled fields, as these are not
254 // submitted with the form. So we should also disable fields when hiding them.
255 if ( self
instanceof jQuery
) {
256 // This also finds elements inside any nested fields (in case of HTMLFormFieldCloner),
257 // which is problematic. But it works because:
258 // * HTMLFormFieldCloner::createFieldsForKey() copies 'hide-if' rules to nested fields
259 // * jQuery collections like $fields are in document order, so we register event
260 // handlers for parents first
261 // * Event handlers are fired in the order they were registered, so even if the handler
262 // for parent messed up the child, the handle for child will run next and fix it
263 self
.find( 'input, textarea, select' ).each( function () {
264 var $this = $( this );
266 if ( $this.data( 'was-disabled' ) === undefined ) {
267 $this.data( 'was-disabled', $this.prop( 'disabled' ) );
269 $this.prop( 'disabled', true );
271 $this.prop( 'disabled', $this.data( 'was-disabled' ) );
275 // self is a OO.ui.FieldLayout
277 if ( self
.wasDisabled
=== undefined ) {
278 self
.wasDisabled
= self
.fieldWidget
.isDisabled();
280 self
.fieldWidget
.setDisabled( true );
281 } else if ( self
.wasDisabled
!== undefined ) {
282 self
.fieldWidget
.setDisabled( self
.wasDisabled
);
286 for ( i
= 0; i
< fields
.length
; i
++ ) {
287 // The .on() method works mostly the same for jQuery objects and OO.ui.Widget
288 fields
[ i
].on( 'change', func
);
295 }( mediaWiki
, jQuery
) );