3 * View model for a filter group
5 * @mixins OO.EventEmitter
6 * @mixins OO.EmitterList
9 * @param {string} name Group name
10 * @param {Object} [config] Configuration options
11 * @cfg {string} [type='send_unselected_if_any'] Group type
12 * @cfg {string} [view='default'] Name of the display group this group
14 * @cfg {boolean} [sticky] This group is 'sticky'. It is synchronized
15 * with a preference, does not participate in Saved Queries, and is
16 * not shown in the active filters area.
17 * @cfg {string} [title] Group title
18 * @cfg {boolean} [hidden] This group is hidden from the regular menu views
19 * and the active filters area.
20 * @cfg {boolean} [allowArbitrary] Allows for an arbitrary value to be added to the
21 * group from the URL, even if it wasn't initially set up.
22 * @cfg {number} [range] An object defining minimum and maximum values for numeric
23 * groups. { min: x, max: y }
24 * @cfg {number} [minValue] Minimum value for numeric groups
25 * @cfg {string} [separator='|'] Value separator for 'string_options' groups
26 * @cfg {boolean} [active] Group is active
27 * @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results
28 * @cfg {Object} [conflicts] Defines the conflicts for this filter group
29 * @cfg {string|Object} [labelPrefixKey] An i18n key defining the prefix label for this
30 * group. If the prefix has 'invert' state, the parameter is expected to be an object
31 * with 'default' and 'inverted' as keys.
32 * @cfg {Object} [whatsThis] Defines the messages that should appear for the 'what's this' popup
33 * @cfg {string} [whatsThis.header] The header of the whatsThis popup message
34 * @cfg {string} [whatsThis.body] The body of the whatsThis popup message
35 * @cfg {string} [whatsThis.url] The url for the link in the whatsThis popup message
36 * @cfg {string} [whatsThis.linkMessage] The text for the link in the whatsThis popup message
38 mw
.rcfilters
.dm
.FilterGroup
= function MwRcfiltersDmFilterGroup( name
, config
) {
39 config
= config
|| {};
42 OO
.EventEmitter
.call( this );
43 OO
.EmitterList
.call( this );
46 this.type
= config
.type
|| 'send_unselected_if_any';
47 this.view
= config
.view
|| 'default';
48 this.sticky
= !!config
.sticky
;
49 this.title
= config
.title
|| name
;
50 this.hidden
= !!config
.hidden
;
51 this.allowArbitrary
= !!config
.allowArbitrary
;
52 this.numericRange
= config
.range
;
53 this.separator
= config
.separator
|| '|';
54 this.labelPrefixKey
= config
.labelPrefixKey
;
56 this.currSelected
= null;
57 this.active
= !!config
.active
;
58 this.fullCoverage
= !!config
.fullCoverage
;
60 this.whatsThis
= config
.whatsThis
|| {};
62 this.conflicts
= config
.conflicts
|| {};
63 this.defaultParams
= {};
64 this.defaultFilters
= {};
66 this.aggregate( { update
: 'filterItemUpdate' } );
67 this.connect( this, { filterItemUpdate
: 'onFilterItemUpdate' } );
71 OO
.initClass( mw
.rcfilters
.dm
.FilterGroup
);
72 OO
.mixinClass( mw
.rcfilters
.dm
.FilterGroup
, OO
.EventEmitter
);
73 OO
.mixinClass( mw
.rcfilters
.dm
.FilterGroup
, OO
.EmitterList
);
80 * Group state has been updated
86 * Initialize the group and create its filter items
88 * @param {Object} filterDefinition Filter definition for this group
89 * @param {string|Object} [groupDefault] Definition of the group default
91 mw
.rcfilters
.dm
.FilterGroup
.prototype.initializeFilters = function ( filterDefinition
, groupDefault
) {
97 filterDefinition
.forEach( function ( filter
) {
98 // Instantiate an item
100 filterItem
= new mw
.rcfilters
.dm
.FilterItem( filter
.name
, model
, {
101 group
: model
.getName(),
102 label
: filter
.label
|| filter
.name
,
103 description
: filter
.description
|| '',
104 labelPrefixKey
: model
.labelPrefixKey
,
105 cssClass
: filter
.cssClass
,
106 identifiers
: filter
.identifiers
,
107 defaultHighlightColor
: filter
.defaultHighlightColor
110 if ( filter
.subset
) {
111 filter
.subset
= filter
.subset
.map( function ( el
) {
117 filter
.subset
.forEach( function ( subsetFilterName
) { // eslint-disable-line no-loop-func
118 // Subsets (unlike conflicts) are always inside the same group
119 // We can re-map the names of the filters we are getting from
120 // the subsets with the group prefix
121 var subsetName
= model
.getPrefixedName( subsetFilterName
);
122 // For convenience, we should store each filter's "supersets" -- these are
123 // the filters that have that item in their subset list. This will just
124 // make it easier to go through whether the item has any other items
125 // that affect it (and are selected) at any given time
126 supersetMap
[ subsetName
] = supersetMap
[ subsetName
] || [];
127 mw
.rcfilters
.utils
.addArrayElementsUnique(
128 supersetMap
[ subsetName
],
132 // Translate subset param name to add the group name, so we
133 // get consistent naming. We know that subsets are only within
135 subsetNames
.push( subsetName
);
138 // Set translated subset
139 filterItem
.setSubset( subsetNames
);
142 items
.push( filterItem
);
144 // Store default parameter state; in this case, default is defined per filter
146 model
.getType() === 'send_unselected_if_any' ||
147 model
.getType() === 'boolean'
149 // Store the default parameter state
150 // For this group type, parameter values are direct
151 // We need to convert from a boolean to a string ('1' and '0')
152 model
.defaultParams
[ filter
.name
] = String( Number( filter
.default || 0 ) );
153 } else if ( model
.getType() === 'any_value' ) {
154 model
.defaultParams
[ filter
.name
] = filter
.default;
159 this.addItems( items
);
161 // Now that we have all items, we can apply the superset map
162 this.getItems().forEach( function ( filterItem
) {
163 filterItem
.setSuperset( supersetMap
[ filterItem
.getName() ] );
166 // Store default parameter state; in this case, default is defined per the
167 // entire group, given by groupDefault method parameter
168 if ( this.getType() === 'string_options' ) {
169 // Store the default parameter group state
170 // For this group, the parameter is group name and value is the names
172 this.defaultParams
[ this.getName() ] = mw
.rcfilters
.utils
.normalizeParamOptions(
175 groupDefault
.split( this.getSeparator() ) :
178 this.getItems().map( function ( item
) {
179 return item
.getParamName();
181 ).join( this.getSeparator() );
182 } else if ( this.getType() === 'single_option' ) {
183 defaultParam
= groupDefault
!== undefined ?
184 groupDefault
: this.getItems()[ 0 ].getParamName();
186 // For this group, the parameter is the group name,
187 // and a single item can be selected: default or first item
188 this.defaultParams
[ this.getName() ] = defaultParam
;
191 // add highlights to defaultParams
192 this.getItems().forEach( function ( filterItem
) {
193 if ( filterItem
.isHighlighted() ) {
194 this.defaultParams
[ filterItem
.getName() + '_color' ] = filterItem
.getHighlightColor();
198 // Store default filter state based on default params
199 this.defaultFilters
= this.getFilterRepresentation( this.getDefaultParams() );
201 // Check for filters that should be initially selected by their default value
202 if ( this.isSticky() ) {
203 $.each( this.defaultFilters
, function ( filterName
, filterValue
) {
204 model
.getItemByName( filterName
).toggleSelected( filterValue
);
208 // Verify that single_option group has at least one item selected
210 this.getType() === 'single_option' &&
211 this.getSelectedItems().length
=== 0
213 defaultParam
= groupDefault
!== undefined ?
214 groupDefault
: this.getItems()[ 0 ].getParamName();
216 // Single option means there must be a single option
217 // selected, so we have to either select the default
218 // or select the first option
219 this.selectItemByParamName( defaultParam
);
224 * Respond to filterItem update event
226 * @param {mw.rcfilters.dm.FilterItem} item Updated filter item
229 mw
.rcfilters
.dm
.FilterGroup
.prototype.onFilterItemUpdate = function ( item
) {
232 active
= this.areAnySelected(),
235 if ( this.getType() === 'single_option' ) {
236 // This group must have one item selected always
237 // and must never have more than one item selected at a time
238 if ( this.getSelectedItems().length
=== 0 ) {
239 // Nothing is selected anymore
240 // Select the default or the first item
241 this.currSelected
= this.getItemByParamName( this.defaultParams
[ this.getName() ] ) ||
242 this.getItems()[ 0 ];
243 this.currSelected
.toggleSelected( true );
245 } else if ( this.getSelectedItems().length
> 1 ) {
246 // There is more than one item selected
247 // This should only happen if the item given
248 // is the one that is selected, so unselect
249 // all items that is not it
250 this.getSelectedItems().forEach( function ( itemModel
) {
251 // Note that in case the given item is actually
252 // not selected, this loop will end up unselecting
253 // all items, which would trigger the case above
254 // when the last item is unselected anyways
255 var selected
= itemModel
.getName() === item
.getName() &&
258 itemModel
.toggleSelected( selected
);
260 model
.currSelected
= itemModel
;
269 this.active
!== active
||
270 this.currSelected
!== item
272 if ( this.isSticky() ) {
273 // If this group is sticky, then change the default according to the
274 // current selection.
275 this.defaultParams
= this.getParamRepresentation( this.getSelectedState() );
278 this.active
= active
;
279 this.currSelected
= item
;
281 this.emit( 'update' );
286 * Get group active state
288 * @return {boolean} Active state
290 mw
.rcfilters
.dm
.FilterGroup
.prototype.isActive = function () {
295 * Get group hidden state
297 * @return {boolean} Hidden state
299 mw
.rcfilters
.dm
.FilterGroup
.prototype.isHidden = function () {
304 * Get group allow arbitrary state
306 * @return {boolean} Group allows an arbitrary value from the URL
308 mw
.rcfilters
.dm
.FilterGroup
.prototype.isAllowArbitrary = function () {
309 return this.allowArbitrary
;
313 * Get group maximum value for numeric groups
315 * @return {number|null} Group max value
317 mw
.rcfilters
.dm
.FilterGroup
.prototype.getMaxValue = function () {
318 return this.numericRange
&& this.numericRange
.max
!== undefined ?
319 this.numericRange
.max
: null;
323 * Get group minimum value for numeric groups
325 * @return {number|null} Group max value
327 mw
.rcfilters
.dm
.FilterGroup
.prototype.getMinValue = function () {
328 return this.numericRange
&& this.numericRange
.min
!== undefined ?
329 this.numericRange
.min
: null;
335 * @return {string} Group name
337 mw
.rcfilters
.dm
.FilterGroup
.prototype.getName = function () {
342 * Get the default param state of this group
344 * @return {Object} Default param state
346 mw
.rcfilters
.dm
.FilterGroup
.prototype.getDefaultParams = function () {
347 return this.defaultParams
;
351 * Get the default filter state of this group
353 * @return {Object} Default filter state
355 mw
.rcfilters
.dm
.FilterGroup
.prototype.getDefaultFilters = function () {
356 return this.defaultFilters
;
360 * This is for a single_option and string_options group types
361 * it returns the value of the default
363 * @return {string} Value of the default
365 mw
.rcfilters
.dm
.FilterGroup
.prototype.getDefaulParamValue = function () {
366 return this.defaultParams
[ this.getName() ];
369 * Get the messags defining the 'whats this' popup for this group
371 * @return {Object} What's this messages
373 mw
.rcfilters
.dm
.FilterGroup
.prototype.getWhatsThis = function () {
374 return this.whatsThis
;
378 * Check whether this group has a 'what's this' message
380 * @return {boolean} This group has a what's this message
382 mw
.rcfilters
.dm
.FilterGroup
.prototype.hasWhatsThis = function () {
383 return !!this.whatsThis
.body
;
387 * Get the conflicts associated with the entire group.
388 * Conflict object is set up by filter name keys and conflict
389 * definition. For example:
393 * filter: filterName,
399 * filter: filterName2,
404 * @return {Object} Conflict definition
406 mw
.rcfilters
.dm
.FilterGroup
.prototype.getConflicts = function () {
407 return this.conflicts
;
411 * Set conflicts for this group. See #getConflicts for the expected
412 * structure of the definition.
414 * @param {Object} conflicts Conflicts for this group
416 mw
.rcfilters
.dm
.FilterGroup
.prototype.setConflicts = function ( conflicts
) {
417 this.conflicts
= conflicts
;
421 * Set conflicts for each filter item in the group based on the
424 * @param {Object} conflicts Object representing the conflict map,
425 * keyed by the item name, where its value is an object for all its conflicts
427 mw
.rcfilters
.dm
.FilterGroup
.prototype.setFilterConflicts = function ( conflicts
) {
428 this.getItems().forEach( function ( filterItem
) {
429 if ( conflicts
[ filterItem
.getName() ] ) {
430 filterItem
.setConflicts( conflicts
[ filterItem
.getName() ] );
436 * Check whether this item has a potential conflict with the given item
438 * This checks whether the given item is in the list of conflicts of
439 * the current item, but makes no judgment about whether the conflict
440 * is currently at play (either one of the items may not be selected)
442 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
443 * @return {boolean} This item has a conflict with the given item
445 mw
.rcfilters
.dm
.FilterGroup
.prototype.existsInConflicts = function ( filterItem
) {
446 return Object
.prototype.hasOwnProperty
.call( this.getConflicts(), filterItem
.getName() );
450 * Check whether there are any items selected
452 * @return {boolean} Any items in the group are selected
454 mw
.rcfilters
.dm
.FilterGroup
.prototype.areAnySelected = function () {
455 return this.getItems().some( function ( filterItem
) {
456 return filterItem
.isSelected();
461 * Check whether all items selected
463 * @return {boolean} All items are selected
465 mw
.rcfilters
.dm
.FilterGroup
.prototype.areAllSelected = function () {
469 this.getItems().forEach( function ( filterItem
) {
470 if ( filterItem
.isSelected() ) {
471 selected
.push( filterItem
);
473 unselected
.push( filterItem
);
477 if ( unselected
.length
=== 0 ) {
481 // check if every unselected is a subset of a selected
482 return unselected
.every( function ( unselectedFilterItem
) {
483 return selected
.some( function ( selectedFilterItem
) {
484 return selectedFilterItem
.existsInSubset( unselectedFilterItem
.getName() );
490 * Get all selected items in this group
492 * @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list
493 * @return {mw.rcfilters.dm.FilterItem[]} Selected items
495 mw
.rcfilters
.dm
.FilterGroup
.prototype.getSelectedItems = function ( excludeItem
) {
496 var excludeName
= ( excludeItem
&& excludeItem
.getName() ) || '';
498 return this.getItems().filter( function ( item
) {
499 return item
.getName() !== excludeName
&& item
.isSelected();
504 * Check whether all selected items are in conflict with the given item
506 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
507 * @return {boolean} All selected items are in conflict with this item
509 mw
.rcfilters
.dm
.FilterGroup
.prototype.areAllSelectedInConflictWith = function ( filterItem
) {
510 var selectedItems
= this.getSelectedItems( filterItem
);
512 return selectedItems
.length
> 0 &&
514 // The group as a whole is in conflict with this item
515 this.existsInConflicts( filterItem
) ||
516 // All selected items are in conflict individually
517 selectedItems
.every( function ( selectedFilter
) {
518 return selectedFilter
.existsInConflicts( filterItem
);
524 * Check whether any of the selected items are in conflict with the given item
526 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
527 * @return {boolean} Any of the selected items are in conflict with this item
529 mw
.rcfilters
.dm
.FilterGroup
.prototype.areAnySelectedInConflictWith = function ( filterItem
) {
530 var selectedItems
= this.getSelectedItems( filterItem
);
532 return selectedItems
.length
> 0 && (
533 // The group as a whole is in conflict with this item
534 this.existsInConflicts( filterItem
) ||
535 // Any selected items are in conflict individually
536 selectedItems
.some( function ( selectedFilter
) {
537 return selectedFilter
.existsInConflicts( filterItem
);
543 * Get the parameter representation from this group
545 * @param {Object} [filterRepresentation] An object defining the state
546 * of the filters in this group, keyed by their name and current selected
548 * @return {Object} Parameter representation
550 mw
.rcfilters
.dm
.FilterGroup
.prototype.getParamRepresentation = function ( filterRepresentation
) {
552 areAnySelected
= false,
553 buildFromCurrentState
= !filterRepresentation
,
554 defaultFilters
= this.getDefaultFilters(),
557 filterParamNames
= {},
558 getSelectedParameter = function ( filters
) {
562 // Find if any are selected
563 $.each( filters
, function ( name
, value
) {
565 selected
.push( name
);
569 item
= model
.getItemByName( selected
[ 0 ] );
570 return ( item
&& item
.getParamName() ) || '';
573 filterRepresentation
= filterRepresentation
|| {};
575 // Create or complete the filterRepresentation definition
576 this.getItems().forEach( function ( item
) {
577 // Map filter names to their parameter names
578 filterParamNames
[ item
.getName() ] = item
.getParamName();
580 if ( buildFromCurrentState
) {
581 // This means we have not been given a filter representation
582 // so we are building one based on current state
583 filterRepresentation
[ item
.getName() ] = item
.getValue();
584 } else if ( filterRepresentation
[ item
.getName() ] === undefined ) {
585 // We are given a filter representation, but we have to make
586 // sure that we fill in the missing filters if there are any
587 // we will assume they are all falsey
588 if ( model
.isSticky() ) {
589 filterRepresentation
[ item
.getName() ] = !!defaultFilters
[ item
.getName() ];
591 filterRepresentation
[ item
.getName() ] = false;
595 if ( filterRepresentation
[ item
.getName() ] ) {
596 areAnySelected
= true;
602 this.getType() === 'send_unselected_if_any' ||
603 this.getType() === 'boolean' ||
604 this.getType() === 'any_value'
606 // First, check if any of the items are selected at all.
607 // If none is selected, we're treating it as if they are
610 // Go over the items and define the correct values
611 $.each( filterRepresentation
, function ( name
, value
) {
612 // We must store all parameter values as strings '0' or '1'
613 if ( model
.getType() === 'send_unselected_if_any' ) {
614 result
[ filterParamNames
[ name
] ] = areAnySelected
?
615 String( Number( !value
) ) :
617 } else if ( model
.getType() === 'boolean' ) {
618 // Representation is straight-forward and direct from
619 // the parameter value to the filter state
620 result
[ filterParamNames
[ name
] ] = String( Number( !!value
) );
621 } else if ( model
.getType() === 'any_value' ) {
622 result
[ filterParamNames
[ name
] ] = value
;
625 } else if ( this.getType() === 'string_options' ) {
628 $.each( filterRepresentation
, function ( name
, value
) {
631 values
.push( filterParamNames
[ name
] );
635 result
[ this.getName() ] = ( values
.length
=== Object
.keys( filterRepresentation
).length
) ?
636 'all' : values
.join( this.getSeparator() );
637 } else if ( this.getType() === 'single_option' ) {
638 result
[ this.getName() ] = getSelectedParameter( filterRepresentation
);
645 * Get the filter representation this group would provide
646 * based on given parameter states.
648 * @param {Object} [paramRepresentation] An object defining a parameter
649 * state to translate the filter state from. If not given, an object
650 * representing all filters as falsey is returned; same as if the parameter
651 * given were an empty object, or had some of the filters missing.
652 * @return {Object} Filter representation
654 mw
.rcfilters
.dm
.FilterGroup
.prototype.getFilterRepresentation = function ( paramRepresentation
) {
655 var areAnySelected
, paramValues
, item
, currentValue
,
656 oneWasSelected
= false,
657 defaultParams
= this.getDefaultParams(),
658 expandedParams
= $.extend( true, {}, paramRepresentation
),
660 paramToFilterMap
= {},
663 if ( this.isSticky() ) {
664 // If the group is sticky, check if all parameters are represented
665 // and for those that aren't represented, add them with their default
667 paramRepresentation
= $.extend( true, {}, this.getDefaultParams(), paramRepresentation
);
670 paramRepresentation
= paramRepresentation
|| {};
672 this.getType() === 'send_unselected_if_any' ||
673 this.getType() === 'boolean' ||
674 this.getType() === 'any_value'
676 // Go over param representation; map and check for selections
677 this.getItems().forEach( function ( filterItem
) {
678 var paramName
= filterItem
.getParamName();
680 expandedParams
[ paramName
] = paramRepresentation
[ paramName
] || '0';
681 paramToFilterMap
[ paramName
] = filterItem
;
683 if ( Number( paramRepresentation
[ filterItem
.getParamName() ] ) ) {
684 areAnySelected
= true;
688 $.each( expandedParams
, function ( paramName
, paramValue
) {
689 var filterItem
= paramToFilterMap
[ paramName
];
691 if ( model
.getType() === 'send_unselected_if_any' ) {
692 // Flip the definition between the parameter
693 // state and the filter state
694 // This is what the 'toggleSelected' value of the filter is
695 result
[ filterItem
.getName() ] = areAnySelected
?
696 !Number( paramValue
) :
697 // Otherwise, there are no selected items in the
698 // group, which means the state is false
700 } else if ( model
.getType() === 'boolean' ) {
701 // Straight-forward definition of state
702 result
[ filterItem
.getName() ] = !!Number( paramRepresentation
[ filterItem
.getParamName() ] );
703 } else if ( model
.getType() === 'any_value' ) {
704 result
[ filterItem
.getName() ] = paramRepresentation
[ filterItem
.getParamName() ];
707 } else if ( this.getType() === 'string_options' ) {
708 currentValue
= paramRepresentation
[ this.getName() ] || '';
710 // Normalize the given parameter values
711 paramValues
= mw
.rcfilters
.utils
.normalizeParamOptions(
717 this.getItems().map( function ( filterItem
) {
718 return filterItem
.getParamName();
721 // Translate the parameter values into a filter selection state
722 this.getItems().forEach( function ( filterItem
) {
723 // All true (either because all values are written or the term 'all' is written)
724 // is the same as all filters set to true
725 result
[ filterItem
.getName() ] = (
726 // If it is the word 'all'
727 paramValues
.length
=== 1 && paramValues
[ 0 ] === 'all' ||
728 // All values are written
729 paramValues
.length
=== model
.getItemCount()
732 // Otherwise, the filter is selected only if it appears in the parameter values
733 paramValues
.indexOf( filterItem
.getParamName() ) > -1;
735 } else if ( this.getType() === 'single_option' ) {
736 // There is parameter that fits a single filter and if not, get the default
737 this.getItems().forEach( function ( filterItem
) {
738 var selected
= filterItem
.getParamName() === paramRepresentation
[ model
.getName() ];
740 result
[ filterItem
.getName() ] = selected
;
741 oneWasSelected
= oneWasSelected
|| selected
;
745 // Go over result and make sure all filters are represented.
746 // If any filters are missing, they will get a falsey value
747 this.getItems().forEach( function ( filterItem
) {
748 if ( result
[ filterItem
.getName() ] === undefined ) {
749 result
[ filterItem
.getName() ] = this.getFalsyValue();
753 // Make sure that at least one option is selected in
754 // single_option groups, no matter what path was taken
755 // If none was selected by the given definition, then
756 // we need to select the one in the base state -- either
757 // the default given, or the first item
759 this.getType() === 'single_option' &&
762 item
= this.getItems()[ 0 ];
763 if ( defaultParams
[ this.getName() ] ) {
764 item
= this.getItemByParamName( defaultParams
[ this.getName() ] );
767 result
[ item
.getName() ] = true;
774 * @return {*} The appropriate falsy value for this group type
776 mw
.rcfilters
.dm
.FilterGroup
.prototype.getFalsyValue = function () {
777 return this.getType() === 'any_value' ? '' : false;
781 * Get current selected state of all filter items in this group
783 * @return {Object} Selected state
785 mw
.rcfilters
.dm
.FilterGroup
.prototype.getSelectedState = function () {
788 this.getItems().forEach( function ( filterItem
) {
789 state
[ filterItem
.getName() ] = filterItem
.getValue();
796 * Get item by its filter name
798 * @param {string} filterName Filter name
799 * @return {mw.rcfilters.dm.FilterItem} Filter item
801 mw
.rcfilters
.dm
.FilterGroup
.prototype.getItemByName = function ( filterName
) {
802 return this.getItems().filter( function ( item
) {
803 return item
.getName() === filterName
;
808 * Select an item by its parameter name
810 * @param {string} paramName Filter parameter name
812 mw
.rcfilters
.dm
.FilterGroup
.prototype.selectItemByParamName = function ( paramName
) {
813 this.getItems().forEach( function ( item
) {
814 item
.toggleSelected( item
.getParamName() === String( paramName
) );
819 * Get item by its parameter name
821 * @param {string} paramName Parameter name
822 * @return {mw.rcfilters.dm.FilterItem} Filter item
824 mw
.rcfilters
.dm
.FilterGroup
.prototype.getItemByParamName = function ( paramName
) {
825 return this.getItems().filter( function ( item
) {
826 return item
.getParamName() === String( paramName
);
833 * @return {string} Group type
835 mw
.rcfilters
.dm
.FilterGroup
.prototype.getType = function () {
840 * Check whether this group is represented by a single parameter
841 * or whether each item is its own parameter
843 * @return {boolean} This group is a single parameter
845 mw
.rcfilters
.dm
.FilterGroup
.prototype.isPerGroupRequestParameter = function () {
847 this.getType() === 'string_options' ||
848 this.getType() === 'single_option'
855 * @return {string} Display group
857 mw
.rcfilters
.dm
.FilterGroup
.prototype.getView = function () {
862 * Get the prefix used for the filter names inside this group.
864 * @param {string} [name] Filter name to prefix
865 * @return {string} Group prefix
867 mw
.rcfilters
.dm
.FilterGroup
.prototype.getNamePrefix = function () {
868 return this.getName() + '__';
872 * Get a filter name with the prefix used for the filter names inside this group.
874 * @param {string} name Filter name to prefix
875 * @return {string} Group prefix
877 mw
.rcfilters
.dm
.FilterGroup
.prototype.getPrefixedName = function ( name
) {
878 return this.getNamePrefix() + name
;
884 * @return {string} Title
886 mw
.rcfilters
.dm
.FilterGroup
.prototype.getTitle = function () {
891 * Get group's values separator
893 * @return {string} Values separator
895 mw
.rcfilters
.dm
.FilterGroup
.prototype.getSeparator = function () {
896 return this.separator
;
900 * Check whether the group is defined as full coverage
902 * @return {boolean} Group is full coverage
904 mw
.rcfilters
.dm
.FilterGroup
.prototype.isFullCoverage = function () {
905 return this.fullCoverage
;
909 * Check whether the group is defined as sticky default
911 * @return {boolean} Group is sticky default
913 mw
.rcfilters
.dm
.FilterGroup
.prototype.isSticky = function () {
918 * Normalize a value given to this group. This is mostly for correcting
919 * arbitrary values for 'single option' groups, given by the user settings
920 * or the URL that can go outside the limits that are allowed.
922 * @param {string} value Given value
923 * @return {string} Corrected value
925 mw
.rcfilters
.dm
.FilterGroup
.prototype.normalizeArbitraryValue = function ( value
) {
927 this.getType() === 'single_option' &&
928 this.isAllowArbitrary()
931 this.getMaxValue() !== null &&
932 value
> this.getMaxValue()
934 // Change the value to the actual max value
935 return String( this.getMaxValue() );
937 this.getMinValue() !== null &&
938 value
< this.getMinValue()
940 // Change the value to the actual min value
941 return String( this.getMinValue() );