Framework:Javascript Data GridAngular Data GridReact Data GridVue Data Grid

React Data Grid: Floating Filter Component

Floating Filter components allow you to add your own floating filter types to AG Grid. Use them:

  • When the provided floating filter for a provided filter does not meet your requirements and you want to replace it with one of your own
  • When you have a custom filter and want to provide a floating filter for your custom filter

This page focuses on writing your own floating filter components. To see general information about floating filters in AG Grid see floating filters.

Simple Floating Filter

Below is a simple example of filter component as a Hook:

export default forwardRef((props, ref) => {
   const [currentValue, setCurrentValue] = useState(null);
   const inputRef = useRef(null);

   // expose AG Grid Filter Lifecycle callbacks
   useImperativeHandle(ref, () => {
       return {
           onParentModelChanged(parentModel) {
               // When the filter is empty we will receive a null value here
               if (!parentModel) {
                   inputRef.current.value = '';
                   setCurrentValue(null);
               } else {
                   inputRef.current.value = parentModel.filter + '';
                   setCurrentValue(parentModel.filter);
               }
           }

       }
   });


   const onInputBoxChanged = input => {
       if (input.target.value === '') {
           // clear the filter
           props.parentFilterInstance(instance => {
               instance.onFloatingFilterChanged(null, null);
           });
           return;
       }

       setCurrentValue(Number(input.target.value));
       props.parentFilterInstance(instance => {
           instance.onFloatingFilterChanged('greaterThan', input.target.value);
       });
   }

   const style = {
       color: props.color,
       width: "30px"
   };

   return (
       <Fragment>
           &gt; <input ref={inputRef} style={style} type="number" min="0" onInput={onInputBoxChanged}/>
       </Fragment>
   );
});

And here is the same example as a Class-based Component:

export default class NumberFloatingFilterComponent extends Component {
   constructor(props) {
       super(props);

       this.state = {
           currentValue: null
       }

       this.inputRef = createRef();
   }

   onParentModelChanged(parentModel) {
       // When the filter is empty we will receive a null value here
       if (!parentModel) {
           this.inputRef.current.value = '';
           this.setState({currentValue: null});
       } else {
           this.inputRef.current.value = parentModel.filter + '';
           this.setState({currentValue: parentModel.filter});
       }
   }

   onInputBoxChanged = input => {
       if (input.target.value === '') {
           // clear the filter
           this.props.parentFilterInstance(instance => {
               instance.onFloatingFilterChanged(null, null);
           });
           return;
       }

       this.setState({currentValue: Number(input.target.value)});
       this.props.parentFilterInstance(instance => {
           instance.onFloatingFilterChanged('greaterThan', input.target.value);
       });
   }

   render() {
       const style = {
           color: this.props.color,
           width: "30px"
       };

       return (
           <Fragment>
               &gt; <input ref={this.inputRef} style={style} type="number" min="0" onInput={this.onInputBoxChanged}/>
           </Fragment>
       );
   }
}

Example: Custom Floating Filter

In the following example you can see how the Gold, Silver, Bronze and Total columns have a custom floating filter NumberFloatingFilter. This filter substitutes the standard floating filter for a input box that the user can change to adjust how many medals of each column to filter by based on a greater than filter.

Since this example is using standard filters, the object that needs to be passed to the method onParentFilterChanged() needs to provide two properties:

  • apply: If true the filter is changed AND applied, otherwise if it is false, the filter is only changed. However, this is ignored unless buttons contains 'apply' (i.e. it is ignored unless the Apply button is being used).
  • model: The model object that represents the new filter state.

If the user removes the content of the input box, the filter is removed.

Note that in this example:

  1. The columns with the floating filter are using the standard Number filter as the base filter
  2. Since the parent filter is the Number filter, the floating filter methods onFloatingFilterChanged(parentModel), and currentParentModel() take and receive model objects that correspond to the model for the Number filter
  3. Since these floating filters are providing a subset of the functionality of their parent filter, which can filter for other conditions which are not 'greaterThan', the user is prevented from seeing the parent filter by adding suppressFilterButton: true in the floatingFilterComponentParams and suppressMenu: true in the colDef
  4. floatingFilterComponentParams for all the medal columns have an additional param that is used to customise the font colour of the floating filter input text box.

Custom Floating Filter Interface

The interface for a custom filter component is as follows:

interface {
   // Gets called every time the parent filter changes. Your floating
   // filter would typically refresh its UI to reflect the new filter
   // state. The provided parentModel is what the parent filter returns
   // from its getModel() method. The event is the FilterChangedEvent
   // that the grid fires.
   onParentModelChanged(parentModel: any, event: FilterChangedEvent): void;

}

Note that if you're using Hooks for Grid Components that have lifecycle/callbacks that the grid will call (for example, the onParentModelChanged callback from an Editor Component), then you'll need to expose them with forwardRef & useImperativeHandle.

Please refer to the Hook documentation (or the examples on this page) for more information.

