* @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 {boolean} [hidden] This group is hidden from the regular menu views
+ * @cfg {boolean} [allowArbitrary] Allows for an arbitrary value to be added to the
+ * group from the URL, even if it wasn't initially set up.
* @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.title = config.title;
+ this.view = config.view || 'default';
+ this.title = config.title || name;
+ this.hidden = !!config.hidden;
+ this.allowArbitrary = !!config.allowArbitrary;
this.separator = config.separator || '|';
+ this.labelPrefixKey = config.labelPrefixKey;
+ this.currSelected = null;
this.active = !!config.active;
this.fullCoverage = !!config.fullCoverage;
* @param {string|Object} [groupDefault] Definition of the group default
*/
mw.rcfilters.dm.FilterGroup.prototype.initializeFilters = function ( filterDefinition, groupDefault ) {
- var supersetMap = {},
+ var defaultParam,
+ supersetMap = {},
model = this,
items = [];
var subsetNames = [],
filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
group: model.getName(),
- label: mw.msg( filter.label ),
- description: mw.msg( filter.description ),
- cssClass: filter.cssClass
+ 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 ) {
+ filter.subset = filter.subset.map( function ( el ) {
+ return el.filter;
+ } );
+
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
return item.getParamName();
} )
).join( this.getSeparator() );
+ } else if ( this.getType() === 'single_option' ) {
+ defaultParam = groupDefault !== undefined ?
+ groupDefault : this.getItems()[ 0 ].getParamName();
+
+ // For this group, the parameter is the group name,
+ // and a single item can be selected: default or first item
+ this.defaultParams[ this.getName() ] = defaultParam;
+
+ // Single option means there must be a single option
+ // selected, so we have to either select the default
+ // or select the first option
+ this.selectItemByParamName( defaultParam );
}
};
/**
* Respond to filterItem update event
*
+ * @param {mw.rcfilters.dm.FilterItem} item Updated filter item
* @fires update
*/
- mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function () {
+ mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function ( item ) {
// Update state
var active = this.areAnySelected();
- if ( this.active !== active ) {
+ if (
+ item.isSelected() &&
+ this.getType() === 'single_option' &&
+ this.currSelected &&
+ this.currSelected !== item
+ ) {
+ this.currSelected.toggleSelected( false );
+ }
+
+ if (
+ this.active !== active ||
+ this.currSelected !== item
+ ) {
this.active = active;
+ this.currSelected = item;
+
this.emit( 'update' );
}
};
return this.active;
};
+ /**
+ * Get group hidden state
+ *
+ * @return {boolean} Hidden state
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.isHidden = function () {
+ return this.hidden;
+ };
+
+ /**
+ * Get group allow arbitrary state
+ *
+ * @return {boolean} Group allows an arbitrary value from the URL
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.isAllowArbitrary = function () {
+ return this.allowArbitrary;
+ };
+
/**
* Get group name
*
return this.defaultParams;
};
+ /**
+ * This is for a single_option and string_options group types
+ * it returns the value of the default
+ *
+ * @return {string} Value of the default
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getDefaulParamValue = function () {
+ return this.defaultParams[ this.getName() ];
+ };
/**
* Get the messags defining the 'whats this' popup for this group
*
areAnySelected = false,
buildFromCurrentState = !filterRepresentation,
result = {},
- filterParamNames = {};
+ model = this,
+ filterParamNames = {},
+ getSelectedParameter = function ( filters ) {
+ var item,
+ selected = [];
+
+ // Find if any are selected
+ $.each( filters, function ( name, value ) {
+ if ( value ) {
+ selected.push( name );
+ }
+ } );
+
+ item = model.getItemByName( selected[ 0 ] );
+ return ( item && item.getParamName() ) || '';
+ };
filterRepresentation = filterRepresentation || {};
// Go over the items and define the correct values
$.each( filterRepresentation, function ( name, value ) {
+ // We must store all parameter values as strings '0' or '1'
result[ filterParamNames[ name ] ] = areAnySelected ?
- // We must store all parameter values as strings '0' or '1'
String( Number( !value ) ) :
'0';
} );
result[ this.getName() ] = ( values.length === Object.keys( filterRepresentation ).length ) ?
'all' : values.join( this.getSeparator() );
+ } else if ( this.getType() === 'single_option' ) {
+ result[ this.getName() ] = getSelectedParameter( filterRepresentation );
}
return result;
* @return {Object} Filter representation
*/
mw.rcfilters.dm.FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
- var areAnySelected, paramValues,
+ var areAnySelected, paramValues, defaultValue, item,
+ oneWasSelected = false,
model = this,
paramToFilterMap = {},
result = {};
$.each( paramRepresentation, function ( paramName, paramValue ) {
var filterItem = paramToFilterMap[ paramName ];
+ // Flip the definition between the parameter
+ // state and the filter state
+ // This is what the 'toggleSelected' value of the filter is
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
);
// Translate the parameter values into a filter selection state
this.getItems().forEach( function ( filterItem ) {
+ // All true (either because all values are written or the term 'all' is written)
+ // is the same as all filters set to true
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
+ // If it is the word 'all'
+ paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
+ // All values are written
+ paramValues.length === model.getItemCount()
+ ) ?
true :
// Otherwise, the filter is selected only if it appears in the parameter values
paramValues.indexOf( filterItem.getParamName() ) > -1;
} );
+ } else if ( this.getType() === 'single_option' ) {
+ // There is parameter that fits a single filter and if not, get the default
+ this.getItems().forEach( function ( filterItem ) {
+ result[ filterItem.getName() ] = filterItem.getParamName() === paramRepresentation;
+ oneWasSelected = oneWasSelected || filterItem.getParamName() === paramRepresentation;
+ } );
}
// 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() ];
+ oneWasSelected = oneWasSelected || !!result[ filterItem.getName() ];
} );
+ // Make sure that at least one option is selected in
+ // single_option groups, no matter what path was taken
+ // If none was selected by the given definition, then
+ // we need to select the one in the base state -- either
+ // the default given, or the first item
+ if (
+ this.getType() === 'single_option' &&
+ !oneWasSelected
+ ) {
+ defaultValue = this.getDefaultParams();
+ item = this.getItemByParamName( defaultValue[ this.getName() ] );
+ result[ item.getName() ] = true;
+ }
+
return result;
};
+ /**
+ * Get item by its filter name
+ *
+ * @param {string} filterName Filter name
+ * @return {mw.rcfilters.dm.FilterItem} Filter item
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.getItemByName = function ( filterName ) {
+ return this.getItems().filter( function ( item ) {
+ return item.getName() === filterName;
+ } )[ 0 ];
+ };
+
+ /**
+ * Select an item by its parameter name
+ *
+ * @param {string} paramName Filter parameter name
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.selectItemByParamName = function ( paramName ) {
+ this.getItems().forEach( function ( item ) {
+ item.toggleSelected( item.getParamName() === paramName );
+ } );
+ };
+
/**
* Get item by its parameter name
*
return this.type;
};
+ /**
+ * 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.
*