Merge "Linker: Avoid Title in normaliseSpecialPage()"
[lhc/web/wiklou.git] / resources / src / mediawiki / htmlform / hide-if.js
1 /*
2 * HTMLForm enhancements:
3 * Set up 'hide-if' behaviors for form fields that have them.
4 */
5 ( function ( mw, $ ) {
6
7 /*jshint -W024*/
8
9 /**
10 * Helper function for hide-if to find the nearby form field.
11 *
12 * Find the closest match for the given name, "closest" being the minimum
13 * level of parents to go to find a form field matching the given name or
14 * ending in array keys matching the given name (e.g. "baz" matches
15 * "foo[bar][baz]").
16 *
17 * @ignore
18 * @private
19 * @param {jQuery} $el
20 * @param {string} name
21 * @return {jQuery|OO.ui.Widget|null}
22 */
23 function hideIfGetField( $el, name ) {
24 var $found, $p, $widget,
25 suffix = name.replace( /^([^\[]+)/, '[$1]' );
26
27 function nameFilter() {
28 return this.name === name ||
29 ( this.name === ( 'wp' + name ) ) ||
30 this.name.slice( -suffix.length ) === suffix;
31 }
32
33 for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
34 $found = $p.find( '[name]' ).filter( nameFilter );
35 if ( $found.length ) {
36 $widget = $found.closest( '.oo-ui-widget[data-ooui]' );
37 if ( $widget.length ) {
38 return OO.ui.Widget.static.infuse( $widget );
39 }
40 return $found;
41 }
42 }
43 return null;
44 }
45
46 /**
47 * Helper function for hide-if to return a test function and list of
48 * dependent fields for a hide-if specification.
49 *
50 * @ignore
51 * @private
52 * @param {jQuery} $el
53 * @param {Array} spec
54 * @return {Array}
55 * @return {Array} return.0 Dependent fields, array of jQuery objects or OO.ui.Widgets
56 * @return {Function} return.1 Test function
57 */
58 function hideIfParse( $el, spec ) {
59 var op, i, l, v, field, $field, fields, func, funcs, getVal;
60
61 op = spec[ 0 ];
62 l = spec.length;
63 switch ( op ) {
64 case 'AND':
65 case 'OR':
66 case 'NAND':
67 case 'NOR':
68 funcs = [];
69 fields = [];
70 for ( i = 1; i < l; i++ ) {
71 if ( !$.isArray( spec[ i ] ) ) {
72 throw new Error( op + ' parameters must be arrays' );
73 }
74 v = hideIfParse( $el, spec[ i ] );
75 fields = fields.concat( v[ 0 ] );
76 funcs.push( v[ 1 ] );
77 }
78
79 l = funcs.length;
80 switch ( op ) {
81 case 'AND':
82 func = function () {
83 var i;
84 for ( i = 0; i < l; i++ ) {
85 if ( !funcs[ i ]() ) {
86 return false;
87 }
88 }
89 return true;
90 };
91 break;
92
93 case 'OR':
94 func = function () {
95 var i;
96 for ( i = 0; i < l; i++ ) {
97 if ( funcs[ i ]() ) {
98 return true;
99 }
100 }
101 return false;
102 };
103 break;
104
105 case 'NAND':
106 func = function () {
107 var i;
108 for ( i = 0; i < l; i++ ) {
109 if ( !funcs[ i ]() ) {
110 return true;
111 }
112 }
113 return false;
114 };
115 break;
116
117 case 'NOR':
118 func = function () {
119 var i;
120 for ( i = 0; i < l; i++ ) {
121 if ( funcs[ i ]() ) {
122 return false;
123 }
124 }
125 return true;
126 };
127 break;
128 }
129
130 return [ fields, func ];
131
132 case 'NOT':
133 if ( l !== 2 ) {
134 throw new Error( 'NOT takes exactly one parameter' );
135 }
136 if ( !$.isArray( spec[ 1 ] ) ) {
137 throw new Error( 'NOT parameters must be arrays' );
138 }
139 v = hideIfParse( $el, spec[ 1 ] );
140 fields = v[ 0 ];
141 func = v[ 1 ];
142 return [ fields, function () {
143 return !func();
144 } ];
145
146 case '===':
147 case '!==':
148 if ( l !== 3 ) {
149 throw new Error( op + ' takes exactly two parameters' );
150 }
151 field = hideIfGetField( $el, spec[ 1 ] );
152 if ( !field ) {
153 return [ [], function () {
154 return false;
155 } ];
156 }
157 v = spec[ 2 ];
158
159 if ( !( field instanceof jQuery ) ) {
160 // field is a OO.ui.Widget
161 if ( field.supports( 'isSelected' ) ) {
162 getVal = function () {
163 var selected = field.isSelected();
164 return selected ? field.getValue() : '';
165 };
166 } else {
167 getVal = function () {
168 return field.getValue();
169 };
170 }
171 } else {
172 $field = $( field );
173 if ( $field.prop( 'type' ) === 'radio' || $field.prop( 'type' ) === 'checkbox' ) {
174 getVal = function () {
175 var $selected = $field.filter( ':checked' );
176 return $selected.length ? $selected.val() : '';
177 };
178 } else {
179 getVal = function () {
180 return $field.val();
181 };
182 }
183 }
184
185 switch ( op ) {
186 case '===':
187 func = function () {
188 return getVal() === v;
189 };
190 break;
191 case '!==':
192 func = function () {
193 return getVal() !== v;
194 };
195 break;
196 }
197
198 return [ [ field ], func ];
199
200 default:
201 throw new Error( 'Unrecognized operation \'' + op + '\'' );
202 }
203 }
204
205 mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
206 $root.find( '.mw-htmlform-hide-if' ).each( function () {
207 var v, i, fields, test, func, spec, self, modules, data,extraModules,
208 $el = $( this );
209
210 modules = [];
211 if ( $el.is( '[data-ooui]' ) ) {
212 modules.push( 'mediawiki.htmlform.ooui' );
213 data = $el.data( 'mw-modules' );
214 if ( data ) {
215 // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
216 extraModules = data.split( ',' );
217 modules.push.apply( modules, extraModules );
218 }
219 }
220
221 mw.loader.using( modules ).done( function () {
222 if ( $el.is( '[data-ooui]' ) ) {
223 // self should be a FieldLayout that mixes in mw.htmlform.Element
224 self = OO.ui.FieldLayout.static.infuse( $el );
225 spec = self.hideIf;
226 // The original element has been replaced with infused one
227 $el = self.$element;
228 } else {
229 self = $el;
230 spec = $el.data( 'hideIf' );
231 }
232
233 if ( !spec ) {
234 return;
235 }
236
237 v = hideIfParse( $el, spec );
238 fields = v[ 0 ];
239 test = v[ 1 ];
240 // The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
241 func = function () {
242 self.toggle( !test() );
243 };
244 for ( i = 0; i < fields.length; i++ ) {
245 // The .on() method works mostly the same for jQuery objects and OO.ui.Widget
246 fields[ i ].on( 'change', func );
247 }
248 func();
249 } );
250 } );
251 } );
252
253 }( mediaWiki, jQuery ) );