RCFilters: Query using current (not default) sticky parameters values
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FiltersViewModel.js
index 0d65466..f7c2aaf 100644 (file)
@@ -14,9 +14,9 @@
 
                this.groups = {};
                this.defaultParams = {};
-               this.defaultFiltersEmpty = null;
                this.highlightEnabled = false;
                this.parameterMap = {};
+               this.emptyParameterState = null;
 
                this.views = {};
                this.currentView = 'default';
                $.each( this.groups, function ( group, groupModel ) {
                        if (
                                groupModel.getType() === 'send_unselected_if_any' ||
-                               groupModel.getType() === 'boolean'
+                               groupModel.getType() === 'boolean' ||
+                               groupModel.getType() === 'any_value'
                        ) {
                                // Individual filters
                                groupModel.getItems().forEach( function ( filterItem ) {
 
                this.currentView = 'default';
 
-               if ( this.getHighlightedItems().length > 0 ) {
-                       this.toggleHighlight( true );
-               }
+               this.updateHighlightedState();
 
                // Finish initialization
                this.emit( 'initialize' );
        };
 
        /**
-        * Get the names of all available filters
+        * Update filter view model state based on a parameter object
+        *
+        * @param {Object} params Parameters object
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.updateStateFromParams = function ( params ) {
+               var filtersValue;
+               // 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 values
+               filtersValue = this.getFiltersFromParameters( params );
+               Object.keys( filtersValue ).forEach( function ( filterName ) {
+                       this.getItemByName( filterName ).setValue( filtersValue[ filterName ] );
+               }.bind( this ) );
+
+               // 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
+        *
+        * @return {Object} Full parameter representation
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getExpandedParamRepresentation = function () {
+               return $.extend(
+                       true,
+                       {},
+                       this.getEmptyParameterState(),
+                       this.getCurrentParameterState()
+               );
+       };
+
+       /**
+        * Get a parameter representation of the current state of the model
         *
-        * @return {string[]} An array of filter names
+        * @param {boolean} [removeStickyParams] Remove sticky filters from final result
+        * @return {Object} Parameter representation of the current state of the model
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.getFilterNames = function () {
-               return this.getItems().map( function ( item ) { return item.getName(); } );
+       mw.rcfilters.dm.FiltersViewModel.prototype.getCurrentParameterState = function ( removeStickyParams ) {
+               var state = this.getMinimizedParamRepresentation( $.extend(
+                       true,
+                       {},
+                       this.getParametersFromFilters( this.getSelectedState() ),
+                       this.getHighlightParameters()
+               ) );
+
+               if ( removeStickyParams ) {
+                       state = this.removeStickyParams( state );
+               }
+
+               return state;
+       };
+
+       /**
+        * Delete sticky parameters from given object.
+        *
+        * @param {Object} paramState Parameter state
+        * @return {Object} Parameter state without sticky parameters
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.removeStickyParams = function ( paramState ) {
+               this.getStickyParams().forEach( function ( paramName ) {
+                       delete paramState[ paramName ];
+               } );
+
+               return paramState;
+       };
+
+       /**
+        * Turn the highlight feature on or off
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.updateHighlightedState = function () {
+               this.toggleHighlight( this.getHighlightedItems().length > 0 );
        };
 
        /**
        mw.rcfilters.dm.FiltersViewModel.prototype.getViewTrigger = function ( view ) {
                return ( this.views[ view ] && this.views[ view ].trigger ) || '';
        };
+
        /**
         * Get the value of a specific parameter
         *
        /**
         * Get the current selected state of the filters
         *
+        * @param {boolean} [onlySelected] return an object containing only the filters with a value
         * @return {Object} Filters selected state
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.getSelectedState = function () {
+       mw.rcfilters.dm.FiltersViewModel.prototype.getSelectedState = function ( onlySelected ) {
                var i,
                        items = this.getItems(),
                        result = {};
 
                for ( i = 0; i < items.length; i++ ) {
-                       result[ items[ i ].getName() ] = items[ i ].isSelected();
+                       if ( !onlySelected || items[ i ].getValue() ) {
+                               result[ items[ i ].getName() ] = items[ i ].getValue();
+                       }
                }
 
                return result;
 
                // Get default filter state
                $.each( this.groups, function ( name, model ) {
-                       $.extend( true, result, model.getDefaultParams() );
+                       if ( !model.isSticky() ) {
+                               $.extend( true, result, model.getDefaultParams() );
+                       }
                } );
 
                return result;
         * @return {Object} Sticky parameter values
         */
        mw.rcfilters.dm.FiltersViewModel.prototype.getStickyParams = function () {
-               var result = {};
+               var result = [];
 
                $.each( this.groups, function ( name, model ) {
                        if ( model.isSticky() ) {
-                               $.extend( true, result, model.getDefaultParams() );
+                               if ( model.isPerGroupRequestParameter() ) {
+                                       result.push( name );
+                               } else {
+                                       // Each filter is its own param
+                                       result = result.concat( model.getItems().map( function ( filterItem ) {
+                                               return filterItem.getParamName();
+                                       } ) );
+                               }
                        }
                } );
 
        };
 
        /**
-        * Get a filter representation of all sticky parameters
+        * Get a parameter representation of all sticky parameters
         *
-        * @return {Object} Sticky filters values
+        * @return {Object} Sticky parameter values
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.getStickyFiltersState = function () {
+       mw.rcfilters.dm.FiltersViewModel.prototype.getStickyParamsValues = function () {
                var result = {};
 
                $.each( this.groups, function ( name, model ) {
                        if ( model.isSticky() ) {
-                               $.extend( true, result, model.getSelectedState() );
-                       }
-               } );
-
-               return result;
-       };
-
-       /**
-        * Get a filter representation of all parameters that are marked
-        * as being excluded from saved query.
-        *
-        * @return {Object} Excluded filters values
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.getExcludedFiltersState = function () {
-               var result = {};
-
-               $.each( this.groups, function ( name, model ) {
-                       if ( model.isExcludedFromSavedQueries() ) {
-                               $.extend( true, result, model.getSelectedState() );
-                       }
-               } );
-
-               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();
-                                       } ) );
-                               }
+                               $.extend( true, result, model.getParamRepresentation() );
                        }
                } );
 
                        // all filters (set to false)
                        this.getItems().forEach( function ( filterItem ) {
                                groupItemDefinition[ filterItem.getGroupName() ] = groupItemDefinition[ filterItem.getGroupName() ] || {};
-                               groupItemDefinition[ filterItem.getGroupName() ][ filterItem.getName() ] = !!filterDefinition[ filterItem.getName() ];
+                               groupItemDefinition[ filterItem.getGroupName() ][ filterItem.getName() ] = filterItem.coerceValue( filterDefinition[ filterItem.getName() ] );
                        } );
                }
 
         *                  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;
        };
                var result = {};
 
                this.getItems().forEach( function ( filterItem ) {
-                       result[ filterItem.getName() + '_color' ] = null;
-               } );
-               result.highlight = '0';
-
-               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.
-        *
-        * @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.
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.extractHighlightValues = function ( representation ) {
-               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 current filter state is set to all false.
+        * Check whether no visible filter is selected.
+        *
+        * Filter groups that are hidden or sticky are not shown in the
+        * active filters area and therefore not included in this check.
         *
-        * @return {boolean} Current filters are all empty
+        * @return {boolean} No visible filter is selected
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.areCurrentFiltersEmpty = function () {
+       mw.rcfilters.dm.FiltersViewModel.prototype.areVisibleFiltersEmpty = function () {
                // Check if there are either any selected items or any items
                // that have highlight enabled
                return !this.getItems().some( function ( filterItem ) {
-                       return !filterItem.getGroupModel().isHidden() && ( filterItem.isSelected() || filterItem.isHighlighted() );
+                       var visible = !filterItem.getGroupModel().isSticky() && !filterItem.getGroupModel().isHidden(),
+                               active = ( filterItem.isSelected() || filterItem.isHighlighted() );
+                       return visible && active;
                } );
        };
 
+       /**
+        * 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
         *
 
                return allSelected;
        };
+
        /**
         * Switch the current view
         *
                return this.views[ viewName ] && this.views[ viewName ].title;
        };
 
-       /**
-        * Get an array of all available view names
-        *
-        * @return {string} Available view names
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.getAvailableViews = function () {
-               return Object.keys( this.views );
-       };
-
        /**
         * Get the view that fits the given trigger
         *
                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 );
                }
                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.