Merge "Make update.php file executable"
[mediawiki.git] / resources / src / mediawiki / mediawiki.htmlform.js
blobf7aa7f8bd011d0d28ecd065af8a80a9e5d913733
1 /**
2  * Utility functions for jazzing up HTMLForm elements.
3  *
4  * @class jQuery.plugin.htmlform
5  */
6 ( function ( mw, $ ) {
8         var cloneCounter = 0;
10         /**
11          * Helper function for hide-if to find the nearby form field.
12          *
13          * Find the closest match for the given name, "closest" being the minimum
14          * level of parents to go to find a form field matching the given name or
15          * ending in array keys matching the given name (e.g. "baz" matches
16          * "foo[bar][baz]").
17          *
18          * @param {jQuery} element
19          * @param {string} name
20          * @return {jQuery|null}
21          */
22         function hideIfGetField( $el, name ) {
23                 var sel, $found, $p;
25                 sel = '[name="' + name + '"],' +
26                         '[name="wp' + name + '"],' +
27                         '[name$="' + name.replace( /^([^\[]+)/, '[$1]' ) + '"]';
28                 for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
29                         $found = $p.find( sel );
30                         if ( $found.length > 0 ) {
31                                 return $found;
32                         }
33                 }
34                 return null;
35         }
37         /**
38          * Helper function for hide-if to return a test function and list of
39          * dependent fields for a hide-if specification.
40          *
41          * @param {jQuery} element
42          * @param {Array} hide-if spec
43          * @return {Array} 2 elements: jQuery of dependent fields, and test function
44          */
45         function hideIfParse( $el, spec ) {
46                 var op, i, l, v, $field, $fields, func, funcs, getVal;
48                 op = spec[0];
49                 l = spec.length;
50                 switch ( op ) {
51                         case 'AND':
52                         case 'OR':
53                         case 'NAND':
54                         case 'NOR':
55                                 funcs = [];
56                                 $fields = $();
57                                 for ( i = 1; i < l; i++ ) {
58                                         if ( !$.isArray( spec[i] ) ) {
59                                                 throw new Error( op + ' parameters must be arrays' );
60                                         }
61                                         v = hideIfParse( $el, spec[i] );
62                                         $fields = $fields.add( v[0] );
63                                         funcs.push( v[1] );
64                                 }
66                                 l = funcs.length;
67                                 switch ( op ) {
68                                         case 'AND':
69                                                 func = function () {
70                                                         var i;
71                                                         for ( i = 0; i < l; i++ ) {
72                                                                 if ( !funcs[i]() ) {
73                                                                         return false;
74                                                                 }
75                                                         }
76                                                         return true;
77                                                 };
78                                                 break;
80                                         case 'OR':
81                                                 func = function () {
82                                                         var i;
83                                                         for ( i = 0; i < l; i++ ) {
84                                                                 if ( funcs[i]() ) {
85                                                                         return true;
86                                                                 }
87                                                         }
88                                                         return false;
89                                                 };
90                                                 break;
92                                         case 'NAND':
93                                                 func = function () {
94                                                         var i;
95                                                         for ( i = 0; i < l; i++ ) {
96                                                                 if ( !funcs[i]() ) {
97                                                                         return true;
98                                                                 }
99                                                         }
100                                                         return false;
101                                                 };
102                                                 break;
104                                         case 'NOR':
105                                                 func = function () {
106                                                         var i;
107                                                         for ( i = 0; i < l; i++ ) {
108                                                                 if ( funcs[i]() ) {
109                                                                         return false;
110                                                                 }
111                                                         }
112                                                         return true;
113                                                 };
114                                                 break;
115                                 }
117                                 return [ $fields, func ];
119                         case 'NOT':
120                                 if ( l !== 2 ) {
121                                         throw new Error( 'NOT takes exactly one parameter' );
122                                 }
123                                 if ( !$.isArray( spec[1] ) ) {
124                                         throw new Error( 'NOT parameters must be arrays' );
125                                 }
126                                 v = hideIfParse( $el, spec[1] );
127                                 $fields = v[0];
128                                 func = v[1];
129                                 return [ $fields, function () {
130                                         return !func();
131                                 } ];
133                         case '===':
134                         case '!==':
135                                 if ( l !== 3 ) {
136                                         throw new Error( op + ' takes exactly two parameters' );
137                                 }
138                                 $field = hideIfGetField( $el, spec[1] );
139                                 if ( !$field ) {
140                                         return [ $(), function () {
141                                                 return false;
142                                         } ];
143                                 }
144                                 v = spec[2];
146                                 if ( $field.first().attr( 'type' ) === 'radio' ||
147                                         $field.first().attr( 'type' ) === 'checkbox'
148                                 ) {
149                                         getVal = function () {
150                                                 var $selected = $field.filter( ':checked' );
151                                                 return $selected.length > 0 ? $selected.val() : '';
152                                         };
153                                 } else {
154                                         getVal = function () {
155                                                 return $field.val();
156                                         };
157                                 }
159                                 switch ( op ) {
160                                         case '===':
161                                                 func = function () {
162                                                         return getVal() === v;
163                                                 };
164                                                 break;
165                                         case '!==':
166                                                 func = function () {
167                                                         return getVal() !== v;
168                                                 };
169                                                 break;
170                                 }
172                                 return [ $field, func ];
174                         default:
175                                 throw new Error( 'Unrecognized operation \'' + op + '\'' );
176                 }
177         }
179         /**
180          * jQuery plugin to fade or snap to visible state.
181          *
182          * @param {boolean} [instantToggle=false]
183          * @return {jQuery}
184          * @chainable
185          */
186         $.fn.goIn = function ( instantToggle ) {
187                 if ( instantToggle === true ) {
188                         return $( this ).show();
189                 }
190                 return $( this ).stop( true, true ).fadeIn();
191         };
193         /**
194          * jQuery plugin to fade or snap to hiding state.
195          *
196          * @param {boolean} [instantToggle=false]
197          * @return jQuery
198          * @chainable
199          */
200         $.fn.goOut = function ( instantToggle ) {
201                 if ( instantToggle === true ) {
202                         return $( this ).hide();
203                 }
204                 return $( this ).stop( true, true ).fadeOut();
205         };
207         /**
208          * Bind a function to the jQuery object via live(), and also immediately trigger
209          * the function on the objects with an 'instant' parameter set to true.
210          * @param {Function} callback
211          * @param {boolean|jQuery.Event} callback.immediate True when the event is called immediately,
212          *  an event object when triggered from an event.
213          */
214         $.fn.liveAndTestAtStart = function ( callback ) {
215                 $( this )
216                         .live( 'change', callback )
217                         .each( function () {
218                                 callback.call( this, true );
219                         } );
220         };
222         function enhance( $root ) {
224                 // Animate the SelectOrOther fields, to only show the text field when
225                 // 'other' is selected.
226                 $root.find( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) {
227                         var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
228                         $other = $other.add( $other.siblings( 'br' ) );
229                         if ( $( this ).val() === 'other' ) {
230                                 $other.goIn( instant );
231                         } else {
232                                 $other.goOut( instant );
233                         }
234                 } );
236                 // Set up hide-if elements
237                 $root.find( '.mw-htmlform-hide-if' ).each( function () {
238                         var $el = $( this ),
239                                 spec = $el.data( 'hideIf' ),
240                                 v, $fields, test, func;
242                         if ( !spec ) {
243                                 return;
244                         }
246                         v = hideIfParse( $el, spec );
247                         $fields = v[0];
248                         test = v[1];
249                         func = function () {
250                                 if ( test() ) {
251                                         $el.hide();
252                                 } else {
253                                         $el.show();
254                                 }
255                         };
256                         $fields.change( func );
257                         func();
258                 } );
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                 }
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                 }
308                 if ( $root.find( '.mw-chosen' ).length ) {
309                         mw.loader.using( 'jquery.chosen', function () {
310                                 $root.find( '.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                 }
318                 var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
319                 if ( $matrixTooltips.length ) {
320                         mw.loader.using( 'jquery.tipsy', function () {
321                                 $matrixTooltips.tipsy( { gravity: 's' } );
322                         } );
323                 }
325                 // Add/remove cloner clones without having to resubmit the form
326                 $root.find( '.mw-htmlform-cloner-delete-button' ).click( function ( ev ) {
327                         ev.preventDefault();
328                         $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
329                 } );
331                 $root.find( '.mw-htmlform-cloner-create-button' ).click( function ( ev ) {
332                         var $ul, $li, html;
334                         ev.preventDefault();
336                         $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
338                         html = $ul.data( 'template' ).replace(
339                                 $ul.data( 'uniqueId' ), 'clone' + ( ++cloneCounter ), 'g'
340                         );
342                         $li = $( '<li>' )
343                                 .addClass( 'mw-htmlform-cloner-li' )
344                                 .html( html )
345                                 .appendTo( $ul );
347                         enhance( $li );
348                 } );
350                 mw.hook( 'htmlform.enhance' ).fire( $root );
352         }
354         $( function () {
355                 enhance( $( document ) );
356         } );
358         /**
359          * @class jQuery
360          * @mixins jQuery.plugin.htmlform
361          */
362 }( mediaWiki, jQuery ) );