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