From: Bartosz DziewoƄski Date: Sat, 30 Jul 2016 23:15:07 +0000 (+0200) Subject: Split the 'mediawiki.htmlform' module code into multiple files X-Git-Tag: 1.31.0-rc.0~5985^2 X-Git-Url: http://git.cyclocoop.org/%24image?a=commitdiff_plain;h=e1959b29741761edf413256becd63fe692351f6c;p=lhc%2Fweb%2Fwiklou.git Split the 'mediawiki.htmlform' module code into multiple files This module implements several related, but separate enhancements to HTMLForm. While it makes sense to use a single ResourceLoader module to serve this code, it doesn't make sense to keep all of it in a single file. It was approaching 500 lines of code, and pieces of the separate features were mixed together. This commit mostly shuffles code around, only tweaking some indentation, 'var' statements and function wrappers. There is one small functional change: the enhancements now use the 'htmlform.enhance' mw.hook, which is fired on document-ready and when new fields are added dynamically. Previously it was only used to allow extensions to define their own HTMLForm enhancements. (Also moved HTMLForm styles into the same directory as the newly-split scripts.) Change-Id: I22054b39868239ddb59317dadfaaa067653f8804 --- diff --git a/resources/Resources.php b/resources/Resources.php index d2ee1cdc0b..1372a8e8f0 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1068,7 +1068,16 @@ return [ 'styles' => 'resources/src/mediawiki/mediawiki.hlist.css', ], 'mediawiki.htmlform' => [ - 'scripts' => 'resources/src/mediawiki/mediawiki.htmlform.js', + 'scripts' => [ + 'resources/src/mediawiki/htmlform/htmlform.js', + 'resources/src/mediawiki/htmlform/autocomplete.js', + 'resources/src/mediawiki/htmlform/checkmatrix.js', + 'resources/src/mediawiki/htmlform/cloner.js', + 'resources/src/mediawiki/htmlform/hide-if.js', + 'resources/src/mediawiki/htmlform/multiselect.js', + 'resources/src/mediawiki/htmlform/selectandother.js', + 'resources/src/mediawiki/htmlform/selectorother.js', + ], 'dependencies' => [ 'mediawiki.RegExp', 'jquery.byteLimit', @@ -1081,12 +1090,12 @@ return [ 'targets' => [ 'desktop', 'mobile' ], ], 'mediawiki.htmlform.styles' => [ - 'styles' => 'resources/src/mediawiki/mediawiki.htmlform.css', + 'styles' => 'resources/src/mediawiki/htmlform/styles.css', 'position' => 'top', 'targets' => [ 'desktop', 'mobile' ], ], 'mediawiki.htmlform.ooui.styles' => [ - 'styles' => 'resources/src/mediawiki/mediawiki.htmlform.ooui.css', + 'styles' => 'resources/src/mediawiki/htmlform/ooui.styles.css', 'position' => 'top', 'targets' => [ 'desktop', 'mobile' ], ], diff --git a/resources/src/mediawiki/htmlform/autocomplete.js b/resources/src/mediawiki/htmlform/autocomplete.js new file mode 100644 index 0000000000..8157975560 --- /dev/null +++ b/resources/src/mediawiki/htmlform/autocomplete.js @@ -0,0 +1,25 @@ +/* + * HTMLForm enhancements: + * Set up autocomplete fields. + */ +( function ( mw, $ ) { + + mw.hook( 'htmlform.enhance' ).add( function ( $root ) { + var $autocomplete = $root.find( '.mw-htmlform-autocomplete' ); + if ( $autocomplete.length ) { + mw.loader.using( 'jquery.suggestions', function () { + $autocomplete.suggestions( { + fetch: function ( val ) { + var $el = $( this ); + $el.suggestions( 'suggestions', + $.grep( $el.data( 'autocomplete' ), function ( v ) { + return v.indexOf( val ) === 0; + } ) + ); + } + } ); + } ); + } + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/htmlform/checkmatrix.js b/resources/src/mediawiki/htmlform/checkmatrix.js new file mode 100644 index 0000000000..b825f12b98 --- /dev/null +++ b/resources/src/mediawiki/htmlform/checkmatrix.js @@ -0,0 +1,16 @@ +/* + * HTMLForm enhancements: + * Show fancy tooltips for checkmatrix fields. + */ +( function ( mw ) { + + mw.hook( 'htmlform.enhance' ).add( function ( $root ) { + var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' ); + if ( $matrixTooltips.length ) { + mw.loader.using( 'jquery.tipsy', function () { + $matrixTooltips.tipsy( { gravity: 's' } ); + } ); + } + } ); + +}( mediaWiki ) ); diff --git a/resources/src/mediawiki/htmlform/cloner.js b/resources/src/mediawiki/htmlform/cloner.js new file mode 100644 index 0000000000..ab81580bee --- /dev/null +++ b/resources/src/mediawiki/htmlform/cloner.js @@ -0,0 +1,36 @@ +/* + * HTMLForm enhancements: + * Add/remove cloner clones without having to resubmit the form. + */ +( function ( mw, $ ) { + + var cloneCounter = 0; + + mw.hook( 'htmlform.enhance' ).add( function ( $root ) { + $root.find( '.mw-htmlform-cloner-delete-button' ).filter( ':input' ).click( function ( ev ) { + ev.preventDefault(); + $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove(); + } ); + + $root.find( '.mw-htmlform-cloner-create-button' ).filter( ':input' ).click( function ( ev ) { + var $ul, $li, html; + + ev.preventDefault(); + + $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' ); + + html = $ul.data( 'template' ).replace( + new RegExp( mw.RegExp.escape( $ul.data( 'uniqueId' ) ), 'g' ), + 'clone' + ( ++cloneCounter ) + ); + + $li = $( '
  • ' ) + .addClass( 'mw-htmlform-cloner-li' ) + .html( html ) + .appendTo( $ul ); + + mw.hook( 'htmlform.enhance' ).fire( $li ); + } ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/htmlform/hide-if.js b/resources/src/mediawiki/htmlform/hide-if.js new file mode 100644 index 0000000000..f739746f83 --- /dev/null +++ b/resources/src/mediawiki/htmlform/hide-if.js @@ -0,0 +1,212 @@ +/* + * 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|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. + * + * @ignore + * @private + * @param {jQuery} $el + * @param {Array} spec + * @return {Array} + * @return {jQuery} return.0 Dependent fields + * @return {Function} return.1 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 = fields.concat( v[ 0 ].toArray() ); + 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 + '\'' ); + } + } + + mw.hook( 'htmlform.enhance' ).add( function ( $root ) { + $root.find( '.mw-htmlform-hide-if' ).each( function () { + var v, $fields, test, func, + $el = $( this ), + spec = $el.data( 'hideIf' ); + + if ( !spec ) { + return; + } + + v = hideIfParse( $el, spec ); + $fields = v[ 0 ]; + test = v[ 1 ]; + func = function () { + if ( test() ) { + $el.hide(); + } else { + $el.show(); + } + }; + $fields.on( 'change', func ); + func(); + } ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/htmlform/htmlform.js b/resources/src/mediawiki/htmlform/htmlform.js new file mode 100644 index 0000000000..19f8f3ece5 --- /dev/null +++ b/resources/src/mediawiki/htmlform/htmlform.js @@ -0,0 +1,7 @@ +( function ( mw, $ ) { + + $( function () { + mw.hook( 'htmlform.enhance' ).fire( $( document ) ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/htmlform/images/question.png b/resources/src/mediawiki/htmlform/images/question.png new file mode 100644 index 0000000000..acce58c8ce Binary files /dev/null and b/resources/src/mediawiki/htmlform/images/question.png differ diff --git a/resources/src/mediawiki/htmlform/images/question.svg b/resources/src/mediawiki/htmlform/images/question.svg new file mode 100644 index 0000000000..98fbe8dd75 --- /dev/null +++ b/resources/src/mediawiki/htmlform/images/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/src/mediawiki/htmlform/multiselect.js b/resources/src/mediawiki/htmlform/multiselect.js new file mode 100644 index 0000000000..3cdab3c63c --- /dev/null +++ b/resources/src/mediawiki/htmlform/multiselect.js @@ -0,0 +1,67 @@ +/* + * HTMLForm enhancements: + * Convert multiselect fields from checkboxes to Chosen selector when requested. + */ +( function ( mw, $ ) { + + 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 = $( '' ), - 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 = $( '
  • ' ) - .addClass( 'mw-htmlform-cloner-li' ) - .html( html ) - .appendTo( $ul ); - - enhance( $li ); - } ); - - mw.hook( 'htmlform.enhance' ).fire( $root ); - - } - - $( function () { - enhance( $( document ) ); - } ); - - /** - * @class jQuery - * @mixins jQuery.plugin.htmlform - */ -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/mediawiki.htmlform.ooui.css b/resources/src/mediawiki/mediawiki.htmlform.ooui.css deleted file mode 100644 index a9e75d7163..0000000000 --- a/resources/src/mediawiki/mediawiki.htmlform.ooui.css +++ /dev/null @@ -1,29 +0,0 @@ -/* OOUIHTMLForm styles */ - -.mw-htmlform-ooui-wrapper { - margin: 1em 0; -} - -.mw-htmlform-ooui .mw-htmlform-submit-buttons { - margin-top: 1em; -} - -.mw-htmlform-ooui .mw-htmlform-field-HTMLCheckMatrix, -.mw-htmlform-ooui .mw-htmlform-matrix, -.mw-htmlform-ooui .mw-htmlform-matrix tr { - width: 100%; -} - -.mw-htmlform-ooui .mw-htmlform-matrix tr td.first { - margin-right: 5%; - width: 39%; -} - -/* Flatlist styling for PHP widgets... */ -.mw-htmlform-flatlist .oo-ui-fieldLayout-align-inline, -/* ...and for JS widgets */ -.mw-htmlform-flatlist .oo-ui-optionWidget, -.mw-htmlform-flatlist .oo-ui-multioptionWidget { - display: inline-block; - margin-right: 1em; -}