2 var FilterItem
= require( './FilterItem.js' ),
6 * View model for a filter group
8 * @class mw.rcfilters.dm.FilterGroup
9 * @mixins OO.EventEmitter
10 * @mixins OO.EmitterList
13 * @param {string} name Group name
14 * @param {Object} [config] Configuration options
15 * @cfg {string} [type='send_unselected_if_any'] Group type
16 * @cfg {string} [view='default'] Name of the display group this group
18 * @cfg {boolean} [sticky] This group is 'sticky'. It is synchronized
19 * with a preference, does not participate in Saved Queries, and is
20 * not shown in the active filters area.
21 * @cfg {string} [title] Group title
22 * @cfg {boolean} [hidden] This group is hidden from the regular menu views
23 * and the active filters area.
24 * @cfg {boolean} [allowArbitrary] Allows for an arbitrary value to be added to the
25 * group from the URL, even if it wasn't initially set up.
26 * @cfg {number} [range] An object defining minimum and maximum values for numeric
27 * groups. { min: x, max: y }
28 * @cfg {number} [minValue] Minimum value for numeric groups
29 * @cfg {string} [separator='|'] Value separator for 'string_options' groups
30 * @cfg {boolean} [active] Group is active
31 * @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results
32 * @cfg {Object} [conflicts] Defines the conflicts for this filter group
33 * @cfg {string|Object} [labelPrefixKey] An i18n key defining the prefix label for this
34 * group. If the prefix has 'invert' state, the parameter is expected to be an object
35 * with 'default' and 'inverted' as keys.
36 * @cfg {Object} [whatsThis] Defines the messages that should appear for the 'what's this' popup
37 * @cfg {string} [whatsThis.header] The header of the whatsThis popup message
38 * @cfg {string} [whatsThis.body] The body of the whatsThis popup message
39 * @cfg {string} [whatsThis.url] The url for the link in the whatsThis popup message
40 * @cfg {string} [whatsThis.linkMessage] The text for the link in the whatsThis popup message
41 * @cfg {boolean} [visible=true] The visibility of the group
43 FilterGroup
= function MwRcfiltersDmFilterGroup( name
, config
) {
44 config
= config
|| {};
47 OO
.EventEmitter
.call( this );
48 OO
.EmitterList
.call( this );
51 this.type
= config
.type
|| 'send_unselected_if_any';
52 this.view
= config
.view
|| 'default';
53 this.sticky
= !!config
.sticky
;
54 this.title
= config
.title
|| name
;
55 this.hidden
= !!config
.hidden
;
56 this.allowArbitrary
= !!config
.allowArbitrary
;
57 this.numericRange
= config
.range
;
58 this.separator
= config
.separator
|| '|';
59 this.labelPrefixKey
= config
.labelPrefixKey
;
60 this.visible
= config
.visible
=== undefined ? true : !!config
.visible
;
62 this.currSelected
= null;
63 this.active
= !!config
.active
;
64 this.fullCoverage
= !!config
.fullCoverage
;
66 this.whatsThis
= config
.whatsThis
|| {};
68 this.conflicts
= config
.conflicts
|| {};
69 this.defaultParams
= {};
70 this.defaultFilters
= {};
72 this.aggregate( { update
: 'filterItemUpdate' } );
73 this.connect( this, { filterItemUpdate
: 'onFilterItemUpdate' } );
77 OO
.initClass( FilterGroup
);
78 OO
.mixinClass( FilterGroup
, OO
.EventEmitter
);
79 OO
.mixinClass( FilterGroup
, OO
.EmitterList
);
86 * Group state has been updated
92 * Initialize the group and create its filter items
94 * @param {Object} filterDefinition Filter definition for this group
95 * @param {string|Object} [groupDefault] Definition of the group default
97 FilterGroup
.prototype.initializeFilters = function ( filterDefinition
, groupDefault
) {
103 filterDefinition
.forEach( function ( filter
) {
104 // Instantiate an item
105 var subsetNames
= [],
106 filterItem
= new FilterItem( filter
.name
, model
, {
107 group
: model
.getName(),
108 label
: filter
.label
|| filter
.name
,
109 description
: filter
.description
|| '',
110 labelPrefixKey
: model
.labelPrefixKey
,
111 cssClass
: filter
.cssClass
,
112 identifiers
: filter
.identifiers
,
113 defaultHighlightColor
: filter
.defaultHighlightColor
116 if ( filter
.subset
) {
117 filter
.subset
= filter
.subset
.map( function ( el
) {
123 filter
.subset
.forEach( function ( subsetFilterName
) {
124 // Subsets (unlike conflicts) are always inside the same group
125 // We can re-map the names of the filters we are getting from
126 // the subsets with the group prefix
127 var subsetName
= model
.getPrefixedName( subsetFilterName
);
128 // For convenience, we should store each filter's "supersets" -- these are
129 // the filters that have that item in their subset list. This will just
130 // make it easier to go through whether the item has any other items
131 // that affect it (and are selected) at any given time
132 supersetMap
[ subsetName
] = supersetMap
[ subsetName
] || [];
133 mw
.rcfilters
.utils
.addArrayElementsUnique(
134 supersetMap
[ subsetName
],
138 // Translate subset param name to add the group name, so we
139 // get consistent naming. We know that subsets are only within
141 subsetNames
.push( subsetName
);
144 // Set translated subset
145 filterItem
.setSubset( subsetNames
);
148 items
.push( filterItem
);
150 // Store default parameter state; in this case, default is defined per filter
152 model
.getType() === 'send_unselected_if_any' ||
153 model
.getType() === 'boolean'
155 // Store the default parameter state
156 // For this group type, parameter values are direct
157 // We need to convert from a boolean to a string ('1' and '0')
158 model
.defaultParams
[ filter
.name
] = String( Number( filter
.default || 0 ) );
159 } else if ( model
.getType() === 'any_value' ) {
160 model
.defaultParams
[ filter
.name
] = filter
.default;
165 this.addItems( items
);
167 // Now that we have all items, we can apply the superset map
168 this.getItems().forEach( function ( filterItem
) {
169 filterItem
.setSuperset( supersetMap
[ filterItem
.getName() ] );
172 // Store default parameter state; in this case, default is defined per the
173 // entire group, given by groupDefault method parameter
174 if ( this.getType() === 'string_options' ) {
175 // Store the default parameter group state
176 // For this group, the parameter is group name and value is the names
178 this.defaultParams
[ this.getName() ] = mw
.rcfilters
.utils
.normalizeParamOptions(
181 groupDefault
.split( this.getSeparator() ) :
184 this.getItems().map( function ( item
) {
185 return item
.getParamName();
187 ).join( this.getSeparator() );
188 } else if ( this.getType() === 'single_option' ) {
189 defaultParam
= groupDefault
!== undefined ?
190 groupDefault
: this.getItems()[ 0 ].getParamName();
192 // For this group, the parameter is the group name,
193 // and a single item can be selected: default or first item
194 this.defaultParams
[ this.getName() ] = defaultParam
;
197 // add highlights to defaultParams
198 this.getItems().forEach( function ( filterItem
) {
199 if ( filterItem
.isHighlighted() ) {
200 this.defaultParams
[ filterItem
.getName() + '_color' ] = filterItem
.getHighlightColor();
204 // Store default filter state based on default params
205 this.defaultFilters
= this.getFilterRepresentation( this.getDefaultParams() );
207 // Check for filters that should be initially selected by their default value
208 if ( this.isSticky() ) {
209 // eslint-disable-next-line no-jquery/no-each-util
210 $.each( this.defaultFilters
, function ( filterName
, filterValue
) {
211 model
.getItemByName( filterName
).toggleSelected( filterValue
);
215 // Verify that single_option group has at least one item selected
217 this.getType() === 'single_option' &&
218 this.findSelectedItems().length
=== 0
220 defaultParam
= groupDefault
!== undefined ?
221 groupDefault
: this.getItems()[ 0 ].getParamName();
223 // Single option means there must be a single option
224 // selected, so we have to either select the default
225 // or select the first option
226 this.selectItemByParamName( defaultParam
);
231 * Respond to filterItem update event
233 * @param {mw.rcfilters.dm.FilterItem} item Updated filter item
236 FilterGroup
.prototype.onFilterItemUpdate = function ( item
) {
239 active
= this.areAnySelected(),
242 if ( this.getType() === 'single_option' ) {
243 // This group must have one item selected always
244 // and must never have more than one item selected at a time
245 if ( this.findSelectedItems().length
=== 0 ) {
246 // Nothing is selected anymore
247 // Select the default or the first item
248 this.currSelected
= this.getItemByParamName( this.defaultParams
[ this.getName() ] ) ||
249 this.getItems()[ 0 ];
250 this.currSelected
.toggleSelected( true );
252 } else if ( this.findSelectedItems().length
> 1 ) {
253 // There is more than one item selected
254 // This should only happen if the item given
255 // is the one that is selected, so unselect
256 // all items that is not it
257 this.findSelectedItems().forEach( function ( itemModel
) {
258 // Note that in case the given item is actually
259 // not selected, this loop will end up unselecting
260 // all items, which would trigger the case above
261 // when the last item is unselected anyways
262 var selected
= itemModel
.getName() === item
.getName() &&
265 itemModel
.toggleSelected( selected
);
267 model
.currSelected
= itemModel
;
274 if ( this.isSticky() ) {
275 // If this group is sticky, then change the default according to the
276 // current selection.
277 this.defaultParams
= this.getParamRepresentation( this.getSelectedState() );
282 this.active
!== active
||
283 this.currSelected
!== item
285 this.active
= active
;
286 this.currSelected
= item
;
288 this.emit( 'update' );
293 * Get group active state
295 * @return {boolean} Active state
297 FilterGroup
.prototype.isActive = function () {
302 * Get group hidden state
304 * @return {boolean} Hidden state
306 FilterGroup
.prototype.isHidden = function () {
311 * Get group allow arbitrary state
313 * @return {boolean} Group allows an arbitrary value from the URL
315 FilterGroup
.prototype.isAllowArbitrary = function () {
316 return this.allowArbitrary
;
320 * Get group maximum value for numeric groups
322 * @return {number|null} Group max value
324 FilterGroup
.prototype.getMaxValue = function () {
325 return this.numericRange
&& this.numericRange
.max
!== undefined ?
326 this.numericRange
.max
: null;
330 * Get group minimum value for numeric groups
332 * @return {number|null} Group max value
334 FilterGroup
.prototype.getMinValue = function () {
335 return this.numericRange
&& this.numericRange
.min
!== undefined ?
336 this.numericRange
.min
: null;
342 * @return {string} Group name
344 FilterGroup
.prototype.getName = function () {
349 * Get the default param state of this group
351 * @return {Object} Default param state
353 FilterGroup
.prototype.getDefaultParams = function () {
354 return this.defaultParams
;
358 * Get the default filter state of this group
360 * @return {Object} Default filter state
362 FilterGroup
.prototype.getDefaultFilters = function () {
363 return this.defaultFilters
;
367 * This is for a single_option and string_options group types
368 * it returns the value of the default
370 * @return {string} Value of the default
372 FilterGroup
.prototype.getDefaulParamValue = function () {
373 return this.defaultParams
[ this.getName() ];
376 * Get the messags defining the 'whats this' popup for this group
378 * @return {Object} What's this messages
380 FilterGroup
.prototype.getWhatsThis = function () {
381 return this.whatsThis
;
385 * Check whether this group has a 'what's this' message
387 * @return {boolean} This group has a what's this message
389 FilterGroup
.prototype.hasWhatsThis = function () {
390 return !!this.whatsThis
.body
;
394 * Get the conflicts associated with the entire group.
395 * Conflict object is set up by filter name keys and conflict
396 * definition. For example:
400 * filter: filterName,
406 * filter: filterName2,
411 * @return {Object} Conflict definition
413 FilterGroup
.prototype.getConflicts = function () {
414 return this.conflicts
;
418 * Set conflicts for this group. See #getConflicts for the expected
419 * structure of the definition.
421 * @param {Object} conflicts Conflicts for this group
423 FilterGroup
.prototype.setConflicts = function ( conflicts
) {
424 this.conflicts
= conflicts
;
428 * Set conflicts for each filter item in the group based on the
431 * @param {Object} conflicts Object representing the conflict map,
432 * keyed by the item name, where its value is an object for all its conflicts
434 FilterGroup
.prototype.setFilterConflicts = function ( conflicts
) {
435 this.getItems().forEach( function ( filterItem
) {
436 if ( conflicts
[ filterItem
.getName() ] ) {
437 filterItem
.setConflicts( conflicts
[ filterItem
.getName() ] );
443 * Check whether this item has a potential conflict with the given item
445 * This checks whether the given item is in the list of conflicts of
446 * the current item, but makes no judgment about whether the conflict
447 * is currently at play (either one of the items may not be selected)
449 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
450 * @return {boolean} This item has a conflict with the given item
452 FilterGroup
.prototype.existsInConflicts = function ( filterItem
) {
453 return Object
.prototype.hasOwnProperty
.call( this.getConflicts(), filterItem
.getName() );
457 * Check whether there are any items selected
459 * @return {boolean} Any items in the group are selected
461 FilterGroup
.prototype.areAnySelected = function () {
462 return this.getItems().some( function ( filterItem
) {
463 return filterItem
.isSelected();
468 * Check whether all items selected
470 * @return {boolean} All items are selected
472 FilterGroup
.prototype.areAllSelected = function () {
476 this.getItems().forEach( function ( filterItem
) {
477 if ( filterItem
.isSelected() ) {
478 selected
.push( filterItem
);
480 unselected
.push( filterItem
);
484 if ( unselected
.length
=== 0 ) {
488 // check if every unselected is a subset of a selected
489 return unselected
.every( function ( unselectedFilterItem
) {
490 return selected
.some( function ( selectedFilterItem
) {
491 return selectedFilterItem
.existsInSubset( unselectedFilterItem
.getName() );
497 * Get all selected items in this group
499 * @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list
500 * @return {mw.rcfilters.dm.FilterItem[]} Selected items
502 FilterGroup
.prototype.findSelectedItems = function ( excludeItem
) {
503 var excludeName
= ( excludeItem
&& excludeItem
.getName() ) || '';
505 return this.getItems().filter( function ( item
) {
506 return item
.getName() !== excludeName
&& item
.isSelected();
511 * Check whether all selected items are in conflict with the given item
513 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
514 * @return {boolean} All selected items are in conflict with this item
516 FilterGroup
.prototype.areAllSelectedInConflictWith = function ( filterItem
) {
517 var selectedItems
= this.findSelectedItems( filterItem
);
519 return selectedItems
.length
> 0 &&
521 // The group as a whole is in conflict with this item
522 this.existsInConflicts( filterItem
) ||
523 // All selected items are in conflict individually
524 selectedItems
.every( function ( selectedFilter
) {
525 return selectedFilter
.existsInConflicts( filterItem
);
531 * Check whether any of the selected items are in conflict with the given item
533 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
534 * @return {boolean} Any of the selected items are in conflict with this item
536 FilterGroup
.prototype.areAnySelectedInConflictWith = function ( filterItem
) {
537 var selectedItems
= this.findSelectedItems( filterItem
);
539 return selectedItems
.length
> 0 && (
540 // The group as a whole is in conflict with this item
541 this.existsInConflicts( filterItem
) ||
542 // Any selected items are in conflict individually
543 selectedItems
.some( function ( selectedFilter
) {
544 return selectedFilter
.existsInConflicts( filterItem
);
550 * Get the parameter representation from this group
552 * @param {Object} [filterRepresentation] An object defining the state
553 * of the filters in this group, keyed by their name and current selected
555 * @return {Object} Parameter representation
557 FilterGroup
.prototype.getParamRepresentation = function ( filterRepresentation
) {
559 areAnySelected
= false,
560 buildFromCurrentState
= !filterRepresentation
,
561 defaultFilters
= this.getDefaultFilters(),
564 filterParamNames
= {},
565 getSelectedParameter = function ( filters
) {
569 // Find if any are selected
570 // eslint-disable-next-line no-jquery/no-each-util
571 $.each( filters
, function ( name
, value
) {
573 selected
.push( name
);
577 item
= model
.getItemByName( selected
[ 0 ] );
578 return ( item
&& item
.getParamName() ) || '';
581 filterRepresentation
= filterRepresentation
|| {};
583 // Create or complete the filterRepresentation definition
584 this.getItems().forEach( function ( item
) {
585 // Map filter names to their parameter names
586 filterParamNames
[ item
.getName() ] = item
.getParamName();
588 if ( buildFromCurrentState
) {
589 // This means we have not been given a filter representation
590 // so we are building one based on current state
591 filterRepresentation
[ item
.getName() ] = item
.getValue();
592 } else if ( filterRepresentation
[ item
.getName() ] === undefined ) {
593 // We are given a filter representation, but we have to make
594 // sure that we fill in the missing filters if there are any
595 // we will assume they are all falsey
596 if ( model
.isSticky() ) {
597 filterRepresentation
[ item
.getName() ] = !!defaultFilters
[ item
.getName() ];
599 filterRepresentation
[ item
.getName() ] = false;
603 if ( filterRepresentation
[ item
.getName() ] ) {
604 areAnySelected
= true;
610 this.getType() === 'send_unselected_if_any' ||
611 this.getType() === 'boolean' ||
612 this.getType() === 'any_value'
614 // First, check if any of the items are selected at all.
615 // If none is selected, we're treating it as if they are
618 // Go over the items and define the correct values
619 // eslint-disable-next-line no-jquery/no-each-util
620 $.each( filterRepresentation
, function ( name
, value
) {
621 // We must store all parameter values as strings '0' or '1'
622 if ( model
.getType() === 'send_unselected_if_any' ) {
623 result
[ filterParamNames
[ name
] ] = areAnySelected
?
624 String( Number( !value
) ) :
626 } else if ( model
.getType() === 'boolean' ) {
627 // Representation is straight-forward and direct from
628 // the parameter value to the filter state
629 result
[ filterParamNames
[ name
] ] = String( Number( !!value
) );
630 } else if ( model
.getType() === 'any_value' ) {
631 result
[ filterParamNames
[ name
] ] = value
;
634 } else if ( this.getType() === 'string_options' ) {
637 // eslint-disable-next-line no-jquery/no-each-util
638 $.each( filterRepresentation
, function ( name
, value
) {
641 values
.push( filterParamNames
[ name
] );
645 result
[ this.getName() ] = ( values
.length
=== Object
.keys( filterRepresentation
).length
) ?
646 'all' : values
.join( this.getSeparator() );
647 } else if ( this.getType() === 'single_option' ) {
648 result
[ this.getName() ] = getSelectedParameter( filterRepresentation
);
655 * Get the filter representation this group would provide
656 * based on given parameter states.
658 * @param {Object} [paramRepresentation] An object defining a parameter
659 * state to translate the filter state from. If not given, an object
660 * representing all filters as falsey is returned; same as if the parameter
661 * given were an empty object, or had some of the filters missing.
662 * @return {Object} Filter representation
664 FilterGroup
.prototype.getFilterRepresentation = function ( paramRepresentation
) {
665 var areAnySelected
, paramValues
, item
, currentValue
,
666 oneWasSelected
= false,
667 defaultParams
= this.getDefaultParams(),
668 expandedParams
= $.extend( true, {}, paramRepresentation
),
670 paramToFilterMap
= {},
673 if ( this.isSticky() ) {
674 // If the group is sticky, check if all parameters are represented
675 // and for those that aren't represented, add them with their default
677 paramRepresentation
= $.extend( true, {}, this.getDefaultParams(), paramRepresentation
);
680 paramRepresentation
= paramRepresentation
|| {};
682 this.getType() === 'send_unselected_if_any' ||
683 this.getType() === 'boolean' ||
684 this.getType() === 'any_value'
686 // Go over param representation; map and check for selections
687 this.getItems().forEach( function ( filterItem
) {
688 var paramName
= filterItem
.getParamName();
690 expandedParams
[ paramName
] = paramRepresentation
[ paramName
] || '0';
691 paramToFilterMap
[ paramName
] = filterItem
;
693 if ( Number( paramRepresentation
[ filterItem
.getParamName() ] ) ) {
694 areAnySelected
= true;
698 // eslint-disable-next-line no-jquery/no-each-util
699 $.each( expandedParams
, function ( paramName
, paramValue
) {
700 var filterItem
= paramToFilterMap
[ paramName
];
702 if ( model
.getType() === 'send_unselected_if_any' ) {
703 // Flip the definition between the parameter
704 // state and the filter state
705 // This is what the 'toggleSelected' value of the filter is
706 result
[ filterItem
.getName() ] = areAnySelected
?
707 !Number( paramValue
) :
708 // Otherwise, there are no selected items in the
709 // group, which means the state is false
711 } else if ( model
.getType() === 'boolean' ) {
712 // Straight-forward definition of state
713 result
[ filterItem
.getName() ] = !!Number( paramRepresentation
[ filterItem
.getParamName() ] );
714 } else if ( model
.getType() === 'any_value' ) {
715 result
[ filterItem
.getName() ] = paramRepresentation
[ filterItem
.getParamName() ];
718 } else if ( this.getType() === 'string_options' ) {
719 currentValue
= paramRepresentation
[ this.getName() ] || '';
721 // Normalize the given parameter values
722 paramValues
= mw
.rcfilters
.utils
.normalizeParamOptions(
728 this.getItems().map( function ( filterItem
) {
729 return filterItem
.getParamName();
732 // Translate the parameter values into a filter selection state
733 this.getItems().forEach( function ( filterItem
) {
734 // All true (either because all values are written or the term 'all' is written)
735 // is the same as all filters set to true
736 result
[ filterItem
.getName() ] = (
737 // If it is the word 'all'
738 paramValues
.length
=== 1 && paramValues
[ 0 ] === 'all' ||
739 // All values are written
740 paramValues
.length
=== model
.getItemCount()
743 // Otherwise, the filter is selected only if it appears in the parameter values
744 paramValues
.indexOf( filterItem
.getParamName() ) > -1;
746 } else if ( this.getType() === 'single_option' ) {
747 // There is parameter that fits a single filter and if not, get the default
748 this.getItems().forEach( function ( filterItem
) {
749 var selected
= filterItem
.getParamName() === paramRepresentation
[ model
.getName() ];
751 result
[ filterItem
.getName() ] = selected
;
752 oneWasSelected
= oneWasSelected
|| selected
;
756 // Go over result and make sure all filters are represented.
757 // If any filters are missing, they will get a falsey value
758 this.getItems().forEach( function ( filterItem
) {
759 if ( result
[ filterItem
.getName() ] === undefined ) {
760 result
[ filterItem
.getName() ] = this.getFalsyValue();
764 // Make sure that at least one option is selected in
765 // single_option groups, no matter what path was taken
766 // If none was selected by the given definition, then
767 // we need to select the one in the base state -- either
768 // the default given, or the first item
770 this.getType() === 'single_option' &&
773 item
= this.getItems()[ 0 ];
774 if ( defaultParams
[ this.getName() ] ) {
775 item
= this.getItemByParamName( defaultParams
[ this.getName() ] );
778 result
[ item
.getName() ] = true;
785 * @return {*} The appropriate falsy value for this group type
787 FilterGroup
.prototype.getFalsyValue = function () {
788 return this.getType() === 'any_value' ? '' : false;
792 * Get current selected state of all filter items in this group
794 * @return {Object} Selected state
796 FilterGroup
.prototype.getSelectedState = function () {
799 this.getItems().forEach( function ( filterItem
) {
800 state
[ filterItem
.getName() ] = filterItem
.getValue();
807 * Get item by its filter name
809 * @param {string} filterName Filter name
810 * @return {mw.rcfilters.dm.FilterItem} Filter item
812 FilterGroup
.prototype.getItemByName = function ( filterName
) {
813 return this.getItems().filter( function ( item
) {
814 return item
.getName() === filterName
;
819 * Select an item by its parameter name
821 * @param {string} paramName Filter parameter name
823 FilterGroup
.prototype.selectItemByParamName = function ( paramName
) {
824 this.getItems().forEach( function ( item
) {
825 item
.toggleSelected( item
.getParamName() === String( paramName
) );
830 * Get item by its parameter name
832 * @param {string} paramName Parameter name
833 * @return {mw.rcfilters.dm.FilterItem} Filter item
835 FilterGroup
.prototype.getItemByParamName = function ( paramName
) {
836 return this.getItems().filter( function ( item
) {
837 return item
.getParamName() === String( paramName
);
844 * @return {string} Group type
846 FilterGroup
.prototype.getType = function () {
851 * Check whether this group is represented by a single parameter
852 * or whether each item is its own parameter
854 * @return {boolean} This group is a single parameter
856 FilterGroup
.prototype.isPerGroupRequestParameter = function () {
858 this.getType() === 'string_options' ||
859 this.getType() === 'single_option'
866 * @return {string} Display group
868 FilterGroup
.prototype.getView = function () {
873 * Get the prefix used for the filter names inside this group.
875 * @param {string} [name] Filter name to prefix
876 * @return {string} Group prefix
878 FilterGroup
.prototype.getNamePrefix = function () {
879 return this.getName() + '__';
883 * Get a filter name with the prefix used for the filter names inside this group.
885 * @param {string} name Filter name to prefix
886 * @return {string} Group prefix
888 FilterGroup
.prototype.getPrefixedName = function ( name
) {
889 return this.getNamePrefix() + name
;
895 * @return {string} Title
897 FilterGroup
.prototype.getTitle = function () {
902 * Get group's values separator
904 * @return {string} Values separator
906 FilterGroup
.prototype.getSeparator = function () {
907 return this.separator
;
911 * Check whether the group is defined as full coverage
913 * @return {boolean} Group is full coverage
915 FilterGroup
.prototype.isFullCoverage = function () {
916 return this.fullCoverage
;
920 * Check whether the group is defined as sticky default
922 * @return {boolean} Group is sticky default
924 FilterGroup
.prototype.isSticky = function () {
929 * Normalize a value given to this group. This is mostly for correcting
930 * arbitrary values for 'single option' groups, given by the user settings
931 * or the URL that can go outside the limits that are allowed.
933 * @param {string} value Given value
934 * @return {string} Corrected value
936 FilterGroup
.prototype.normalizeArbitraryValue = function ( value
) {
938 this.getType() === 'single_option' &&
939 this.isAllowArbitrary()
942 this.getMaxValue() !== null &&
943 value
> this.getMaxValue()
945 // Change the value to the actual max value
946 return String( this.getMaxValue() );
948 this.getMinValue() !== null &&
949 value
< this.getMinValue()
951 // Change the value to the actual min value
952 return String( this.getMinValue() );
960 * Toggle the visibility of this group
962 * @param {boolean} [isVisible] Item is visible
964 FilterGroup
.prototype.toggleVisible = function ( isVisible
) {
965 isVisible
= isVisible
=== undefined ? !this.visible
: isVisible
;
967 if ( this.visible
!== isVisible
) {
968 this.visible
= isVisible
;
969 this.emit( 'update' );
974 * Check whether the group is visible
976 * @return {boolean} Group is visible
978 FilterGroup
.prototype.isVisible = function () {
983 * Set the visibility of the items under this group by the given items array
985 * @param {mw.rcfilters.dm.ItemModel[]} visibleItems An array of visible items
987 FilterGroup
.prototype.setVisibleItems = function ( visibleItems
) {
988 this.getItems().forEach( function ( itemModel
) {
989 itemModel
.toggleVisible( visibleItems
.indexOf( itemModel
) !== -1 );
993 module
.exports
= FilterGroup
;