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 {Object} filterStructure Filter definition and structure for the model
22 mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
23 // Initialize the model
24 this.filtersModel.initializeFilters( filterStructure );
25 this.updateStateBasedOnUrl();
29 * Update filter state (selection and highlighting) based
30 * on current URL and default values.
32 mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
33 var uri = new mw.Uri();
35 // Set filter states based on defaults and URL params
36 this.filtersModel.toggleFiltersSelected(
37 this.filtersModel.getFiltersFromParameters(
38 // Merge defaults with URL params for initialization
42 this.filtersModel.getDefaultParams(),
43 // URI query overrides defaults
49 // Initialize highlights
50 this.filtersModel.toggleHighlight( !!uri.query.highlight );
51 this.filtersModel.getItems().forEach( function ( filterItem ) {
52 var color = uri.query[ filterItem.getName() + '_color' ];
54 filterItem.setHighlightColor( color );
56 filterItem.clearHighlightColor();
60 // Check all filter interactions
61 this.filtersModel.reassessFilterInteractions();
65 * Reset to default filters
67 mw.rcfilters.Controller.prototype.resetToDefaults = function () {
68 this.filtersModel.setFiltersToDefaults();
69 this.filtersModel.clearAllHighlightColors();
70 // Check all filter interactions
71 this.filtersModel.reassessFilterInteractions();
73 this.updateChangesList();
77 * Empty all selected filters
79 mw.rcfilters.Controller.prototype.emptyFilters = function () {
80 this.filtersModel.emptyAllFilters();
81 this.filtersModel.clearAllHighlightColors();
82 // Check all filter interactions
83 this.filtersModel.reassessFilterInteractions();
85 this.updateChangesList();
89 * Update the selected state of a filter
91 * @param {string} filterName Filter name
92 * @param {boolean} [isSelected] Filter selected state
94 mw.rcfilters.Controller.prototype.toggleFilterSelect = function ( filterName, isSelected ) {
95 var filterItem = this.filtersModel.getItemByName( filterName );
97 isSelected = isSelected === undefined ? !filterItem.isSelected() : isSelected;
99 if ( filterItem.isSelected() !== isSelected ) {
100 this.filtersModel.toggleFilterSelected( filterName, isSelected );
102 this.updateChangesList();
104 // Check filter interactions
105 this.filtersModel.reassessFilterInteractions( filterItem );
110 * Update the URL of the page to reflect current filters
112 * This should not be called directly from outside the controller.
113 * If an action requires changing the URL, it should either use the
114 * highlighting actions below, or call #updateChangesList which does
115 * the uri corrections already.
118 * @param {Object} [params] Extra parameters to add to the API call
120 mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
122 notEquivalent = function ( obj1, obj2 ) {
123 var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
124 return keys.some( function ( key ) {
125 return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
129 params = params || {};
131 updatedUri = this.getUpdatedUri();
132 updatedUri.extend( params );
134 if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
135 window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
140 * Get an updated mw.Uri object based on the model state
142 * @return {mw.Uri} Updated Uri
144 mw.rcfilters.Controller.prototype.getUpdatedUri = function () {
145 var uri = new mw.Uri(),
146 highlightParams = this.filtersModel.getHighlightParameters();
148 // Add to existing queries in URL
149 // TODO: Clean up the list of filters; perhaps 'falsy' filters
150 // shouldn't appear at all? Or compare to existing query string
151 // and see if current state of a specific filter is needed?
152 uri.extend( this.filtersModel.getParametersFromFilters() );
155 Object.keys( highlightParams ).forEach( function ( paramName ) {
156 if ( highlightParams[ paramName ] ) {
157 uri.query[ paramName ] = highlightParams[ paramName ];
159 delete uri.query[ paramName ];
167 * Fetch the list of changes from the server for the current filters
169 * @return {jQuery.Promise} Promise object that will resolve with the changes list
170 * or with a string denoting no results.
172 mw.rcfilters.Controller.prototype.fetchChangesList = function () {
173 var uri = this.getUpdatedUri(),
174 requestId = ++this.requestCounter,
175 latestRequest = function () {
176 return requestId === this.requestCounter;
179 return $.ajax( uri.toString(), { contentType: 'html' } )
184 if ( !latestRequest() ) {
185 return $.Deferred().reject();
188 $parsed = $( $.parseHTML( html ) );
192 changes: $parsed.find( '.mw-changeslist' ).first().contents(),
194 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
198 function ( responseObj ) {
201 if ( !latestRequest() ) {
202 return $.Deferred().reject();
205 $parsed = $( $.parseHTML( responseObj.responseText ) );
207 // Force a resolve state to this promise
208 return $.Deferred().resolve( {
209 changes: 'NO_RESULTS',
210 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
217 * Update the list of changes and notify the model
219 * @param {Object} [params] Extra parameters to add to the API call
221 mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
222 this.updateURL( params );
223 this.changesListModel.invalidate();
224 this.fetchChangesList()
227 function ( pieces ) {
228 var $changesListContent = pieces.changes,
229 $fieldset = pieces.fieldset;
230 this.changesListModel.update( $changesListContent, $fieldset );
232 // Do nothing for failure
237 * Toggle the highlight feature on and off
239 mw.rcfilters.Controller.prototype.toggleHighlight = function () {
240 this.filtersModel.toggleHighlight();
245 * Set the highlight color for a filter item
247 * @param {string} filterName Name of the filter item
248 * @param {string} color Selected color
250 mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
251 this.filtersModel.setHighlightColor( filterName, color );
256 * Clear highlight for a filter item
258 * @param {string} filterName Name of the filter item
260 mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
261 this.filtersModel.clearHighlightColor( filterName );
266 * Clear both highlight and selection of a filter
268 * @param {string} filterName Name of the filter item
270 mw.rcfilters.Controller.prototype.clearFilter = function ( filterName ) {
271 var filterItem = this.filtersModel.getItemByName( filterName );
273 if ( filterItem.isSelected() || filterItem.isHighlighted() ) {
274 this.filtersModel.clearHighlightColor( filterName );
275 this.filtersModel.toggleFilterSelected( filterName, false );
276 this.updateChangesList();
277 this.filtersModel.reassessFilterInteractions( filterItem );
282 * Synchronize the URL with the current state of the filters
283 * without adding an history entry.
285 mw.rcfilters.Controller.prototype.replaceUrl = function () {
286 window.history.replaceState(
287 { tag: 'rcfilters' },
289 this.getUpdatedUri().toString()
292 }( mediaWiki, jQuery ) );