Merge "Improve sorting on SpecialWanted*-Pages"
[mediawiki.git] / resources / src / mediawiki.widgets / mw.widgets.UsersMultiselectWidget.js
blob70d7cb50f2529998a19fa618efe7e648ceec9671
1 /*!
2  * MediaWiki Widgets - UsersMultiselectWidget class.
3  *
4  * @copyright 2017 MediaWiki Widgets Team and others; see AUTHORS.txt
5  * @license The MIT License (MIT); see LICENSE.txt
6  */
7 ( function ( $, mw ) {
9         /**
10          * UsersMultiselectWidget can be used to input list of users in a single
11          * line.
12          *
13          * If used inside HTML form the results will be sent as the list of
14          * newline-separated usernames.
15          *
16          * @class
17          * @extends OO.ui.CapsuleMultiselectWidget
18          *
19          * @constructor
20          * @param {Object} [config] Configuration options
21          * @cfg {mw.Api} [api] Instance of mw.Api (or subclass thereof) to use for queries
22          * @cfg {number} [limit=10] Number of results to show in autocomplete menu
23          * @cfg {string} [name] Name of input to submit results (when used in HTML forms)
24          */
25         mw.widgets.UsersMultiselectWidget = function MwWidgetsUsersMultiselectWidget( config ) {
26                 // Config initialization
27                 config = $.extend( {
28                         limit: 10
29                 }, config, {
30                         // Because of using autocomplete (constantly changing menu), we need to
31                         // allow adding usernames, which do not present in the menu.
32                         allowArbitrary: true
33                 } );
35                 // Parent constructor
36                 mw.widgets.UsersMultiselectWidget.parent.call( this, $.extend( {}, config, {} ) );
38                 // Mixin constructors
39                 OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$handle } ) );
41                 // Properties
42                 this.limit = config.limit;
44                 if ( 'name' in config ) {
45                         // If used inside HTML form, then create hidden input, which will store
46                         // the results.
47                         this.hiddenInput = $( '<input>' )
48                                 .attr( 'type', 'hidden' )
49                                 .attr( 'name', config.name )
50                                 .appendTo( this.$element );
52                         // Update with preset values
53                         this.updateHiddenInput();
54                 }
56                 this.menu = this.getMenu();
58                 // Events
59                 // Update contents of autocomplete menu as user types letters
60                 this.$input.on( {
61                         keyup: this.updateMenuItems.bind( this )
62                 } );
63                 // When option is selected from autocomplete menu, update the menu
64                 this.menu.connect( this, {
65                         select: 'updateMenuItems'
66                 } );
67                 // When list of selected usernames changes, update hidden input
68                 this.connect( this, {
69                         change: 'updateHiddenInput'
70                 } );
72                 // API init
73                 this.api = config.api || new mw.Api();
74         };
76         /* Setup */
78         OO.inheritClass( mw.widgets.UsersMultiselectWidget, OO.ui.CapsuleMultiselectWidget );
79         OO.mixinClass( mw.widgets.UsersMultiselectWidget, OO.ui.mixin.PendingElement );
81         /* Methods */
83         /**
84          * Get currently selected usernames
85          *
86          * @return {Array} usernames
87          */
88         mw.widgets.UsersMultiselectWidget.prototype.getSelectedUsernames = function() {
89                 return this.getItemsData();
90         };
92         /**
93          * Update autocomplete menu with items
94          *
95          * @private
96          */
97         mw.widgets.UsersMultiselectWidget.prototype.updateMenuItems = function() {
98                 var inputValue = this.$input.val();
100                 if ( inputValue === this.inputValue ) {
101                         // Do not restart api query if nothing has changed in the input
102                         return;
103                 } else {
104                         this.inputValue = inputValue;
105                 }
107                 this.api.abort(); // Abort all unfinished api requests
109                 if ( inputValue.length > 0 ) {
110                         this.pushPending();
112                         this.api.get( {
113                                 action: 'query',
114                                 list: 'allusers',
115                                 // Prefix of list=allusers is case sensitive. Normalise first
116                                 // character to uppercase so that "fo" may yield "Foo".
117                                 auprefix: inputValue[ 0 ].toUpperCase() + inputValue.slice( 1 ),
118                                 aulimit: this.limit
119                         } ).done( function( response ) {
120                                 var suggestions = response.query.allusers,
121                                         selected = this.getSelectedUsernames();
123                                 // Remove usernames, which are already selected from suggestions
124                                 suggestions = suggestions.map( function ( user ) {
125                                         if ( selected.indexOf( user.name ) === -1 ) {
126                                                 return new OO.ui.MenuOptionWidget( {
127                                                         data: user.name,
128                                                         label: user.name
129                                                 } );
130                                         }
131                                 } ).filter( function( item ) {
132                                         return item !== undefined;
133                                 } );
135                                 // Remove all items from menu add fill it with new
136                                 this.menu.clearItems();
138                                 // Additional check to prevent bug of autoinserting first suggestion
139                                 // while removing user from the list
140                                 if ( inputValue.length > 1 || suggestions.length > 1 ) {
141                                         this.menu.addItems( suggestions );
142                                 }
144                                 this.popPending();
145                         }.bind( this ) ).fail( this.popPending.bind( this ) );
146                 } else {
147                         this.menu.clearItems();
148                 }
149         };
151         /**
152          * If used inside HTML form, then update hiddenInput with list o
153          * newline-separated usernames.
154          *
155          * @private
156          */
157         mw.widgets.UsersMultiselectWidget.prototype.updateHiddenInput = function() {
158                 if ( 'hiddenInput' in this ) {
159                         this.hiddenInput.val( this.getSelectedUsernames().join( '\n' ) );
160                 }
161         };
163 }( jQuery, mediaWiki ) );