Implement UserInputWidget in OOUI/MW Widgets
authorFlorian <florian.schmidt.stargatewissen@gmail.com>
Fri, 17 Jul 2015 22:04:02 +0000 (00:04 +0200)
committerFlorian <florian.schmidt.stargatewissen@gmail.com>
Mon, 20 Jul 2015 04:11:16 +0000 (06:11 +0200)
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
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLUserTextField.php [new file with mode: 0644]
includes/widget/UserInputWidget.php [new file with mode: 0644]
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js [new file with mode: 0644]

index fd75c8a..734aa6a 100644 (file)
@@ -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',
index d49963d..47200d9 100644 (file)
@@ -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 (file)
index 0000000..9f7100f
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+use MediaWiki\Widget\UserInputWidget;
+
+/**
+ * Implements a text input field for user names.
+ * Automatically auto-completes if using the OOUI display format.
+ *
+ * FIXME: Does not work for forms that support GET requests.
+ *
+ * Optional parameters:
+ * 'exists' - Whether to validate that the user already exists
+ *
+ * @since 1.26
+ */
+class HTMLUserTextField extends HTMLTextField {
+       public function __construct( $params ) {
+               $params += array(
+                       'exists' => 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 (file)
index 0000000..4147fca
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * MediaWiki Widgets \96 UserInputWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+namespace MediaWiki\Widget;
+
+use OOUI\TextInputWidget;
+
+/**
+ * User input widget.
+ */
+class UserInputWidget extends TextInputWidget {
+       /**
+        * @param array $config Configuration options
+        */
+       public function __construct( array $config = array() ) {
+               // Parent constructor
+               parent::__construct( array_merge( $config, array( 'infusable' => true ) ) );
+
+               // Initialization
+               $this->addClasses( array( 'mw-widget-userInputWidget' ) );
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.UserInputWidget';
+       }
+}
index 00bd980..1fdd75d 100644 (file)
        "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": "<strong>$1</strong> does not exist.",
+       "htmlform-user-not-valid": "<strong>$1</strong> 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",
index cc9dcc6..872a89f 100644 (file)
        "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]]}}",
index 7bf0f2c..0fc8ade 100644 (file)
@@ -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 (file)
index 0000000..903660a
--- /dev/null
@@ -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 ) );