From f637ad308f73a1686f8faee4b1123480f8417cda Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Fri, 27 Jun 2014 16:16:59 -0400 Subject: [PATCH] Add HTMLAutoCompleteSelectField This is much like the one OAuth has in Special:OAuthConsumerRegistration/propose, except it stores the autocompletion options in a data property rather than a global and uses jquery.suggestions rather than jquery.ui.autocomplete. Change-Id: I42473cea75f3706cc0125167f9191275ca6cb3b0 --- RELEASE-NOTES-1.24 | 1 + includes/AutoLoader.php | 1 + .../htmlform/HTMLAutoCompleteSelectField.php | 165 ++++++++++++++++++ includes/htmlform/HTMLForm.php | 1 + resources/src/mediawiki/mediawiki.htmlform.js | 20 ++- 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 includes/htmlform/HTMLAutoCompleteSelectField.php diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index 51b6c5db92..e7fba76233 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -173,6 +173,7 @@ production. the user will now see "Animals#Dog" in their browser instead of "Dog#Dog". * API token handling has been rewritten. Any API module using tokens will need to be updated. +* Added HTMLAutoCompleteSelectField. === Bug fixes in 1.24 === * (bug 50572) MediaWiki:Blockip should support gender diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index a3a1558fde..2fb4aa76d3 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -81,6 +81,7 @@ $wgAutoloadLocalClasses = array( 'Html' => 'includes/Html.php', 'HtmlFormatter' => 'includes/HtmlFormatter.php', 'HTMLApiField' => 'includes/htmlform/HTMLApiField.php', + 'HTMLAutoCompleteSelectField' => 'includes/htmlform/HTMLAutoCompleteSelectField.php', 'HTMLButtonField' => 'includes/htmlform/HTMLButtonField.php', 'HTMLCheckField' => 'includes/htmlform/HTMLCheckField.php', 'HTMLCheckMatrix' => 'includes/htmlform/HTMLCheckMatrix.php', diff --git a/includes/htmlform/HTMLAutoCompleteSelectField.php b/includes/htmlform/HTMLAutoCompleteSelectField.php new file mode 100644 index 0000000000..4ea5e992fd --- /dev/null +++ b/includes/htmlform/HTMLAutoCompleteSelectField.php @@ -0,0 +1,165 @@ + false, + ); + + parent::__construct( $params ); + + if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) { + foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) { + $key = $this->msg( $key )->plain(); + $this->autocomplete[$key] = strval( $value ); + } + } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) { + foreach ( $this->mParams['autocomplete'] as $key => $value ) { + $this->autocomplete[$key] = strval( $value ); + } + } + if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) { + throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' ); + } + + $this->getOptions(); + if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) { + if ( isset( $params['other-message'] ) ) { + $msg = wfMessage( $params['other-message'] )->text(); + } elseif ( isset( $params['other'] ) ) { + $msg = $params['other']; + } else { + $msg = wfMessage( 'htmlform-selectorother-other' )->text(); + } + $this->mOptions[$msg] = 'other'; + } + } + + function loadDataFromRequest( $request ) { + if ( $request->getCheck( $this->mName ) ) { + $val = $request->getText( $this->mName . '-select', 'other' ); + + if ( $val === 'other' ) { + $val = $request->getText( $this->mName ); + if ( isset( $this->autocomplete[$val] ) ) { + $val = $this->autocomplete[$val]; + } + } + + return $val; + } else { + return $this->getDefault(); + } + } + + function validate( $value, $alldata ) { + $p = parent::validate( $value, $alldata ); + + if ( $p !== true ) { + return $p; + } + + $validOptions = HTMLFormField::flattenOptions( $this->getOptions() ); + + if ( in_array( strval( $value ), $validOptions, true ) ) { + return true; + } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) { + return true; + } elseif ( $this->mParams['require-match'] ) { + return $this->msg( 'htmlform-select-badoption' )->parse(); + } + + return true; + } + + function getAttributes( array $list ) { + $attribs = array( + 'type' => 'text', + 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ), + ) + parent::getAttributes( $list ); + + if ( $this->getOptions() ) { + $attribs['data-hide-if'] = FormatJson::encode( + array( '!==', $this->mName . '-select', 'other' ) + ); + } + + return $attribs; + } + + function getInputHTML( $value ) { + $oldClass = $this->mClass; + $this->mClass = (array)$this->mClass; + + $valInSelect = false; + $ret = ''; + + if ( $this->getOptions() ) { + if ( $value !== false ) { + $value = strval( $value ); + $valInSelect = in_array( + $value, HTMLFormField::flattenOptions( $this->getOptions() ), true + ); + } + + $selected = $valInSelect ? $value : 'other'; + $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected ); + $select->addOptions( $this->getOptions() ); + $select->setAttribute( 'class', 'mw-htmlform-select-or-other' ); + + if ( !empty( $this->mParams['disabled'] ) ) { + $select->setAttribute( 'disabled', 'disabled' ); + } + + if ( isset( $this->mParams['tabindex'] ) ) { + $select->setAttribute( 'tabindex', $this->mParams['tabindex'] ); + } + + $ret = $select->getHTML() . "
\n"; + + $this->mClass[] = 'mw-htmlform-hide-if'; + } + + if ( $valInSelect ) { + $value = ''; + } else { + $key = array_search( strval( $value ), $this->autocomplete, true ); + if ( $key !== false ) { + $value = $key; + } + } + + $this->mClass[] = 'mw-htmlform-autocomplete'; + $ret .= parent::getInputHTML( $valInSelect ? '' : $value ); + $this->mClass = $oldClass; + + return $ret; + } + +} diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 3a455ab1d8..50f70379c0 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -120,6 +120,7 @@ class HTMLForm extends ContextSource { 'edittools' => 'HTMLEditTools', 'checkmatrix' => 'HTMLCheckMatrix', 'cloner' => 'HTMLFormFieldCloner', + 'autocompleteselect' => 'HTMLAutoCompleteSelectField', // HTMLTextField will output the correct type="" attribute automagically. // There are about four zillion other HTML5 input types, like range, but // we don't use those at the moment, so no point in adding all of them. diff --git a/resources/src/mediawiki/mediawiki.htmlform.js b/resources/src/mediawiki/mediawiki.htmlform.js index 5027f7a016..594800e15d 100644 --- a/resources/src/mediawiki/mediawiki.htmlform.js +++ b/resources/src/mediawiki/mediawiki.htmlform.js @@ -237,6 +237,7 @@ } ); function enhance( $root ) { + var $matrixTooltips, $autocomplete; /** * @ignore @@ -342,13 +343,30 @@ } ); } - var $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' ); + $matrixTooltips = $root.find( '.mw-htmlform-matrix .mw-htmlform-tooltip' ); if ( $matrixTooltips.length ) { mw.loader.using( 'jquery.tipsy', function () { $matrixTooltips.tipsy( { gravity: 's' } ); } ); } + // Set up autocomplete fields + $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; + } ) + ); + } + } ); + } ); + } + // Add/remove cloner clones without having to resubmit the form $root.find( '.mw-htmlform-cloner-delete-button' ).click( function ( ev ) { ev.preventDefault(); -- 2.20.1