this.defaultParams = {};
this.defaultFiltersEmpty = null;
this.highlightEnabled = false;
- this.invertedNamespaces = false;
this.parameterMap = {};
+ this.emptyParameterState = null;
this.views = {};
this.currentView = 'default';
* Highlight feature has been toggled enabled or disabled
*/
- /**
- * @event invertChange
- * @param {boolean} isInverted Namespace selected is inverted
- *
- * Namespace selection is inverted or straight forward
- */
-
/* Methods */
/**
this.currentView = 'default';
+ this.updateHighlightedState();
+
// Finish initialization
this.emit( 'initialize' );
};
+ /**
+ * Update filter view model state based on a parameter object
+ *
+ * @param {Object} params Parameters object
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.updateStateFromParams = function ( params ) {
+ // For arbitrary numeric single_option values make sure the values
+ // are normalized to fit within the limits
+ $.each( this.getFilterGroups(), function ( groupName, groupModel ) {
+ params[ groupName ] = groupModel.normalizeArbitraryValue( params[ groupName ] );
+ } );
+
+ // Update filter states
+ this.toggleFiltersSelected(
+ this.getFiltersFromParameters(
+ params
+ )
+ );
+
+ // Update highlight state
+ this.getItemsSupportingHighlights().forEach( function ( filterItem ) {
+ var color = params[ filterItem.getName() + '_color' ];
+ if ( color ) {
+ filterItem.setHighlightColor( color );
+ } else {
+ filterItem.clearHighlightColor();
+ }
+ } );
+ this.updateHighlightedState();
+
+ // Check all filter interactions
+ this.reassessFilterInteractions();
+ };
+
+ /**
+ * Get a representation of an empty (falsey) parameter state
+ *
+ * @return {Object} Empty parameter state
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getEmptyParameterState = function () {
+ if ( !this.emptyParameterState ) {
+ this.emptyParameterState = $.extend(
+ true,
+ {},
+ this.getParametersFromFilters( {} ),
+ this.getEmptyHighlightParameters()
+ );
+ }
+ return this.emptyParameterState;
+ };
+
+ /**
+ * Get a representation of only the non-falsey parameters
+ *
+ * @param {Object} [parameters] A given parameter state to minimize. If not given the current
+ * state of the system will be used.
+ * @return {Object} Empty parameter state
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getMinimizedParamRepresentation = function ( parameters ) {
+ var result = {};
+
+ parameters = parameters ? $.extend( true, {}, parameters ) : this.getCurrentParameterState();
+
+ // Params
+ $.each( this.getEmptyParameterState(), function ( param, value ) {
+ if ( parameters[ param ] !== undefined && parameters[ param ] !== value ) {
+ result[ param ] = parameters[ param ];
+ }
+ } );
+
+ // Highlights
+ Object.keys( this.getEmptyHighlightParameters() ).forEach( function ( param ) {
+ if ( parameters[ param ] ) {
+ // If a highlight parameter is not undefined and not null
+ // add it to the result
+ result[ param ] = parameters[ param ];
+ }
+ } );
+
+ return result;
+ };
+
+ /**
+ * Get a representation of the full parameter list, including all base values
+ *
+ * @param {Object} [parameters] A given parameter state to minimize. If not given the current
+ * state of the system will be used.
+ * @param {boolean} [removeExcluded] Remove excluded and sticky parameters
+ * @return {Object} Full parameter representation
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getExpandedParamRepresentation = function ( parameters, removeExcluded ) {
+ var result = {};
+
+ parameters = parameters ? $.extend( true, {}, parameters ) : this.getCurrentParameterState();
+
+ result = $.extend(
+ true,
+ {},
+ this.getEmptyParameterState(),
+ parameters
+ );
+
+ if ( removeExcluded ) {
+ result = this.removeExcludedParams( result );
+ }
+
+ return result;
+ };
+
+ /**
+ * Get a parameter representation of the current state of the model
+ *
+ * @param {boolean} [removeExcludedParams] Remove excluded filters from final result
+ * @return {Object} Parameter representation of the current state of the model
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentParameterState = function ( removeExcludedParams ) {
+ var excludedParams,
+ state = this.getMinimizedParamRepresentation( $.extend(
+ true,
+ {},
+ this.getParametersFromFilters( this.getSelectedState() ),
+ this.getHighlightParameters()
+ ) );
+
+ if ( removeExcludedParams ) {
+ excludedParams = this.getExcludedParams();
+ // Delete all excluded filters
+ $.each( state, function ( param ) {
+ if ( excludedParams.indexOf( param ) > -1 ) {
+ delete state[ param ];
+ }
+ } );
+ }
+
+ return state;
+ };
+
+ /**
+ * Delete excluded and sticky filters from given object. If object isn't given, output
+ * the current filter state without the excluded values
+ *
+ * @param {Object} [filterState] Filter state
+ * @return {Object} Filter state without excluded filters
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.removeExcludedFilters = function ( filterState ) {
+ filterState = filterState !== undefined ?
+ $.extend( true, {}, filterState ) :
+ this.getFiltersFromParameters();
+
+ // Remove excluded filters
+ Object.keys( this.getExcludedFiltersState() ).forEach( function ( filterName ) {
+ delete filterState[ filterName ];
+ } );
+
+ // Remove sticky filters
+ Object.keys( this.getStickyFiltersState() ).forEach( function ( filterName ) {
+ delete filterState[ filterName ];
+ } );
+
+ return filterState;
+ };
+ /**
+ * Delete excluded and sticky parameters from given object. If object isn't given, output
+ * the current param state without the excluded values
+ *
+ * @param {Object} [paramState] Parameter state
+ * @return {Object} Parameter state without excluded filters
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.removeExcludedParams = function ( paramState ) {
+ paramState = paramState !== undefined ?
+ $.extend( true, {}, paramState ) :
+ this.getCurrentParameterState();
+
+ // Remove excluded filters
+ this.getExcludedParams().forEach( function ( paramName ) {
+ delete paramState[ paramName ];
+ } );
+
+ // Remove sticky filters
+ this.getStickyParams().forEach( function ( paramName ) {
+ delete paramState[ paramName ];
+ } );
+
+ return paramState;
+ };
+
/**
* Get the names of all available filters
*
return this.getItems().map( function ( item ) { return item.getName(); } );
};
+ /**
+ * Turn the highlight feature on or off
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.updateHighlightedState = function () {
+ this.toggleHighlight( this.getHighlightedItems().length > 0 );
+ };
+
/**
* Get the object that defines groups by their name.
*
/**
* Get an object representing default parameters state
*
+ * @param {boolean} [excludeHiddenParams] Exclude hidden and sticky params
* @return {Object} Default parameter values
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.getDefaultParams = function () {
+ mw.rcfilters.dm.FiltersViewModel.prototype.getDefaultParams = function ( excludeHiddenParams ) {
var result = {};
// Get default filter state
$.extend( true, result, model.getDefaultParams() );
} );
+ if ( excludeHiddenParams ) {
+ Object.keys( this.getDefaultHiddenParams() ).forEach( function ( paramName ) {
+ delete result[ paramName ];
+ } );
+ }
+
+ return result;
+ };
+
+ /**
+ * Get an object representing defaults for the hidden parameters state
+ *
+ * @return {Object} Default values for hidden parameters
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getDefaultHiddenParams = function () {
+ var result = {};
+
+ // Get default filter state
+ $.each( this.groups, function ( name, model ) {
+ if ( model.isHidden() ) {
+ $.extend( true, result, model.getDefaultParams() );
+ }
+ } );
+
return result;
};
* @return {Object} Sticky parameter values
*/
mw.rcfilters.dm.FiltersViewModel.prototype.getStickyParams = function () {
+ var result = [];
+
+ $.each( this.groups, function ( name, model ) {
+ if ( model.isSticky() ) {
+ if ( model.isPerGroupRequestParameter() ) {
+ result.push( name );
+ } else {
+ // Each filter is its own param
+ result = result.concat( model.getItems().map( function ( filterItem ) {
+ return filterItem.getParamName();
+ } ) );
+ }
+ }
+ } );
+
+ return result;
+ };
+
+ /**
+ * Get a parameter representation of all sticky parameters
+ *
+ * @return {Object} Sticky parameter values
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getStickyParamsValues = function () {
var result = {};
$.each( this.groups, function ( name, model ) {
return result;
};
+ /**
+ * Get the parameter names that represent filters that are excluded
+ * from saved queries.
+ *
+ * @return {string[]} Parameter names
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getExcludedParams = function () {
+ var result = [];
+
+ $.each( this.groups, function ( name, model ) {
+ if ( model.isExcludedFromSavedQueries() ) {
+ if ( model.isPerGroupRequestParameter() ) {
+ result.push( name );
+ } else {
+ // Each filter is its own param
+ result = result.concat( model.getItems().map( function ( filterItem ) {
+ return filterItem.getParamName();
+ } ) );
+ }
+ }
+ } );
+
+ return result;
+ };
+
/**
* Analyze the groups and their filters and output an object representing
* the state of the parameters they represent.
* are the selected highlight colors.
*/
mw.rcfilters.dm.FiltersViewModel.prototype.getHighlightParameters = function () {
- var result = {};
+ var highlightEnabled = this.isHighlightEnabled(),
+ result = {};
this.getItems().forEach( function ( filterItem ) {
- result[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor() || null;
+ if ( filterItem.isHighlightSupported() ) {
+ result[ filterItem.getName() + '_color' ] = highlightEnabled && filterItem.isHighlighted() ?
+ filterItem.getHighlightColor() :
+ null;
+ }
} );
- result.highlight = String( Number( this.isHighlightEnabled() ) );
return result;
};
/**
- * Extract the highlight values from given object. Since highlights are
- * the same for filter and parameters, it doesn't matter which one is
- * given; values will be returned with a full list of the highlights
- * with colors or null values.
+ * Get an object representing the complete empty state of highlights
*
- * @param {Object} representation Object containing representation of
- * some or all highlight values
- * @return {Object} Object where keys are `<filter name>_color` and values
- * are the selected highlight colors. The returned object
- * contains all available filters either with a color value
- * or with null.
+ * @return {Object} Object containing all the highlight parameters set to their negative value
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.extractHighlightValues = function ( representation ) {
+ mw.rcfilters.dm.FiltersViewModel.prototype.getEmptyHighlightParameters = function () {
var result = {};
this.getItems().forEach( function ( filterItem ) {
- var highlightName = filterItem.getName() + '_color';
- result[ highlightName ] = representation[ highlightName ] || null;
+ if ( filterItem.isHighlightSupported() ) {
+ result[ filterItem.getName() + '_color' ] = null;
+ }
} );
return result;
mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentlyUsedHighlightColors = function () {
var result = [];
- this.getHighlightedItems().forEach( function ( filterItem ) {
- var color = filterItem.getHighlightColor();
+ if ( this.isHighlightEnabled() ) {
+ this.getHighlightedItems().forEach( function ( filterItem ) {
+ var color = filterItem.getHighlightColor();
- if ( result.indexOf( color ) === -1 ) {
- result.push( color );
- }
- } );
+ if ( result.indexOf( color ) === -1 ) {
+ result.push( color );
+ }
+ } );
+ }
return result;
};
} );
};
+ /**
+ * Check whether the invert state is a valid one. A valid invert state is one where
+ * there are actual namespaces selected.
+ *
+ * This is done to compare states to previous ones that may have had the invert model
+ * selected but effectively had no namespaces, so are not effectively different than
+ * ones where invert is not selected.
+ *
+ * @return {boolean} Invert is effectively selected
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.areNamespacesEffectivelyInverted = function () {
+ return this.getInvertModel().isSelected() &&
+ this.getSelectedItems().some( function ( itemModel ) {
+ return itemModel.getGroupModel().getView() === 'namespace';
+ } );
+ };
+
/**
* Get the item that matches the given name
*
enable = enable === undefined ? !this.highlightEnabled : enable;
if ( this.highlightEnabled !== enable ) {
- // HACK make sure highlights are disabled globally while we toggle on the items,
- // otherwise we'll call clearHighlight() and applyHighlight() many many times
- this.highlightEnabled = false;
- this.getItems().forEach( function ( filterItem ) {
- filterItem.toggleHighlight( enable );
- } );
-
this.highlightEnabled = enable;
this.emit( 'highlightChange', this.highlightEnabled );
}
* Propagate the change to namespace filter items.
*
* @param {boolean} enable Inverted property is enabled
- * @fires invertChange
*/
mw.rcfilters.dm.FiltersViewModel.prototype.toggleInvertedNamespaces = function ( enable ) {
- enable = enable === undefined ? !this.invertedNamespaces : enable;
-
- if ( this.invertedNamespaces !== enable ) {
- this.invertedNamespaces = enable;
-
- this.getFiltersByView( 'namespaces' ).forEach( function ( filterItem ) {
- filterItem.toggleInverted( this.invertedNamespaces );
- }.bind( this ) );
-
- this.emit( 'invertChange', this.invertedNamespaces );
- }
+ this.toggleFilterSelected( this.getInvertModel().getName(), enable );
};
/**
- * Check if the namespaces selection is set to be inverted
- * @return {boolean}
+ * Get the model object that represents the 'invert' filter
+ *
+ * @return {mw.rcfilters.dm.FilterItem}
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.areNamespacesInverted = function () {
- return !!this.invertedNamespaces;
+ mw.rcfilters.dm.FiltersViewModel.prototype.getInvertModel = function () {
+ return this.getGroup( 'invertGroup' ).getItemByParamName( 'invert' );
};
/**
this.getItemByName( filterName ).clearHighlightColor();
};
- /**
- * Clear highlight for all filter items
- */
- mw.rcfilters.dm.FiltersViewModel.prototype.clearAllHighlightColors = function () {
- this.getItems().forEach( function ( filterItem ) {
- filterItem.clearHighlightColor();
- } );
- };
-
/**
* Return a version of the given string that is without any
* view triggers.