Custom Filter Parameters

When a React component is instantiated the grid will make the grid APIs, a number of utility methods as well as the cell & row values available to you via props - the interface for what is provided is documented below.

If the user provides params via the colDef.floatingFilterComponentParams attribute, these will be additionally added to the params object, overriding items of the same name if a name clash exists.

Properties available on the IFloatingFilterParams interface.

column
The column this filter is for.
filterParams
IFilterParams
The params object passed to the filter. This is to allow the floating filter access to the configuration of the parent filter. For example, the provided filters use debounceMs from the parent filter params.
filterParams: IFilterParams;

interface IFilterParams {
  // The column this filter is for. 
  column: Column;
  // The column definition for the column. 
  colDef: ColDef;
  // The row model, helpful for looking up data values if needed.
  // If the filter needs to know which rows are
  // a) in the table,
  // b) currently visible (i.e. not already filtered),
  // c) which groups,
  // d) what order - all of this can be read from the rowModel. 
  rowModel: IRowModel;
  // A function callback to be called when the filter changes. The
  // grid will then respond by filtering the grid data. The callback
  // takes one optional parameter which, if included, will get merged
  // to the FilterChangedEvent object (useful for passing additional
  // information to anyone listening to this event, however such extra
  // attributes are not used by the grid). 
  filterChangedCallback: (additionalEventAttributes?: any) => void;
  // A function callback, to be optionally called, when the filter UI changes.
  // The grid will respond with emitting a FilterModifiedEvent.
  // Apart from emitting the event, the grid takes no further action. 
  filterModifiedCallback: () => void;
  // A function callback for the filter to get cell values from the row data.
  // Call with a node to be given the value for that filter's column for that node.
  // The callback takes care of selecting the right column definition and deciding whether to use valueGetter or field etc.
  // This is useful in, for example, creating an Excel style filter,
  // where the filter needs to lookup available values to allow the user to select from. 
  valueGetter: (rowNode: RowNode) => any;
  // A function callback, call with a node to be told whether the node passes all filters except the current filter.
  // This is useful if you want to only present to the user values that this filter can filter given the status of the other filters.
  // The set filter uses this to remove from the list,
  // items that are no longer available due to the state of other filters (like Excel type filtering). 
  doesRowPassOtherFilter: (rowNode: RowNode) => boolean;
  api: GridApi;
  columnApi: ColumnApi;
  // The context as provided on `gridOptions.context` 
  context: any;
}

interface IRowModel {
  // Returns the rowNode at the given index. 
  getRow(index: number): RowNode | undefined;
  // Returns the rowNode for given id. 
  getRowNode(id: string): RowNode | undefined;
  // This is legacy, not used by AG Grid, but keeping for backward compatibility 
  getRowCount(): number;
  getTopLevelRowCount(): number;
  getTopLevelRowDisplayedIndex(topLevelIndex: number): number;
  // Returns the row index at the given pixel 
  getRowIndexAtPixel(pixel: number): number;
  // Returns true if the provided rowNode is in the list of rows to render 
  isRowPresent(rowNode: RowNode): boolean;
  // Returns row top and bottom for a given row 
  getRowBounds(index: number): RowBounds | null;
  // Returns true if this model has no rows, regardless of model filter. EG if rows present, but filtered
  // out, this still returns false. If it returns true, then the grid shows the 'no rows' overlay - but we
  // don't show that overlay if the rows are just filtered out. 
  isEmpty(): boolean;
  // Returns true if no rows (either no rows at all, or the rows are filtered out). This is what the grid
  // uses to know if there are rows to render or not. 
  isRowsToRender(): boolean;
  // Returns all rows in range that should be selected. If there is a gap in range (non ClientSideRowModel) then
  // then no rows should be returned 
  getNodesInRangeForSelection(first: RowNode, last: RowNode | null): RowNode[];
  // Iterate through each node. What this does depends on the model type. For clientSide, goes through
  // all nodes. For serverSide, goes through what's loaded in memory. 
  forEachNode(callback: (rowNode: RowNode, index: number) => void): void;
  // The base class returns the type. We use this instead of 'instanceof' as the client might provide
  // their own implementation of the models in the future. 
  getType(): string;
  // It tells us if this row model knows about the last row that it can produce. This is used by the
  // PaginationPanel, if last row is not found, then the 'last' button is disabled and the last page is
  // not shown. This is always true for ClientSideRowModel. It toggles for InfiniteRowModel. 
  isLastRowIndexKnown(): boolean;
  // Used by CSRM only - is makes sure there are now estimated row heights within the range. 
  ensureRowHeightsValid(startPixel: number, endPixel: number, startLimitIndex: number, endLimitIndex: number): boolean;
  // Gets called after grid is initialised. What happens depends on row model. Client Side will take rowData
  // from gridOptions, the other row models will start calling their datasources. 
  start(): void;
}

