Merge "selenium: invoke jobs to enforce eventual consistency"
[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 () {
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 // Use this instead of <input type="hidden">, because hidden inputs do not have separate
46 // 'value' and 'defaultValue' properties. The script on Special:Preferences
47 // (mw.special.preferences.confirmClose) checks this property to see if a field was changed.
48 this.hiddenInput = $( '<textarea>' )
49 .addClass( 'oo-ui-element-hidden' )
50 .attr( 'name', config.name )
51 .appendTo( this.$element );
52 // Update with preset values
53 this.updateHiddenInput();
54 // Set the default value (it might be different from just being empty)
55 this.hiddenInput.prop( 'defaultValue', this.getSelectedUsernames().join( '\n' ) );
56 }
57
58 this.menu = this.getMenu();
59
60 // Events
61 // When list of selected usernames changes, update hidden input
62 this.connect( this, {
63 change: 'onMultiselectChange'
64 } );
65
66 // API init
67 this.api = config.api || new mw.Api();
68 };
69
70 /* Setup */
71
72 OO.inheritClass( mw.widgets.UsersMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
73 OO.mixinClass( mw.widgets.UsersMultiselectWidget, OO.ui.mixin.PendingElement );
74
75 /* Methods */
76
77 /**
78 * Get currently selected usernames
79 *
80 * @return {string[]} usernames
81 */
82 mw.widgets.UsersMultiselectWidget.prototype.getSelectedUsernames = function () {
83 return this.getValue();
84 };
85
86 /**
87 * Update autocomplete menu with items
88 *
89 * @private
90 */
91 mw.widgets.UsersMultiselectWidget.prototype.updateMenuItems = function () {
92 var inputValue = this.input.getValue();
93
94 if ( inputValue === this.inputValue ) {
95 // Do not restart api query if nothing has changed in the input
96 return;
97 } else {
98 this.inputValue = inputValue;
99 }
100
101 this.api.abort(); // Abort all unfinished api requests
102
103 if ( inputValue.length > 0 ) {
104 this.pushPending();
105
106 this.api.get( {
107 action: 'query',
108 list: 'allusers',
109 // Prefix of list=allusers is case sensitive. Normalise first
110 // character to uppercase so that "fo" may yield "Foo".
111 auprefix: inputValue[ 0 ].toUpperCase() + inputValue.slice( 1 ),
112 aulimit: this.limit
113 } ).done( function ( response ) {
114 var suggestions = response.query.allusers,
115 selected = this.getSelectedUsernames();
116
117 // Remove usernames, which are already selected from suggestions
118 suggestions = suggestions.map( function ( user ) {
119 if ( selected.indexOf( user.name ) === -1 ) {
120 return new OO.ui.MenuOptionWidget( {
121 data: user.name,
122 label: user.name
123 } );
124 }
125 } ).filter( function ( item ) {
126 return item !== undefined;
127 } );
128
129 // Remove all items from menu add fill it with new
130 this.menu.clearItems();
131 this.menu.addItems( suggestions );
132 // Make the menu visible; it might not be if it was previously empty
133 this.menu.toggle( true );
134
135 this.popPending();
136 }.bind( this ) ).fail( this.popPending.bind( this ) );
137 } else {
138 this.menu.clearItems();
139 }
140 };
141
142 mw.widgets.UsersMultiselectWidget.prototype.onInputChange = function () {
143 mw.widgets.UsersMultiselectWidget.parent.prototype.onInputChange.apply( this, arguments );
144
145 this.updateMenuItems();
146 };
147
148 /**
149 * If used inside HTML form, then update hiddenInput with list o
150 * newline-separated usernames.
151 *
152 * @private
153 */
154 mw.widgets.UsersMultiselectWidget.prototype.updateHiddenInput = function () {
155 if ( 'hiddenInput' in this ) {
156 this.hiddenInput.val( this.getSelectedUsernames().join( '\n' ) );
157 // Trigger a 'change' event as if a user edited the text
158 // (it is not triggered when changing the value from JS code).
159 this.hiddenInput.trigger( 'change' );
160 }
161 };
162
163 /**
164 * React to the 'change' event.
165 *
166 * Updates the hidden input and clears the text from the text box.
167 */
168 mw.widgets.UsersMultiselectWidget.prototype.onMultiselectChange = function () {
169 this.updateHiddenInput();
170 this.input.setValue( '' );
171 };
172
173 }() );