RCFilters: Change reset button title when it changes state
[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 this.resetButton.setTitle(
187 currFiltersAreEmpty ?
188 mw.msg( 'rcfilters-clear-filters-to-defaults' ) :
189 mw.msg( 'rcfilters-clear-all-filters' )
190 );
191
192 this.resetButton.toggle( !hideResetButton );
193 this.emptyFilterMessage.toggle( currFiltersAreEmpty );
194 };
195
196 /**
197 * Mark an item widget as selected
198 *
199 * @param {mw.rcfilters.ui.CapsuleItemWidget} item Capsule widget
200 */
201 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.select = function ( item ) {
202 if ( this.selected !== item ) {
203 // Unselect previous
204 if ( this.selected ) {
205 this.selected.toggleSelected( false );
206 }
207
208 // Select new one
209 this.selected = item;
210 if ( this.selected ) {
211 item.toggleSelected( true );
212 }
213 }
214 };
215
216 /**
217 * Reset selection and remove selected states from all items
218 */
219 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.resetSelection = function () {
220 if ( this.selected !== null ) {
221 this.selected = null;
222 this.getItems().forEach( function ( capsuleWidget ) {
223 capsuleWidget.toggleSelected( false );
224 } );
225 }
226 };
227
228 /**
229 * @inheritdoc
230 */
231 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.createItemWidget = function ( data ) {
232 var item = this.model.getItemByName( data );
233
234 if ( !item ) {
235 return;
236 }
237
238 return new mw.rcfilters.ui.CapsuleItemWidget(
239 this.controller,
240 item,
241 { $overlay: this.$overlay }
242 );
243 };
244
245 /**
246 * Add items by their filter name
247 *
248 * @param {string} name Filter name
249 */
250 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.addItemByName = function ( name ) {
251 var item = this.model.getItemByName( name );
252
253 if ( !item ) {
254 return;
255 }
256
257 // Check that the item isn't already added
258 if ( !this.getItemFromData( name ) ) {
259 this.addItems( [ this.createItemWidget( name ) ] );
260 }
261 };
262
263 /**
264 * Remove items by their filter name
265 *
266 * @param {string} name Filter name
267 */
268 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItemByName = function ( name ) {
269 this.removeItemsFromData( [ name ] );
270 };
271
272 /**
273 * @inheritdoc
274 */
275 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.focus = function () {
276 // Override this method; we don't want to focus on the popup, and we
277 // don't want to bind the size to the handle.
278 if ( !this.isDisabled() ) {
279 this.popup.toggle( true );
280 this.filterInput.$input.get( 0 ).focus();
281 }
282 return this;
283 };
284
285 /**
286 * @inheritdoc
287 */
288 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onFocusForPopup = function () {
289 // HACK can be removed once I21b8cff4048 is merged in oojs-ui
290 this.focus();
291 };
292
293 /**
294 * @inheritdoc
295 */
296 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onKeyDown = function () {};
297
298 /**
299 * @inheritdoc
300 */
301 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onPopupFocusOut = function () {};
302
303 /**
304 * @inheritdoc
305 */
306 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.clearInput = function () {
307 if ( this.filterInput ) {
308 this.filterInput.setValue( '' );
309 }
310 this.menu.toggle( false );
311 this.menu.selectItem();
312 this.menu.highlightItem();
313 };
314
315 /**
316 * @inheritdoc
317 */
318 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItems = function ( items ) {
319 // Parent call
320 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.prototype.removeItems.call( this, items );
321
322 // Destroy the item widget when it is removed
323 // This is done because we re-add items by recreating them, rather than hiding them
324 // and items include popups, that will just continue to be created and appended
325 // unnecessarily.
326 items.forEach( function ( widget ) {
327 widget.destroy();
328 } );
329 };
330
331 /**
332 * Override 'editItem' since it tries to use $input which does
333 * not exist when a popup is available.
334 */
335 mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.editItem = function () {};
336 }( mediaWiki, jQuery ) );