( function ( mw, $ ) { /** * View model for saved queries * * @class * @mixins OO.EventEmitter * @mixins OO.EmitterList * * @constructor * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters model * @param {Object} [config] Configuration options * @cfg {string} [default] Default query ID */ mw.rcfilters.dm.SavedQueriesModel = function MwRcfiltersDmSavedQueriesModel( filtersModel, config ) { config = config || {}; // Mixin constructor OO.EventEmitter.call( this ); OO.EmitterList.call( this ); this.default = config.default; this.filtersModel = filtersModel; this.converted = false; // Events this.aggregate( { update: 'itemUpdate' } ); }; /* Initialization */ OO.initClass( mw.rcfilters.dm.SavedQueriesModel ); OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EventEmitter ); OO.mixinClass( mw.rcfilters.dm.SavedQueriesModel, OO.EmitterList ); /* Events */ /** * @event initialize * * Model is initialized */ /** * @event itemUpdate * @param {mw.rcfilters.dm.SavedQueryItemModel} Changed item * * An item has changed */ /** * @event default * @param {string} New default ID * * The default has changed */ /* Methods */ /** * Initialize the saved queries model by reading it from the user's settings. * The structure of the saved queries is: * { * version: (string) Version number; if version 2, the query represents * parameters. Otherwise, the older version represented filters * and needs to be readjusted, * default: (string) Query ID * queries:{ * query_id_1: { * data:{ * filters: (Object) Minimal definition of the filters * highlights: (Object) Definition of the highlights * }, * label: (optional) Name of this query * } * } * } * * @param {Object} [savedQueries] An object with the saved queries with * the above structure. * @fires initialize */ mw.rcfilters.dm.SavedQueriesModel.prototype.initialize = function ( savedQueries ) { var model = this; savedQueries = savedQueries || {}; this.clearItems(); this.default = null; this.converted = false; if ( savedQueries.version !== '2' ) { // Old version dealt with filter names. We need to migrate to the new structure // The new structure: // { // version: (string) '2', // default: (string) Query ID, // queries: { // query_id: { // label: (string) Name of the query // data: { // params: (object) Representing all the parameter states // highlights: (object) Representing all the filter highlight states // } // } // } $.each( savedQueries.queries || {}, function ( id, obj ) { if ( obj.data && obj.data.filters ) { obj.data = model.convertToParameters( obj.data ); } } ); this.converted = true; savedQueries.version = '2'; } // Initialize the query items $.each( savedQueries.queries || {}, function ( id, obj ) { var normalizedData = obj.data, isDefault = String( savedQueries.default ) === String( id ); if ( normalizedData && normalizedData.params ) { // Backwards-compat fix: Remove sticky parameters from // the given data, if they exist normalizedData.params = model.filtersModel.removeStickyParams( normalizedData.params ); // Correct the invert state for effective selection if ( normalizedData.params.invert && !normalizedData.params.namespace ) { delete normalizedData.params.invert; } model.cleanupHighlights( normalizedData ); id = String( id ); // Skip the addNewQuery method because we don't want to unnecessarily manipulate // the given saved queries unless we literally intend to (like in backwards compat fixes) // And the addNewQuery method also uses a minimization routine that checks for the // validity of items and minimizes the query. This isn't necessary for queries loaded // from the backend, and has the risk of removing values if they're temporarily // invalid (example: if we temporarily removed a cssClass from a filter in the backend) model.addItems( [ new mw.rcfilters.dm.SavedQueryItemModel( id, obj.label, normalizedData, { 'default': isDefault } ) ] ); if ( isDefault ) { model.default = id; } } } ); this.emit( 'initialize' ); }; /** * Clean up highlight parameters. * 'highlight' used to be stored, it's not inferred based on the presence of absence of * filter colors. * * @param {Object} data Saved query data */ mw.rcfilters.dm.SavedQueriesModel.prototype.cleanupHighlights = function ( data ) { if ( data.params.highlight === '0' && data.highlights && Object.keys( data.highlights ).length ) { data.highlights = {}; } delete data.params.highlight; }; /** * Convert from representation of filters to representation of parameters * * @param {Object} data Query data * @return {Object} New converted query data */ mw.rcfilters.dm.SavedQueriesModel.prototype.convertToParameters = function ( data ) { var newData = {}, defaultFilters = this.filtersModel.getFiltersFromParameters( this.filtersModel.getDefaultParams() ), fullFilterRepresentation = $.extend( true, {}, defaultFilters, data.filters ), highlightEnabled = data.highlights.highlight; delete data.highlights.highlight; // Filters newData.params = this.filtersModel.getMinimizedParamRepresentation( this.filtersModel.getParametersFromFilters( fullFilterRepresentation ) ); // Highlights: appending _color to keys newData.highlights = {}; $.each( data.highlights, function ( highlightedFilterName, value ) { if ( value ) { newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ]; } } ); // Add highlight newData.params.highlight = String( Number( highlightEnabled || 0 ) ); return newData; }; /** * Add a query item * * @param {string} label Label for the new query * @param {Object} fulldata Full data representation for the new query, combining highlights and filters * @param {boolean} isDefault Item is default * @param {string} [id] Query ID, if exists. If this isn't given, a random * new ID will be created. * @return {string} ID of the newly added query */ mw.rcfilters.dm.SavedQueriesModel.prototype.addNewQuery = function ( label, fulldata, isDefault, id ) { var normalizedData = { params: {}, highlights: {} }, highlightParamNames = Object.keys( this.filtersModel.getEmptyHighlightParameters() ), randomID = String( id || ( new Date() ).getTime() ), data = this.filtersModel.getMinimizedParamRepresentation( fulldata ); // Split highlight/params $.each( data, function ( param, value ) { if ( param !== 'highlight' && highlightParamNames.indexOf( param ) > -1 ) { normalizedData.highlights[ param ] = value; } else { normalizedData.params[ param ] = value; } } ); // Correct the invert state for effective selection if ( normalizedData.params.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) { delete normalizedData.params.invert; } // Add item this.addItems( [ new mw.rcfilters.dm.SavedQueryItemModel( randomID, label, normalizedData, { 'default': isDefault } ) ] ); if ( isDefault ) { this.setDefault( randomID ); } return randomID; }; /** * Remove query from model * * @param {string} queryID Query ID */ mw.rcfilters.dm.SavedQueriesModel.prototype.removeQuery = function ( queryID ) { var query = this.getItemByID( queryID ); if ( query ) { // Check if this item was the default if ( String( this.getDefault() ) === String( queryID ) ) { // Nulify the default this.setDefault( null ); } this.removeItems( [ query ] ); } }; /** * Get an item that matches the requested query * * @param {Object} fullQueryComparison Object representing all filters and highlights to compare * @return {mw.rcfilters.dm.SavedQueryItemModel} Matching item model */ mw.rcfilters.dm.SavedQueriesModel.prototype.findMatchingQuery = function ( fullQueryComparison ) { // Minimize before comparison fullQueryComparison = this.filtersModel.getMinimizedParamRepresentation( fullQueryComparison ); // Correct the invert state for effective selection if ( fullQueryComparison.invert && !this.filtersModel.areNamespacesEffectivelyInverted() ) { delete fullQueryComparison.invert; } return this.getItems().filter( function ( item ) { return OO.compare( item.getCombinedData(), fullQueryComparison ); } )[ 0 ]; }; /** * Get query by its identifier * * @param {string} queryID Query identifier * @return {mw.rcfilters.dm.SavedQueryItemModel|undefined} Item matching * the search. Undefined if not found. */ mw.rcfilters.dm.SavedQueriesModel.prototype.getItemByID = function ( queryID ) { return this.getItems().filter( function ( item ) { return item.getID() === queryID; } )[ 0 ]; }; /** * Get the full data representation of the default query, if it exists * * @return {Object|null} Representation of the default params if exists. * Null if default doesn't exist or if the user is not logged in. */ mw.rcfilters.dm.SavedQueriesModel.prototype.getDefaultParams = function () { return ( !mw.user.isAnon() && this.getItemParams( this.getDefault() ) ) || {}; }; /** * Get a full parameter representation of an item data * * @param {Object} queryID Query ID * @return {Object} Parameter representation */ mw.rcfilters.dm.SavedQueriesModel.prototype.getItemParams = function ( queryID ) { var item = this.getItemByID( queryID ), data = item ? item.getData() : {}; return !$.isEmptyObject( data ) ? this.buildParamsFromData( data ) : {}; }; /** * Build a full parameter representation given item data and model sticky values state * * @param {Object} data Item data * @return {Object} Full param representation */ mw.rcfilters.dm.SavedQueriesModel.prototype.buildParamsFromData = function ( data ) { data = data || {}; // Return parameter representation return this.filtersModel.getMinimizedParamRepresentation( $.extend( true, {}, data.params, data.highlights ) ); }; /** * Get the object representing the state of the entire model and items * * @return {Object} Object representing the state of the model and items */ mw.rcfilters.dm.SavedQueriesModel.prototype.getState = function () { var obj = { queries: {}, version: '2' }; // Translate the items to the saved object this.getItems().forEach( function ( item ) { obj.queries[ item.getID() ] = item.getState(); } ); if ( this.getDefault() ) { obj.default = this.getDefault(); } return obj; }; /** * Set a default query. Null to unset default. * * @param {string} itemID Query identifier * @fires default */ mw.rcfilters.dm.SavedQueriesModel.prototype.setDefault = function ( itemID ) { if ( this.default !== itemID ) { this.default = itemID; // Set for individual itens this.getItems().forEach( function ( item ) { item.toggleDefault( item.getID() === itemID ); } ); this.emit( 'default', itemID ); } }; /** * Get the default query ID * * @return {string} Default query identifier */ mw.rcfilters.dm.SavedQueriesModel.prototype.getDefault = function () { return this.default; }; /** * Check if the saved queries were converted * * @return {boolean} Saved queries were converted from the previous * version to the new version */ mw.rcfilters.dm.SavedQueriesModel.prototype.isConverted = function () { return this.converted; }; }( mediaWiki, jQuery ) );