* @param {string} name Group name
* @param {Object} [config] Configuration options
* @cfg {string} [type='send_unselected_if_any'] Group type
+ * @cfg {string} [view='default'] Name of the display group this group
+ * is a part of.
* @cfg {string} [title] Group title
* @cfg {string} [separator='|'] Value separator for 'string_options' groups
* @cfg {boolean} [active] Group is active
* @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results
* @cfg {Object} [conflicts] Defines the conflicts for this filter group
+ * @cfg {string|Object} [labelPrefixKey] An i18n key defining the prefix label for this
+ * group. If the prefix has 'invert' state, the parameter is expected to be an object
+ * with 'default' and 'inverted' as keys.
* @cfg {Object} [whatsThis] Defines the messages that should appear for the 'what's this' popup
* @cfg {string} [whatsThis.header] The header of the whatsThis popup message
* @cfg {string} [whatsThis.body] The body of the whatsThis popup message
this.name = name;
this.type = config.type || 'send_unselected_if_any';
+ this.view = config.view || 'default';
this.title = config.title;
this.separator = config.separator || '|';
+ this.labelPrefixKey = config.labelPrefixKey;
this.active = !!config.active;
this.fullCoverage = !!config.fullCoverage;
this.whatsThis = config.whatsThis || {};
this.conflicts = config.conflicts || {};
+ this.defaultParams = {};
this.aggregate( { update: 'filterItemUpdate' } );
this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
/* Methods */
+ /**
+ * Initialize the group and create its filter items
+ *
+ * @param {Object} filterDefinition Filter definition for this group
+ * @param {string|Object} [groupDefault] Definition of the group default
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.initializeFilters = function ( filterDefinition, groupDefault ) {
+ var supersetMap = {},
+ model = this,
+ items = [];
+
+ filterDefinition.forEach( function ( filter ) {
+ // Instantiate an item
+ var subsetNames = [],
+ filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
+ group: model.getName(),
+ label: filter.label || filter.name,
+ description: filter.description || '',
+ labelPrefixKey: model.labelPrefixKey,
+ cssClass: filter.cssClass,
+ identifiers: filter.identifiers
+ } );
+
+ filter.subset = filter.subset || [];
+ filter.subset = filter.subset.map( function ( el ) {
+ return el.filter;
+ } );
+
+ if ( filter.subset ) {
+ subsetNames = [];
+ filter.subset.forEach( function ( subsetFilterName ) { // eslint-disable-line no-loop-func
+ // Subsets (unlike conflicts) are always inside the same group
+ // We can re-map the names of the filters we are getting from
+ // the subsets with the group prefix
+ var subsetName = model.getPrefixedName( subsetFilterName );
+ // For convenience, we should store each filter's "supersets" -- these are
+ // the filters that have that item in their subset list. This will just
+ // make it easier to go through whether the item has any other items
+ // that affect it (and are selected) at any given time
+ supersetMap[ subsetName ] = supersetMap[ subsetName ] || [];
+ mw.rcfilters.utils.addArrayElementsUnique(
+ supersetMap[ subsetName ],
+ filterItem.getName()
+ );
+
+ // Translate subset param name to add the group name, so we
+ // get consistent naming. We know that subsets are only within
+ // the same group
+ subsetNames.push( subsetName );
+ } );
+
+ // Set translated subset
+ filterItem.setSubset( subsetNames );
+ }
+
+ items.push( filterItem );
+
+ // Store default parameter state; in this case, default is defined per filter
+ if ( model.getType() === 'send_unselected_if_any' ) {
+ // Store the default parameter state
+ // For this group type, parameter values are direct
+ // We need to convert from a boolean to a string ('1' and '0')
+ model.defaultParams[ filter.name ] = String( Number( !!filter.default ) );
+ }
+ } );
+
+ // Add items
+ this.addItems( items );
+
+ // Now that we have all items, we can apply the superset map
+ this.getItems().forEach( function ( filterItem ) {
+ filterItem.setSuperset( supersetMap[ filterItem.getName() ] );
+ } );
+
+ // Store default parameter state; in this case, default is defined per the
+ // entire group, given by groupDefault method parameter
+ if ( this.getType() === 'string_options' ) {
+ // Store the default parameter group state
+ // For this group, the parameter is group name and value is the names
+ // of selected items
+ this.defaultParams[ this.getName() ] = mw.rcfilters.utils.normalizeParamOptions(
+ // Current values
+ groupDefault ?
+ groupDefault.split( this.getSeparator() ) :
+ [],
+ // Legal values
+ this.getItems().map( function ( item ) {
+ return item.getParamName();
+ } )
+ ).join( this.getSeparator() );
+ }
+ };
+
/**
* Respond to filterItem update event
*
return this.name;
};
+ /**
+ * Get the default param state of this group
+ *
+ * @return {Object} Default param state
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getDefaultParams = function () {
+ return this.defaultParams;
+ };
+
/**
* Get the messags defining the 'whats this' popup for this group
*
this.conflicts = conflicts;
};
+ /**
+ * Set conflicts for each filter item in the group based on the
+ * given conflict map
+ *
+ * @param {Object} conflicts Object representing the conflict map,
+ * keyed by the item name, where its value is an object for all its conflicts
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.setFilterConflicts = function ( conflicts ) {
+ this.getItems().forEach( function ( filterItem ) {
+ if ( conflicts[ filterItem.getName() ] ) {
+ filterItem.setConflicts( conflicts[ filterItem.getName() ] );
+ }
+ } );
+ };
+
/**
* Check whether this item has a potential conflict with the given item
*
/**
* Get the parameter representation from this group
*
+ * @param {Object} [filterRepresentation] An object defining the state
+ * of the filters in this group, keyed by their name and current selected
+ * state value.
* @return {Object} Parameter representation
*/
- mw.rcfilters.dm.FilterGroup.prototype.getParamRepresentation = function () {
- var i, values,
+ mw.rcfilters.dm.FilterGroup.prototype.getParamRepresentation = function ( filterRepresentation ) {
+ var values,
+ areAnySelected = false,
+ buildFromCurrentState = !filterRepresentation,
result = {},
- filterItems = this.getItems();
+ filterParamNames = {};
+
+ filterRepresentation = filterRepresentation || {};
+
+ // Create or complete the filterRepresentation definition
+ this.getItems().forEach( function ( item ) {
+ // Map filter names to their parameter names
+ filterParamNames[ item.getName() ] = item.getParamName();
+
+ if ( buildFromCurrentState ) {
+ // This means we have not been given a filter representation
+ // so we are building one based on current state
+ filterRepresentation[ item.getName() ] = item.isSelected();
+ } else if ( !filterRepresentation[ item.getName() ] ) {
+ // We are given a filter representation, but we have to make
+ // sure that we fill in the missing filters if there are any
+ // we will assume they are all falsey
+ filterRepresentation[ item.getName() ] = false;
+ }
+
+ if ( filterRepresentation[ item.getName() ] ) {
+ areAnySelected = true;
+ }
+ } );
+ // Build result
if ( this.getType() === 'send_unselected_if_any' ) {
// First, check if any of the items are selected at all.
// If none is selected, we're treating it as if they are
// all false
// Go over the items and define the correct values
- for ( i = 0; i < filterItems.length; i++ ) {
- result[ filterItems[ i ].getParamName() ] = this.areAnySelected() ?
- Number( !filterItems[ i ].isSelected() ) : 0;
- }
-
+ $.each( filterRepresentation, function ( name, value ) {
+ result[ filterParamNames[ name ] ] = areAnySelected ?
+ // We must store all parameter values as strings '0' or '1'
+ String( Number( !value ) ) :
+ '0';
+ } );
} else if ( this.getType() === 'string_options' ) {
values = [];
- for ( i = 0; i < filterItems.length; i++ ) {
- if ( filterItems[ i ].isSelected() ) {
- values.push( filterItems[ i ].getParamName() );
+
+ $.each( filterRepresentation, function ( name, value ) {
+ // Collect values
+ if ( value ) {
+ values.push( filterParamNames[ name ] );
}
- }
+ } );
- result[ this.getName() ] = ( values.length === filterItems.length ) ?
+ result[ this.getName() ] = ( values.length === Object.keys( filterRepresentation ).length ) ?
'all' : values.join( this.getSeparator() );
}
return result;
};
+ /**
+ * Get the filter representation this group would provide
+ * based on given parameter states.
+ *
+ * @param {Object|string} [paramRepresentation] An object defining a parameter
+ * state to translate the filter state from. If not given, an object
+ * representing all filters as falsey is returned; same as if the parameter
+ * given were an empty object, or had some of the filters missing.
+ * @return {Object} Filter representation
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
+ var areAnySelected, paramValues,
+ model = this,
+ paramToFilterMap = {},
+ result = {};
+
+ if ( this.getType() === 'send_unselected_if_any' ) {
+ paramRepresentation = paramRepresentation || {};
+ // Expand param representation to include all filters in the group
+ this.getItems().forEach( function ( filterItem ) {
+ var paramName = filterItem.getParamName();
+
+ paramRepresentation[ paramName ] = paramRepresentation[ paramName ] || '0';
+ paramToFilterMap[ paramName ] = filterItem;
+
+ if ( Number( paramRepresentation[ filterItem.getParamName() ] ) ) {
+ areAnySelected = true;
+ }
+ } );
+
+ $.each( paramRepresentation, function ( paramName, paramValue ) {
+ var filterItem = paramToFilterMap[ paramName ];
+
+ result[ filterItem.getName() ] = areAnySelected ?
+ // Flip the definition between the parameter
+ // state and the filter state
+ // This is what the 'toggleSelected' value of the filter is
+ !Number( paramValue ) :
+ // Otherwise, there are no selected items in the
+ // group, which means the state is false
+ false;
+ } );
+ } else if ( this.getType() === 'string_options' ) {
+ paramRepresentation = paramRepresentation || '';
+
+ // Normalize the given parameter values
+ paramValues = mw.rcfilters.utils.normalizeParamOptions(
+ // Given
+ paramRepresentation.split(
+ this.getSeparator()
+ ),
+ // Allowed values
+ this.getItems().map( function ( filterItem ) {
+ return filterItem.getParamName();
+ } )
+ );
+ // Translate the parameter values into a filter selection state
+ this.getItems().forEach( function ( filterItem ) {
+ result[ filterItem.getName() ] = (
+ // If it is the word 'all'
+ paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
+ // All values are written
+ paramValues.length === model.getItemCount()
+ ) ?
+ // All true (either because all values are written or the term 'all' is written)
+ // is the same as all filters set to true
+ true :
+ // Otherwise, the filter is selected only if it appears in the parameter values
+ paramValues.indexOf( filterItem.getParamName() ) > -1;
+ } );
+ }
+
+ // Go over result and make sure all filters are represented.
+ // If any filters are missing, they will get a falsey value
+ this.getItems().forEach( function ( filterItem ) {
+ result[ filterItem.getName() ] = !!result[ filterItem.getName() ];
+ } );
+
+ return result;
+ };
+
+ /**
+ * Get item by its parameter name
+ *
+ * @param {string} paramName Parameter name
+ * @return {mw.rcfilters.dm.FilterItem} Filter item
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getItemByParamName = function ( paramName ) {
+ return this.getItems().filter( function ( item ) {
+ return item.getParamName() === paramName;
+ } )[ 0 ];
+ };
+
/**
* Get group type
*
};
/**
- * Get the prefix used for the filter names inside this group
+ * Get display group
+ *
+ * @return {string} Display group
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getView = function () {
+ return this.view;
+ };
+
+ /**
+ * Get the prefix used for the filter names inside this group.
*
+ * @param {string} [name] Filter name to prefix
* @return {string} Group prefix
*/
mw.rcfilters.dm.FilterGroup.prototype.getNamePrefix = function () {
return this.getName() + '__';
};
+ /**
+ * Get a filter name with the prefix used for the filter names inside this group.
+ *
+ * @param {string} name Filter name to prefix
+ * @return {string} Group prefix
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getPrefixedName = function ( name ) {
+ return this.getNamePrefix() + name;
+ };
+
/**
* Get group's title
*