Merge "mediawiki.searchSuggest: Show full article title as a tooltip for each suggestion"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.htmlform.js
index 5ba1a54..8be1321 100644 (file)
@@ -5,6 +5,182 @@
  */
 ( function ( mw, $ ) {
 
+       var cloneCounter = 0;
+
+       /**
+        * Helper function for hide-if to find the nearby form field.
+        *
+        * Find the closest match for the given name, "closest" being the minimum
+        * level of parents to go to find a form field matching the given name or
+        * ending in array keys matching the given name (e.g. "baz" matches
+        * "foo[bar][baz]").
+        *
+        * @param {jQuery} element
+        * @param {string} name
+        * @return {jQuery|null}
+        */
+       function hideIfGetField( $el, name ) {
+               var $found, $p,
+                       suffix = name.replace( /^([^\[]+)/, '[$1]' );
+
+               function nameFilter() {
+                       return this.name === name ||
+                               ( this.name === ( 'wp' + name ) ) ||
+                               this.name.slice( -suffix.length ) === suffix;
+               }
+
+               for ( $p = $el.parent(); $p.length > 0; $p = $p.parent() ) {
+                       $found = $p.find( '[name]' ).filter( nameFilter );
+                       if ( $found.length ) {
+                               return $found;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Helper function for hide-if to return a test function and list of
+        * dependent fields for a hide-if specification.
+        *
+        * @param {jQuery} element
+        * @param {Array} hide-if spec
+        * @return {Array} 2 elements: jQuery of dependent fields, and test function
+        */
+       function hideIfParse( $el, spec ) {
+               var op, i, l, v, $field, $fields, fields, func, funcs, getVal;
+
+               op = spec[0];
+               l = spec.length;
+               switch ( op ) {
+                       case 'AND':
+                       case 'OR':
+                       case 'NAND':
+                       case 'NOR':
+                               funcs = [];
+                               fields = [];
+                               for ( i = 1; i < l; i++ ) {
+                                       if ( !$.isArray( spec[i] ) ) {
+                                               throw new Error( op + ' parameters must be arrays' );
+                                       }
+                                       v = hideIfParse( $el, spec[i] );
+                                       fields.push( v[0] );
+                                       funcs.push( v[1] );
+                               }
+                               $fields = $( fields );
+
+                               l = funcs.length;
+                               switch ( op ) {
+                                       case 'AND':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( !funcs[i]() ) {
+                                                                       return false;
+                                                               }
+                                                       }
+                                                       return true;
+                                               };
+                                               break;
+
+                                       case 'OR':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( funcs[i]() ) {
+                                                                       return true;
+                                                               }
+                                                       }
+                                                       return false;
+                                               };
+                                               break;
+
+                                       case 'NAND':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( !funcs[i]() ) {
+                                                                       return true;
+                                                               }
+                                                       }
+                                                       return false;
+                                               };
+                                               break;
+
+                                       case 'NOR':
+                                               func = function () {
+                                                       var i;
+                                                       for ( i = 0; i < l; i++ ) {
+                                                               if ( funcs[i]() ) {
+                                                                       return false;
+                                                               }
+                                                       }
+                                                       return true;
+                                               };
+                                               break;
+                               }
+
+                               return [ $fields, func ];
+
+                       case 'NOT':
+                               if ( l !== 2 ) {
+                                       throw new Error( 'NOT takes exactly one parameter' );
+                               }
+                               if ( !$.isArray( spec[1] ) ) {
+                                       throw new Error( 'NOT parameters must be arrays' );
+                               }
+                               v = hideIfParse( $el, spec[1] );
+                               $fields = v[0];
+                               func = v[1];
+                               return [ $fields, function () {
+                                       return !func();
+                               } ];
+
+                       case '===':
+                       case '!==':
+                               if ( l !== 3 ) {
+                                       throw new Error( op + ' takes exactly two parameters' );
+                               }
+                               $field = hideIfGetField( $el, spec[1] );
+                               if ( !$field ) {
+                                       return [ $(), function () {
+                                               return false;
+                                       } ];
+                               }
+                               v = spec[2];
+
+                               if ( $field.first().prop( 'type' ) === 'radio' ||
+                                       $field.first().prop( 'type' ) === 'checkbox'
+                               ) {
+                                       getVal = function () {
+                                               var $selected = $field.filter( ':checked' );
+                                               return $selected.length ? $selected.val() : '';
+                                       };
+                               } else {
+                                       getVal = function () {
+                                               return $field.val();
+                                       };
+                               }
+
+                               switch ( op ) {
+                                       case '===':
+                                               func = function () {
+                                                       return getVal() === v;
+                                               };
+                                               break;
+                                       case '!==':
+                                               func = function () {
+                                                       return getVal() !== v;
+                                               };
+                                               break;
+                               }
+
+                               return [ $field, func ];
+
+                       default:
+                               throw new Error( 'Unrecognized operation \'' + op + '\'' );
+               }
+       }
+
        /**
         * jQuery plugin to fade or snap to visible state.
         *
         */
        $.fn.goIn = function ( instantToggle ) {
                if ( instantToggle === true ) {
-                       return $( this ).show();
+                       return this.show();
                }
-               return $( this ).stop( true, true ).fadeIn();
+               return this.stop( true, true ).fadeIn();
        };
 
        /**
         */
        $.fn.goOut = function ( instantToggle ) {
                if ( instantToggle === true ) {
-                       return $( this ).hide();
+                       return this.hide();
                }
-               return $( this ).stop( true, true ).fadeOut();
+               return this.stop( true, true ).fadeOut();
        };
 
        /**
         * Bind a function to the jQuery object via live(), and also immediately trigger
         * the function on the objects with an 'instant' parameter set to true.
+        *
+        * @method liveAndTestAtStart
+        * @deprecated since 1.24 Use .on() and .each() directly.
         * @param {Function} callback
         * @param {boolean|jQuery.Event} callback.immediate True when the event is called immediately,
         *  an event object when triggered from an event.
+        * @return jQuery
+        * @chainable
         */
