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
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.
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
++ ) {
81 if ( !funcs
[ i
]() ) {
92 for ( i
= 0; i
< l
; i
++ ) {
104 for ( i
= 0; i
< l
; i
++ ) {
105 if ( !funcs
[ i
]() ) {
116 for ( i
= 0; i
< l
; i
++ ) {
117 if ( funcs
[ 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();
216 function enhance( $root
) {
217 var $matrixTooltips
, $autocomplete
,
218 // cache the separator to avoid object creation on each keypress
219 colonSeparator
= mw
.message( 'colon-separator' ).text();
223 * @param {boolean|jQuery.Event} instant
225 function handleSelectOrOther( instant
) {
226 var $other
= $root
.find( '#' + $( this ).attr( 'id' ) + '-other' );
227 $other
= $other
.add( $other
.siblings( 'br' ) );
228 if ( $( this ).val() === 'other' ) {
229 $other
.goIn( instant
);
231 $other
.goOut( instant
);
235 // Animate the SelectOrOther fields, to only show the text field when
236 // 'other' is selected.
238 .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther
)
240 handleSelectOrOther
.call( this, true );
243 // Add a dynamic max length to the reason field of SelectAndOther
244 // This checks the length together with the value from the select field
245 // When the reason list is changed and the bytelimit is longer than the allowed,
248 .find( '.mw-htmlform-select-and-other-field' )
250 var $this = $( this ),
251 // find the reason list
252 $reasonList
= $root
.find( '#' + $this.data( 'id-select' ) ),
253 // cache the current selection to avoid expensive lookup
254 currentValReasonList
= $reasonList
.val();
256 $reasonList
.change( function () {
257 currentValReasonList
= $reasonList
.val();
260 $this.byteLimit( function ( input
) {
261 // Should be built the same as in HTMLSelectAndOtherField::loadDataFromRequest
262 var comment
= currentValReasonList
;
263 if ( comment
=== 'other' ) {
265 } else if ( input
!== '' ) {
266 // Entry from drop down menu + additional comment
267 comment
+= colonSeparator
+ input
;
273 // Set up hide-if elements
274 $root
.find( '.mw-htmlform-hide-if' ).each( function () {
275 var v
, $fields
, test
, func
,
277 spec
= $el
.data( 'hideIf' );
283 v
= hideIfParse( $el
, spec
);
293 $fields
.on( 'change', func
);
297 function addMulti( $oldContainer
, $container
) {
298 var name
= $oldContainer
.find( 'input:first-child' ).attr( 'name' ),
299 oldClass
= ( ' ' + $oldContainer
.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
300 $select
= $( '<select>' ),
301 dataPlaceholder
= mw
.message( 'htmlform-chosen-placeholder' );
302 oldClass
= $.trim( oldClass
);
305 multiple
: 'multiple',
306 'data-placeholder': dataPlaceholder
.plain(),
307 'class': 'htmlform-chzn-select mw-input ' + oldClass
309 $oldContainer
.find( 'input' ).each( function () {
310 var $oldInput
= $( this ),
311 checked
= $oldInput
.prop( 'checked' ),
312 $option
= $( '<option>' );
313 $option
.prop( 'value', $oldInput
.prop( 'value' ) );
315 $option
.prop( 'selected', true );
317 $option
.text( $oldInput
.prop( 'value' ) );
318 $select
.append( $option
);
320 $container
.append( $select
);
323 function convertCheckboxesToMulti( $oldContainer
, type
) {
324 var $fieldLabel
= $( '<td>' ),
326 $fieldLabelText
= $( '<label>' ),
328 if ( type
=== 'tr' ) {
329 addMulti( $oldContainer
, $td
);
330 $container
= $( '<tr>' );
331 $container
.append( $td
);
332 } else if ( type
=== 'div' ) {
333 $fieldLabel
= $( '<div>' );
334 $container
= $( '<div>' );
335 addMulti( $oldContainer
, $container
);
337 $fieldLabel
.attr( 'class', 'mw-label' );
338 $fieldLabelText
.text( $oldContainer
.find( '.mw-label label' ).text() );
339 $fieldLabel
.append( $fieldLabelText
);
340 $container
.prepend( $fieldLabel
);
341 $oldContainer
.replaceWith( $container
);
345 if ( $root
.find( '.mw-chosen' ).length
) {
346 mw
.loader
.using( 'jquery.chosen', function () {
347 $root
.find( '.mw-chosen' ).each( function () {
348 var type
= this.nodeName
.toLowerCase(),
349 $converted
= convertCheckboxesToMulti( $( this ), type
);
350 $converted
.find( '.htmlform-chzn-select' ).chosen( { width
: 'auto' } );
355 $matrixTooltips
= $root
.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
356 if ( $matrixTooltips
.length
) {
357 mw
.loader
.using( 'jquery.tipsy', function () {
358 $matrixTooltips
.tipsy( { gravity
: 's' } );
362 // Set up autocomplete fields
363 $autocomplete
= $root
.find( '.mw-htmlform-autocomplete' );
364 if ( $autocomplete
.length
) {
365 mw
.loader
.using( 'jquery.suggestions', function () {
366 $autocomplete
.suggestions( {
367 fetch: function ( val
) {
369 $el
.suggestions( 'suggestions',
370 $.grep( $el
.data( 'autocomplete' ), function ( v
) {
371 return v
.indexOf( val
) === 0;
379 // Add/remove cloner clones without having to resubmit the form
380 $root
.find( '.mw-htmlform-cloner-delete-button' ).filter( ':input' ).click( function ( ev
) {
382 $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
385 $root
.find( '.mw-htmlform-cloner-create-button' ).filter( ':input' ).click( function ( ev
) {
390 $ul
= $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
392 html
= $ul
.data( 'template' ).replace(
393 new RegExp( mw
.RegExp
.escape( $ul
.data( 'uniqueId' ) ), 'g' ),
394 'clone' + ( ++cloneCounter
)
398 .addClass( 'mw-htmlform-cloner-li' )
405 mw
.hook( 'htmlform.enhance' ).fire( $root
);
410 enhance( $( document
) );
415 * @mixins jQuery.plugin.htmlform
417 }( mediaWiki
, jQuery
) );