/**
* Initialize the filter and parameter states
*
- * @param {Object} filterStructure Filter definition and structure for the model
+ * @param {Array} filterStructure Filter definition and structure for the model
*/
mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
- var uri = new mw.Uri();
-
+ var $changesList = $( '.mw-changeslist' ).first().contents();
// Initialize the model
this.filtersModel.initializeFilters( filterStructure );
+ this.updateStateBasedOnUrl();
+
+ // Update the changes list with the existing data
+ // so it gets processed
+ this.changesListModel.update(
+ $changesList.length ? $changesList : 'NO_RESULTS',
+ $( 'fieldset.rcoptions' ).first()
+ );
+
+ };
+
+ /**
+ * Update filter state (selection and highlighting) based
+ * on current URL and default values.
+ */
+ mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
+ var uri = new mw.Uri();
// Set filter states based on defaults and URL params
- this.filtersModel.updateFilters(
+ this.filtersModel.toggleFiltersSelected(
this.filtersModel.getFiltersFromParameters(
// Merge defaults with URL params for initialization
$.extend(
this.filtersModel.toggleHighlight( !!uri.query.highlight );
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = uri.query[ filterItem.getName() + '_color' ];
- if ( !color ) {
- return;
+ if ( color ) {
+ filterItem.setHighlightColor( color );
+ } else {
+ filterItem.clearHighlightColor();
}
-
- filterItem.setHighlightColor( color );
} );
// Check all filter interactions
*/
mw.rcfilters.Controller.prototype.resetToDefaults = function () {
this.filtersModel.setFiltersToDefaults();
+ this.filtersModel.clearAllHighlightColors();
// Check all filter interactions
this.filtersModel.reassessFilterInteractions();
- this.updateURL();
this.updateChangesList();
};
* Empty all selected filters
*/
mw.rcfilters.Controller.prototype.emptyFilters = function () {
+ var highlightedFilterNames = this.filtersModel
+ .getHighlightedItems()
+ .map( function ( filterItem ) { return { name: filterItem.getName() }; } );
+
this.filtersModel.emptyAllFilters();
this.filtersModel.clearAllHighlightColors();
// Check all filter interactions
this.filtersModel.reassessFilterInteractions();
- this.updateURL();
this.updateChangesList();
+
+ if ( highlightedFilterNames ) {
+ this.trackHighlight( 'clearAll', highlightedFilterNames );
+ }
};
/**
- * Update the state of a filter
+ * Update the selected state of a filter
*
* @param {string} filterName Filter name
- * @param {boolean} isSelected Filter selected state
+ * @param {boolean} [isSelected] Filter selected state
*/
- mw.rcfilters.Controller.prototype.updateFilter = function ( filterName, isSelected ) {
- var obj = {},
- filterItem = this.filtersModel.getItemByName( filterName );
+ mw.rcfilters.Controller.prototype.toggleFilterSelect = function ( filterName, isSelected ) {
+ var filterItem = this.filtersModel.getItemByName( filterName );
+
+ isSelected = isSelected === undefined ? !filterItem.isSelected() : isSelected;
if ( filterItem.isSelected() !== isSelected ) {
- obj[ filterName ] = isSelected;
- this.filtersModel.updateFilters( obj );
+ this.filtersModel.toggleFilterSelected( filterName, isSelected );
- this.updateURL();
this.updateChangesList();
// Check filter interactions
- this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
+ this.filtersModel.reassessFilterInteractions( filterItem );
}
};
/**
* Update the URL of the page to reflect current filters
+ *
+ * This should not be called directly from outside the controller.
+ * If an action requires changing the URL, it should either use the
+ * highlighting actions below, or call #updateChangesList which does
+ * the uri corrections already.
+ *
+ * @private
+ * @param {Object} [params] Extra parameters to add to the API call
*/
- mw.rcfilters.Controller.prototype.updateURL = function () {
- var uri = this.getUpdatedUri();
- window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
+ mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
+ var updatedUri,
+ notEquivalent = function ( obj1, obj2 ) {
+ var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
+ return keys.some( function ( key ) {
+ return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
+ } );
+ };
+
+ params = params || {};
+
+ updatedUri = this.getUpdatedUri();
+ updatedUri.extend( params );
+
+ if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
+ window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ }
};
/**
* Fetch the list of changes from the server for the current filters
*
* @return {jQuery.Promise} Promise object that will resolve with the changes list
+ * or with a string denoting no results.
*/
mw.rcfilters.Controller.prototype.fetchChangesList = function () {
var uri = this.getUpdatedUri(),
latestRequest = function () {
return requestId === this.requestCounter;
}.bind( this );
- uri.extend( this.filtersModel.getParametersFromFilters() );
+
return $.ajax( uri.toString(), { contentType: 'html' } )
- .then( function ( html ) {
- return latestRequest() ?
- $( $.parseHTML( html ) ).find( '.mw-changeslist' ).first().contents() :
- null;
- } ).then( null, function () {
- return latestRequest() ? 'NO_RESULTS' : null;
- } );
+ .then(
+ // Success
+ function ( html ) {
+ var $parsed;
+ if ( !latestRequest() ) {
+ return $.Deferred().reject();
+ }
+
+ $parsed = $( $.parseHTML( html ) );
+
+ return {
+ // Changes list
+ changes: $parsed.find( '.mw-changeslist' ).first().contents(),
+ // Fieldset
+ fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+ };
+ },
+ // Failure
+ function ( responseObj ) {
+ var $parsed;
+
+ if ( !latestRequest() ) {
+ return $.Deferred().reject();
+ }
+
+ $parsed = $( $.parseHTML( responseObj.responseText ) );
+
+ // Force a resolve state to this promise
+ return $.Deferred().resolve( {
+ changes: 'NO_RESULTS',
+ fieldset: $parsed.find( 'fieldset.rcoptions' ).first()
+ } ).promise();
+ }
+ );
};
/**
* Update the list of changes and notify the model
+ *
+ * @param {Object} [params] Extra parameters to add to the API call
*/
- mw.rcfilters.Controller.prototype.updateChangesList = function () {
+ mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) {
+ this.updateURL( params );
this.changesListModel.invalidate();
this.fetchChangesList()
- .always( function ( changesListContent ) {
- if ( changesListContent ) {
- this.changesListModel.update( changesListContent );
- }
- }.bind( this ) );
+ .then(
+ // Success
+ function ( pieces ) {
+ var $changesListContent = pieces.changes,
+ $fieldset = pieces.fieldset;
+ this.changesListModel.update( $changesListContent, $fieldset );
+ }.bind( this )
+ // Do nothing for failure
+ );
};
/**
mw.rcfilters.Controller.prototype.setHighlightColor = function ( filterName, color ) {
this.filtersModel.setHighlightColor( filterName, color );
this.updateURL();
+ this.trackHighlight( 'set', { name: filterName, color: color } );
};
/**
mw.rcfilters.Controller.prototype.clearHighlightColor = function ( filterName ) {
this.filtersModel.clearHighlightColor( filterName );
this.updateURL();
+ this.trackHighlight( 'clear', filterName );
+ };
+
+ /**
+ * Clear both highlight and selection of a filter
+ *
+ * @param {string} filterName Name of the filter item
+ */
+ mw.rcfilters.Controller.prototype.clearFilter = function ( filterName ) {
+ var filterItem = this.filtersModel.getItemByName( filterName ),
+ isHighlighted = filterItem.isHighlighted();
+
+ if ( filterItem.isSelected() || isHighlighted ) {
+ this.filtersModel.clearHighlightColor( filterName );
+ this.filtersModel.toggleFilterSelected( filterName, false );
+ this.updateChangesList();
+ this.filtersModel.reassessFilterInteractions( filterItem );
+ }
+
+ if ( isHighlighted ) {
+ this.trackHighlight( 'clear', filterName );
+ }
+ };
+
+ /**
+ * Synchronize the URL with the current state of the filters
+ * without adding an history entry.
+ */
+ mw.rcfilters.Controller.prototype.replaceUrl = function () {
+ window.history.replaceState(
+ { tag: 'rcfilters' },
+ document.title,
+ this.getUpdatedUri().toString()
+ );
+ };
+
+ /**
+ * Track usage of highlight feature
+ *
+ * @param {string} action
+ * @param {array|object|string} filters
+ */
+ mw.rcfilters.Controller.prototype.trackHighlight = function ( action, filters ) {
+ filters = $.type( filters ) === 'string' ? { name: filters } : filters;
+ filters = $.type( filters ) === 'object' ? [ filters ] : filters;
+ mw.track(
+ 'event.ChangesListHighlights',
+ {
+ action: action,
+ filters: filters
+ }
+ );
};
}( mediaWiki, jQuery ) );