RCFilters: Add 'boolean' group
authorMoriel Schottlender <moriel@gmail.com>
Thu, 20 Jul 2017 05:40:32 +0000 (22:40 -0700)
committerMoriel Schottlender <moriel@gmail.com>
Fri, 21 Jul 2017 23:09:53 +0000 (16:09 -0700)
The group allows filters to be represented 'directly' with their
selected values corresponding to their parameter "1" or "0" value.

Change-Id: I56e9b52ff79a46227de71c905b2ecd97a3823624

resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js
tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js

index 9a85291..8bedd68 100644 (file)
@@ -52,6 +52,7 @@
 
                this.conflicts = config.conflicts || {};
                this.defaultParams = {};
+               this.defaultFilters = {};
 
                this.aggregate( { update: 'filterItemUpdate' } );
                this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
@@ -89,6 +90,7 @@
                        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,
                        items.push( filterItem );
 
                        // Store default parameter state; in this case, default is defined per filter
-                       if ( model.getType() === 'send_unselected_if_any' ) {
+                       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')
                        // 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() ] );
+                       }
+               } );
        };
 
        /**
                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
                var values,
                        areAnySelected = false,
                        buildFromCurrentState = !filterRepresentation,
+                       defaultFilters = this.getDefaultFilters(),
                        result = {},
                        model = this,
                        filterParamNames = {},
                        } else if ( !filterRepresentation[ item.getName() ] ) {
                                // 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
-                               filterRepresentation[ item.getName() ] = false;
+                               // 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'
+                                       )
+                               ) {
+                                       filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ];
+                               } else {
+                                       filterRepresentation[ item.getName() ] = false;
+                               }
                        }
 
                        if ( filterRepresentation[ item.getName() ] ) {
                } );
 
                // Build result
-               if ( this.getType() === 'send_unselected_if_any' ) {
+               if (
+                       this.getType() === 'send_unselected_if_any' ||
+                       this.getType() === 'boolean'
+               ) {
                        // 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'
-                               result[ filterParamNames[ name ] ] = areAnySelected ?
-                                       String( Number( !value ) ) :
-                                       '0';
+                               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 ( this.getType() === 'string_options' ) {
                        values = [];
         * Get the filter representation this group would provide
         * based on given parameter states.
         *
-        * @param {Object|string} [paramRepresentation] An object defining a parameter
+        * @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, defaultValue, item,
+               var areAnySelected, paramValues, defaultValue, item, currentValue,
                        oneWasSelected = false,
+                       defaultParams = this.getDefaultParams(),
+                       defaultFilters = this.getDefaultFilters(),
+                       expandedParams = $.extend( true, {}, paramRepresentation ),
                        model = this,
                        paramToFilterMap = {},
                        result = {};
 
-               if ( this.getType() === 'send_unselected_if_any' ) {
-                       paramRepresentation = paramRepresentation || {};
-                       // Expand param representation to include all filters in the group
+               paramRepresentation = paramRepresentation || {};
+               if (
+                       this.getType() === 'send_unselected_if_any' ||
+                       this.getType() === 'boolean'
+               ) {
+                       // Go over param representation; map and check for selections
                        this.getItems().forEach( function ( filterItem ) {
                                var paramName = filterItem.getParamName();
 
-                               paramRepresentation[ paramName ] = paramRepresentation[ paramName ] || '0';
+                               expandedParams[ paramName ] = paramRepresentation[ paramName ] || '0';
                                paramToFilterMap[ paramName ] = filterItem;
 
                                if ( Number( paramRepresentation[ filterItem.getParamName() ] ) ) {
                                }
                        } );
 
-                       $.each( paramRepresentation, function ( paramName, paramValue ) {
-                               var filterItem = paramToFilterMap[ paramName ];
-
-                               // 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;
+                       $.each( expandedParams, function ( paramName, paramValue ) {
+                               var value = paramValue,
+                                       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
+                                       if (
+                                               filterItem.isUsingDefaultAsBaseValue() &&
+                                               paramRepresentation[ filterItem.getParamName() ] === undefined
+                                       ) {
+                                               value = defaultParams[ filterItem.getParamName() ];
+                                       }
+                                       result[ filterItem.getName() ] = !!Number( value );
+                               }
                        } );
                } else if ( this.getType() === 'string_options' ) {
-                       paramRepresentation = paramRepresentation || '';
+                       currentValue = paramRepresentation[ this.getName() ] || '';
 
                        // Normalize the given parameter values
                        paramValues = mw.rcfilters.utils.normalizeParamOptions(
                                // Given
-                               paramRepresentation.split(
+                               currentValue.split(
                                        this.getSeparator()
                                ),
                                // Allowed values
                } 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 ) {
-                               result[ filterItem.getName() ] = filterItem.getParamName() === paramRepresentation;
-                               oneWasSelected = oneWasSelected || filterItem.getParamName() === paramRepresentation;
+                               var selected = false;
+
+                               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 ) {
-                       result[ filterItem.getName() ] = !!result[ filterItem.getName() ];
+                       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() ];
+                       }
                        oneWasSelected = oneWasSelected || !!result[ filterItem.getName() ];
                } );
 
index 06fa0aa..8f318ef 100644 (file)
 
                // Create a map between known parameters and their models
                $.each( this.groups, function ( group, groupModel ) {
-                       if ( groupModel.getType() === 'send_unselected_if_any' ) {
+                       if (
+                               groupModel.getType() === 'send_unselected_if_any' ||
+                               groupModel.getType() === 'boolean'
+                       ) {
                                // Individual filters
                                groupModel.getItems().forEach( function ( filterItem ) {
                                        model.parameterMap[ filterItem.getParamName() ] = filterItem;
                //    group2: "param4|param5"
                // }
                $.each( params, function ( paramName, paramValue ) {
-                       var itemOrGroup = model.parameterMap[ paramName ];
-
-                       if ( itemOrGroup instanceof mw.rcfilters.dm.FilterItem ) {
-                               groupMap[ itemOrGroup.getGroupName() ] = groupMap[ itemOrGroup.getGroupName() ] || {};
-                               groupMap[ itemOrGroup.getGroupName() ][ itemOrGroup.getParamName() ] = paramValue;
-                       } else if ( itemOrGroup instanceof mw.rcfilters.dm.FilterGroup ) {
-                               // This parameter represents a group (values are the filters)
-                               // this is equivalent to checking if the group is 'string_options'
-                               groupMap[ itemOrGroup.getName() ] = groupMap[ itemOrGroup.getName() ] || {};
-                               groupMap[ itemOrGroup.getName() ] = paramValue;
+                       var groupName,
+                               itemOrGroup = model.parameterMap[ paramName ];
+
+                       if ( itemOrGroup ) {
+                               groupName = itemOrGroup instanceof mw.rcfilters.dm.FilterItem ?
+                                       itemOrGroup.getGroupName() : itemOrGroup.getName();
+
+                               groupMap[ groupName ] = groupMap[ groupName ] || {};
+                               groupMap[ groupName ][ paramName ] = paramValue;
                        }
                } );
 
index aa82e21..54a4dbe 100644 (file)
@@ -32,6 +32,7 @@
                this.namePrefix = config.namePrefix || 'item_';
                this.name = this.namePrefix + param;
 
+               this.useDefaultAsBaseValue = !!config.useDefaultAsBaseValue;
                this.label = config.label || this.name;
                this.labelPrefixKey = config.labelPrefixKey;
                this.description = config.description || '';
                return this.identifiers;
        };
 
+       /**
+        * Check whether the item uses its default state as a base value
+        *
+        * @return {boolean} Use default as base value
+        */
+       mw.rcfilters.dm.ItemModel.prototype.isUsingDefaultAsBaseValue = function () {
+               return this.useDefaultAsBaseValue;
+       };
+
        /**
         * Toggle the highlight feature on and off for this filter.
         * It only works if highlight is supported for this filter.
index edaef79..da7bafd 100644 (file)
                                { name: 'option2', label: 'group5option2-label', description: 'group5option2-desc' },
                                { name: 'option3', label: 'group5option3-label', description: 'group5option3-desc' }
                        ]
+               }, {
+                       name: 'group6',
+                       type: 'boolean',
+                       filters: [
+                               { name: 'group6option1', label: 'group6option1-label', description: 'group5option1-desc' },
+                               { name: 'group6option2', label: 'group6option2-label', description: 'group5option2-desc', default: true, useDefaultAsBaseValue: true },
+                               { name: 'group6option3', label: 'group6option3-label', description: 'group5option3-desc', default: true }
+                       ]
                } ],
                viewsDefinition = {
                        namespaces: {
                        group3: 'filter8',
                        group4: 'option2',
                        group5: 'option1',
+                       group6option1: '0',
+                       group6option2: '1',
+                       group6option3: '1',
                        namespace: ''
                },
                baseParamRepresentation = {
                        group3: '',
                        group4: 'option2',
                        group5: 'option1',
+                       group6option1: '0',
+                       group6option2: '1',
+                       group6option3: '0',
                        namespace: ''
                },
                baseFilterRepresentation = {
                        group5__option1: true, // No default set, first item is default value
                        group5__option2: false,
                        group5__option3: false,
+                       group6__group6option1: false,
+                       group6__group6option2: true,
+                       group6__group6option3: false,
                        namespace__0: false,
                        namespace__1: false,
                        namespace__2: false,
                        group5__option1: { selected: true, conflicted: false, included: false },
                        group5__option2: { selected: false, conflicted: false, included: false },
                        group5__option3: { selected: false, conflicted: false, included: false },
+                       group6__group6option1: { selected: false, conflicted: false, included: false },
+                       group6__group6option2: { selected: true, conflicted: false, included: false },
+                       group6__group6option3: { selected: false, conflicted: false, included: false },
                        namespace__0: { selected: false, conflicted: false, included: false },
                        namespace__1: { selected: false, conflicted: false, included: false },
                        namespace__2: { selected: false, conflicted: false, included: false },