From c226b135459c07644e4b9f5d70ae7d3deaa9ee6f Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 18 Jul 2015 00:04:02 +0200 Subject: [PATCH] Implement UserInputWidget in OOUI/MW Widgets To use OOUI for forms with user name autocomplete, the new widget UserInputWidget interacts like the jQuery pendant (working with css class "mw-autocomplete-user"). It is also available in HTMLForm as "user". Example usage: Iaeff912e6437d6ebef0d5b1919ce8cf53a7fd5f1 Change-Id: I9501c85f4288c255bbe3a5284e99b57b6169916f --- autoload.php | 2 + includes/htmlform/HTMLForm.php | 1 + includes/htmlform/HTMLUserTextField.php | 43 +++++++ includes/widget/UserInputWidget.php | 30 +++++ languages/i18n/en.json | 2 + languages/i18n/qqq.json | 2 + resources/Resources.php | 1 + .../mw.widgets.UserInputWidget.js | 120 ++++++++++++++++++ 8 files changed, 201 insertions(+) create mode 100644 includes/htmlform/HTMLUserTextField.php create mode 100644 includes/widget/UserInputWidget.php create mode 100644 resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js diff --git a/autoload.php b/autoload.php index fd75c8a534..734aa6aae0 100644 --- a/autoload.php +++ b/autoload.php @@ -510,6 +510,7 @@ $wgAutoloadLocalClasses = array( 'HTMLTextField' => __DIR__ . '/includes/htmlform/HTMLTextField.php', 'HTMLTextFieldWithButton' => __DIR__ . '/includes/htmlform/HTMLTextFieldWithButton.php', 'HTMLTitleTextField' => __DIR__ . '/includes/htmlform/HTMLTitleTextField.php', + 'HTMLUserTextField' => __DIR__ . '/includes/htmlform/HTMLUserTextField.php', 'HWLDFWordAccumulator' => __DIR__ . '/includes/diff/DairikiDiff.php', 'HashBagOStuff' => __DIR__ . '/includes/libs/objectcache/HashBagOStuff.php', 'HashConfig' => __DIR__ . '/includes/config/HashConfig.php', @@ -756,6 +757,7 @@ $wgAutoloadLocalClasses = array( 'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php', 'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php', 'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php', + 'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php', 'MemCachedClientforWiki' => __DIR__ . '/includes/objectcache/MemcachedClient.php', 'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php', 'MemcachedBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedBagOStuff.php', diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index d49963d73e..47200d9b51 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -154,6 +154,7 @@ class HTMLForm extends ContextSource { 'password' => 'HTMLTextField', 'url' => 'HTMLTextField', 'title' => 'HTMLTitleTextField', + 'user' => 'HTMLUserTextField', ); public $mFieldData; diff --git a/includes/htmlform/HTMLUserTextField.php b/includes/htmlform/HTMLUserTextField.php new file mode 100644 index 0000000000..9f7100f5ef --- /dev/null +++ b/includes/htmlform/HTMLUserTextField.php @@ -0,0 +1,43 @@ + false, + ); + + parent::__construct( $params ); + } + + public function validate( $value, $alldata ) { + // check, if a user exists with the given username + $user = User::newFromName( $value ); + + if ( !$user ) { + return $this->msg( 'htmlform-user-not-valid', $value )->parse(); + } else if ( $this->mParams['exists'] && $user->getId() === 0 ) { + return $this->msg( 'htmlform-user-not-exists', $user->getName() )->parse(); + } + + return parent::validate( $value, $alldata ); + } + + protected function getInputWidget( $params ) { + $this->mParent->getOutput()->addModules( 'mediawiki.widgets' ); + + return new UserInputWidget( $params ); + } +} diff --git a/includes/widget/UserInputWidget.php b/includes/widget/UserInputWidget.php new file mode 100644 index 0000000000..4147fca525 --- /dev/null +++ b/includes/widget/UserInputWidget.php @@ -0,0 +1,30 @@ + true ) ) ); + + // Initialization + $this->addClasses( array( 'mw-widget-userInputWidget' ) ); + } + + protected function getJavaScriptClassName() { + return 'mw.widgets.UserInputWidget'; + } +} diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 00bd9805d4..1fdd75d7b8 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -3538,6 +3538,8 @@ "htmlform-title-badnamespace": "[[:$1]] is not in the \"{{ns:$2}}\" namespace.", "htmlform-title-not-creatable": "\"$1\" is not a creatable page title", "htmlform-title-not-exists": "[[:$1]] does not exist.", + "htmlform-user-not-exists": "$1 does not exist.", + "htmlform-user-not-valid": "$1 isn't a valid username.", "sqlite-has-fts": "$1 with full-text search support", "sqlite-no-fts": "$1 without full-text search support", "logentry-delete-delete": "$1 {{GENDER:$2|deleted}} page $3", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index cc9dcc6d54..872a89f39c 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -3709,6 +3709,8 @@ "htmlform-title-badnamespace": "Error message shown if the page title provided by the user is not in the required namespace. $1 is the page, $2 is the numerical namespace index.", "htmlform-title-not-creatable": "Error message shown if the page title provided by the user is not creatable (a special page). $1 is the page title.", "htmlform-title-not-exists": "Error message shown if the page title provided by the user does not exist. $1 is the page title.", + "htmlform-user-not-exists": "Error message shown if a user with the name provided by the user does not exist. $1 is the username.", + "htmlform-user-not-valid": "Error message shown if the name provided by the user isn't a valid username. $1 is the username.", "sqlite-has-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version", "sqlite-no-fts": "Shown on [[Special:Version]].\nParameters:\n* $1 - version", "logentry-delete-delete": "{{Logentry|[[Special:Log/delete]]}}", diff --git a/resources/Resources.php b/resources/Resources.php index 7bf0f2c429..0fc8ade62e 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1758,6 +1758,7 @@ return array( 'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js', 'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js', 'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js', + 'resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js', ), 'skinStyles' => array( 'default' => array( diff --git a/resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js new file mode 100644 index 0000000000..903660af0d --- /dev/null +++ b/resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js @@ -0,0 +1,120 @@ +/*! + * MediaWiki Widgets - UserInputWidget class. + * + * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ +( function ( $, mw ) { + /** + * Creates a mw.widgets.UserInputWidget object. + * + * @class + * @extends OO.ui.TextInputWidget + * @mixins OO.ui.mixin.LookupElement + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {number} [limit=10] Number of results to show + */ + mw.widgets.UserInputWidget = function MwWidgetsUserInputWidget( config ) { + // Config initialization + config = config || {}; + + // Parent constructor + OO.ui.TextInputWidget.call( this, $.extend( {}, config, { autocomplete: false } ) ); + + // Mixin constructors + OO.ui.mixin.LookupElement.call( this, config ); + + // Properties + this.limit = config.limit || 10; + + // Initialization + this.$element.addClass( 'mw-widget-userInputWidget' ); + this.lookupMenu.$element.addClass( 'mw-widget-userInputWidget-menu' ); + }; + + /* Inheritance */ + + OO.inheritClass( mw.widgets.UserInputWidget, OO.ui.TextInputWidget ); + + OO.mixinClass( mw.widgets.UserInputWidget, OO.ui.mixin.LookupElement ); + + /* Methods */ + + /** + * @inheritdoc + */ + mw.widgets.UserInputWidget.prototype.onLookupMenuItemChoose = function ( item ) { + this.closeLookupMenu(); + this.setLookupsDisabled( true ); + this.setValue( item.getData() ); + this.setLookupsDisabled( false ); + }; + + /** + * @inheritdoc + */ + mw.widgets.UserInputWidget.prototype.focus = function () { + var retval; + + // Prevent programmatic focus from opening the menu + this.setLookupsDisabled( true ); + + // Parent method + retval = OO.ui.TextInputWidget.prototype.focus.apply( this, arguments ); + + this.setLookupsDisabled( false ); + + return retval; + }; + + /** + * @inheritdoc + */ + mw.widgets.UserInputWidget.prototype.getLookupRequest = function () { + var inputValue = this.value; + + return new mw.Api().get( { + action: 'query', + list: 'allusers', + // Prefix of list=allusers is case sensitive. Normalise first + // character to uppercase so that "fo" may yield "Foo". + auprefix: inputValue[0].toUpperCase() + inputValue.slice( 1 ), + aulimit: this.limit + } ); + }; + + /** + * Get lookup cache item from server response data. + * + * @method + * @param {Mixed} data Response from server + */ + mw.widgets.UserInputWidget.prototype.getLookupCacheDataFromResponse = function ( data ) { + return data.query || {}; + }; + + /** + * Get list of menu items from a server response. + * + * @param {Object} data Query result + * @returns {OO.ui.MenuOptionWidget[]} Menu items + */ + mw.widgets.UserInputWidget.prototype.getLookupMenuOptionsFromData = function ( data ) { + var len, i, user, + users = data.allusers, + items = []; + + for ( i = 0, len = users.length; i < len; i++ ) { + user = users[i] || {}; + items.push( new OO.ui.MenuOptionWidget( { + label: user.name, + data: user.name + } ) ); + } + + return items; + }; + +}( jQuery, mediaWiki ) ); -- 2.20.1