Merge "Improve sorting on SpecialWanted*-Pages"
[mediawiki.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
blobe56205784535275db437f918bef349db3804bf8c
1 ( function ( mw, $ ) {
2         /**
3          * Controller for the filters in Recent Changes
4          *
5          * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
6          * @param {mw.rcfilters.dm.ChangesListViewModel} changesListModel Changes list view model
7          */
8         mw.rcfilters.Controller = function MwRcfiltersController( filtersModel, changesListModel ) {
9                 this.filtersModel = filtersModel;
10                 this.changesListModel = changesListModel;
11                 this.requestCounter = 0;
12         };
14         /* Initialization */
15         OO.initClass( mw.rcfilters.Controller );
17         /**
18          * Initialize the filter and parameter states
19          *
20          * @param {Object} filterStructure Filter definition and structure for the model
21          */
22         mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
23                 // Initialize the model
24                 this.filtersModel.initializeFilters( filterStructure );
25                 this.updateStateBasedOnUrl();
26         };
28         /**
29          * Update filter state (selection and highlighting) based
30          * on current URL and default values.
31          */
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
39                                 $.extend(
40                                         true,
41                                         {},
42                                         this.filtersModel.getDefaultParams(),
43                                         // URI query overrides defaults
44                                         uri.query
45                                 )
46                         )
47                 );
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' ];
53                         if ( color ) {
54                                 filterItem.setHighlightColor( color );
55                         } else {
56                                 filterItem.clearHighlightColor();
57                         }
58                 } );
60                 // Check all filter interactions
61                 this.filtersModel.reassessFilterInteractions();
62         };
64         /**
65          * Reset to default filters
66          */
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();
74         };
76         /**
77          * Empty all selected filters
78          */
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();
86         };
88         /**
89          * Update the selected state of a filter
90          *
91          * @param {string} filterName Filter name
92          * @param {boolean} [isSelected] Filter selected state
93          */
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 );
106                 }
107         };
109         /**
110          * Update the URL of the page to reflect current filters
111          *
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.
116          *
117          * @private
118          * @param {Object} [params] Extra parameters to add to the API call
119          */
120         mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
121                 var updatedUri,
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
126                                 } );
127                         };
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() );
136                 }
137         };
139         /**
140          * Get an updated mw.Uri object based on the model state
141          *
142          * @return {mw.Uri} Updated Uri
143          */
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() );
154                 // highlight params
155                 Object.keys( highlightParams ).forEach( function ( paramName ) {
156                         if ( highlightParams[ paramName ] ) {
157                                 uri.query[ paramName ] = highlightParams[ paramName ];
158                         } else {
159                                 delete uri.query[ paramName ];
160                         }
161                 } );
163                 return uri;
164         };
166         /**
167          * Fetch the list of changes from the server for the current filters
168          *
169          * @return {jQuery.Promise} Promise object that will resolve with the changes list
170          *  or with a string denoting no results.
171          */
172         mw.rcfilters.Controller.prototype.fetchChangesList = function () {
173                 var uri = this.getUpdatedUri(),
174                         requestId = ++this.requestCounter,
175                         latestRequest = function () {
176                                 return requestId === this.requestCounter;
177                         }.bind( this );
179                 return $.ajax( uri.toString(), { contentType: 'html' } )
180                         .then(
181                                 // Success
182                                 function ( html ) {
183                                         var $parsed;
184                                         if ( !latestRequest() ) {
185                                                 return $.Deferred().reject();
186                                         }
188                                         $parsed = $( $.parseHTML( html ) );
190                                         return {
191                                                 // Changes list
192                                                 changes: $parsed.find( '.mw-changeslist' ).first().contents(),
193                                                 // Fieldset
194                                                 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
195                                         };
196                                 },
197                                 // Failure
198                                 function ( responseObj ) {
199                                         var $parsed;
201                                         if ( !latestRequest() ) {
202                                                 return $.Deferred().reject();
203                                         }
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()
211                                         } ).promise();
212                                 }
213                         );
214         };
216         /**
217          * Update the list of changes and notify the model
218          *
219          * @param {Object} [params] Extra parameters to add to the API call
220          */
221         mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
222                 this.updateURL( params );
223                 this.changesListModel.invalidate();
224                 this.fetchChangesList()
225                         .then(
226                                 // Success
227                                 function ( pieces ) {
228                                         var $changesListContent = pieces.changes,
229                                                 $fieldset = pieces.fieldset;
230                                         this.changesListModel.update( $changesListContent, $fieldset );
231                                 }.bind( this )
232                                 // Do nothing for failure
233                         );
234         };
236         /**
237          * Toggle the highlight feature on and off
238          */
239         mw.rcfilters.Controller.prototype.toggleHighlight = function () {
240                 this.filtersModel.toggleHighlight();
241                 this.updateURL();
242         };
244         /**
245          * Set the highlight color for a filter item
246          *
247          * @param {string} filterName Name of the filter item
248          * @param {string} color Selected color
249          */
250         mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
251                 this.filtersModel.setHighlightColor( filterName, color );
252                 this.updateURL();
253         };
255         /**
256          * Clear highlight for a filter item
257          *
258          * @param {string} filterName Name of the filter item
259          */
260         mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
261                 this.filtersModel.clearHighlightColor( filterName );
262                 this.updateURL();
263         };
265         /**
266          * Clear both highlight and selection of a filter
267          *
268          * @param {string} filterName Name of the filter item
269          */
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 );
278                 }
279         };
281         /**
282          * Synchronize the URL with the current state of the filters
283          * without adding an history entry.
284          */
285         mw.rcfilters.Controller.prototype.replaceUrl = function () {
286                 window.history.replaceState(
287                         { tag: 'rcfilters' },
288                         document.title,
289                         this.getUpdatedUri().toString()
290                 );
291         };
292 }( mediaWiki, jQuery ) );