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