-       $.fn.liveAndTestAtStart = function ( callback ) {
-               $( this )
+       mw.log.deprecate( $.fn, 'liveAndTestAtStart', function ( callback ) {
+               this
+                       // Can't really migrate to .on() generically, needs knowledge of
+                       // calling code to know the correct selector. Fix callers and
+                       // get rid of this .liveAndTestAtStart() hack.
                        .live( 'change', callback )
                        .each( function () {
                                callback.call( this, true );
                        } );
-       };
+       } );
 
-       $( function () {
+       function enhance( $root ) {
 
-               // Animate the SelectOrOther fields, to only show the text field when
-               // 'other' is selected.
-               $( '.mw-htmlform-select-or-other' ).liveAndTestAtStart( function ( instant ) {
-                       var $other = $( '#' + $( this ).attr( 'id' ) + '-other' );
+               /**
+                * @ignore
+                * @param {boolean|jQuery.Event} instant
+                */
+               function handleSelectOrOther( instant ) {
+                       var $other = $root.find( '#' + $( this ).attr( 'id' ) + '-other' );
                        $other = $other.add( $other.siblings( 'br' ) );
                        if ( $( this ).val() === 'other' ) {
                                $other.goIn( instant );
                        } else {
                                $other.goOut( instant );
                        }
-               } );
+               }
 
-       } );
+               // Animate the SelectOrOther fields, to only show the text field when
+               // 'other' is selected.
+               $root
+                       .on( 'change', '.mw-htmlform-select-or-other', handleSelectOrOther )
+                       .each( function () {
+                               handleSelectOrOther.call( this, true );
+                       } );
 
-       function addMulti( $oldContainer, $container ) {
-               var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
-                       oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
-                       $select = $( '<select>' ),
-                       dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
-               oldClass = $.trim( oldClass );
-               $select.attr( {
-                       name: name,
-                       multiple: 'multiple',
-                       'data-placeholder': dataPlaceholder.plain(),
-                       'class': 'htmlform-chzn-select mw-input ' + oldClass
-               } );
-               $oldContainer.find( 'input' ).each( function () {
-                       var $oldInput = $( this ),
-                       checked = $oldInput.prop( 'checked' ),
-                       $option = $( '<option>' );
-                       $option.prop( 'value', $oldInput.prop( 'value' ) );
-                       if ( checked ) {
-                               $option.prop( 'selected', true );
+               // Set up hide-if elements
+               $root.find( '.mw-htmlform-hide-if' ).each( function () {
+                       var v, $fields, test, func,
+                               $el = $( this ),
+                               spec = $el.data( 'hideIf' );
+
+                       if ( !spec ) {
+                               return;
                        }
-                       $option.text( $oldInput.prop( 'value' ) );
-                       $select.append( $option );
+
+                       v = hideIfParse( $el, spec );
+                       $fields = v[0];
+                       test = v[1];
+                       func = function () {
+                               if ( test() ) {
+                                       $el.hide();
+                               } else {
+                                       $el.show();
+                               }
+                       };
+                       $fields.on( 'change', func );
+                       func();
                } );
-               $container.append( $select );
-       }
 
-       function convertCheckboxesToMulti( $oldContainer, type ) {
-               var $fieldLabel = $( '<td>' ),
-               $td = $( '<td>' ),
-               $fieldLabelText = $( '<label>' ),
-               $container;
-               if ( type === 'tr' ) {
-                       addMulti( $oldContainer, $td );
-                       $container = $( '<tr>' );
-                       $container.append( $td );
-               } else if ( type === 'div' ) {
-                       $fieldLabel = $( '<div>' );
-                       $container = $( '<div>' );
-                       addMulti( $oldContainer, $container );
+               function addMulti( $oldContainer, $container ) {
+                       var name = $oldContainer.find( 'input:first-child' ).attr( 'name' ),
+                               oldClass = ( ' ' + $oldContainer.attr( 'class' ) + ' ' ).replace( /(mw-htmlform-field-HTMLMultiSelectField|mw-chosen)/g, '' ),
+                               $select = $( '<select>' ),
+                               dataPlaceholder = mw.message( 'htmlform-chosen-placeholder' );
+                       oldClass = $.trim( oldClass );
+                       $select.attr( {
+                               name: name,
+                               multiple: 'multiple',
+                               'data-placeholder': dataPlaceholder.plain(),
+                               'class': 'htmlform-chzn-select mw-input ' + oldClass
+                       } );
+                       $oldContainer.find( 'input' ).each( function () {
+                               var $oldInput = $( this ),
+                               checked = $oldInput.prop( 'checked' ),
+                               $option = $( '<option>' );
+                               $option.prop( 'value', $oldInput.prop( 'value' ) );
+                               if ( checked ) {
+                                       $option.prop( 'selected', true );
+                               }
+                               $option.text( $oldInput.prop( 'value' ) );
+                               $select.append( $option );
+                       } );
+                       $container.append( $select );
+               }
+
+               function convertCheckboxesToMulti( $oldContainer, type ) {
+                       var $fieldLabel = $( '<td>' ),
+                       $td = $( '<td>' ),
+                       $fieldLabelText = $( '<label>' ),
+                       $container;
+                       if ( type === 'tr' ) {
+                               addMulti( $oldContainer, $td );
+                               $container = $( '<tr>' );
+                               $container.append( $td );
+                       } else if ( type === 'div' ) {
+                               $fieldLabel = $( '<div>' );
+                               $container = $( '<div>' );
+                               addMulti( $oldContainer, $container );
+                       }
+                       $fieldLabel.attr( 'class', 'mw-label' );
+                       $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
+                       $fieldLabel.append( $fieldLabelText );
+                       $container.prepend( $fieldLabel );
+                       $oldContainer.replaceWith( $container );
+                       return $container;
                }
-               $fieldLabel.attr( 'class', 'mw-label' );
-               $fieldLabelText.text( $oldContainer.find( '.mw-label label' ).text() );
-               $fieldLabel.append( $fieldLabelText );
-               $container.prepend( $fieldLabel );
-               $oldContainer.replaceWith( $container );
-               return $container;
-       }
 
-       if ( $( '.mw-chosen' ).length ) {
-               mw.loader.using( 'jquery.chosen', function () {
-                       $( '.mw-chosen' ).each( function () {
-                               var type = this.nodeName.toLowerCase(),
-                                       $converted = convertCheckboxesToMulti( $( this ), type );
-                               $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
+               if ( $root.find( '.mw-chosen' ).length ) {
+                       mw.loader.using( 'jquery.chosen', function () {
+                               $root.find( '.mw-chosen' ).each( function () {
+                                       var type = this.nodeName.toLowerCase(),
+                                               $converted = convertCheckboxesToMulti( $( this ), type );
+                                       $converted.find( '.htmlform-chzn-select' ).chosen( { width: 'auto' } );
+                               } );
                        } );
-               } );
-       }
+               }
 
-       $( function () {
-               var $matrixTooltips = $( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
+               var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' );
                if ( $matrixTooltips.length ) {
                        mw.loader.using( 'jquery.tipsy', function () {
                                $matrixTooltips.tipsy( { gravity: 's' } );
                        } );
                }
+
+               // Add/remove cloner clones without having to resubmit the form
+               $root.find( '.mw-htmlform-cloner-delete-button' ).click( function ( ev ) {
+                       ev.preventDefault();
+                       $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
+               } );
+
+               $root.find( '.mw-htmlform-cloner-create-button' ).click( function ( ev ) {
+                       var $ul, $li, html;
+
+                       ev.preventDefault();
+
+                       $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
+
+                       html = $ul.data( 'template' ).replace(
+                               $ul.data( 'uniqueId' ), 'clone' + ( ++cloneCounter ), 'g'
+                       );
+
+                       $li = $( '<li>' )
+                               .addClass( 'mw-htmlform-cloner-li' )
+                               .html( html )
+                               .appendTo( $ul );
+
+                       enhance( $li );
+               } );
+
+               mw.hook( 'htmlform.enhance' ).fire( $root );
+
+       }
+
+       $( function () {
+               enhance( $( document ) );
        } );
 
        /**