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