interface RowBounds {
  rowTop: number;
  rowHeight: number;
  rowIndex?: number;
}
suppressFilterButton
boolean
Boolean flag to indicate if the button in the floating filter that opens the parent filter in a popup should be displayed.
api
GridApi
currentParentModel
Function
This is a shortcut to invoke getModel on the parent filter. If the parent filter doesn't exist (filters are lazily created as needed) then it returns null rather than calling getModel() on the parent filter.
currentParentModel = () => any;
parentFilterInstance
Function
Gets a reference to the parent filter. The result is returned asynchronously via a callback as the parent filter may not exist yet. If it does not exist, it is created and asynchronously returned (AG Grid itself does not create components asynchronously, however if providing a framework provided filter e.g. React, it might be). The floating filter can then call any method it likes on the parent filter. The parent filter will typically provide its own method for the floating filter to call to set the filter. For example, if creating custom filter A, it should have a method your floating A can call to set the state when the user updates via the floating filter.
parentFilterInstance = (
    callback: (filterInstance: IFilterComp) => void
) => void;
showParentFilter
Function
Shows the parent filter popup.
showParentFilter = () => void;

Floating Filter Lifecycle

Floating filters do not contain filter state themselves, but show the state of the actual underlying filter. Floating filters are just another view for the main filter. For this reason, the floating filters lifecycle is bound to the visibility of the column; if you hide a column (either set not visible, or horizontally scroll the column out of view) then the floating filter UI component is destroyed. If the column comes back into view, it is created again. This is different to column filters, where the column filter will exist as long as the column exists, regardless of the column's visibility.

For details on how the floating filter interacts with its associated column filter, see the methods getModelAsString() and onFloatingFilterChanged(change) in the filter component interface.

To see examples of the different ways to implement floating filters please refer to the examples below.

Floating Filter Methods on Provided Filters

When the user interacts with a floating filter, the floating filter must set the state of the main parent filter in order for filter changes to take effect. This is done by the floating filter getting a reference to the parent filter instance and calling a method on it.

If you create your own filter and floating filter, it is up to you which method you expose on the filter for the floating filter to call. This contract is between the filter and the floating filter and doesn't go through the grid.

The simple provided filters (Text, Number, Date) provide methods that the corresponding provided floating filters can call. This information is useful if a) you want to create your own floating filter that is paired with a provided parent filter or b) you are just interested to know how the interaction works to help build your own filters and floating filters.

  • Date, Text and Number Filters: all these filters provide a method onFloatingFilterChanged(type: string, value: string) where type is the type ('lessThan', 'equals', etc.) and the value is the text value to use (the number and date filters will convert the text to the corresponding type).
  • Set Filter: The floating set filter is not editable, so no method is exposed on the parent filter for the floating filter to call.

You could also call setModel() on the filters as an alternative. For example, you could build your own floating filter for the Set Filter that allows picking all European or Asian countries, or you could provide your own Number floating filter that allows selecting ranges (the provided Number floating filter does not allow editing ranges).

Example: Custom Filter And Custom Floating Filter

This example extends the previous example by also providing its own custom filter NumberFilter in the Gold, Silver, Bronze and Total columns.

In this example it is important to note that:

  1. NumberFilter.getModel() returns a number representing the current greater than filter.
  2. NumberFilter.setModel(model) takes an object that can be of any type. If the value passed is numeric then the filter gets applied with a condition of greater than.
  3. NumberFloatingFilter.onParentModelChanged(parentModel) receives the result of NumberFilter.getModel() every time the NumberFilter model changes
  4. NumberFloatingFilter calls params.onFloatingFilterChanged(modelToAccept) every time the user changes the slider value. This will cause an automatic call into NumberFilter.setModel(modelToAccept)
  5. Since NumberFilter.onFloatingFilterChanged(change) is not implemented, every time the user changes the input value the filter gets updated automatically. If this method was implemented it would get called every time the floating filter would change, and would be responsible for performing the filtering.

Example: Custom Filter And Read-Only Floating Filter

If you want to provide a custom filter but don't want to provide an equivalent custom floating filter, you can implement the method filter.getModelAsString() and you will get a read-only floating filter for free.

This example uses the previous custom filter but implementing the getModelAsString() method. Note how there are no custom floating filters and yet each column using NumberFilter (Gold, Silver, Bronze and Total) has a read-only floating filter that gets updated as you change the values from the main filter.

Sliding Floating Filters

The below example shows how to create a custom floating filter re-using the out-of-the-box Number filter with React.

Note that in this example we make use of useImperativeHandle for lifecycle methods - please see here for more information.

Complex example with jQuery

The following example illustrates a complex scenario. All the columns have floating filters. The first 6 columns (Athlete to Sport) have the standard provided floating filters. The last 4 (Gold to Total) have custom filters and custom floating filters that use jQuery sliders.

Note that:

  • Athlete has a debounce of 2 seconds (debounceMs: 2000)
  • Age has no debounce (debounceMs: 0)
  • All the other columns have the standard 500ms debounce