X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Fsrc%2Fmediawiki.rcfilters%2Fdm%2Fmw.rcfilters.dm.FilterGroup.js;h=f7021e2bb922b914beb111b778ab8298feb424ea;hb=684b0dc227777739b29cb8596275bdb9a4658628;hp=4915803c9f7cdf27c3de2dff9dc2f57024a29e67;hpb=d7bc76523800bf3debc573178ff4cb0abe3f59da;p=lhc%2Fweb%2Fwiklou.git 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 4915803c9f..f7021e2bb9 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js @@ -11,8 +11,15 @@ * @cfg {string} [type='send_unselected_if_any'] Group type * @cfg {string} [view='default'] Name of the display group this group * is a part of. + * @cfg {boolean} [isSticky] This group is using a 'sticky' default; meaning + * that every time a value is changed, it becomes the new default * @cfg {string} [title] Group title * @cfg {boolean} [hidden] This group is hidden from the regular menu views + * @cfg {boolean} [allowArbitrary] Allows for an arbitrary value to be added to the + * group from the URL, even if it wasn't initially set up. + * @cfg {number} [range] An object defining minimum and maximum values for numeric + * groups. { min: x, max: y } + * @cfg {number} [minValue] Minimum value for numeric groups * @cfg {string} [separator='|'] Value separator for 'string_options' groups * @cfg {boolean} [active] Group is active * @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results @@ -36,11 +43,15 @@ this.name = name; this.type = config.type || 'send_unselected_if_any'; this.view = config.view || 'default'; + this.sticky = !!config.isSticky; this.title = config.title || name; this.hidden = !!config.hidden; + this.allowArbitrary = !!config.allowArbitrary; + this.numericRange = config.range; this.separator = config.separator || '|'; this.labelPrefixKey = config.labelPrefixKey; + this.currSelected = null; this.active = !!config.active; this.fullCoverage = !!config.fullCoverage; @@ -48,6 +59,7 @@ this.conflicts = config.conflicts || {}; this.defaultParams = {}; + this.defaultFilters = {}; this.aggregate( { update: 'filterItemUpdate' } ); this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } ); @@ -75,7 +87,8 @@ * @param {string|Object} [groupDefault] Definition of the group default */ mw.rcfilters.dm.FilterGroup.prototype.initializeFilters = function ( filterDefinition, groupDefault ) { - var supersetMap = {}, + var defaultParam, + supersetMap = {}, model = this, items = []; @@ -126,11 +139,14 @@ 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') - model.defaultParams[ filter.name ] = String( Number( !!filter.default ) ); + model.defaultParams[ filter.name ] = String( Number( filter.default || 0 ) ); } } ); @@ -159,11 +175,36 @@ } ) ).join( this.getSeparator() ); } else if ( this.getType() === 'single_option' ) { + defaultParam = groupDefault !== undefined ? + groupDefault : this.getItems()[ 0 ].getParamName(); + // For this group, the parameter is the group name, - // and a single item can be selected, or none at all - // The item also must be recognized or none is set as - // default - model.defaultParams[ this.getName() ] = this.getItemByParamName( groupDefault ) ? groupDefault : ''; + // and a single item can be selected: default or first item + this.defaultParams[ this.getName() ] = 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 + if ( this.isSticky() ) { + $.each( this.defaultFilters, function ( filterName, filterValue ) { + model.getItemByName( filterName ).toggleSelected( filterValue ); + } ); + } + + // Verify that single_option group has at least one item selected + if ( + this.getType() === 'single_option' && + this.getSelectedItems().length === 0 + ) { + defaultParam = groupDefault !== undefined ? + groupDefault : this.getItems()[ 0 ].getParamName(); + + // Single option means there must be a single option + // selected, so we have to either select the default + // or select the first option + this.selectItemByParamName( defaultParam ); } }; @@ -175,20 +216,51 @@ */ mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function ( item ) { // Update state - var active = this.areAnySelected(), - itemName = item && item.getName(); + var changed = false, + active = this.areAnySelected(); + + if ( + item.isSelected() && + this.getType() === 'single_option' && + this.currSelected && + this.currSelected !== item + ) { + this.currSelected.toggleSelected( false ); + } - if ( item.isSelected() && this.getType() === 'single_option' ) { - // Change the selection to only be the newly selected item - this.getItems().forEach( function ( filterItem ) { - if ( filterItem.getName() !== itemName ) { - filterItem.toggleSelected( false ); - } - } ); + // For 'single_option' groups, check if we just unselected all + // items. This should never be the result. If we did unselect + // all (like resetting all filters to false) then this group + // must choose its default item or the first item in the group + if ( + this.getType() === 'single_option' && + !this.getItems().some( function ( filterItem ) { + return filterItem.isSelected(); + } ) + ) { + // Single option means there must be a single option + // selected, so we have to either select the default + // or select the first option + this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] ) || + this.getItems()[ 0 ]; + this.currSelected.toggleSelected( true ); + changed = true; } - if ( this.active !== active ) { + if ( + changed || + this.active !== active || + this.currSelected !== item + ) { + if ( this.isSticky() ) { + // If this group is sticky, then change the default according to the + // current selection. + this.defaultParams = this.getParamRepresentation( this.getSelectedState() ); + } + this.active = active; + this.currSelected = item; + this.emit( 'update' ); } }; @@ -211,6 +283,35 @@ return this.hidden; }; + /** + * Get group allow arbitrary state + * + * @return {boolean} Group allows an arbitrary value from the URL + */ + mw.rcfilters.dm.FilterGroup.prototype.isAllowArbitrary = function () { + return this.allowArbitrary; + }; + + /** + * Get group maximum value for numeric groups + * + * @return {number|null} Group max value + */ + mw.rcfilters.dm.FilterGroup.prototype.getMaxValue = function () { + return this.numericRange && this.numericRange.max !== undefined ? + this.numericRange.max : null; + }; + + /** + * Get group minimum value for numeric groups + * + * @return {number|null} Group max value + */ + mw.rcfilters.dm.FilterGroup.prototype.getMinValue = function () { + return this.numericRange && this.numericRange.min !== undefined ? + this.numericRange.min : null; + }; + /** * Get group name * @@ -229,6 +330,24 @@ 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 + * + * @return {string} Value of the default + */ + mw.rcfilters.dm.FilterGroup.prototype.getDefaulParamValue = function () { + return this.defaultParams[ this.getName() ]; + }; /** * Get the messags defining the 'whats this' popup for this group * @@ -415,6 +534,7 @@ var values, areAnySelected = false, buildFromCurrentState = !filterRepresentation, + defaultFilters = this.getDefaultFilters(), result = {}, model = this, filterParamNames = {}, @@ -444,11 +564,15 @@ // This means we have not been given a filter representation // so we are building one based on current state filterRepresentation[ item.getName() ] = item.isSelected(); - } else if ( !filterRepresentation[ item.getName() ] ) { + } else if ( filterRepresentation[ item.getName() ] === undefined ) { // We are given a filter representation, but we have to make // sure that we fill in the missing filters if there are any // we will assume they are all falsey - filterRepresentation[ item.getName() ] = false; + if ( model.isSticky() ) { + filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ]; + } else { + filterRepresentation[ item.getName() ] = false; + } } if ( filterRepresentation[ item.getName() ] ) { @@ -457,7 +581,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 @@ -465,9 +592,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 = []; @@ -492,25 +625,38 @@ * 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, + var areAnySelected, paramValues, item, currentValue, + oneWasSelected = false, + defaultParams = this.getDefaultParams(), + 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 + if ( this.isSticky() ) { + // If the group is sticky, check if all parameters are represented + // and for those that aren't represented, add them with their default + // values + paramRepresentation = $.extend( true, {}, this.getDefaultParams(), paramRepresentation ); + } + + paramRepresentation = paramRepresentation || {}; + if ( + this.getType() === 'send_unselected_if_any' || + 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() ] ) ) { @@ -518,25 +664,30 @@ } } ); - $.each( paramRepresentation, function ( paramName, paramValue ) { + $.each( expandedParams, 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; + 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 + result[ filterItem.getName() ] = !!Number( paramRepresentation[ filterItem.getParamName() ] ); + } } ); } 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 @@ -559,21 +710,58 @@ paramValues.indexOf( filterItem.getParamName() ) > -1; } ); } else if ( this.getType() === 'single_option' ) { - // There is parameter that fits a single filter, or none at all + // 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; + var 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 ( result[ filterItem.getName() ] === undefined ) { + result[ filterItem.getName() ] = false; + } } ); + // Make sure that at least one option is selected in + // single_option groups, no matter what path was taken + // If none was selected by the given definition, then + // we need to select the one in the base state -- either + // the default given, or the first item + if ( + this.getType() === 'single_option' && + !oneWasSelected + ) { + item = this.getItems()[ 0 ]; + if ( defaultParams[ this.getName() ] ) { + item = this.getItemByParamName( defaultParams[ this.getName() ] ); + } + + result[ item.getName() ] = true; + } + return result; }; + /** + * Get current selected state of all filter items in this group + * + * @return {Object} Selected state + */ + mw.rcfilters.dm.FilterGroup.prototype.getSelectedState = function () { + var state = {}; + + this.getItems().forEach( function ( filterItem ) { + state[ filterItem.getName() ] = filterItem.isSelected(); + } ); + + return state; + }; + /** * Get item by its filter name * @@ -586,6 +774,17 @@ } )[ 0 ]; }; + /** + * Select an item by its parameter name + * + * @param {string} paramName Filter parameter name + */ + mw.rcfilters.dm.FilterGroup.prototype.selectItemByParamName = function ( paramName ) { + this.getItems().forEach( function ( item ) { + item.toggleSelected( item.getParamName() === String( paramName ) ); + } ); + }; + /** * Get item by its parameter name * @@ -594,7 +793,7 @@ */ mw.rcfilters.dm.FilterGroup.prototype.getItemByParamName = function ( paramName ) { return this.getItems().filter( function ( item ) { - return item.getParamName() === paramName; + return item.getParamName() === String( paramName ); } )[ 0 ]; }; @@ -662,4 +861,13 @@ mw.rcfilters.dm.FilterGroup.prototype.isFullCoverage = function () { return this.fullCoverage; }; + + /** + * Check whether the group is defined as sticky default + * + * @return {boolean} Group is sticky default + */ + mw.rcfilters.dm.FilterGroup.prototype.isSticky = function () { + return this.sticky; + }; }( mediaWiki ) );