Merge "Replace deprecated jQuery.nodeName"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / mw.rcfilters.Controller.js
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 };
13
14 /* Initialization */
15 OO.initClass( mw.rcfilters.Controller );
16
17 /**
18 * Initialize the filter and parameter states
19 *
20 * @param {Array} filterStructure Filter definition and structure for the model
21 */
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();
27
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()
33 );
34
35 };
36
37 /**
38 * Update filter state (selection and highlighting) based
39 * on current URL and default values.
40 */
41 mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
42 var uri = new mw.Uri();
43
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
48 $.extend(
49 true,
50 {},
51 this.filtersModel.getDefaultParams(),
52 // URI query overrides defaults
53 uri.query
54 )
55 )
56 );
57
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' ];
62 if ( color ) {
63 filterItem.setHighlightColor( color );
64 } else {
65 filterItem.clearHighlightColor();
66 }
67 } );
68
69 // Check all filter interactions
70 this.filtersModel.reassessFilterInteractions();
71 };
72
73 /**
74 * Reset to default filters
75 */
76 mw.rcfilters.Controller.prototype.resetToDefaults = function () {
77 this.filtersModel.setFiltersToDefaults();
78 this.filtersModel.clearAllHighlightColors();
79 // Check all filter interactions
80 this.filtersModel.reassessFilterInteractions();
81
82 this.updateChangesList();
83 };
84
85 /**
86 * Empty all selected filters
87 */
88 mw.rcfilters.Controller.prototype.emptyFilters = function () {
89 this.filtersModel.emptyAllFilters();
90 this.filtersModel.clearAllHighlightColors();
91 // Check all filter interactions
92 this.filtersModel.reassessFilterInteractions();
93
94 this.updateChangesList();
95 };
96
97 /**
98 * Update the selected state of a filter
99 *
100 * @param {string} filterName Filter name
101 * @param {boolean} [isSelected] Filter selected state
102 */
103 mw.rcfilters.Controller.prototype.toggleFilterSelect = function ( filterName, isSelected ) {
104 var filterItem = this.filtersModel.getItemByName( filterName );
105
106 isSelected = isSelected === undefined ? !filterItem.isSelected() : isSelected;
107
108 if ( filterItem.isSelected() !== isSelected ) {
109 this.filtersModel.toggleFilterSelected( filterName, isSelected );
110
111 this.updateChangesList();
112
113 // Check filter interactions
114 this.filtersModel.reassessFilterInteractions( filterItem );
115 }
116 };
117
118 /**
119 * Update the URL of the page to reflect current filters
120 *
121 * This should not be called directly from outside the controller.
122 * If an action requires changing the URL, it should either use the
123 * highlighting actions below, or call #updateChangesList which does
124 * the uri corrections already.
125 *
126 * @private
127 * @param {Object} [params] Extra parameters to add to the API call
128 */
129 mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
130 var updatedUri,
131 notEquivalent = function ( obj1, obj2 ) {
132 var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
133 return keys.some( function ( key ) {
134 return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
135 } );
136 };
137
138 params = params || {};
139
140 updatedUri = this.getUpdatedUri();
141 updatedUri.extend( params );
142
143 if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
144 window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
145 }
146 };
147
148 /**
149 * Get an updated mw.Uri object based on the model state
150 *
151 * @return {mw.Uri} Updated Uri
152 */
153 mw.rcfilters.Controller.prototype.getUpdatedUri = function () {
154 var uri = new mw.Uri(),
155 highlightParams = this.filtersModel.getHighlightParameters();
156
157 // Add to existing queries in URL
158 // TODO: Clean up the list of filters; perhaps 'falsy' filters
159 // shouldn't appear at all? Or compare to existing query string
160 // and see if current state of a specific filter is needed?
161 uri.extend( this.filtersModel.getParametersFromFilters() );
162
163 // highlight params
164 Object.keys( highlightParams ).forEach( function ( paramName ) {
165 if ( highlightParams[ paramName ] ) {
166 uri.query[ paramName ] = highlightParams[ paramName ];
167 } else {
168 delete uri.query[ paramName ];
169 }
170 } );
171
172 return uri;
173 };
174
175 /**
176 * Fetch the list of changes from the server for the current filters
177 *
178 * @return {jQuery.Promise} Promise object that will resolve with the changes list
179 * or with a string denoting no results.
180 */
181 mw.rcfilters.Controller.prototype.fetchChangesList = function () {
182 var uri = this.getUpdatedUri(),
183 requestId = ++this.requestCounter,
184 latestRequest = function () {
185 return requestId === this.requestCounter;
186 }.bind( this );
187
188 return $.ajax( uri.toString(), { contentType: 'html' } )
189 .then(
190 // Success
191 function ( html ) {
192 var $parsed;
193 if ( !latestRequest() ) {
194 return $.Deferred().reject();
195 }
196
197 $parsed = $( $.parseHTML( html ) );
198
199 return {
200 // Changes list
201 changes: $parsed.find( '.mw-changeslist' ).first().contents(),
202 // Fieldset
203 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
204 };
205 },
206 // Failure
207 function ( responseObj ) {
208 var $parsed;
209
210 if ( !latestRequest() ) {
211 return $.Deferred().reject();
212 }
213
214 $parsed = $( $.parseHTML( responseObj.responseText ) );
215
216 // Force a resolve state to this promise
217 return $.Deferred().resolve( {
218 changes: 'NO_RESULTS',
219 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
220 } ).promise();
221 }
222 );
223 };
224
225 /**
226 * Update the list of changes and notify the model
227 *
228 * @param {Object} [params] Extra parameters to add to the API call
229 */
230 mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
231 this.updateURL( params );
232 this.changesListModel.invalidate();
233 this.fetchChangesList()
234 .then(
235 // Success
236 function ( pieces ) {
237 var $changesListContent = pieces.changes,
238 $fieldset = pieces.fieldset;
239 this.changesListModel.update( $changesListContent, $fieldset );
240 }.bind( this )
241 // Do nothing for failure
242 );
243 };
244
245 /**
246 * Toggle the highlight feature on and off
247 */
248 mw.rcfilters.Controller.prototype.toggleHighlight = function () {
249 this.filtersModel.toggleHighlight();
250 this.updateURL();
251 };
252
253 /**
254 * Set the highlight color for a filter item
255 *
256 * @param {string} filterName Name of the filter item
257 * @param {string} color Selected color
258 */
259 mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
260 this.filtersModel.setHighlightColor( filterName, color );
261 this.updateURL();
262 };
263
264 /**
265 * Clear highlight for a filter item
266 *
267 * @param {string} filterName Name of the filter item
268 */
269 mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
270 this.filtersModel.clearHighlightColor( filterName );
271 this.updateURL();
272 };
273
274 /**
275 * Clear both highlight and selection of a filter
276 *
277 * @param {string} filterName Name of the filter item
278 */
279 mw.rcfilters.Controller.prototype.clearFilter = function ( filterName ) {
280 var filterItem = this.filtersModel.getItemByName( filterName );
281
282 if ( filterItem.isSelected() || filterItem.isHighlighted() ) {
283 this.filtersModel.clearHighlightColor( filterName );
284 this.filtersModel.toggleFilterSelected( filterName, false );
285 this.updateChangesList();
286 this.filtersModel.reassessFilterInteractions( filterItem );
287 }
288 };
289
290 /**
291 * Synchronize the URL with the current state of the filters
292 * without adding an history entry.
293 */
294 mw.rcfilters.Controller.prototype.replaceUrl = function () {
295 window.history.replaceState(
296 { tag: 'rcfilters' },
297 document.title,
298 this.getUpdatedUri().toString()
299 );
300 };
301 }( mediaWiki, jQuery ) );