Merge "RCFilters UI: Implement conflict global result message"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / ui / mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js
1 ( function ( mw, $ ) {
2 /**
3 * Filter-specific CapsuleMultiselectWidget
4 *
5 * @class
6 * @extends OO.ui.CapsuleMultiselectWidget
7 *
8 * @constructor
9 * @param {mw.rcfilters.Controller} controller RCFilters controller
10 * @param {mw.rcfilters.dm.FiltersViewModel} model RCFilters view model
11 * @param {OO.ui.InputWidget} filterInput A filter input that focuses the capsule widget
12 * @param {Object} config Configuration object
13 * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
14 */
15 mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) {
16 var title = new OO.ui.LabelWidget( {
17 label: mw.msg( 'rcfilters-activefilters' ),
18 classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper-content-title' ]
19 } ),
20 $contentWrapper = $( '<div>' )
21 .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-wrapper' );
22
23 this.$overlay = config.$overlay || this.$element;
24
25 // Parent
26 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.call( this, $.extend( true, {
27 popup: {
28 $autoCloseIgnore: filterInput.$element.add( this.$overlay ),
29 $floatableContainer: filterInput.$element
30 }
31 }, config ) );
32
33 this.controller = controller;
34 this.model = model;
35 this.filterInput = filterInput;
36 this.isSelecting = false;
37 this.selected = null;
38
39 this.resetButton = new OO.ui.ButtonWidget( {
40 icon: 'trash',
41 framed: false,
42 title: mw.msg( 'rcfilters-clear-all-filters' ),
43 classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-resetButton' ]
44 } );
45
46 this.emptyFilterMessage = new OO.ui.LabelWidget( {
47 label: mw.msg( 'rcfilters-empty-filter' ),
48 classes: [ 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-emptyFilters' ]
49 } );
50 this.$content.append( this.emptyFilterMessage.$element );
51
52 // Events
53 this.resetButton.connect( this, { click: 'onResetButtonClick' } );
54 this.model.connect( this, {
55 itemUpdate: 'onModelItemUpdate',
56 highlightChange: 'onModelHighlightChange'
57 } );
58 this.aggregate( { click: 'capsuleItemClick' } );
59
60 // Add the filterInput as trigger
61 this.filterInput.$input
62 .on( 'focus', this.focus.bind( this ) );
63
64 // Build the content
65 $contentWrapper.append(
66 title.$element,
67 $( '<div>' )
68 .addClass( 'mw-rcfilters-ui-table' )
69 .append(
70 // The filter list and button should appear side by side regardless of how
71 // wide the button is; the button also changes its width depending
72 // on language and its state, so the safest way to present both side
73 // by side is with a table layout
74 $( '<div>' )
75 .addClass( 'mw-rcfilters-ui-row' )
76 .append(
77 this.$content
78 .addClass( 'mw-rcfilters-ui-cell' )
79 .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-filters' ),
80 $( '<div>' )
81 .addClass( 'mw-rcfilters-ui-cell' )
82 .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-cell-reset' )
83 .append( this.resetButton.$element )
84 )
85 )
86 );
87
88 // Initialize
89 this.$handle.append( $contentWrapper );
90
91 this.$element
92 .addClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget' );
93
94 this.reevaluateResetRestoreState();
95 };
96
97 /* Initialization */
98
99 OO.inheritClass( mw.rcfilters.ui.FilterCapsuleMultiselectWidget, OO.ui.CapsuleMultiselectWidget );
100
101 /* Events */
102
103 /**
104 * @event remove
105 * @param {string[]} filters Array of names of removed filters
106 *
107 * Filters were removed
108 */
109
110 /* Methods */
111
112 /**
113 * Respond to model itemUpdate event
114 *
115 * @param {mw.rcfilters.dm.FilterItem} item Filter item model
116 */
117 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
118 if (
119 item.isSelected() ||
120 (
121 this.model.isHighlightEnabled() &&
122 item.isHighlightSupported() &&
123 item.getHighlightColor()
124 )
125 ) {
126 this.addItemByName( item.getName() );
127 } else {
128 this.removeItemByName( item.getName() );
129 }
130
131 // Re-evaluate reset state
132 this.reevaluateResetRestoreState();
133 };
134
135 /**
136 * Respond to highlightChange event
137 *
138 * @param {boolean} isHighlightEnabled Highlight is enabled
139 */
140 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
141 var highlightedItems = this.model.getHighlightedItems();
142
143 if ( isHighlightEnabled ) {
144 // Add capsule widgets
145 highlightedItems.forEach( function ( filterItem ) {
146 this.addItemByName( filterItem.getName() );
147 }.bind( this ) );
148 } else {
149 // Remove capsule widgets if they're not selected
150 highlightedItems.forEach( function ( filterItem ) {
151 if ( !filterItem.isSelected() ) {
152 this.removeItemByName( filterItem.getName() );
153 }
154 }.bind( this ) );
155 }
156 };
157
158 /**
159 * Respond to click event on the reset button
160 */
161 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onResetButtonClick = function () {
162 if ( this.model.areCurrentFiltersEmpty() ) {
163 // Reset to default filters
164 this.controller.resetToDefaults();
165 } else {
166 // Reset to have no filters
167 this.controller.emptyFilters();
168 }
169 };
170
171 /**
172 * Reevaluate the restore state for the widget between setting to defaults and clearing all filters
173 */
174 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.reevaluateResetRestoreState = function () {
175 var defaultsAreEmpty = this.model.areDefaultFiltersEmpty(),
176 currFiltersAreEmpty = this.model.areCurrentFiltersEmpty(),
177 hideResetButton = currFiltersAreEmpty && defaultsAreEmpty;
178
179 this.resetButton.setIcon(
180 currFiltersAreEmpty ? 'history' : 'trash'
181 );
182
183 this.resetButton.setLabel(
184 currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
185 );
186
187 this.resetButton.toggle( !hideResetButton );
188 this.emptyFilterMessage.toggle( currFiltersAreEmpty );
189 };
190
191 /**
192 * Mark an item widget as selected
193 *
194 * @param {mw.rcfilters.ui.CapsuleItemWidget} item Capsule widget
195 */
196 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.select = function ( item ) {
197 if ( this.selected !== item ) {
198 // Unselect previous
199 if ( this.selected ) {
200 this.selected.toggleSelected( false );
201 }
202
203 // Select new one
204 this.selected = item;
205 if ( this.selected ) {
206 item.toggleSelected( true );
207 }
208 }
209 };
210
211 /**
212 * Reset selection and remove selected states from all items
213 */
214 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.resetSelection = function () {
215 if ( this.selected !== null ) {
216 this.selected = null;
217 this.getItems().forEach( function ( capsuleWidget ) {
218 capsuleWidget.toggleSelected( false );
219 } );
220 }
221 };
222
223 /**
224 * @inheritdoc
225 */
226 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.createItemWidget = function ( data ) {
227 var item = this.model.getItemByName( data );
228
229 if ( !item ) {
230 return;
231 }
232
233 return new mw.rcfilters.ui.CapsuleItemWidget(
234 this.controller,
235 item,
236 { $overlay: this.$overlay }
237 );
238 };
239
240 /**
241 * Add items by their filter name
242 *
243 * @param {string} name Filter name
244 */
245 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.addItemByName = function ( name ) {
246 var item = this.model.getItemByName( name );
247
248 if ( !item ) {
249 return;
250 }
251
252 // Check that the item isn't already added
253 if ( !this.getItemFromData( name ) ) {
254 this.addItems( [ this.createItemWidget( name ) ] );
255 }
256 };
257
258 /**
259 * Remove items by their filter name
260 *
261 * @param {string} name Filter name
262 */
263 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItemByName = function ( name ) {
264 this.removeItemsFromData( [ name ] );
265 };
266
267 /**
268 * @inheritdoc
269 */
270 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.focus = function () {
271 // Override this method; we don't want to focus on the popup, and we
272 // don't want to bind the size to the handle.
273 if ( !this.isDisabled() ) {
274 this.popup.toggle( true );
275 this.filterInput.$input.get( 0 ).focus();
276 }
277 return this;
278 };
279
280 /**
281 * @inheritdoc
282 */
283 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onFocusForPopup = function () {
284 // HACK can be removed once I21b8cff4048 is merged in oojs-ui
285 this.focus();
286 };
287
288 /**
289 * @inheritdoc
290 */
291 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onKeyDown = function () {};
292
293 /**
294 * @inheritdoc
295 */
296 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onPopupFocusOut = function () {};
297
298 /**
299 * @inheritdoc
300 */
301 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.clearInput = function () {
302 if ( this.filterInput ) {
303 this.filterInput.setValue( '' );
304 }
305 this.menu.toggle( false );
306 this.menu.selectItem();
307 this.menu.highlightItem();
308 };
309
310 /**
311 * @inheritdoc
312 */
313 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItems = function ( items ) {
314 // Parent call
315 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.prototype.removeItems.call( this, items );
316
317 // Destroy the item widget when it is removed
318 // This is done because we re-add items by recreating them, rather than hiding them
319 // and items include popups, that will just continue to be created and appended
320 // unnecessarily.
321 items.forEach( function ( widget ) {
322 widget.destroy();
323 } );
324 };
325
326 /**
327 * Override 'editItem' since it tries to use $input which does
328 * not exist when a popup is available.
329 */
330 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.editItem = function () {};
331 }( mediaWiki, jQuery ) );