From dab88cde22c8c681b251b450769ecda5f2f6e2e1 Mon Sep 17 00:00:00 2001 From: Moriel Schottlender Date: Wed, 19 Jul 2017 22:40:32 -0700 Subject: [PATCH] RCFilters: Add 'boolean' group The group allows filters to be represented 'directly' with their selected values corresponding to their parameter "1" or "0" value. Change-Id: I56e9b52ff79a46227de71c905b2ecd97a3823624 --- .../dm/mw.rcfilters.dm.FilterGroup.js | 154 ++++++++++++++---- .../dm/mw.rcfilters.dm.FiltersViewModel.js | 24 +-- .../dm/mw.rcfilters.dm.ItemModel.js | 10 ++ .../dm.FiltersViewModel.test.js | 20 +++ 4 files changed, 168 insertions(+), 40 deletions(-) diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js index 9a85291bc6..8bedd688f0 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js @@ -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, @@ -131,7 +133,10 @@ 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') @@ -176,6 +181,25 @@ // 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() ] ); + } + } ); }; /** @@ -253,6 +277,15 @@ 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 @@ -448,6 +481,7 @@ var values, areAnySelected = false, buildFromCurrentState = !filterRepresentation, + defaultFilters = this.getDefaultFilters(), result = {}, model = this, filterParamNames = {}, @@ -480,8 +514,22 @@ } 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() ] ) { @@ -490,7 +538,10 @@ } ); // 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 @@ -498,9 +549,15 @@ // 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 = []; @@ -525,26 +582,32 @@ * 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() ] ) ) { @@ -552,25 +615,37 @@ } } ); - $.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 @@ -595,15 +670,36 @@ } 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() ]; } ); diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js index 06fa0aa946..8f318efb98 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -396,7 +396,10 @@ // 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; @@ -612,16 +615,15 @@ // 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; } } ); diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js index aa82e218f8..54a4dbe971 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js @@ -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 || ''; @@ -248,6 +249,15 @@ 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. diff --git a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js index edaef7953d..da7bafdbdb 100644 --- a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js +++ b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js @@ -71,6 +71,14 @@ { 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: { @@ -100,6 +108,9 @@ group3: 'filter8', group4: 'option2', group5: 'option1', + group6option1: '0', + group6option2: '1', + group6option3: '1', namespace: '' }, baseParamRepresentation = { @@ -112,6 +123,9 @@ group3: '', group4: 'option2', group5: 'option1', + group6option1: '0', + group6option2: '1', + group6option3: '0', namespace: '' }, baseFilterRepresentation = { @@ -132,6 +146,9 @@ 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, @@ -153,6 +170,9 @@ 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 }, -- 2.20.1