resources/src/mediawiki.rcfilters/dm/FilterGroup.js
var FilterItem = require( './FilterItem.js' ),
utils = require( '../utils.js' ),
FilterGroup;
/**
* View model for a filter group.
*
* @class mw.rcfilters.dm.FilterGroup
* @ignore
* @mixes OO.EventEmitter
* @mixes OO.EmitterList
*
* @param {string} name Group name
* @param {Object} [config] Configuration options
* @param {string} [config.type='send_unselected_if_any'] Group type
* @param {string} [config.view='default'] Name of the display group this group
* is a part of.
* @param {boolean} [config.sticky] This group is 'sticky'. It is synchronized
* with a preference, does not participate in Saved Queries, and is
* not shown in the active filters area.
* @param {string} [config.title] Group title
* @param {boolean} [config.hidden] This group is hidden from the regular menu views
* and the active filters area.
* @param {boolean} [config.allowArbitrary] Allows for an arbitrary value to be added to the
* group from the URL, even if it wasn't initially set up.
* @param {number} [config.range] An object defining minimum and maximum values for numeric
* groups. { min: x, max: y }
* @param {number} [config.minValue] Minimum value for numeric groups
* @param {string} [config.separator='|'] Value separator for 'string_options' groups
* @param {boolean} [config.supportsAll=true] For 'string_options' groups, whether the magic 'all' value
* is understood to mean all options are selected.
* @param {boolean} [config.active] Group is active
* @param {boolean} [config.fullCoverage] This filters in this group collectively cover all results
* @param {Object} [config.conflicts] Defines the conflicts for this filter group
* @param {string|Object} [config.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.
* @param {Object} [config.whatsThis] Defines the messages that should appear for the 'what's this' popup
* @param {string} [config.whatsThis.header] The header of the whatsThis popup message
* @param {string} [config.whatsThis.body] The body of the whatsThis popup message
* @param {string} [config.whatsThis.url] The url for the link in the whatsThis popup message
* @param {string} [config.whatsThis.linkMessage] The text for the link in the whatsThis popup message
* @param {boolean} [config.visible=true] The visibility of the group
*/
FilterGroup = function MwRcfiltersDmFilterGroup( name, config ) {
config = config || {};
// Mixin constructor
OO.EventEmitter.call( this );
OO.EmitterList.call( this );
this.name = name;
this.type = config.type || 'send_unselected_if_any';
this.view = config.view || 'default';
this.sticky = !!config.sticky;
this.title = config.title || name;
this.hidden = !!config.hidden;
this.allowArbitrary = !!config.allowArbitrary;
this.numericRange = config.range;
this.separator = config.separator || '|';
this.supportsAll = config.supportsAll === undefined ? true : !!config.supportsAll;
this.labelPrefixKey = config.labelPrefixKey;
this.visible = config.visible === undefined ? true : !!config.visible;
this.currSelected = null;
this.active = !!config.active;
this.fullCoverage = !!config.fullCoverage;
this.whatsThis = config.whatsThis || {};
this.conflicts = config.conflicts || {};
this.defaultParams = {};
this.defaultFilters = {};
this.aggregate( { update: 'filterItemUpdate' } );
this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
};
/* Initialization */
OO.initClass( FilterGroup );
OO.mixinClass( FilterGroup, OO.EventEmitter );
OO.mixinClass( FilterGroup, OO.EmitterList );
/* Events */
/**
* Group state has been updated.
*
* @event update
* @ignore
*/
/* 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
*/
FilterGroup.prototype.initializeFilters = function ( filterDefinition, groupDefault ) {
var defaultParam,
supersetMap = {},
model = this,
items = [];
filterDefinition.forEach( ( filter ) => {
// Instantiate an item
var subsetNames = [],
filterItem = new FilterItem( filter.name, model, {
group: model.getName(),
label: filter.label || filter.name,
description: filter.description || '',
labelPrefixKey: model.labelPrefixKey,
cssClass: filter.cssClass,
helpLink: filter.helpLink,
identifiers: filter.identifiers,
defaultHighlightColor: filter.defaultHighlightColor
} );
if ( filter.subset ) {
filter.subset = filter.subset.map( ( el ) => el.filter );
subsetNames = [];
filter.subset.forEach( ( subsetFilterName ) => {
// 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 ] || [];
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' ||
model.getType() === 'boolean'
) {
// 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 || 0 ) );
} else if ( model.getType() === 'any_value' ) {
model.defaultParams[ filter.name ] = filter.default;
}
} );
// Add items
this.addItems( items );
// Now that we have all items, we can apply the superset map
this.getItems().forEach( ( 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() ] = utils.normalizeParamOptions(
// Current values
groupDefault ?
groupDefault.split( this.getSeparator() ) :
[],
// Legal values
this.getItems().map( ( item ) => 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;
}
// add highlights to defaultParams
this.getItems().forEach( ( filterItem ) => {
if ( filterItem.isHighlighted() ) {
this.defaultParams[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor();
}
} );
// Store default filter state based on default params
this.defaultFilters = this.getFilterRepresentation( this.getDefaultParams() );
// Check for filters that should be initially selected by their default value
if ( this.isSticky() ) {
var defaultFilters = this.defaultFilters;
for ( var filterName in defaultFilters ) {
var filterValue = defaultFilters[ filterName ];
model.getItemByName( filterName ).toggleSelected( filterValue );
}
}
// Verify that single_option group has at least one item selected
if (
this.getType() === 'single_option' &&
this.findSelectedItems().length === 0
) {
defaultParam = groupDefault !== undefined ?
groupDefault : this.getItems()[ 0 ].getParamName();
// 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
*/
FilterGroup.prototype.onFilterItemUpdate = function ( item ) {
// Update state
var changed = false,
active = this.areAnySelected(),
model = this;
if ( this.getType() === 'single_option' ) {
// This group must have one item selected always
// and must never have more than one item selected at a time
if ( this.findSelectedItems().length === 0 ) {
// Nothing is selected anymore
// Select the default or the first item
this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] ) ||
this.getItems()[ 0 ];
this.currSelected.toggleSelected( true );
changed = true;
} else if ( this.findSelectedItems().length > 1 ) {
// There is more than one item selected
// This should only happen if the item given
// is the one that is selected, so unselect
// all items that is not it
this.findSelectedItems().forEach( ( itemModel ) => {
// Note that in case the given item is actually
// not selected, this loop will end up unselecting
// all items, which would trigger the case above
// when the last item is unselected anyways
var selected = itemModel.getName() === item.getName() &&
item.isSelected();
itemModel.toggleSelected( selected );
if ( selected ) {
model.currSelected = itemModel;
}
} );
changed = true;
}
}
if ( this.isSticky() ) {
// If this group is sticky, then change the default according to the
// current selection.
this.defaultParams = this.getParamRepresentation( this.getSelectedState() );
}
if (
changed ||
this.active !== active ||
this.currSelected !== item
) {
this.active = active;
this.currSelected = item;
this.emit( 'update' );
}
};
/**
* Get group active state
*
* @return {boolean} Active state
*/
FilterGroup.prototype.isActive = function () {
return this.active;
};
/**
* Get group hidden state
*
* @return {boolean} Hidden state
*/
FilterGroup.prototype.isHidden = function () {
return this.hidden;
};
/**
* Get group allow arbitrary state
*
* @return {boolean} Group allows an arbitrary value from the URL
*/
FilterGroup.prototype.isAllowArbitrary = function () {
return this.allowArbitrary;
};
/**
* Get group maximum value for numeric groups
*
* @return {number|null} Group max value
*/
FilterGroup.prototype.getMaxValue = function () {
return this.numericRange && this.numericRange.max !== undefined ?
this.numericRange.max : null;
};
/**
* Get group minimum value for numeric groups
*
* @return {number|null} Group max value
*/
FilterGroup.prototype.getMinValue = function () {
return this.numericRange && this.numericRange.min !== undefined ?
this.numericRange.min : null;
};
/**
* Get group name
*
* @return {string} Group name
*/
FilterGroup.prototype.getName = function () {
return this.name;
};
/**
* Get the default param state of this group
*
* @return {Object} Default param state
*/
FilterGroup.prototype.getDefaultParams = function () {
return this.defaultParams;
};
/**
* Get the default filter state of this group
*
* @return {Object} Default filter state
*/
FilterGroup.prototype.getDefaultFilters = function () {
return this.defaultFilters;
};
/**
* Get the messags defining the 'whats this' popup for this group
*
* @return {Object} What's this messages
*/
FilterGroup.prototype.getWhatsThis = function () {
return this.whatsThis;
};
/**
* Check whether this group has a 'what's this' message
*
* @return {boolean} This group has a what's this message
*/
FilterGroup.prototype.hasWhatsThis = function () {
return !!this.whatsThis.body;
};
/**
* Get the conflicts associated with the entire group.
*
* Conflict object is set up by filter name keys and conflict
* definition.
*
* @example
* [
* {
* filterName: {
* filter: filterName,
* group: group1
* }
* },
* {
* filterName2: {
* filter: filterName2,
* group: group2
* }
* }
* ]
*
* @return {Object} Conflict definition
*/
FilterGroup.prototype.getConflicts = function () {
return this.conflicts;
};
/**
* Set conflicts for this group. See #getConflicts for the expected
* structure of the definition.
*
* @param {Object} conflicts Conflicts for this group
*/
FilterGroup.prototype.setConflicts = function ( conflicts ) {
this.conflicts = conflicts;
};
/**
* Check whether this item has a potential conflict with the given item
*
* This checks whether the given item is in the list of conflicts of
* the current item, but makes no judgment about whether the conflict
* is currently at play (either one of the items may not be selected)
*
* @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
* @return {boolean} This item has a conflict with the given item
*/
FilterGroup.prototype.existsInConflicts = function ( filterItem ) {
return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
};
/**
* Check whether there are any items selected
*
* @return {boolean} Any items in the group are selected
*/
FilterGroup.prototype.areAnySelected = function () {
return this.getItems().some( ( filterItem ) => filterItem.isSelected() );
};
/**
* Check whether all items selected
*
* @return {boolean} All items are selected
*/
FilterGroup.prototype.areAllSelected = function () {
var selected = [],
unselected = [];
this.getItems().forEach( ( filterItem ) => {
if ( filterItem.isSelected() ) {
selected.push( filterItem );
} else {
unselected.push( filterItem );
}
} );
if ( unselected.length === 0 ) {
return true;
}
// check if every unselected is a subset of a selected
return unselected.every( ( unselectedFilterItem ) => selected.some( ( selectedFilterItem ) => selectedFilterItem.existsInSubset( unselectedFilterItem.getName() ) ) );
};
/**
* Get all selected items in this group
*
* @ignore
* @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list
* @return {mw.rcfilters.dm.FilterItem[]} Selected items
*/
FilterGroup.prototype.findSelectedItems = function ( excludeItem ) {
var excludeName = ( excludeItem && excludeItem.getName() ) || '';
return this.getItems().filter( ( item ) => item.getName() !== excludeName && item.isSelected() );
};
/**
* Check whether all selected items are in conflict with the given item
*
* @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
* @return {boolean} All selected items are in conflict with this item
*/
FilterGroup.prototype.areAllSelectedInConflictWith = function ( filterItem ) {
var selectedItems = this.findSelectedItems( filterItem );
return selectedItems.length > 0 &&
(
// The group as a whole is in conflict with this item
this.existsInConflicts( filterItem ) ||
// All selected items are in conflict individually
selectedItems.every( ( selectedFilter ) => selectedFilter.existsInConflicts( filterItem ) )
);
};
/**
* Check whether any of the selected items are in conflict with the given item
*
* @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
* @return {boolean} Any of the selected items are in conflict with this item
*/
FilterGroup.prototype.areAnySelectedInConflictWith = function ( filterItem ) {
var selectedItems = this.findSelectedItems( filterItem );
return selectedItems.length > 0 && (
// The group as a whole is in conflict with this item
this.existsInConflicts( filterItem ) ||
// Any selected items are in conflict individually
selectedItems.some( ( selectedFilter ) => selectedFilter.existsInConflicts( filterItem ) )
);
};
/**
* 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
*/
FilterGroup.prototype.getParamRepresentation = function ( filterRepresentation ) {
var values,
areAnySelected = false,
buildFromCurrentState = !filterRepresentation,
defaultFilters = this.getDefaultFilters(),
result = {},
model = this,
filterParamNames = {},
getSelectedParameter = function ( filters ) {
var item,
selected = [];
// Find if any are selected
// eslint-disable-next-line no-jquery/no-each-util
$.each( filters, ( name, value ) => {
if ( value ) {
selected.push( name );
}
} );
item = model.getItemByName( selected[ 0 ] );
return ( item && item.getParamName() ) || '';
};
filterRepresentation = filterRepresentation || {};
// Create or complete the filterRepresentation definition
this.getItems().forEach( ( 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.getValue();
} else if ( filterRepresentation[ item.getName() ] === undefined ) {
// 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
if ( model.isSticky() ) {
filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ];
} else {
filterRepresentation[ item.getName() ] = false;
}
}
if ( filterRepresentation[ item.getName() ] ) {
areAnySelected = true;
}
} );
// Build result
if (
this.getType() === 'send_unselected_if_any' ||
this.getType() === 'boolean' ||
this.getType() === 'any_value'
) {
// 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
// eslint-disable-next-line no-jquery/no-each-util
$.each( filterRepresentation, ( name, value ) => {
// We must store all parameter values as strings '0' or '1'
if ( model.getType() === 'send_unselected_if_any' ) {
result[ filterParamNames[ name ] ] = areAnySelected ?
String( Number( !value ) ) :
'0';
} else if ( model.getType() === 'boolean' ) {
// Representation is straight-forward and direct from
// the parameter value to the filter state
result[ filterParamNames[ name ] ] = String( Number( !!value ) );
} else if ( model.getType() === 'any_value' ) {
result[ filterParamNames[ name ] ] = value;
}
} );
} else if ( this.getType() === 'string_options' ) {
values = [];
// eslint-disable-next-line no-jquery/no-each-util
$.each( filterRepresentation, ( name, value ) => {
// Collect values
if ( value ) {
values.push( filterParamNames[ name ] );
}
} );
result[ this.getName() ] = this.getSupportsAll() &&
values.length === Object.keys( filterRepresentation ).length ?
'all' : values.join( this.getSeparator() );
} else if ( this.getType() === 'single_option' ) {
result[ this.getName() ] = getSelectedParameter( filterRepresentation );
}
return result;
};
/**
* Get the filter representation this group would provide
* based on given parameter states.
*
* @param {Object} [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
*/
FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
var areAnySelected, paramValues, item, currentValue,
oneWasSelected = false,
defaultParams = this.getDefaultParams(),
expandedParams = $.extend( true, {}, paramRepresentation ),
model = this,
paramToFilterMap = {},
result = {};
if ( this.isSticky() ) {
// If the group is sticky, check if all parameters are represented
// and for those that aren't represented, add them with their default
// values
paramRepresentation = $.extend( true, {}, this.getDefaultParams(), paramRepresentation );
}
paramRepresentation = paramRepresentation || {};
if (
this.getType() === 'send_unselected_if_any' ||
this.getType() === 'boolean' ||
this.getType() === 'any_value'
) {
// Go over param representation; map and check for selections
this.getItems().forEach( ( filterItem ) => {
var paramName = filterItem.getParamName();
expandedParams[ paramName ] = paramRepresentation[ paramName ] || '0';
paramToFilterMap[ paramName ] = filterItem;
if ( Number( paramRepresentation[ filterItem.getParamName() ] ) ) {
areAnySelected = true;
}
} );
// eslint-disable-next-line no-jquery/no-each-util
$.each( expandedParams, ( paramName, paramValue ) => {
var filterItem = paramToFilterMap[ paramName ];
if ( model.getType() === 'send_unselected_if_any' ) {
// 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 ?
!Number( paramValue ) :
// Otherwise, there are no selected items in the
// group, which means the state is false
false;
} else if ( model.getType() === 'boolean' ) {
// Straight-forward definition of state
result[ filterItem.getName() ] = !!Number( paramRepresentation[ filterItem.getParamName() ] );
} else if ( model.getType() === 'any_value' ) {
result[ filterItem.getName() ] = paramRepresentation[ filterItem.getParamName() ];
}
} );
} else if ( this.getType() === 'string_options' ) {
currentValue = paramRepresentation[ this.getName() ] || '';
// Normalize the given parameter values
paramValues = utils.normalizeParamOptions(
// Given
currentValue.split(
this.getSeparator()
),
// Allowed values
this.getItems().map( ( filterItem ) => filterItem.getParamName() ),
this.getSupportsAll()
);
// Translate the parameter values into a filter selection state
this.getItems().forEach( ( filterItem ) => {
// If the parameter is set to 'all', set all filters to true
result[ filterItem.getName() ] = (
this.getSupportsAll() && paramValues.length === 1 && paramValues[ 0 ] === 'all'
) ?
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( ( filterItem ) => {
var selected = filterItem.getParamName() === paramRepresentation[ model.getName() ];
result[ filterItem.getName() ] = selected;
oneWasSelected = oneWasSelected || selected;
} );
}
// Go over result and make sure all filters are represented.
// If any filters are missing, they will get a falsey value
this.getItems().forEach( ( filterItem ) => {
if ( result[ filterItem.getName() ] === undefined ) {
result[ filterItem.getName() ] = this.getFalsyValue();
}
} );
// 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
) {
item = this.getItems()[ 0 ];
if ( defaultParams[ this.getName() ] ) {
item = this.getItemByParamName( defaultParams[ this.getName() ] );
}
result[ item.getName() ] = true;
}
return result;
};
/**
* @return {any} The appropriate falsy value for this group type
*/
FilterGroup.prototype.getFalsyValue = function () {
return this.getType() === 'any_value' ? '' : false;
};
/**
* Get current selected state of all filter items in this group
*
* @return {Object} Selected state
*/
FilterGroup.prototype.getSelectedState = function () {
var state = {};
this.getItems().forEach( ( filterItem ) => {
state[ filterItem.getName() ] = filterItem.getValue();
} );
return state;
};
/**
* Get item by its filter name
*
* @ignore
* @param {string} filterName Filter name
* @return {mw.rcfilters.dm.FilterItem} Filter item
*/
FilterGroup.prototype.getItemByName = function ( filterName ) {
return this.getItems().filter( ( item ) => item.getName() === filterName )[ 0 ];
};
/**
* Select an item by its parameter name
*
* @param {string} paramName Filter parameter name
*/
FilterGroup.prototype.selectItemByParamName = function ( paramName ) {
this.getItems().forEach( ( item ) => {
item.toggleSelected( item.getParamName() === String( paramName ) );
} );
};
/**
* Get item by its parameter name
*
* @ignore
* @param {string} paramName Parameter name
* @return {mw.rcfilters.dm.FilterItem} Filter item
*/
FilterGroup.prototype.getItemByParamName = function ( paramName ) {
return this.getItems().filter( ( item ) => item.getParamName() === String( paramName ) )[ 0 ];
};
/**
* Get group type
*
* @return {string} Group type
*/
FilterGroup.prototype.getType = function () {
return this.type;
};
/**
* Check whether this group is represented by a single parameter
* or whether each item is its own parameter
*
* @return {boolean} This group is a single parameter
*/
FilterGroup.prototype.isPerGroupRequestParameter = function () {
return (
this.getType() === 'string_options' ||
this.getType() === 'single_option'
);
};
/**
* Get display group
*
* @return {string} Display group
*/
FilterGroup.prototype.getView = function () {
return this.view;
};
/**
* Get the prefix used for the filter names inside this group.
*
* @return {string} Group prefix
*/
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
*/
FilterGroup.prototype.getPrefixedName = function ( name ) {
return this.getNamePrefix() + name;
};
/**
* Get group's title
*
* @return {string} Title
*/
FilterGroup.prototype.getTitle = function () {
return this.title;
};
/**
* Get group's values separator
*
* @return {string} Values separator
*/
FilterGroup.prototype.getSeparator = function () {
return this.separator;
};
/**
* Check whether the group supports the magic 'all' value to indicate that all values are selected.
*
* @return {boolean} Group supports the magic 'all' value
*/
FilterGroup.prototype.getSupportsAll = function () {
return this.supportsAll;
};
/**
* Check whether the group is defined as full coverage
*
* @return {boolean} Group is full coverage
*/
FilterGroup.prototype.isFullCoverage = function () {
return this.fullCoverage;
};
/**
* Check whether the group is defined as sticky default
*
* @return {boolean} Group is sticky default
*/
FilterGroup.prototype.isSticky = function () {
return this.sticky;
};
/**
* Normalize a value given to this group. This is mostly for correcting
* arbitrary values for 'single option' groups, given by the user settings
* or the URL that can go outside the limits that are allowed.
*
* @param {string} value Given value
* @return {string} Corrected value
*/
FilterGroup.prototype.normalizeArbitraryValue = function ( value ) {
if (
this.getType() === 'single_option' &&
this.isAllowArbitrary()
) {
if (
this.getMaxValue() !== null &&
value > this.getMaxValue()
) {
// Change the value to the actual max value
return String( this.getMaxValue() );
} else if (
this.getMinValue() !== null &&
value < this.getMinValue()
) {
// Change the value to the actual min value
return String( this.getMinValue() );
}
}
return value;
};
/**
* Toggle the visibility of this group
*
* @param {boolean} [isVisible] Item is visible
*/
FilterGroup.prototype.toggleVisible = function ( isVisible ) {
isVisible = isVisible === undefined ? !this.visible : isVisible;
if ( this.visible !== isVisible ) {
this.visible = isVisible;
this.emit( 'update' );
}
};
/**
* Check whether the group is visible
*
* @return {boolean} Group is visible
*/
FilterGroup.prototype.isVisible = function () {
return this.visible;
};
/**
* Set the visibility of the items under this group by the given items array
*
* @param {mw.rcfilters.dm.ItemModel[]} visibleItems An array of visible items
*/
FilterGroup.prototype.setVisibleItems = function ( visibleItems ) {
this.getItems().forEach( ( itemModel ) => {
itemModel.toggleVisible( visibleItems.indexOf( itemModel ) !== -1 );
} );
};
module.exports = FilterGroup;