123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- /*
- * HTMLForm enhancements:
- * Set up 'hide-if' behaviors for form fields that have them.
- */
- ( function () {
- /**
- * 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]").
- *
- * @ignore
- * @private
- * @param {jQuery} $el
- * @param {string} name
- * @return {jQuery|OO.ui.Widget|null}
- */
- function hideIfGetField( $el, name ) {
- var $found, $p, $widget,
- 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 ) {
- $widget = $found.closest( '.oo-ui-widget[data-ooui]' );
- if ( $widget.length ) {
- return OO.ui.Widget.static.infuse( $widget );
- }
- return $found;
- }
- }
- return null;
- }
- /**
- * Helper function for hide-if to return a test function and list of
- * dependent fields for a hide-if specification.
- *
- * @ignore
- * @private
- * @param {jQuery} $el
- * @param {Array} spec
- * @return {Array}
- * @return {Array} return.0 Dependent fields, array of jQuery objects or OO.ui.Widgets
- * @return {Function} return.1 Test function
- */
- function hideIfParse( $el, spec ) {
- var op, i, l, v, field, $field, 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 ( !Array.isArray( spec[ i ] ) ) {
- throw new Error( op + ' parameters must be arrays' );
- }
- v = hideIfParse( $el, spec[ i ] );
- fields = fields.concat( v[ 0 ] );
- funcs.push( v[ 1 ] );
- }
- 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 ( !Array.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 instanceof $ ) ) {
- // field is a OO.ui.Widget
- if ( field.supports( 'isSelected' ) ) {
- getVal = function () {
- var selected = field.isSelected();
- return selected ? field.getValue() : '';
- };
- } else {
- getVal = function () {
- return field.getValue();
- };
- }
- } else {
- $field = $( field );
- if ( $field.prop( 'type' ) === 'radio' || $field.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 + '\'' );
- }
- }
- mw.hook( 'htmlform.enhance' ).add( function ( $root ) {
- var
- $fields = $root.find( '.mw-htmlform-hide-if' ),
- $oouiFields = $fields.filter( '[data-ooui]' ),
- modules = [];
- if ( $oouiFields.length ) {
- modules.push( 'mediawiki.htmlform.ooui' );
- $oouiFields.each( function () {
- var data, extraModules,
- $el = $( this );
- data = $el.data( 'mw-modules' );
- if ( data ) {
- // We can trust this value, 'data-mw-*' attributes are banned from user content in Sanitizer
- extraModules = data.split( ',' );
- modules.push.apply( modules, extraModules );
- }
- } );
- }
- mw.loader.using( modules ).done( function () {
- $fields.each( function () {
- var v, i, fields, test, func, spec, self,
- $el = $( this );
- if ( $el.is( '[data-ooui]' ) ) {
- // self should be a FieldLayout that mixes in mw.htmlform.Element
- self = OO.ui.FieldLayout.static.infuse( $el );
- spec = self.hideIf;
- // The original element has been replaced with infused one
- $el = self.$element;
- } else {
- self = $el;
- spec = $el.data( 'hideIf' );
- }
- if ( !spec ) {
- return;
- }
- v = hideIfParse( $el, spec );
- fields = v[ 0 ];
- test = v[ 1 ];
- // The .toggle() method works mostly the same for jQuery objects and OO.ui.Widget
- func = function () {
- var shouldHide = test();
- self.toggle( !shouldHide );
- // It is impossible to submit a form with hidden fields failing validation, e.g. one that
- // is required. However, validity is not checked for disabled fields, as these are not
- // submitted with the form. So we should also disable fields when hiding them.
- if ( self instanceof $ ) {
- // This also finds elements inside any nested fields (in case of HTMLFormFieldCloner),
- // which is problematic. But it works because:
- // * HTMLFormFieldCloner::createFieldsForKey() copies 'hide-if' rules to nested fields
- // * jQuery collections like $fields are in document order, so we register event
- // handlers for parents first
- // * Event handlers are fired in the order they were registered, so even if the handler
- // for parent messed up the child, the handle for child will run next and fix it
- self.find( 'input, textarea, select' ).each( function () {
- var $this = $( this );
- if ( shouldHide ) {
- if ( $this.data( 'was-disabled' ) === undefined ) {
- $this.data( 'was-disabled', $this.prop( 'disabled' ) );
- }
- $this.prop( 'disabled', true );
- } else {
- $this.prop( 'disabled', $this.data( 'was-disabled' ) );
- }
- } );
- } else {
- // self is a OO.ui.FieldLayout
- if ( shouldHide ) {
- if ( self.wasDisabled === undefined ) {
- self.wasDisabled = self.fieldWidget.isDisabled();
- }
- self.fieldWidget.setDisabled( true );
- } else if ( self.wasDisabled !== undefined ) {
- self.fieldWidget.setDisabled( self.wasDisabled );
- }
- }
- };
- for ( i = 0; i < fields.length; i++ ) {
- // The .on() method works mostly the same for jQuery objects and OO.ui.Widget
- fields[ i ].on( 'change', func );
- }
- func();
- } );
- } );
- } );
- }() );
|