3 * Controller for the filters in Recent Changes
5 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
6 * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel Changes list view model
8 mw
.rcfilters
.Controller
= function MwRcfiltersController( filtersModel
, changesListModel
) {
9 this.filtersModel
= filtersModel
;
10 this.changesListModel
= changesListModel
;
11 this.requestCounter
= 0;
15 OO
.initClass( mw
.rcfilters
.Controller
);
18 * Initialize the filter and parameter states
20 * @param {Array} filterStructure Filter definition and structure for the model
22 mw
.rcfilters
.Controller
.prototype.initialize = function ( filterStructure
) {
23 var $changesList
= $( '.mw-changeslist' ).first().contents();
24 // Initialize the model
25 this.filtersModel
.initializeFilters( filterStructure
);
26 this.updateStateBasedOnUrl();
28 // Update the changes list with the existing data
29 // so it gets processed
30 this.changesListModel
.update(
31 $changesList
.length
? $changesList
: 'NO_RESULTS',
32 $( 'fieldset.rcoptions' ).first()
38 * Update filter state (selection and highlighting) based
39 * on current URL and default values.
41 mw
.rcfilters
.Controller
.prototype.updateStateBasedOnUrl = function () {
42 var uri
= new mw
.Uri();
44 // Set filter states based on defaults and URL params
45 this.filtersModel
.toggleFiltersSelected(
46 this.filtersModel
.getFiltersFromParameters(
47 // Merge defaults with URL params for initialization
51 this.filtersModel
.getDefaultParams(),
52 // URI query overrides defaults
58 // Initialize highlights
59 this.filtersModel
.toggleHighlight( !!uri
.query
.highlight
);
60 this.filtersModel
.getItems().forEach( function ( filterItem
) {
61 var color
= uri
.query
[ filterItem
.getName() + '_color' ];
63 filterItem
.setHighlightColor( color
);
65 filterItem
.clearHighlightColor();
69 // Check all filter interactions
70 this.filtersModel
.reassessFilterInteractions();
74 * Reset to default filters
76 mw
.rcfilters
.Controller
.prototype.resetToDefaults = function () {
77 this.filtersModel
.setFiltersToDefaults();
78 this.filtersModel
.clearAllHighlightColors();
79 // Check all filter interactions
80 this.filtersModel
.reassessFilterInteractions();
82 this.updateChangesList();
86 * Empty all selected filters
88 mw
.rcfilters
.Controller
.prototype.emptyFilters = function () {
89 var highlightedFilterNames
= this.filtersModel
90 .getHighlightedItems()
91 .map( function ( filterItem
) { return { name
: filterItem
.getName() }; } );
93 this.filtersModel
.emptyAllFilters();
94 this.filtersModel
.clearAllHighlightColors();
95 // Check all filter interactions
96 this.filtersModel
.reassessFilterInteractions();
98 this.updateChangesList();
100 if ( highlightedFilterNames
) {
101 this.trackHighlight( 'clearAll', highlightedFilterNames
);
106 * Update the selected state of a filter
108 * @param {string} filterName Filter name
109 * @param {boolean} [isSelected] Filter selected state
111 mw
.rcfilters
.Controller
.prototype.toggleFilterSelect = function ( filterName
, isSelected
) {
112 var filterItem
= this.filtersModel
.getItemByName( filterName
);
115 // If no filter was found, break
119 isSelected
= isSelected
=== undefined ? !filterItem
.isSelected() : isSelected
;
121 if ( filterItem
.isSelected() !== isSelected
) {
122 this.filtersModel
.toggleFilterSelected( filterName
, isSelected
);
124 this.updateChangesList();
126 // Check filter interactions
127 this.filtersModel
.reassessFilterInteractions( filterItem
);
132 * Update the URL of the page to reflect current filters
134 * This should not be called directly from outside the controller.
135 * If an action requires changing the URL, it should either use the
136 * highlighting actions below, or call #updateChangesList which does
137 * the uri corrections already.
140 * @param {Object} [params] Extra parameters to add to the API call
142 mw
.rcfilters
.Controller
.prototype.updateURL = function ( params
) {
144 notEquivalent = function ( obj1
, obj2
) {
145 var keys
= Object
.keys( obj1
).concat( Object
.keys( obj2
) );
146 return keys
.some( function ( key
) {
147 return obj1
[ key
] != obj2
[ key
]; // eslint-disable-line eqeqeq
151 params
= params
|| {};
153 updatedUri
= this.getUpdatedUri();
154 updatedUri
.extend( params
);
156 if ( notEquivalent( updatedUri
.query
, new mw
.Uri().query
) ) {
157 window
.history
.pushState( { tag
: 'rcfilters' }, document
.title
, updatedUri
.toString() );
162 * Get an updated mw.Uri object based on the model state
164 * @return {mw.Uri} Updated Uri
166 mw
.rcfilters
.Controller
.prototype.getUpdatedUri = function () {
167 var uri
= new mw
.Uri(),
168 highlightParams
= this.filtersModel
.getHighlightParameters();
170 // Add to existing queries in URL
171 // TODO: Clean up the list of filters; perhaps 'falsy' filters
172 // shouldn't appear at all? Or compare to existing query string
173 // and see if current state of a specific filter is needed?
174 uri
.extend( this.filtersModel
.getParametersFromFilters() );
177 Object
.keys( highlightParams
).forEach( function ( paramName
) {
178 if ( highlightParams
[ paramName
] ) {
179 uri
.query
[ paramName
] = highlightParams
[ paramName
];
181 delete uri
.query
[ paramName
];
189 * Fetch the list of changes from the server for the current filters
191 * @return {jQuery.Promise} Promise object that will resolve with the changes list
192 * or with a string denoting no results.
194 mw
.rcfilters
.Controller
.prototype.fetchChangesList = function () {
195 var uri
= this.getUpdatedUri(),
196 requestId
= ++this.requestCounter
,
197 latestRequest = function () {
198 return requestId
=== this.requestCounter
;
201 return $.ajax( uri
.toString(), { contentType
: 'html' } )
206 if ( !latestRequest() ) {
207 return $.Deferred().reject();
210 $parsed
= $( $.parseHTML( html
) );
214 changes
: $parsed
.find( '.mw-changeslist' ).first().contents(),
216 fieldset
: $parsed
.find( 'fieldset.rcoptions' ).first()
220 function ( responseObj
) {
223 if ( !latestRequest() ) {
224 return $.Deferred().reject();
227 $parsed
= $( $.parseHTML( responseObj
.responseText
) );
229 // Force a resolve state to this promise
230 return $.Deferred().resolve( {
231 changes
: 'NO_RESULTS',
232 fieldset
: $parsed
.find( 'fieldset.rcoptions' ).first()
239 * Update the list of changes and notify the model
241 * @param {Object} [params] Extra parameters to add to the API call
243 mw
.rcfilters
.Controller
.prototype.updateChangesList = function ( params
) {
244 this.updateURL( params
);
245 this.changesListModel
.invalidate();
246 this.fetchChangesList()
249 function ( pieces
) {
250 var $changesListContent
= pieces
.changes
,
251 $fieldset
= pieces
.fieldset
;
252 this.changesListModel
.update( $changesListContent
, $fieldset
);
254 // Do nothing for failure
259 * Toggle the highlight feature on and off
261 mw
.rcfilters
.Controller
.prototype.toggleHighlight = function () {
262 this.filtersModel
.toggleHighlight();
265 if ( this.filtersModel
.isHighlightEnabled() ) {
266 mw
.hook( 'RcFilters.highlight.enable' ).fire();
271 * Set the highlight color for a filter item
273 * @param {string} filterName Name of the filter item
274 * @param {string} color Selected color
276 mw
.rcfilters
.Controller
.prototype.setHighlightColor = function ( filterName
, color
) {
277 this.filtersModel
.setHighlightColor( filterName
, color
);
279 this.trackHighlight( 'set', { name
: filterName
, color
: color
} );
283 * Clear highlight for a filter item
285 * @param {string} filterName Name of the filter item
287 mw
.rcfilters
.Controller
.prototype.clearHighlightColor = function ( filterName
) {
288 this.filtersModel
.clearHighlightColor( filterName
);
290 this.trackHighlight( 'clear', filterName
);
294 * Clear both highlight and selection of a filter
296 * @param {string} filterName Name of the filter item
298 mw
.rcfilters
.Controller
.prototype.clearFilter = function ( filterName
) {
299 var filterItem
= this.filtersModel
.getItemByName( filterName
),
300 isHighlighted
= filterItem
.isHighlighted();
302 if ( filterItem
.isSelected() || isHighlighted
) {
303 this.filtersModel
.clearHighlightColor( filterName
);
304 this.filtersModel
.toggleFilterSelected( filterName
, false );
305 this.updateChangesList();
306 this.filtersModel
.reassessFilterInteractions( filterItem
);
309 if ( isHighlighted
) {
310 this.trackHighlight( 'clear', filterName
);
315 * Synchronize the URL with the current state of the filters
316 * without adding an history entry.
318 mw
.rcfilters
.Controller
.prototype.replaceUrl = function () {
319 window
.history
.replaceState(
320 { tag
: 'rcfilters' },
322 this.getUpdatedUri().toString()
327 * Track usage of highlight feature
329 * @param {string} action
330 * @param {array|object|string} filters
332 mw
.rcfilters
.Controller
.prototype.trackHighlight = function ( action
, filters
) {
333 filters
= typeof filters
=== 'string' ? { name
: filters
} : filters
;
334 filters
= !Array
.isArray( filters
) ? [ filters
] : filters
;
336 'event.ChangesListHighlights',
340 userId
: mw
.user
.getId()
344 }( mediaWiki
, jQuery
) );