RCFilters UI: Ajaxify everything
[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 {Object} filterStructure Filter definition and structure for the model
21 */
22 mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
23 var uri = new mw.Uri();
24
25 // Initialize the model
26 this.filtersModel.initializeFilters( filterStructure );
27
28 // Set filter states based on defaults and URL params
29 this.filtersModel.updateFilters(
30 this.filtersModel.getFiltersFromParameters(
31 // Merge defaults with URL params for initialization
32 $.extend(
33 true,
34 {},
35 this.filtersModel.getDefaultParams(),
36 // URI query overrides defaults
37 uri.query
38 )
39 )
40 );
41
42 // Initialize highlights
43 this.filtersModel.toggleHighlight( !!uri.query.highlight );
44 this.filtersModel.getItems().forEach( function ( filterItem ) {
45 var color = uri.query[ filterItem.getName() + '_color' ];
46 if ( !color ) {
47 return;
48 }
49
50 filterItem.setHighlightColor( color );
51 } );
52
53 // Check all filter interactions
54 this.filtersModel.reassessFilterInteractions();
55 };
56
57 /**
58 * Reset to default filters
59 */
60 mw.rcfilters.Controller.prototype.resetToDefaults = function () {
61 this.filtersModel.setFiltersToDefaults();
62 // Check all filter interactions
63 this.filtersModel.reassessFilterInteractions();
64
65 this.updateChangesList();
66 };
67
68 /**
69 * Empty all selected filters
70 */
71 mw.rcfilters.Controller.prototype.emptyFilters = function () {
72 this.filtersModel.emptyAllFilters();
73 this.filtersModel.clearAllHighlightColors();
74 // Check all filter interactions
75 this.filtersModel.reassessFilterInteractions();
76
77 this.updateChangesList();
78 };
79
80 /**
81 * Update the state of a filter
82 *
83 * @param {string} filterName Filter name
84 * @param {boolean} isSelected Filter selected state
85 */
86 mw.rcfilters.Controller.prototype.updateFilter = function ( filterName, isSelected ) {
87 var obj = {},
88 filterItem = this.filtersModel.getItemByName( filterName );
89
90 if ( filterItem.isSelected() !== isSelected ) {
91 obj[ filterName ] = isSelected;
92 this.filtersModel.updateFilters( obj );
93
94 this.updateChangesList();
95
96 // Check filter interactions
97 this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
98 }
99 };
100
101 /**
102 * Update the URL of the page to reflect current filters
103 *
104 * This should not be called directly from outside the controller.
105 * If an action requires changing the URL, it should either use the
106 * highlighting actions below, or call #updateChangesList which does
107 * the uri corrections already.
108 *
109 * @private
110 * @param {Object} [params] Extra parameters to add to the API call
111 */
112 mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
113 var uri;
114
115 params = params || {};
116
117 uri = this.getUpdatedUri();
118 uri.extend( params );
119
120 window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
121 };
122
123 /**
124 * Get an updated mw.Uri object based on the model state
125 *
126 * @return {mw.Uri} Updated Uri
127 */
128 mw.rcfilters.Controller.prototype.getUpdatedUri = function () {
129 var uri = new mw.Uri(),
130 highlightParams = this.filtersModel.getHighlightParameters();
131
132 // Add to existing queries in URL
133 // TODO: Clean up the list of filters; perhaps 'falsy' filters
134 // shouldn't appear at all? Or compare to existing query string
135 // and see if current state of a specific filter is needed?
136 uri.extend( this.filtersModel.getParametersFromFilters() );
137
138 // highlight params
139 Object.keys( highlightParams ).forEach( function ( paramName ) {
140 if ( highlightParams[ paramName ] ) {
141 uri.query[ paramName ] = highlightParams[ paramName ];
142 } else {
143 delete uri.query[ paramName ];
144 }
145 } );
146
147 return uri;
148 };
149
150 /**
151 * Fetch the list of changes from the server for the current filters
152 *
153 * @return {jQuery.Promise} Promise object that will resolve with the changes list
154 * or with a string denoting no results.
155 */
156 mw.rcfilters.Controller.prototype.fetchChangesList = function () {
157 var uri = this.getUpdatedUri(),
158 requestId = ++this.requestCounter,
159 latestRequest = function () {
160 return requestId === this.requestCounter;
161 }.bind( this );
162
163 return $.ajax( uri.toString(), { contentType: 'html' } )
164 .then(
165 // Success
166 function ( html ) {
167 var $parsed;
168 if ( !latestRequest() ) {
169 return $.Deferred().reject();
170 }
171
172 $parsed = $( $.parseHTML( html ) );
173
174 return {
175 // Changes list
176 changes: $parsed.find( '.mw-changeslist' ).first().contents(),
177 // Fieldset
178 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
179 };
180 },
181 // Failure
182 function ( responseObj ) {
183 var $parsed;
184
185 if ( !latestRequest() ) {
186 return $.Deferred().reject();
187 }
188
189 $parsed = $( $.parseHTML( responseObj.responseText ) );
190
191 // Force a resolve state to this promise
192 return $.Deferred().resolve( {
193 changes: 'NO_RESULTS',
194 fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
195 } ).promise();
196 }
197 );
198 };
199
200 /**
201 * Update the list of changes and notify the model
202 *
203 * @param {Object} [params] Extra parameters to add to the API call
204 */
205 mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
206 this.updateURL( params );
207 this.changesListModel.invalidate();
208 this.fetchChangesList()
209 .then(
210 // Success
211 function ( pieces ) {
212 var $changesListContent = pieces.changes,
213 $fieldset = pieces.fieldset;
214
215 this.changesListModel.update( $changesListContent, $fieldset );
216 }.bind( this )
217 // Do nothing for failure
218 );
219 };
220
221 /**
222 * Toggle the highlight feature on and off
223 */
224 mw.rcfilters.Controller.prototype.toggleHighlight = function () {
225 this.filtersModel.toggleHighlight();
226 this.updateURL();
227 };
228
229 /**
230 * Set the highlight color for a filter item
231 *
232 * @param {string} filterName Name of the filter item
233 * @param {string} color Selected color
234 */
235 mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
236 this.filtersModel.setHighlightColor( filterName, color );
237 this.updateURL();
238 };
239
240 /**
241 * Clear highlight for a filter item
242 *
243 * @param {string} filterName Name of the filter item
244 */
245 mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
246 this.filtersModel.clearHighlightColor( filterName );
247 this.updateURL();
248 };
249 }( mediaWiki, jQuery ) );