* @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} [isSticky] This group is using a 'sticky' default; meaning
+ * that every time a value is changed, it becomes the new default
+ * @cfg {boolean} [excludedFromSavedQueries] A specific requirement to exclude
+ * this filter from saved queries. This is always true if the filter is 'sticky'
+ * but can be used for non-sticky filters as an additional requirement. Similarly
+ * to 'sticky' it works for the entire group as a whole.
* @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 {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
this.name = name;
this.type = config.type || 'send_unselected_if_any';
this.view = config.view || 'default';
+ this.sticky = !!config.isSticky;
+ this.excludedFromSavedQueries = this.sticky || !!config.excludedFromSavedQueries;
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;
var subsetNames = [],
filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
group: model.getName(),
- useDefaultAsBaseValue: !!filter.useDefaultAsBaseValue,
label: filter.label || filter.name,
description: filter.description || '',
labelPrefixKey: model.labelPrefixKey,
// 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 ) );
+ model.defaultParams[ filter.name ] = String( Number( filter.default || 0 ) );
}
} );
// 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 );
}
// 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
- this.getItems().forEach( function ( item ) {
- if (
- item.isUsingDefaultAsBaseValue() &&
- (
- // This setting can only be applied to these groups
- // the other groups are way too complex for that
- model.getType() === 'single_option' ||
- model.getType() === 'boolean'
- )
- ) {
- // Apply selection
- item.toggleSelected( !!model.defaultFilters[ item.getName() ] );
- }
- } );
+ 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.getSelectedItems().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 );
+ }
};
/**
mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function ( item ) {
// Update state
var changed = false,
- active = this.areAnySelected();
-
- if (
- item.isSelected() &&
- this.getType() === 'single_option' &&
- this.currSelected &&
- this.currSelected !== item
- ) {
- this.currSelected.toggleSelected( false );
- }
-
- // For 'single_option' groups, check if we just unselected all
- // items. This should never be the result. If we did unselect
- // all (like resetting all filters to false) then this group
- // must choose its default item or the first item in the group
- if (
- this.getType() === 'single_option' &&
- !this.getItems().some( function ( filterItem ) {
- return filterItem.isSelected();
- } )
- ) {
- // Single option means there must be a single option
- // selected, so we have to either select the default
- // or select the first option
- this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] );
- this.currSelected.toggleSelected( true );
- changed = true;
+ 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.getSelectedItems().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.getSelectedItems().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.getSelectedItems().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.active !== active ||
this.currSelected !== item
) {
+ if ( this.isSticky() ) {
+ // If this group is sticky, then change the default according to the
+ // current selection.
+ this.defaultParams = this.getParamRepresentation( this.getSelectedState() );
+ }
+
this.active = active;
this.currSelected = item;
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
*
// 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() ] ) {
+ } 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, unless they have
- // isUsingDefaultAsBaseValue, in which case they get their
- // default state
- if (
- item.isUsingDefaultAsBaseValue() &&
- (
- // This setting can only be applied to these groups
- // the other groups are way too complex for that
- model.getType() === 'single_option' ||
- model.getType() === 'boolean'
- )
- ) {
+ // we will assume they are all falsey
+ if ( model.isSticky() ) {
filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ];
} else {
filterRepresentation[ item.getName() ] = false;
* @return {Object} Filter representation
*/
mw.rcfilters.dm.FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
- var areAnySelected, paramValues, defaultValue, item, currentValue,
+ var areAnySelected, paramValues, item, currentValue,
oneWasSelected = false,
defaultParams = this.getDefaultParams(),
- defaultFilters = this.getDefaultFilters(),
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' ||
} );
$.each( expandedParams, function ( paramName, paramValue ) {
- var value = paramValue,
- filterItem = paramToFilterMap[ paramName ];
+ var filterItem = paramToFilterMap[ paramName ];
if ( model.getType() === 'send_unselected_if_any' ) {
// Flip the definition between the parameter
false;
} else if ( model.getType() === 'boolean' ) {
// Straight-forward definition of state
- if (
- filterItem.isUsingDefaultAsBaseValue() &&
- paramRepresentation[ filterItem.getParamName() ] === undefined
- ) {
- value = defaultParams[ filterItem.getParamName() ];
- }
- result[ filterItem.getName() ] = !!Number( value );
+ result[ filterItem.getName() ] = !!Number( paramRepresentation[ filterItem.getParamName() ] );
}
} );
} else if ( this.getType() === 'string_options' ) {
} 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 = false;
+ var selected = filterItem.getParamName() === paramRepresentation[ model.getName() ];
- if (
- filterItem.isUsingDefaultAsBaseValue() &&
- paramRepresentation[ model.getName() ] === undefined
- ) {
- selected = !!Number( paramRepresentation[ model.getName() ] );
- } else {
- 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 (
- (
- // This setting can only be applied to these groups
- // the other groups are way too complex for that
- model.getType() === 'single_option' ||
- model.getType() === 'boolean'
- ) &&
- result[ filterItem.getName() ] === undefined &&
- filterItem.isUsingDefaultAsBaseValue()
- ) {
- result[ filterItem.getName() ] = !!defaultFilters[ filterItem.getName() ];
+ if ( result[ filterItem.getName() ] === undefined ) {
+ result[ filterItem.getName() ] = false;
}
- oneWasSelected = oneWasSelected || !!result[ filterItem.getName() ];
} );
// Make sure that at least one option is selected in
this.getType() === 'single_option' &&
!oneWasSelected
) {
- defaultValue = this.getDefaultParams();
- item = this.getItemByParamName( defaultValue[ this.getName() ] );
+ item = this.getItems()[ 0 ];
+ if ( defaultParams[ this.getName() ] ) {
+ item = this.getItemByParamName( defaultParams[ this.getName() ] );
+ }
+
result[ item.getName() ] = true;
}
return result;
};
+ /**
+ * 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.isSelected();
+ } );
+
+ return state;
+ };
+
/**
* Get item by its filter name
*
*/
mw.rcfilters.dm.FilterGroup.prototype.selectItemByParamName = function ( paramName ) {
this.getItems().forEach( function ( item ) {
- item.toggleSelected( item.getParamName() === paramName );
+ item.toggleSelected( item.getParamName() === String( paramName ) );
} );
};
*/
mw.rcfilters.dm.FilterGroup.prototype.getItemByParamName = function ( paramName ) {
return this.getItems().filter( function ( item ) {
- return item.getParamName() === paramName;
+ return item.getParamName() === String( paramName );
} )[ 0 ];
};
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;
+ };
+
+ /**
+ * Check whether the group value is excluded from saved queries
+ *
+ * @return {boolean} Group value is excluded from saved queries
+ */
+ mw.rcfilters.dm.FilterGroup.prototype.isExcludedFromSavedQueries = function () {
+ return this.excludedFromSavedQueries;
+ };
}( mediaWiki ) );