Merge "registration: Only allow one extension to set a specific config setting"
[lhc/web/wiklou.git] / resources / src / mediawiki.widgets / mw.widgets.UsersMultiselectWidget.js
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 ) {
8
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.MenuTagMultiselectWidget
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 } );
34
35 // Parent constructor
36 mw.widgets.UsersMultiselectWidget.parent.call( this, $.extend( {}, config, {} ) );
37
38 // Mixin constructors
39 OO.ui.mixin.PendingElement.call( this, $.extend( {}, config, { $pending: this.$handle } ) );
40
41 // Properties
42 this.limit = config.limit;
43
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 );
51
52 // Update with preset values
53 this.updateHiddenInput();
54 }
55
56 this.menu = this.getMenu();
57
58 // Events
59 // When list of selected usernames changes, update hidden input
60 this.connect( this, {
61 change: 'onMultiselectChange'
62 } );
63
64 // API init
65 this.api = config.api || new mw.Api();
66 };
67
68 /* Setup */
69
70 OO.inheritClass( mw.widgets.UsersMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
71 OO.mixinClass( mw.widgets.UsersMultiselectWidget, OO.ui.mixin.PendingElement );
72
73 /* Methods */
74
75 /**
76 * Get currently selected usernames
77 *
78 * @return {string[]} usernames
79 */
80 mw.widgets.UsersMultiselectWidget.prototype.getSelectedUsernames = function () {
81 return this.getValue();
82 };
83
84 /**
85 * Update autocomplete menu with items
86 *
87 * @private
88 */
89 mw.widgets.UsersMultiselectWidget.prototype.updateMenuItems = function () {
90 var inputValue = this.input.getValue();
91
92 if ( inputValue === this.inputValue ) {
93 // Do not restart api query if nothing has changed in the input
94 return;
95 } else {
96 this.inputValue = inputValue;
97 }
98
99 this.api.abort(); // Abort all unfinished api requests
100
101 if ( inputValue.length > 0 ) {
102 this.pushPending();
103
104 this.api.get( {
105 action: 'query',
106 list: 'allusers',
107 // Prefix of list=allusers is case sensitive. Normalise first
108 // character to uppercase so that "fo" may yield "Foo".
109 auprefix: inputValue[ 0 ].toUpperCase() + inputValue.slice( 1 ),
110 aulimit: this.limit
111 } ).done( function ( response ) {
112 var suggestions = response.query.allusers,
113 selected = this.getSelectedUsernames();
114
115 // Remove usernames, which are already selected from suggestions
116 suggestions = suggestions.map( function ( user ) {
117 if ( selected.indexOf( user.name ) === -1 ) {
118 return new OO.ui.MenuOptionWidget( {
119 data: user.name,
120 label: user.name
121 } );
122 }
123 } ).filter( function ( item ) {
124 return item !== undefined;
125 } );
126
127 // Remove all items from menu add fill it with new
128 this.menu.clearItems();
129 this.menu.addItems( suggestions );
130 // Make the menu visible; it might not be if it was previously empty
131 this.menu.toggle( true );
132
133 this.popPending();
134 }.bind( this ) ).fail( this.popPending.bind( this ) );
135 } else {
136 this.menu.clearItems();
137 }
138 };
139
140 mw.widgets.UsersMultiselectWidget.prototype.onInputChange = function () {
141 mw.widgets.UsersMultiselectWidget.parent.prototype.onInputChange.apply( this, arguments );
142
143 this.updateMenuItems();
144 };
145
146 /**
147 * If used inside HTML form, then update hiddenInput with list o
148 * newline-separated usernames.
149 *
150 * @private
151 */
152 mw.widgets.UsersMultiselectWidget.prototype.updateHiddenInput = function () {
153 if ( 'hiddenInput' in this ) {
154 this.hiddenInput.val( this.getSelectedUsernames().join( '\n' ) );
155 // Hidden inputs do not trigger onChange.
156 // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/hidden
157 this.hiddenInput.trigger( 'change' );
158 }
159 };
160
161 /**
162 * React to the 'change' event.
163 *
164 * Updates the hidden input and clears the text from the text box.
165 */
166 mw.widgets.UsersMultiselectWidget.prototype.onMultiselectChange = function () {
167 this.updateHiddenInput();
168 this.input.setValue( '' );
169 };
170
171 }( jQuery, mediaWiki ) );