summaryrefslogtreecommitdiff
path: root/www/wiki/resources/src/mediawiki/htmlform/hide-if.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/resources/src/mediawiki/htmlform/hide-if.js')
-rw-r--r--www/wiki/resources/src/mediawiki/htmlform/hide-if.js295
1 files changed, 295 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki/htmlform/hide-if.js b/www/wiki/resources/src/mediawiki/htmlform/hide-if.js
new file mode 100644
index 00000000..6d3c9fd1
--- /dev/null
+++ b/www/wiki/resources/src/mediawiki/htmlform/hide-if.js
@@ -0,0 +1,295 @@
+/*
+ * HTMLForm enhancements:
+ * Set up 'hide-if' behaviors for form fields that have them.
+ */
+( function ( mw, $ ) {
+
+ /**
+ * 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 jQuery ) ) {
+ // 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 jQuery ) {
+ // 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();
+ } );
+ } );
+ } );
+
+}( mediaWiki, jQuery ) );