this.changesListModel = changesListModel;
this.savedQueriesModel = savedQueriesModel;
this.requestCounter = 0;
- this.baseState = {};
+ this.baseFilterState = {};
+ this.emptyParameterState = {};
+ this.initializing = false;
};
/* Initialization */
* @param {Array} filterStructure Filter definition and structure for the model
*/
mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
- var parsedSavedQueries,
+ var parsedSavedQueries, validParameterNames,
+ uri = new mw.Uri(),
$changesList = $( '.mw-changeslist' ).first().contents();
+
// Initialize the model
this.filtersModel.initializeFilters( filterStructure );
this._buildBaseFilterState();
+ this._buildEmptyParameterState();
+ validParameterNames = Object.keys( this._getEmptyParameterState() )
+ .filter( function ( param ) {
+ // Remove 'highlight' parameter from this check;
+ // if it's the only parameter in the URL we still
+ // want to consider the URL 'empty' for defaults to load
+ return param !== 'highlight';
+ } );
try {
parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' );
// can normalize them per each query item
this.savedQueriesModel.initialize(
parsedSavedQueries,
- this._getBaseState()
+ this._getBaseFilterState()
);
- this.updateStateBasedOnUrl();
+ // Check whether we need to load defaults.
+ // We do this by checking whether the current URI query
+ // contains any parameters recognized by the system.
+ // If it does, we load the given state.
+ // If it doesn't, we have no values at all, and we assume
+ // the user loads the base-page and we load defaults.
+ // Defaults should only be applied on load (if necessary)
+ // or on request
+ if (
+ Object.keys( uri.query ).some( function ( parameter ) {
+ return validParameterNames.indexOf( parameter ) > -1;
+ } )
+ ) {
+ // There are parameters in the url, update model state
+ this.updateStateBasedOnUrl();
+ } else {
+ this.initializing = true;
+ // No valid parameters are given, load defaults
+ this._updateModelState(
+ $.extend(
+ true,
+ // We've ignored the highlight parameter for the sake
+ // of seeing whether we need to apply defaults - but
+ // while we do load the defaults, we still want to retain
+ // the actual value given in the URL for it on top of the
+ // defaults
+ { highlight: String( Number( uri.query.highlight ) ) },
+ this._getDefaultParams()
+ )
+ );
+ this.updateChangesList();
+ this.initializing = false;
+ }
// Update the changes list with the existing data
// so it gets processed
* Reset to default filters
*/
mw.rcfilters.Controller.prototype.resetToDefaults = function () {
- this._updateModelState( this._getDefaultParams() );
+ this._updateModelState( $.extend( true, { highlight: '0' }, this._getDefaultParams() ) );
this.updateChangesList();
};
highlightedItems[ item.getName() ] = highlightEnabled ?
item.getHighlightColor() : null;
} );
- // Stored as a string '0' or '1'
- highlightedItems.highlight = String( Number( this.filtersModel.isHighlightEnabled() ) );
+ // These are filter states; highlight is stored as boolean
+ highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
// Add item
this.savedQueriesModel.addNewQuery(
this.filtersModel.toggleFiltersSelected( data.filters );
// Update highlight state
- this.filtersModel.toggleHighlight( !!highlights.highlight );
+ this.filtersModel.toggleHighlight( !!Number( highlights.highlight ) );
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = highlights[ filterItem.getName() ];
if ( color ) {
} );
highlightedItems.highlight = false;
- this.baseState = {
+ this.baseFilterState = {
filters: this.filtersModel.getFiltersFromParameters( defaultParams ),
highlights: highlightedItems
};
};
/**
- * Get an object representing the base state of parameters
- * and highlights. The structure is similar to what we use
+ * Build an empty representation of the parameters, where all parameters
+ * are either set to '0' or '' depending on their type.
+ * This must run during initialization, before highlights are set.
+ */
+ mw.rcfilters.Controller.prototype._buildEmptyParameterState = function () {
+ var emptyParams = this.filtersModel.getParametersFromFilters( {} ),
+ emptyHighlights = this.filtersModel.getHighlightParameters();
+
+ this.emptyParameterState = $.extend(
+ true,
+ {},
+ emptyParams,
+ emptyHighlights,
+ { highlight: '0' }
+ );
+ };
+
+ /**
+ * Get an object representing the base filter state of both
+ * filters and highlights. The structure is similar to what we use
* to store each query in the saved queries object:
* {
* filters: {
* @return {Object} Object representing the base state of
* parameters and highlights
*/
- mw.rcfilters.Controller.prototype._getBaseState = function () {
- return this.baseState;
+ mw.rcfilters.Controller.prototype._getBaseFilterState = function () {
+ return this.baseFilterState;
+ };
+
+ /**
+ * Get an object representing the base state of parameters
+ * and highlights. The structure is similar to what we use
+ * to store each query in the saved queries object:
+ * {
+ * param1: "value",
+ * param2: "value1|value2"
+ * }
+ *
+ * @return {Object} Object representing the base state of
+ * parameters and highlights
+ */
+ mw.rcfilters.Controller.prototype._getEmptyParameterState = function () {
+ return this.emptyParameterState;
};
/**
*/
mw.rcfilters.Controller.prototype._getMinimalFilterList = function ( valuesObject ) {
var result = { filters: {}, highlights: {} },
- baseState = this._getBaseState();
+ baseState = this._getBaseFilterState();
// XOR results
$.each( valuesObject.filters, function ( name, value ) {
/**
* Update filter state (selection and highlighting) based
- * on current URL and default values.
+ * on current URL values.
*/
mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
- var uri = new mw.Uri(),
- defaultParams = this._getDefaultParams();
+ var uri = new mw.Uri();
- this._updateModelState( $.extend( {}, defaultParams, uri.query ) );
+ this._updateModelState( uri.query );
this.updateChangesList();
};
);
// Update highlight state
- this.filtersModel.toggleHighlight( !!parameters.highlight );
+ this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = parameters[ filterItem.getName() + '_color' ];
if ( color ) {
savedParams = this.filtersModel.getParametersFromFilters( data.filters || {} );
// Translate highlights to parameters
- savedHighlights.highlight = queryHighlights.highlight;
+ savedHighlights.highlight = String( Number( queryHighlights.highlight ) );
$.each( queryHighlights, function ( filterName, color ) {
if ( filterName !== 'highlights' ) {
savedHighlights[ filterName + '_color' ] = color;
* @param {Object} [params] Extra parameters to add to the API call
*/
mw.rcfilters.Controller.prototype._updateURL = function ( params ) {
- var updatedUri,
+ var currentFilterState, updatedFilterState, updatedUri,
+ uri = new mw.Uri(),
notEquivalent = function ( obj1, obj2 ) {
var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
return keys.some( function ( key ) {
updatedUri = this._getUpdatedUri();
updatedUri.extend( params );
- if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
- window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ // Compare states instead of parameters
+ // This will allow us to always have a proper check of whether
+ // the requested new url is one to change or not, regardless of
+ // actual parameter visibility/representation in the URL
+ currentFilterState = this.filtersModel.getFiltersFromParameters( uri.query );
+ updatedFilterState = this.filtersModel.getFiltersFromParameters( updatedUri.query );
+ // HACK: Re-merge extra parameters in
+ // This is a hack and a quickfix; a better, more sustainable
+ // fix is being worked on with a UriProcessor, but for now
+ // we need to make sure the **comparison** of whether currentFilterState
+ // and updatedFilterState differ **includes** the extra parameters in the URL
+ currentFilterState = $.extend( true, {}, uri.query, currentFilterState );
+ updatedFilterState = $.extend( true, {}, updatedUri.query, updatedFilterState );
+
+ // Include highlight states
+ $.extend( true,
+ currentFilterState,
+ this.filtersModel.extractHighlightValues( uri.query ),
+ { highlight: !!Number( uri.query.highlight ) }
+ );
+ $.extend( true,
+ updatedFilterState,
+ this.filtersModel.extractHighlightValues( updatedUri.query ),
+ { highlight: !!Number( updatedUri.query.highlight ) }
+ );
+
+ if ( notEquivalent( currentFilterState, updatedFilterState ) ) {
+ if ( this.initializing ) {
+ // Initially, when we just build the first page load
+ // out of defaults, we want to replace the history
+ window.history.replaceState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ } else {
+ window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ }
}
};
*/
mw.rcfilters.Controller.prototype._getUpdatedUri = function () {
var uri = new mw.Uri(),
- highlightParams = this.filtersModel.getHighlightParameters();
-
- // Add to existing queries in URL
- // TODO: Clean up the list of filters; perhaps 'falsy' filters
- // shouldn't appear at all? Or compare to existing query string
- // and see if current state of a specific filter is needed?
- uri.extend( this.filtersModel.getParametersFromFilters() );
+ highlightParams = this.filtersModel.getHighlightParameters(),
+ modelParameters = this.filtersModel.getParametersFromFilters(),
+ baseParams = this._getEmptyParameterState();
+
+ // Minimize values of the model parameters; show only the values that
+ // are non-zero. We assume that all parameters that are not literally
+ // showing in the URL are set to zero or empty
+ $.each( modelParameters, function ( paramName, value ) {
+ if ( baseParams[ paramName ] !== value ) {
+ uri.query[ paramName ] = value;
+ } else {
+ // We need to remove this value from the url
+ delete uri.query[ paramName ];
+ }
+ } );
// highlight params
- uri.query.highlight = Number( this.filtersModel.isHighlightEnabled() );
- Object.keys( highlightParams ).forEach( function ( paramName ) {
- // Always have some value (either the color or null) so that
- // if we have something in the URL that doesn't have the highlight
- // intentionally, it can override default with highlight.
- // Otherwise, the $.extend will always add the highlight that
- // exists in the default even if the URL query that is being
- // refreshed has different highlights, or has highlights enabled
- // but no active highlights anywhere
- uri.query[ paramName ] = highlightParams[ paramName ] ?
- highlightParams[ paramName ] : null;
+ if ( this.filtersModel.isHighlightEnabled() ) {
+ uri.query.highlight = Number( this.filtersModel.isHighlightEnabled() );
+ } else {
+ delete uri.query.highlight;
+ }
+ $.each( highlightParams, function ( paramName, value ) {
+ // Only output if it is different than the base parameters
+ if ( baseParams[ paramName ] !== value ) {
+ uri.query[ paramName ] = value;
+ } else {
+ delete uri.query[ paramName ];
+ }
} );
return uri;