Merge "RCFilters: refactor highlight state"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FiltersViewModel.js
index 1f9fd39..3a6efe2 100644 (file)
@@ -16,8 +16,8 @@
                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 default values of the filters are all false.
-        *
-        * @return {boolean} Default filters are all false
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.areDefaultFiltersEmpty = function () {
-               var defaultFilters;
-
-               if ( this.defaultFiltersEmpty === null ) {
-                       // We only need to do this test once,
-                       // because defaults are set once per session
-                       defaultFilters = this.getFiltersFromParameters( this.getDefaultParams() );
-                       this.defaultFiltersEmpty = $.isEmptyObject( defaultFilters ) || Object.keys( defaultFilters ).every( function ( filterName ) {
-                               return !defaultFilters[ filterName ];
-                       } );
-               }
-
-               return this.defaultFiltersEmpty;
-       };
-
        /**
         * Get the item that matches the given name
         *
 
                if ( this.highlightEnabled !== enable ) {
                        this.highlightEnabled = enable;
-
-                       this.getItems().forEach( function ( filterItem ) {
-                               filterItem.toggleHighlight( this.highlightEnabled );
-                       }.bind( this ) );
-
                        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' );
        };
 
        /**