Merge "Fix login button label to accept RawMessage."
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.SavedQueriesModel.js
1 ( function ( mw, $ ) {
2 /**
3 * View model for saved queries
4 *
5 * @class
6 * @mixins OO.EventEmitter
7 * @mixins OO.EmitterList
8 *
9 * @constructor
10 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters model
11 * @param {Object} [config] Configuration options
12 * @cfg {string} [default] Default query ID
13 */
14 mw.rcfilters.dm.SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( filtersModel, config ) {
15 config = config || {};
16
17 // Mixin constructor
18 OO.EventEmitter.call( this );
19 OO.EmitterList.call( this );
20
21 this.default = config.default;
22 this.filtersModel = filtersModel;
23 this.converted = false;
24
25 // Events
26 this.aggregate( { update: 'itemUpdate' } );
27 };
28
29 /* Initialization */
30
31 OO.initClass( mw.rcfilters.dm.SavedQueriesModel );
32 OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EventEmitter );
33 OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EmitterList );
34
35 /* Events */
36
37 /**
38 * @event initialize
39 *
40 * Model is initialized
41 */
42
43 /**
44 * @event itemUpdate
45 * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item
46 *
47 * An item has changed
48 */
49
50 /**
51 * @event default
52 * @param {string} New default ID
53 *
54 * The default has changed
55 */
56
57 /* Methods */
58
59 /**
60 * Initialize the saved queries model by reading it from the user's settings.
61 * The structure of the saved queries is:
62 * {
63 * version: (string) Version number; if version 2, the query represents
64 * parameters. Otherwise, the older version represented filters
65 * and needs to be readjusted,
66 * default: (string) Query ID
67 * queries:{
68 * query_id_1: {
69 * data:{
70 * filters: (Object) Minimal definition of the filters
71 * highlights: (Object) Definition of the highlights
72 * },
73 * label: (optional) Name of this query
74 * }
75 * }
76 * }
77 *
78 * @param {Object} [savedQueries] An object with the saved queries with
79 * the above structure.
80 * @fires initialize
81 */
82 mw.rcfilters.dm.SavedQueriesModel.prototype.initialize = function ( savedQueries ) {
83 var model = this;
84
85 savedQueries = savedQueries || {};
86
87 this.clearItems();
88 this.default = null;
89 this.converted = false;
90
91 if ( savedQueries.version !== '2' ) {
92 // Old version dealt with filter names. We need to migrate to the new structure
93 // The new structure:
94 // {
95 // version: (string) '2',
96 // default: (string) Query ID,
97 // queries: {
98 // query_id: {
99 // label: (string) Name of the query
100 // data: {
101 // params: (object) Representing all the parameter states
102 // highlights: (object) Representing all the filter highlight states
103 // }
104 // }
105 // }
106 $.each( savedQueries.queries || {}, function ( id, obj ) {
107 if ( obj.data && obj.data.filters ) {
108 obj.data = model.convertToParameters( obj.data );
109 }
110 } );
111
112 this.converted = true;
113 savedQueries.version = '2';
114 }
115
116 // Initialize the query items
117 $.each( savedQueries.queries || {}, function ( id, obj ) {
118 var normalizedData = obj.data,
119 isDefault = String( savedQueries.default ) === String( id );
120
121 if ( normalizedData && normalizedData.params ) {
122 // Backwards-compat fix: Remove excluded parameters from
123 // the given data, if they exist
124 normalizedData.params = model.filtersModel.removeExcludedParams( normalizedData.params );
125
126 id = String( id );
127
128 // Skip the addNewQuery method because we don't want to unnecessarily manipulate
129 // the given saved queries unless we literally intend to (like in backwards compat fixes)
130 // And the addNewQuery method also uses a minimization routine that checks for the
131 // validity of items and minimizes the query. This isn't necessary for queries loaded
132 // from the backend, and has the risk of removing values if they're temporarily
133 // invalid (example: if we temporarily removed a cssClass from a filter in the backend)
134 model.addItems( [
135 new mw.rcfilters.dm.SavedQueryItemModel(
136 id,
137 obj.label,
138 normalizedData,
139 { 'default': isDefault }
140 )
141 ] );
142
143 if ( isDefault ) {
144 model.default = id;
145 }
146 }
147 } );
148
149 this.emit( 'initialize' );
150 };
151
152 /**
153 * Convert from representation of filters to representation of parameters
154 *
155 * @param {Object} data Query data
156 * @return {Object} New converted query data
157 */
158 mw.rcfilters.dm.SavedQueriesModel.prototype.convertToParameters = function ( data ) {
159 var newData = {},
160 defaultFilters = this.filtersModel.getFiltersFromParameters( this.filtersModel.getDefaultParams() ),
161 fullFilterRepresentation = $.extend( true, {}, defaultFilters, data.filters ),
162 highlightEnabled = data.highlights.highlight;
163
164 delete data.highlights.highlight;
165
166 // Filters
167 newData.params = this.filtersModel.getMinimizedParamRepresentation(
168 this.filtersModel.getParametersFromFilters( fullFilterRepresentation )
169 );
170
171 // Highlights (taking out 'highlight' itself, appending _color to keys)
172 newData.highlights = {};
173 $.each( data.highlights, function ( highlightedFilterName, value ) {
174 if ( value ) {
175 newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
176 }
177 } );
178
179 // Add highlight
180 newData.params.highlight = String( Number( highlightEnabled || 0 ) );
181
182 return newData;
183 };
184
185 /**
186 * Add a query item
187 *
188 * @param {string} label Label for the new query
189 * @param {Object} fulldata Full data representation for the new query, combining highlights and filters
190 * @param {boolean} isDefault Item is default
191 * @param {string} [id] Query ID, if exists. If this isn't given, a random
192 * new ID will be created.
193 * @return {string} ID of the newly added query
194 */
195 mw.rcfilters.dm.SavedQueriesModel.prototype.addNewQuery = function ( label, fulldata, isDefault, id ) {
196 var normalizedData = { params: {}, highlights: {} },
197 highlightParamNames = Object.keys( this.filtersModel.getEmptyHighlightParameters() ),
198 randomID = String( id || ( new Date() ).getTime() ),
199 data = this.filtersModel.getMinimizedParamRepresentation( fulldata );
200
201 // Split highlight/params
202 $.each( data, function ( param, value ) {
203 if ( param !== 'highlight' && highlightParamNames.indexOf( param ) > -1 ) {
204 normalizedData.highlights[ param ] = value;
205 } else {
206 normalizedData.params[ param ] = value;
207 }
208 } );
209
210 // Add item
211 this.addItems( [
212 new mw.rcfilters.dm.SavedQueryItemModel(
213 randomID,
214 label,
215 normalizedData,
216 { 'default': isDefault }
217 )
218 ] );
219
220 if ( isDefault ) {
221 this.setDefault( randomID );
222 }
223
224 return randomID;
225 };
226
227 /**
228 * Remove query from model
229 *
230 * @param {string} queryID Query ID
231 */
232 mw.rcfilters.dm.SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
233 var query = this.getItemByID( queryID );
234
235 if ( query ) {
236 // Check if this item was the default
237 if ( String( this.getDefault() ) === String( queryID ) ) {
238 // Nulify the default
239 this.setDefault( null );
240 }
241
242 this.removeItems( [ query ] );
243 }
244 };
245
246 /**
247 * Get an item that matches the requested query
248 *
249 * @param {Object} fullQueryComparison Object representing all filters and highlights to compare
250 * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
251 */
252 mw.rcfilters.dm.SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
253 // Minimize before comparison
254 fullQueryComparison = this.filtersModel.getMinimizedParamRepresentation( fullQueryComparison );
255
256 return this.getItems().filter( function ( item ) {
257 return OO.compare(
258 item.getCombinedData(),
259 fullQueryComparison
260 );
261 } )[ 0 ];
262 };
263
264 /**
265 * Get query by its identifier
266 *
267 * @param {string} queryID Query identifier
268 * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
269 * the search. Undefined if not found.
270 */
271 mw.rcfilters.dm.SavedQueriesModel.prototype.getItemByID = function ( queryID ) {
272 return this.getItems().filter( function ( item ) {
273 return item.getID() === queryID;
274 } )[ 0 ];
275 };
276
277 /**
278 * Get the full data representation of the default query, if it exists
279 *
280 * @param {boolean} [excludeHiddenParams] Exclude hidden parameters in the result
281 * @return {Object|null} Representation of the default params if exists.
282 * Null if default doesn't exist or if the user is not logged in.
283 */
284 mw.rcfilters.dm.SavedQueriesModel.prototype.getDefaultParams = function ( excludeHiddenParams ) {
285 var data = ( !mw.user.isAnon() && this.getItemParams( this.getDefault() ) ) || {};
286
287 if ( excludeHiddenParams ) {
288 Object.keys( this.filtersModel.getDefaultHiddenParams() ).forEach( function ( paramName ) {
289 delete data[ paramName ];
290 } );
291 }
292
293 return data;
294 };
295
296 /**
297 * Get a full parameter representation of an item data
298 *
299 * @param {Object} queryID Query ID
300 * @return {Object} Parameter representation
301 */
302 mw.rcfilters.dm.SavedQueriesModel.prototype.getItemParams = function ( queryID ) {
303 var item = this.getItemByID( queryID ),
304 data = item ? item.getData() : {};
305
306 return !$.isEmptyObject( data ) ? this.buildParamsFromData( data ) : {};
307 };
308
309 /**
310 * Build a full parameter representation given item data and model sticky values state
311 *
312 * @param {Object} data Item data
313 * @return {Object} Full param representation
314 */
315 mw.rcfilters.dm.SavedQueriesModel.prototype.buildParamsFromData = function ( data ) {
316 // Merge saved filter state with sticky filter values
317 var savedFilters;
318
319 data = data || {};
320
321 // In order to merge sticky filters with the data, we have to
322 // transform this to filters first, merge, and then back to
323 // parameters
324 savedFilters = $.extend(
325 true, {},
326 this.filtersModel.getFiltersFromParameters( data.params ),
327 this.filtersModel.getStickyFiltersState()
328 );
329
330 // Return parameter representation
331 return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {},
332 this.filtersModel.getParametersFromFilters( savedFilters ),
333 data.highlights,
334 { highlight: data.params.highlight }
335 ) );
336 };
337
338 /**
339 * Get the object representing the state of the entire model and items
340 *
341 * @return {Object} Object representing the state of the model and items
342 */
343 mw.rcfilters.dm.SavedQueriesModel.prototype.getState = function () {
344 var obj = { queries: {}, version: '2' };
345
346 // Translate the items to the saved object
347 this.getItems().forEach( function ( item ) {
348 obj.queries[ item.getID() ] = item.getState();
349 } );
350
351 if ( this.getDefault() ) {
352 obj.default = this.getDefault();
353 }
354
355 return obj;
356 };
357
358 /**
359 * Set a default query. Null to unset default.
360 *
361 * @param {string} itemID Query identifier
362 * @fires default
363 */
364 mw.rcfilters.dm.SavedQueriesModel.prototype.setDefault = function ( itemID ) {
365 if ( this.default !== itemID ) {
366 this.default = itemID;
367
368 // Set for individual itens
369 this.getItems().forEach( function ( item ) {
370 item.toggleDefault( item.getID() === itemID );
371 } );
372
373 this.emit( 'default', itemID );
374 }
375 };
376
377 /**
378 * Get the default query ID
379 *
380 * @return {string} Default query identifier
381 */
382 mw.rcfilters.dm.SavedQueriesModel.prototype.getDefault = function () {
383 return this.default;
384 };
385
386 /**
387 * Check if the saved queries were converted
388 *
389 * @return {boolean} Saved queries were converted from the previous
390 * version to the new version
391 */
392 mw.rcfilters.dm.SavedQueriesModel.prototype.isConverted = function () {
393 return this.converted;
394 };
395 }( mediaWiki, jQuery ) );