PrefixSearch: Avoid notice when no subpage exists
[mediawiki.git] / resources / src / mediawiki / mediawiki.htmlform.js
blob8be1321ea4d0a3dd6bf53e11f34331cf40508489
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 $found, $p,
24                         suffix = name.replace( /^([^\[]+)/, '[$1]' );
26                 function nameFilter() {
27                         return this.name === name ||
28                                 ( this.name === ( 'wp' + name ) ) ||
29                                 this.name.slice( -suffix.length ) === suffix;
30                 }
32                 for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
33                         $found = $p.find( '[name]' ).filter( nameFilter );
34                         if ( $found.length ) {
35                                 return $found;
36                         }
37                 }
38                 return null;
39         }
41         /**
42          * Helper function for hide-if to return a test function and list of
43          * dependent fields for a hide-if specification.
44          *
45          * @param {jQuery} element
46          * @param {Array} hide-if spec
47          * @return {Array} 2 elements: jQuery of dependent fields, and test function
48          */
49         function hideIfParse( $el, spec ) {
50                 var op, i, l, v, $field, $fields, fields, func, funcs, getVal;
52                 op = spec[0];
53                 l = spec.length;
54                 switch ( op ) {
55                         case 'AND':
56                         case 'OR':
57                         case 'NAND':
58                         case 'NOR':
59                                 funcs = [];
60                                 fields = [];
61                                 for ( i = 1; i < l; i++ ) {
62                                         if ( !$.isArray( spec[i] ) ) {
63                                                 throw new Error( op + ' parameters must be arrays' );
64                                         }
65                                         v = hideIfParse( $el, spec[i] );
66                                         fields.push( v[0] );
67                                         funcs.push( v[1] );
68                                 }
69                                 $fields = $( fields );
71                                 l = funcs.length;
72                                 switch ( op ) {
73                                         case 'AND':
74                                                 func = function () {
75                                                         var i;
76                                                         for ( i = 0; i < l; i++ ) {
77                                                                 if ( !funcs[i]() ) {
78                                                                         return false;
79                                                                 }
80                                                         }
81                                                         return true;
82                                                 };
83                                                 break;
85                                         case 'OR':
86                                                 func = function () {
87                                                         var i;
88                                                         for ( i = 0; i < l; i++ ) {
89                                                                 if ( funcs[i]() ) {
90                                                                         return true;
91                                                                 }
92                                                         }
93                                                         return false;
94                                                 };
95                                                 break;
97                                         case 'NAND':
98                                                 func = function () {
99                                                         var i;
100                                                         for ( i = 0; i < l; i++ ) {
101                                                                 if ( !funcs[i]() ) {
102                                                                         return true;
103                                                                 }
104                                                         }
105                                                         return false;
106                                                 };
107                                                 break;
109                                         case 'NOR':
110                                                 func = function () {
111                                                         var i;
112                                                         for ( i = 0; i < l; i++ ) {
113                                                                 if ( funcs[i]() ) {
114                                                                         return false;
115                                                                 }
116                                                         }
117                                                         return true;
118                                                 };
119                                                 break;
120                                 }
122                                 return [ $fields, func ];
124                         case 'NOT':
125                                 if ( l !== 2 ) {
126                                         throw new Error( 'NOT takes exactly one parameter' );
127                                 }
128                                 if ( !$.isArray( spec[1] ) ) {
129                                         throw new Error( 'NOT parameters must be arrays' );
130                                 }
131                                 v = hideIfParse( $el, spec[1] );
132                                 $fields = v[0];
133                                 func = v[1];
134                                 return [ $fields, function () {
135                                         return !func();
136                                 } ];
138                         case '===':
139                         case '!==':
140                                 if ( l !== 3 ) {
141                                         throw new Error( op + ' takes exactly two parameters' );
142                                 }
143                                 $field = hideIfGetField( $el, spec[1] );
144                                 if ( !$field ) {
145                                         return [ $(), function () {
146                                                 return false;
147                                         } ];
148                                 }
149                                 v = spec[2];
151                                 if ( $field.first().prop( 'type' ) === 'radio' ||
152                                         $field.first().prop( 'type' ) === 'checkbox'
153                                 ) {
154                                         getVal = function () {
155                                                 var $selected = $field.filter( ':checked' );
156                                                 return $selected.length ? $selected.val() : '';
157                                         };
158                                 } else {
159                                         getVal = function () {
160                                                 return $field.val();
161                                         };
162                                 }
164                                 switch ( op ) {
165                                         case '===':
166                                                 func = function () {
167                                                         return getVal() === v;
168                                                 };
169                                                 break;
170                                         case '!==':
171                                                 func = function () {
172                                                         return getVal() !== v;
173                                                 };
174                                                 break;
175                                 }
177                                 return [ $field, func ];
179                         default:
180                                 throw new Error( 'Unrecognized operation \'' + op + '\'' );
181                 }
182         }
184         /**
185          * jQuery plugin to fade or snap to visible state.
186          *
187          * @param {boolean} [instantToggle=false]
188          * @return {jQuery}
189          * @chainable
190          */
191         $.fn.goIn = function ( instantToggle ) {
192                 if ( instantToggle === true ) {
193                         return this.show();
194                 }
195                 return this.stop( true, true ).fadeIn();
196         };
198         /**
199          * jQuery plugin to fade or snap to hiding state.
200          *
201          * @param {boolean} [instantToggle=false]
202          * @return jQuery
203          * @chainable
204          */
205         $.fn.goOut = function ( instantToggle ) {
206                 if ( instantToggle === true ) {
207                         return this.hide();
208                 }
209                 return this.stop( true, true ).fadeOut();
210         };
212         /**
213          * Bind a function to the jQuery object via live(), and also immediately trigger
214          * the function on the objects with an 'instant' parameter set to true.
215          *
216          * @method liveAndTestAtStart
217          * @deprecated since 1.24 Use .on() and .each() directly.
218          * @param {Function} callback
219          * @param {boolean|jQuery.Event} callback.immediate True when the event is called immediately,
220          *  an event object when triggered from an event.
221          * @return jQuery
222          * @chainable
223          */
224         mw.log.deprecate( $.fn, 'liveAndTestAtStart', function ( callback ) {
225                 this
226                         // Can't really migrate to .on() generically, needs knowledge of
227                         // calling code to know the correct selector. Fix callers and
228                         // get rid of this .liveAndTestAtStart() hack.
229                         .live( 'change', callback )
230                         .each( function () {
231                                 callback.call( this, true );
232                         } );
233         } );
235         function enhance( $root ) {
237                 /**
238                  * @ignore
239                  * @param {boolean|jQuery.Event} instant
240                  */
241                 function handleSelectOrOther( instant ) {
242                         var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
243                         $other = $other.add( $other.siblings( 'br' ) );
244                         if ( $( this ).val() === 'other' ) {
245                                 $other.goIn( instant );
246                         } else {
247                                 $other.goOut( instant );
248                         }
249                 }
251                 // Animate the SelectOrOther fields, to only show the text field when
252                 // 'other' is selected.
253                 $root
254                         .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther )
255                         .each( function () {
256                                 handleSelectOrOther.call( this, true );
257                         } );
259                 // Set up hide-if elements
260                 $root.find( '.mw-htmlform-hide-if' ).each( function () {
261                         var v, $fields, test, func,
262                                 $el = $( this ),
263                                 spec = $el.data( 'hideIf' );
265                         if ( !spec ) {
266                                 return;
267                         }
269                         v = hideIfParse( $el, spec );
270                         $fields = v[0];
271                         test = v[1];
272                         func = function () {
273                                 if ( test() ) {
274                                         $el.hide();
275                                 } else {
276                                         $el.show();
277                                 }
278                         };
279                         $fields.on( 'change', func );
280                         func();
281                 } );
283                 function addMulti( $oldContainer, $container ) {
284                         var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
285                                 oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
286                                 $select = $( '<select>' ),
287                                 dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
288                         oldClass = $.trim( oldClass );
289                         $select.attr( {
290                                 name: name,
291                                 multiple: 'multiple',
292                                 'data-placeholder': dataPlaceholder.plain(),
293                                 'class': 'htmlform-chzn-select mw-input ' + oldClass
294                         } );
295                         $oldContainer.find( 'input' ).each( function () {
296                                 var $oldInput = $( this ),
297                                 checked = $oldInput.prop( 'checked' ),
298                                 $option = $( '<option>' );
299                                 $option.prop( 'value', $oldInput.prop( 'value' ) );
300                                 if ( checked ) {
301                                         $option.prop( 'selected', true );
302                                 }
303                                 $option.text( $oldInput.prop( 'value' ) );
304                                 $select.append( $option );
305                         } );
306                         $container.append( $select );
307                 }
309                 function convertCheckboxesToMulti( $oldContainer, type ) {
310                         var $fieldLabel = $( '<td>' ),
311                         $td = $( '<td>' ),
312                         $fieldLabelText = $( '<label>' ),
313                         $container;
314                         if ( type === 'tr' ) {
315                                 addMulti( $oldContainer, $td );
316                                 $container = $( '<tr>' );
317                                 $container.append( $td );
318                         } else if ( type === 'div' ) {
319                                 $fieldLabel = $( '<div>' );
320                                 $container = $( '<div>' );
321                                 addMulti( $oldContainer, $container );
322                         }
323                         $fieldLabel.attr( 'class', 'mw-label' );
324                         $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
325                         $fieldLabel.append( $fieldLabelText );
326                         $container.prepend( $fieldLabel );
327                         $oldContainer.replaceWith( $container );
328                         return $container;
329                 }
331                 if ( $root.find( '.mw-chosen' ).length ) {
332                         mw.loader.using( 'jquery.chosen', function () {
333                                 $root.find( '.mw-chosen' ).each( function () {
334                                         var type = this.nodeName.toLowerCase(),
335                                                 $converted = convertCheckboxesToMulti( $( this ), type );
336                                         $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
337                                 } );
338                         } );
339                 }
341                 var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
342                 if ( $matrixTooltips.length ) {
343                         mw.loader.using( 'jquery.tipsy', function () {
344                                 $matrixTooltips.tipsy( { gravity: 's' } );
345                         } );
346                 }
348                 // Add/remove cloner clones without having to resubmit the form
349                 $root.find( '.mw-htmlform-cloner-delete-button' ).click( function ( ev ) {
350                         ev.preventDefault();
351                         $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
352                 } );
354                 $root.find( '.mw-htmlform-cloner-create-button' ).click( function ( ev ) {
355                         var $ul, $li, html;
357                         ev.preventDefault();
359                         $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
361                         html = $ul.data( 'template' ).replace(
362                                 $ul.data( 'uniqueId' ), 'clone' + ( ++cloneCounter ), 'g'
363                         );
365                         $li = $( '<li>' )
366                                 .addClass( 'mw-htmlform-cloner-li' )
367                                 .html( html )
368                                 .appendTo( $ul );
370                         enhance( $li );
371                 } );
373                 mw.hook( 'htmlform.enhance' ).fire( $root );
375         }
377         $( function () {
378                 enhance( $( document ) );
379         } );
381         /**
382          * @class jQuery
383          * @mixins jQuery.plugin.htmlform
384          */
385 }( mediaWiki, jQuery ) );