build: Update eslint-config-wikimedia to 0.10.0
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.SavedQueriesModel.js
1 ( function () {
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 // eslint-disable-next-line jquery/no-each-util
107 $.each( savedQueries.queries || {}, function ( id, obj ) {
108 if ( obj.data && obj.data.filters ) {
109 obj.data = model.convertToParameters( obj.data );
110 }
111 } );
112
113 this.converted = true;
114 savedQueries.version = '2';
115 }
116
117 // Initialize the query items
118 // eslint-disable-next-line jquery/no-each-util
119 $.each( savedQueries.queries || {}, function ( id, obj ) {
120 var normalizedData = obj.data,
121 isDefault = String( savedQueries.default ) === String( id );
122
123 if ( normalizedData && normalizedData.params ) {
124 // Backwards-compat fix: Remove sticky parameters from
125 // the given data, if they exist
126 normalizedData.params = model.filtersModel.removeStickyParams( normalizedData.params );
127
128 // Correct the invert state for effective selection
129 if ( normalizedData.params.invert && !normalizedData.params.namespace ) {
130 delete normalizedData.params.invert;
131 }
132
133 model.cleanupHighlights( normalizedData );
134
135 id = String( id );
136
137 // Skip the addNewQuery method because we don't want to unnecessarily manipulate
138 // the given saved queries unless we literally intend to (like in backwards compat fixes)
139 // And the addNewQuery method also uses a minimization routine that checks for the
140 // validity of items and minimizes the query. This isn't necessary for queries loaded
141 // from the backend, and has the risk of removing values if they're temporarily
142 // invalid (example: if we temporarily removed a cssClass from a filter in the backend)
143 model.addItems( [
144 new mw.rcfilters.dm.SavedQueryItemModel(
145 id,
146 obj.label,
147 normalizedData,
148 { default: isDefault }
149 )
150 ] );
151
152 if ( isDefault ) {
153 model.default = id;
154 }
155 }
156 } );
157
158 this.emit( 'initialize' );
159 };
160
161 /**
162 * Clean up highlight parameters.
163 * 'highlight' used to be stored, it's not inferred based on the presence of absence of
164 * filter colors.
165 *
166 * @param {Object} data Saved query data
167 */
168 mw.rcfilters.dm.SavedQueriesModel.prototype.cleanupHighlights = function ( data ) {
169 if (
170 data.params.highlight === '0' &&
171 data.highlights && Object.keys( data.highlights ).length
172 ) {
173 data.highlights = {};
174 }
175 delete data.params.highlight;
176 };
177
178 /**
179 * Convert from representation of filters to representation of parameters
180 *
181 * @param {Object} data Query data
182 * @return {Object} New converted query data
183 */
184 mw.rcfilters.dm.SavedQueriesModel.prototype.convertToParameters = function ( data ) {
185 var newData = {},
186 defaultFilters = this.filtersModel.getFiltersFromParameters( this.filtersModel.getDefaultParams() ),
187 fullFilterRepresentation = $.extend( true, {}, defaultFilters, data.filters ),
188 highlightEnabled = data.highlights.highlight;
189
190 delete data.highlights.highlight;
191
192 // Filters
193 newData.params = this.filtersModel.getMinimizedParamRepresentation(
194 this.filtersModel.getParametersFromFilters( fullFilterRepresentation )
195 );
196
197 // Highlights: appending _color to keys
198 newData.highlights = {};
199 // eslint-disable-next-line jquery/no-each-util
200 $.each( data.highlights, function ( highlightedFilterName, value ) {
201 if ( value ) {
202 newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
203 }
204 } );
205
206 // Add highlight
207 newData.params.highlight = String( Number( highlightEnabled || 0 ) );
208
209 return newData;
210 };
211
212 /**
213 * Add a query item
214 *
215 * @param {string} label Label for the new query
216 * @param {Object} fulldata Full data representation for the new query, combining highlights and filters
217 * @param {boolean} isDefault Item is default
218 * @param {string} [id] Query ID, if exists. If this isn't given, a random
219 * new ID will be created.
220 * @return {string} ID of the newly added query
221 */
222 mw.rcfilters.dm.SavedQueriesModel.prototype.addNewQuery = function ( label, fulldata, isDefault, id ) {
223 var normalizedData = { params: {}, highlights: {} },
224 highlightParamNames = Object.keys( this.filtersModel.getEmptyHighlightParameters() ),
225 randomID = String( id || ( new Date() ).getTime() ),
226 data = this.filtersModel.getMinimizedParamRepresentation( fulldata );
227
228 // Split highlight/params
229 // eslint-disable-next-line jquery/no-each-util
230 $.each( data, function ( param, value ) {
231 if ( param !== 'highlight' && highlightParamNames.indexOf( param ) > -1 ) {
232 normalizedData.highlights[ param ] = value;
233 } else {
234 normalizedData.params[ param ] = value;
235 }
236 } );
237
238 // Correct the invert state for effective selection
239 if ( normalizedData.params.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
240 delete normalizedData.params.invert;
241 }
242
243 // Add item
244 this.addItems( [
245 new mw.rcfilters.dm.SavedQueryItemModel(
246 randomID,
247 label,
248 normalizedData,
249 { default: isDefault }
250 )
251 ] );
252
253 if ( isDefault ) {
254 this.setDefault( randomID );
255 }
256
257 return randomID;
258 };
259
260 /**
261 * Remove query from model
262 *
263 * @param {string} queryID Query ID
264 */
265 mw.rcfilters.dm.SavedQueriesModel.prototype.removeQuery = function ( queryID ) {
266 var query = this.getItemByID( queryID );
267
268 if ( query ) {
269 // Check if this item was the default
270 if ( String( this.getDefault() ) === String( queryID ) ) {
271 // Nulify the default
272 this.setDefault( null );
273 }
274
275 this.removeItems( [ query ] );
276 }
277 };
278
279 /**
280 * Get an item that matches the requested query
281 *
282 * @param {Object} fullQueryComparison Object representing all filters and highlights to compare
283 * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model
284 */
285 mw.rcfilters.dm.SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) {
286 // Minimize before comparison
287 fullQueryComparison = this.filtersModel.getMinimizedParamRepresentation( fullQueryComparison );
288
289 // Correct the invert state for effective selection
290 if ( fullQueryComparison.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) {
291 delete fullQueryComparison.invert;
292 }
293
294 return this.getItems().filter( function ( item ) {
295 return OO.compare(
296 item.getCombinedData(),
297 fullQueryComparison
298 );
299 } )[ 0 ];
300 };
301
302 /**
303 * Get query by its identifier
304 *
305 * @param {string} queryID Query identifier
306 * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching
307 * the search. Undefined if not found.
308 */
309 mw.rcfilters.dm.SavedQueriesModel.prototype.getItemByID = function ( queryID ) {
310 return this.getItems().filter( function ( item ) {
311 return item.getID() === queryID;
312 } )[ 0 ];
313 };
314
315 /**
316 * Get the full data representation of the default query, if it exists
317 *
318 * @return {Object|null} Representation of the default params if exists.
319 * Null if default doesn't exist or if the user is not logged in.
320 */
321 mw.rcfilters.dm.SavedQueriesModel.prototype.getDefaultParams = function () {
322 return ( !mw.user.isAnon() && this.getItemParams( this.getDefault() ) ) || {};
323 };
324
325 /**
326 * Get a full parameter representation of an item data
327 *
328 * @param {Object} queryID Query ID
329 * @return {Object} Parameter representation
330 */
331 mw.rcfilters.dm.SavedQueriesModel.prototype.getItemParams = function ( queryID ) {
332 var item = this.getItemByID( queryID ),
333 data = item ? item.getData() : {};
334
335 return !$.isEmptyObject( data ) ? this.buildParamsFromData( data ) : {};
336 };
337
338 /**
339 * Build a full parameter representation given item data and model sticky values state
340 *
341 * @param {Object} data Item data
342 * @return {Object} Full param representation
343 */
344 mw.rcfilters.dm.SavedQueriesModel.prototype.buildParamsFromData = function ( data ) {
345 data = data || {};
346 // Return parameter representation
347 return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {},
348 data.params,
349 data.highlights
350 ) );
351 };
352
353 /**
354 * Get the object representing the state of the entire model and items
355 *
356 * @return {Object} Object representing the state of the model and items
357 */
358 mw.rcfilters.dm.SavedQueriesModel.prototype.getState = function () {
359 var obj = { queries: {}, version: '2' };
360
361 // Translate the items to the saved object
362 this.getItems().forEach( function ( item ) {
363 obj.queries[ item.getID() ] = item.getState();
364 } );
365
366 if ( this.getDefault() ) {
367 obj.default = this.getDefault();
368 }
369
370 return obj;
371 };
372
373 /**
374 * Set a default query. Null to unset default.
375 *
376 * @param {string} itemID Query identifier
377 * @fires default
378 */
379 mw.rcfilters.dm.SavedQueriesModel.prototype.setDefault = function ( itemID ) {
380 if ( this.default !== itemID ) {
381 this.default = itemID;
382
383 // Set for individual itens
384 this.getItems().forEach( function ( item ) {
385 item.toggleDefault( item.getID() === itemID );
386 } );
387
388 this.emit( 'default', itemID );
389 }
390 };
391
392 /**
393 * Get the default query ID
394 *
395 * @return {string} Default query identifier
396 */
397 mw.rcfilters.dm.SavedQueriesModel.prototype.getDefault = function () {
398 return this.default;
399 };
400
401 /**
402 * Check if the saved queries were converted
403 *
404 * @return {boolean} Saved queries were converted from the previous
405 * version to the new version
406 */
407 mw.rcfilters.dm.SavedQueriesModel.prototype.isConverted = function () {
408 return this.converted;
409 };
410 }() );