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