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