2 * Utility functions for jazzing up HTMLForm elements.
4 * @class jQuery.plugin.htmlform
11 * Helper function for hide-if to find the nearby form field.
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
19 * @param {jQuery} element
20 * @param {string} name
21 * @return {jQuery|null}
23 function hideIfGetField( $el, name ) {
25 suffix = name.replace( /^([^\[]+)/, '[$1]' );
27 function nameFilter() {
28 return this.name === name ||
29 ( this.name === ( 'wp' + name ) ) ||
30 this.name.slice( -suffix.length ) === suffix;
33 for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
34 $found = $p.find( '[name]' ).filter( nameFilter );
35 if ( $found.length ) {
43 * Helper function for hide-if to return a test function and list of
44 * dependent fields for a hide-if specification.
47 * @param {jQuery} element
48 * @param {Array} hide-if spec
50 * @return {jQuery} return.0 Dependent fields
51 * @return {Function} return.1 Test function
53 function hideIfParse( $el, spec ) {
54 var op, i, l, v, $field, $fields, fields, func, funcs, getVal;
65 for ( i = 1; i < l; i++ ) {
66 if ( !$.isArray( spec[i] ) ) {
67 throw new Error( op + ' parameters must be arrays' );
69 v = hideIfParse( $el, spec[i] );
70 fields = fields.concat( v[0].toArray() );
73 $fields = $( fields );
80 for ( i = 0; i < l; i++ ) {
92 for ( i = 0; i < l; i++ ) {
104 for ( i = 0; i < l; i++ ) {
116 for ( i = 0; i < l; i++ ) {
126 return [ $fields, func ];
130 throw new Error( 'NOT takes exactly one parameter' );
132 if ( !$.isArray( spec[1] ) ) {
133 throw new Error( 'NOT parameters must be arrays' );
135 v = hideIfParse( $el, spec[1] );
138 return [ $fields, function () {
145 throw new Error( op + ' takes exactly two parameters' );
147 $field = hideIfGetField( $el, spec[1] );
149 return [ $(), function () {
155 if ( $field.first().prop( 'type' ) === 'radio' ||
156 $field.first().prop( 'type' ) === 'checkbox'
158 getVal = function () {
159 var $selected = $field.filter( ':checked' );
160 return $selected.length ? $selected.val() : '';
163 getVal = function () {
171 return getVal() === v;
176 return getVal() !== v;
181 return [ $field, func ];
184 throw new Error( 'Unrecognized operation \'' + op + '\'' );
189 * jQuery plugin to fade or snap to visible state.
191 * @param {boolean} [instantToggle=false]
195 $.fn.goIn = function ( instantToggle ) {
196 if ( instantToggle === true ) {
199 return this.stop( true, true ).fadeIn();
203 * jQuery plugin to fade or snap to hiding state.
205 * @param {boolean} [instantToggle=false]
209 $.fn.goOut = function ( instantToggle ) {
210 if ( instantToggle === true ) {
213 return this.stop( true, true ).fadeOut();
217 * Bind a function to the jQuery object via live(), and also immediately trigger
218 * the function on the objects with an 'instant' parameter set to true.
220 * @method liveAndTestAtStart
221 * @deprecated since 1.24 Use .on() and .each() directly.
222 * @param {Function} callback
223 * @param {boolean|jQuery.Event} callback.immediate True when the event is called immediately,
224 * an event object when triggered from an event.
228 mw.log.deprecate( $.fn, 'liveAndTestAtStart', function ( callback ) {
230 // Can't really migrate to .on() generically, needs knowledge of
231 // calling code to know the correct selector. Fix callers and
232 // get rid of this .liveAndTestAtStart() hack.
233 .live( 'change', callback )
235 callback.call( this, true );
239 function enhance( $root ) {
240 var $matrixTooltips, $autocomplete,
241 // cache the separator to avoid object creation on each keypress
242 colonSeparator = mw.message( 'colon-separator' ).text();
246 * @param {boolean|jQuery.Event} instant
248 function handleSelectOrOther( instant ) {
249 var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
250 $other = $other.add( $other.siblings( 'br' ) );
251 if ( $( this ).val() === 'other' ) {
252 $other.goIn( instant );
254 $other.goOut( instant );
258 // Animate the SelectOrOther fields, to only show the text field when
259 // 'other' is selected.
261 .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther )
263 handleSelectOrOther.call( this, true );
266 // Add a dynamic max length to the reason field of SelectAndOther
267 // This checks the length together with the value from the select field
268 // When the reason list is changed and the bytelimit is longer than the allowed,
271 .find( '.mw-htmlform-select-and-other-field' )
273 var $this = $( this ),
274 // find the reason list
275 $reasonList = $root.find( '#' + $this.data( 'id-select' ) ),
276 // cache the current selection to avoid expensive lookup
277 currentValReasonList = $reasonList.val();
279 $reasonList.change( function () {
280 currentValReasonList = $reasonList.val();
283 $this.byteLimit( function ( input ) {
284 // Should be built the same as in HTMLSelectAndOtherField::loadDataFromRequest
285 var comment = currentValReasonList;
286 if ( comment === 'other' ) {
288 } else if ( input !== '' ) {
289 // Entry from drop down menu + additional comment
290 comment += colonSeparator + input;
296 // Set up hide-if elements
297 $root.find( '.mw-htmlform-hide-if' ).each( function () {
298 var v, $fields, test, func,
300 spec = $el.data( 'hideIf' );
306 v = hideIfParse( $el, spec );
316 $fields.on( 'change', func );
320 function addMulti( $oldContainer, $container ) {
321 var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
322 oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
323 $select = $( '<select>' ),
324 dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
325 oldClass = $.trim( oldClass );
328 multiple: 'multiple',
329 'data-placeholder': dataPlaceholder.plain(),
330 'class': 'htmlform-chzn-select mw-input ' + oldClass
332 $oldContainer.find( 'input' ).each( function () {
333 var $oldInput = $( this ),
334 checked = $oldInput.prop( 'checked' ),
335 $option = $( '<option>' );
336 $option.prop( 'value', $oldInput.prop( 'value' ) );
338 $option.prop( 'selected', true );
340 $option.text( $oldInput.prop( 'value' ) );
341 $select.append( $option );
343 $container.append( $select );
346 function convertCheckboxesToMulti( $oldContainer, type ) {
347 var $fieldLabel = $( '<td>' ),
349 $fieldLabelText = $( '<label>' ),
351 if ( type === 'tr' ) {
352 addMulti( $oldContainer, $td );
353 $container = $( '<tr>' );
354 $container.append( $td );
355 } else if ( type === 'div' ) {
356 $fieldLabel = $( '<div>' );
357 $container = $( '<div>' );
358 addMulti( $oldContainer, $container );
360 $fieldLabel.attr( 'class', 'mw-label' );
361 $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
362 $fieldLabel.append( $fieldLabelText );
363 $container.prepend( $fieldLabel );
364 $oldContainer.replaceWith( $container );
368 if ( $root.find( '.mw-chosen' ).length ) {
369 mw.loader.using( 'jquery.chosen', function () {
370 $root.find( '.mw-chosen' ).each( function () {
371 var type = this.nodeName.toLowerCase(),
372 $converted = convertCheckboxesToMulti( $( this ), type );
373 $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
378 $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
379 if ( $matrixTooltips.length ) {
380 mw.loader.using( 'jquery.tipsy', function () {
381 $matrixTooltips.tipsy( { gravity: 's' } );
385 // Set up autocomplete fields
386 $autocomplete = $root.find( '.mw-htmlform-autocomplete' );
387 if ( $autocomplete.length ) {
388 mw.loader.using( 'jquery.suggestions', function () {
389 $autocomplete.suggestions( {
390 fetch: function ( val ) {
392 $el.suggestions( 'suggestions',
393 $.grep( $el.data( 'autocomplete' ), function ( v ) {
394 return v.indexOf( val ) === 0;
402 // Add/remove cloner clones without having to resubmit the form
403 $root.find( '.mw-htmlform-cloner-delete-button' ).filter( ':input' ).click( function ( ev ) {
405 $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
408 $root.find( '.mw-htmlform-cloner-create-button' ).filter( ':input' ).click( function ( ev ) {
413 $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
415 html = $ul.data( 'template' ).replace(
416 new RegExp( $.escapeRE( $ul.data( 'uniqueId' ) ), 'g' ),
417 'clone' + ( ++cloneCounter )
421 .addClass( 'mw-htmlform-cloner-li' )
428 mw.hook( 'htmlform.enhance' ).fire( $root );
433 enhance( $( document ) );
438 * @mixins jQuery.plugin.htmlform
440 }( mediaWiki, jQuery ) );