( function ( mw ) { /** * View model for a filter group * * @mixins OO.EventEmitter * @mixins OO.EmitterList * * @constructor * @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 {boolean} [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. * @cfg {string} [title] Group title * @cfg {boolean} [hidden] This group is hidden from the regular menu views * and the active filters area. * @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 {number} [range] An object defining minimum and maximum values for numeric * groups. { min: x, max: y } * @cfg {number} [minValue] Minimum value for numeric groups * @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 * @cfg {string} [whatsThis.url] The url for the link in the whatsThis popup message * @cfg {string} [whatsThis.linkMessage] The text for the link in the whatsThis popup message * @cfg {boolean} [visible=true] The visibility of the group */ mw.rcfilters.dm.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.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( mw.rcfilters.dm.FilterGroup ); OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EventEmitter ); OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EmitterList ); /* Events */ /** * @event update * * Group state has been updated */ /* 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 defaultParam, 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, defaultHighlightColor: filter.defaultHighlightColor } ); 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 // 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' || 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( 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() ); } 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( function ( filterItem ) { if ( filterItem.isHighlighted() ) { this.defaultParams[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor(); } }.bind( this ) ); // 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() ) { $.each( this.defaultFilters, function ( filterName, filterValue ) { 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 */ mw.rcfilters.dm.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( function ( 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 */ mw.rcfilters.dm.FilterGroup.prototype.isActive = function () { 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 maximum value for numeric groups * * @return {number|null} Group max value */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.getMinValue = function () { return this.numericRange && this.numericRange.min !== undefined ? this.numericRange.min : null; }; /** * Get group name * * @return {string} Group name */ mw.rcfilters.dm.FilterGroup.prototype.getName = function () { 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 default filter state of this group * * @return {Object} Default filter state */ mw.rcfilters.dm.FilterGroup.prototype.getDefaultFilters = function () { return this.defaultFilters; }; /** * 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 * * @return {Object} What's this messages */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.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. For example: * [ * { * filterName: { * filter: filterName, * group: group1 * } * }, * { * filterName2: { * filter: filterName2, * group: group2 * } * } * ] * @return {Object} Conflict definition */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.setConflicts = function ( conflicts ) { 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 * * 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 */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.areAnySelected = function () { return this.getItems().some( function ( filterItem ) { return filterItem.isSelected(); } ); }; /** * Check whether all items selected * * @return {boolean} All items are selected */ mw.rcfilters.dm.FilterGroup.prototype.areAllSelected = function () { var selected = [], unselected = []; this.getItems().forEach( function ( 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( function ( unselectedFilterItem ) { return selected.some( function ( selectedFilterItem ) { return selectedFilterItem.existsInSubset( unselectedFilterItem.getName() ); } ); } ); }; /** * Get all selected items in this group * * @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list * @return {mw.rcfilters.dm.FilterItem[]} Selected items */ mw.rcfilters.dm.FilterGroup.prototype.findSelectedItems = function ( excludeItem ) { var excludeName = ( excludeItem && excludeItem.getName() ) || ''; return this.getItems().filter( function ( item ) { return 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 */ mw.rcfilters.dm.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( function ( selectedFilter ) { return 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 */ mw.rcfilters.dm.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( function ( selectedFilter ) { return 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 */ mw.rcfilters.dm.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 $.each( filters, function ( 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( 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.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 $.each( filterRepresentation, function ( 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 = []; $.each( filterRepresentation, function ( name, value ) { // Collect values if ( value ) { values.push( filterParamNames[ name ] ); } } ); 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; }; /** * 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 */ mw.rcfilters.dm.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( function ( filterItem ) { var paramName = filterItem.getParamName(); expandedParams[ paramName ] = paramRepresentation[ paramName ] || '0'; paramToFilterMap[ paramName ] = filterItem; if ( Number( paramRepresentation[ filterItem.getParamName() ] ) ) { areAnySelected = true; } } ); $.each( expandedParams, function ( 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 = mw.rcfilters.utils.normalizeParamOptions( // Given currentValue.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 ) { // 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() ) ? 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 ) { 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( function ( filterItem ) { if ( result[ filterItem.getName() ] === undefined ) { result[ filterItem.getName() ] = this.getFalsyValue(); } }.bind( this ) ); // 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 {*} The appropriate falsy value for this group type */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.getSelectedState = function () { var state = {}; this.getItems().forEach( function ( filterItem ) { state[ filterItem.getName() ] = filterItem.getValue(); } ); return state; }; /** * 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() === String( paramName ) ); } ); }; /** * 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() === String( paramName ); } )[ 0 ]; }; /** * Get group type * * @return {string} Group type */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.isPerGroupRequestParameter = function () { return ( this.getType() === 'string_options' || this.getType() === 'single_option' ); }; /** * 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 * * @return {string} Title */ mw.rcfilters.dm.FilterGroup.prototype.getTitle = function () { return this.title; }; /** * Get group's values separator * * @return {string} Values separator */ mw.rcfilters.dm.FilterGroup.prototype.getSeparator = function () { return this.separator; }; /** * Check whether the group is defined as full coverage * * @return {boolean} Group is full coverage */ mw.rcfilters.dm.FilterGroup.prototype.isFullCoverage = function () { return this.fullCoverage; }; /** * Check whether the group is defined as sticky default * * @return {boolean} Group is sticky default */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.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 */ mw.rcfilters.dm.FilterGroup.prototype.setVisibleItems = function ( visibleItems ) { this.getItems().forEach( function ( itemModel ) { itemModel.toggleVisible( visibleItems.indexOf( itemModel ) !== -1 ); } ); }; }( mediaWiki ) );