HTMLForm: Add hide-if
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.htmlform.js
1 /**
2 * Utility functions for jazzing up HTMLForm elements.
3 *
4 * @class jQuery.plugin.htmlform
5 */
6 ( function ( mw, $ ) {
7
8 /**
9 * Helper function for hide-if to find the nearby form field.
10 *
11 * Find the closest match for the given name, "closest" being the minimum
12 * level of parents to go to find a form field matching the given name or
13 * ending in array keys matching the given name (e.g. "baz" matches
14 * "foo[bar][baz]").
15 *
16 * @param {jQuery} element
17 * @param {string} name
18 * @return {jQuery|null}
19 */
20 function hideIfGetField( $el, name ) {
21 var sel, $found, $p;
22
23 sel = '[name="' + name + '"],' +
24 '[name="wp' + name + '"],' +
25 '[name$="' + name.replace( /^([^\[]+)/, '[$1]' ) + '"]';
26 for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
27 $found = $p.find( sel );
28 if ( $found.length > 0 ) {
29 return $found;
30 }
31 }
32 return null;
33 }
34
35 /**
36 * Helper function for hide-if to return a test function and list of
37 * dependent fields for a hide-if specification.
38 *
39 * @param {jQuery} element
40 * @param {Array} hide-if spec
41 * @return {Array} 2 elements: jQuery of dependent fields, and test function
42 */
43 function hideIfParse( $el, spec ) {
44 var op, i, l, v, $field, $fields, func, funcs, getVal;
45
46 op = spec[0];
47 l = spec.length;
48 switch ( op ) {
49 case 'AND':
50 case 'OR':
51 case 'NAND':
52 case 'NOR':
53 funcs = [];
54 $fields = $();
55 for ( i = 1; i < l; i++ ) {
56 if ( !$.isArray( spec[i] ) ) {
57 throw new Error( op + ' parameters must be arrays' );
58 }
59 v = hideIfParse( $el, spec[i] );
60 $fields = $fields.add( v[0] );
61 funcs.push( v[1] );
62 }
63
64 l = funcs.length;
65 switch ( op ) {
66 case 'AND':
67 func = function () {
68 var i;
69 for ( i = 0; i < l; i++ ) {
70 if ( !funcs[i]() ) {
71 return false;
72 }
73 }
74 return true;
75 };
76 break;
77
78 case 'OR':
79 func = function () {
80 var i;
81 for ( i = 0; i < l; i++ ) {
82 if ( funcs[i]() ) {
83 return true;
84 }
85 }
86 return false;
87 };
88 break;
89
90 case 'NAND':
91 func = function () {
92 var i;
93 for ( i = 0; i < l; i++ ) {
94 if ( !funcs[i]() ) {
95 return true;
96 }
97 }
98 return false;
99 };
100 break;
101
102 case 'NOR':
103 func = function () {
104 var i;
105 for ( i = 0; i < l; i++ ) {
106 if ( funcs[i]() ) {
107 return false;
108 }
109 }
110 return true;
111 };
112 break;
113 }
114
115 return [ $fields, func ];
116
117 case 'NOT':
118 if ( l !== 2 ) {
119 throw new Error( 'NOT takes exactly one parameter' );
120 }
121 if ( !$.isArray( spec[1] ) ) {
122 throw new Error( 'NOT parameters must be arrays' );
123 }
124 v = hideIfParse( $el, spec[1] );
125 $fields = v[0];
126 func = v[1];
127 return [ $fields, function () {
128 return !func();
129 } ];
130
131 case '===':
132 case '!==':
133 if ( l !== 3 ) {
134 throw new Error( op + ' takes exactly two parameters' );
135 }
136 $field = hideIfGetField( $el, spec[1] );
137 if ( !$field ) {
138 return [ $(), function () {
139 return false;
140 } ];
141 }
142 v = spec[2];
143
144 if ( $field.first().attr( 'type' ) === 'radio' ||
145 $field.first().attr( 'type' ) === 'checkbox'
146 ) {
147 getVal = function () {
148 var $selected = $field.filter( ':checked' );
149 return $selected.length > 0 ? $selected.val() : '';
150 };
151 } else {
152 getVal = function () {
153 return $field.val();
154 };
155 }
156
157 switch ( op ) {
158 case '===':
159 func = function () {
160 return getVal() === v;
161 };
162 break;
163 case '!==':
164 func = function () {
165 return getVal() !== v;
166 };
167 break;
168 }
169
170 return [ $field, func ];
171
172 default:
173 throw new Error( 'Unrecognized operation \'' + op + '\'' );
174 }
175 }
176
177 /**
178 * jQuery plugin to fade or snap to visible state.
179 *
180 * @param {boolean} [instantToggle=false]
181 * @return {jQuery}
182 * @chainable
183 */
184 $.fn.goIn = function ( instantToggle ) {
185 if ( instantToggle === true ) {
186 return $( this ).show();
187 }
188 return $( this ).stop( true, true ).fadeIn();
189 };
190
191 /**
192 * jQuery plugin to fade or snap to hiding state.
193 *
194 * @param {boolean} [instantToggle=false]
195 * @return jQuery
196 * @chainable
197 */
198 $.fn.goOut = function ( instantToggle ) {
199 if ( instantToggle === true ) {
200 return $( this ).hide();
201 }
202 return $( this ).stop( true, true ).fadeOut();
203 };
204
205 /**
206 * Bind a function to the jQuery object via live(), and also immediately trigger
207 * the function on the objects with an 'instant' parameter set to true.
208 * @param {Function} callback
209 * @param {boolean|jQuery.Event} callback.immediate True when the event is called immediately,
210 * an event object when triggered from an event.
211 */
212 $.fn.liveAndTestAtStart = function ( callback ) {
213 $( this )
214 .live( 'change', callback )
215 .each( function () {
216 callback.call( this, true );
217 } );
218 };
219
220 $( function () {
221
222 // Animate the SelectOrOther fields, to only show the text field when
223 // 'other' is selected.
224 $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) {
225 var $other = $( '#' + $( this ).attr( 'id' ) + '-other' );
226 $other = $other.add( $other.siblings( 'br' ) );
227 if ( $( this ).val() === 'other' ) {
228 $other.goIn( instant );
229 } else {
230 $other.goOut( instant );
231 }
232 } );
233
234 // Set up hide-if elements
235 $( '.mw-htmlform-hide-if' ).each( function () {
236 var $el = $( this ),
237 spec = $el.data( 'hideIf' ),
238 v, $fields, test, func;
239
240 if ( !spec ) {
241 return;
242 }
243
244 v = hideIfParse( $el, spec );
245 $fields = v[0];
246 test = v[1];
247 func = function () {
248 if ( test() ) {
249 $el.hide();
250 } else {
251 $el.show();
252 }
253 };
254 $fields.change( func );
255 func();
256 } );
257
258 } );
259
260 function addMulti( $oldContainer, $container ) {
261 var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
262 oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
263 $select = $( '<select>' ),
264 dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
265 oldClass = $.trim( oldClass );
266 $select.attr( {
267 name: name,
268 multiple: 'multiple',
269 'data-placeholder': dataPlaceholder.plain(),
270 'class': 'htmlform-chzn-select mw-input ' + oldClass
271 } );
272 $oldContainer.find( 'input' ).each( function () {
273 var $oldInput = $( this ),
274 checked = $oldInput.prop( 'checked' ),
275 $option = $( '<option>' );
276 $option.prop( 'value', $oldInput.prop( 'value' ) );
277 if ( checked ) {
278 $option.prop( 'selected', true );
279 }
280 $option.text( $oldInput.prop( 'value' ) );
281 $select.append( $option );
282 } );
283 $container.append( $select );
284 }
285
286 function convertCheckboxesToMulti( $oldContainer, type ) {
287 var $fieldLabel = $( '<td>' ),
288 $td = $( '<td>' ),
289 $fieldLabelText = $( '<label>' ),
290 $container;
291 if ( type === 'tr' ) {
292 addMulti( $oldContainer, $td );
293 $container = $( '<tr>' );
294 $container.append( $td );
295 } else if ( type === 'div' ) {
296 $fieldLabel = $( '<div>' );
297 $container = $( '<div>' );
298 addMulti( $oldContainer, $container );
299 }
300 $fieldLabel.attr( 'class', 'mw-label' );
301 $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
302 $fieldLabel.append( $fieldLabelText );
303 $container.prepend( $fieldLabel );
304 $oldContainer.replaceWith( $container );
305 return $container;
306 }
307
308 if ( $( '.mw-chosen' ).length ) {
309 mw.loader.using( 'jquery.chosen', function () {
310 $( '.mw-chosen' ).each( function () {
311 var type = this.nodeName.toLowerCase(),
312 $converted = convertCheckboxesToMulti( $( this ), type );
313 $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
314 } );
315 } );
316 }
317
318 $( function () {
319 var $matrixTooltips = $( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
320 if ( $matrixTooltips.length ) {
321 mw.loader.using( 'jquery.tipsy', function () {
322 $matrixTooltips.tipsy( { gravity: 's' } );
323 } );
324 }
325 } );
326
327 /**
328 * @class jQuery
329 * @mixins jQuery.plugin.htmlform
330 */
331 }( mediaWiki, jQuery ) );