Custom DateTime Filter For AG-Grid

Hello all, I’m presently trying to use Dash AG-Grid for a use-case in my org. One of those is to have filters through DateTime format (“YYYY-MM-DD HH:mm”). Since, the pre-configured agDateColumnFilter doesn’t allow inputs through the aforementioned format, I’m now working on a custom script for the same. A snippet of it goes like this (I’m obscuring certain aspects of it to maintain confidentiality):

ABC.py:

# Dynamically generate columnDefs
    columnDefs = []
    for column in table_column_info:
        col_name = column["col_name"]
        data_type = column["data_type"]

        # Add the column definition
        column_def = {
            "field": col_name,
            "cellRenderer": "SpinnerCellRenderer",
            "sortable": True,
            "resizable": True,
            "headerClass": "abcd",
            "cellStyle": {"whiteSpace": "normal", "wordWrap": "break-word"},
            "floatingFilter": True,
        }

        # Determine the filter type based on the data type

        if "datetime" in data_type.lower():
            column_def["filter"] = "DateTimeFilter"
       
        elif data_type.lower() in ["string", "object"]:
            column_def["filter"] = "agTextColumnFilter"
        elif "int" or "float" or "double" in data_type.lower():
            column_def["filter"] = "agNumberColumnFilter"

        # if filter_type:
        #     column_def["filter"] = filter_type

        columnDefs.append(column_def)

    # Render the AgGrid component
    return dag.AgGrid(
        id="table-grid-view",
        columnDefs=columnDefs,
        defaultColDef={
            "flex": 1,
            "minWidth": 150,
            "sortable": True,
            "resizable": True,
        },
        rowModelType="infinite",  # Use infinite row model
        dashGridOptions={
            "rowBuffer": 0,
            "maxBlocksInCache": 2,
            "cacheBlockSize": 100,
            "cacheOverflowSize": 2,
            "maxConcurrentDatasourceRequests": 2,
            "infiniteInitialRowCount": 1,
            "pagination": True,
            "rowHeight": 30,
        },
    )

dashAgGridComponentFunctions.js:

var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};
// Custom Date-Time Picker Filter with Menu Options
const [useImperativeHandle, useState, useEffect, forwardRef] = [
    React.useImperativeHandle,
    React.useState,
    React.useEffect,
    React.forwardRef,
];

dagcomponentfuncs.DateTimeFilter = forwardRef((props, ref) => {
    const [filterType, setFilterType] = useState("equals"); // Default filter type
    const [dateFrom, setDateFrom] = useState(""); // Start date
    const [dateTo, setDateTo] = useState(""); // End date (for "inRange")

    // Expose AG Grid Filter Lifecycle callbacks
    useImperativeHandle(ref, () => ({
        doesFilterPass(params) {
            const cellValue = params.data[props.column.colId];
            if (!cellValue) return false;

            const cellDate = new Date(cellValue);
            const fromDate = dateFrom ? new Date(dateFrom) : null;
            const toDate = dateTo ? new Date(dateTo) : null;

            switch (filterType) {
                case "equals":
                    return fromDate && cellDate.getTime() === fromDate.getTime();
                case "notEqual":
                    return fromDate && cellDate.getTime() !== fromDate.getTime();
                case "lessThan":
                    return fromDate && cellDate < fromDate;
                case "greaterThan":
                    return fromDate && cellDate > fromDate;
                case "inRange":
                    return fromDate && toDate && cellDate >= fromDate && cellDate <= toDate;
                default:
                    return true;
            }
        },

        isFilterActive() {
            return !!dateFrom || !!dateTo;
        },

        getModel() {
            if (!this.isFilterActive()) {
                return null;
            }
            return {
                [props.column.colId]: { // Use the column ID as the key
                    type: filterType,
                    dateFrom: dateFrom,
                    dateTo: filterType === "inRange" ? dateTo : null,
                    filterType: "date",
                },
            };
        },

        setModel(model) {
            if (model && model[props.column.colId]) {
                const filterDetails = model[props.column.colId];
                setFilterType(filterDetails.type || "equals");
                setDateFrom(filterDetails.dateFrom || "");
                setDateTo(filterDetails.dateTo || "");
            } else {
                setFilterType("equals");
                setDateFrom("");
                setDateTo("");
            }
        },
    }));

    // Notify Ag-Grid when the filter changes
    useEffect(() => {
        props.filterChangedCallback();
    }, [filterType, dateFrom, dateTo]);

    // Handle changes to the filter type
    function onFilterTypeChange(event) {
        setFilterType(event.target.value);
    }

    // Handle changes to the "dateFrom" input
    function onDateFromChange(event) {
        setDateFrom(event.target.value);
    }

    // Handle changes to the "dateTo" input (only for "inRange")
    function onDateToChange(event) {
        setDateTo(event.target.value);
    }

    return React.createElement(
        "div",
        { style: { display: "flex", flexDirection: "column", padding: "5px" } },
        React.createElement(
            "select",
            {
                value: filterType,
                onChange: onFilterTypeChange,
                style: { marginBottom: "5px", padding: "5px", width: "100%" },
            },
            React.createElement("option", { value: "equals" }, "Equals"),
            React.createElement("option", { value: "notEqual" }, "Does not equal"),
            React.createElement("option", { value: "lessThan" }, "Before"),
            React.createElement("option", { value: "greaterThan" }, "After"),
            React.createElement("option", { value: "inRange" }, "Between")
        ),
        React.createElement("input", {
            type: "datetime-local", // HTML5 date-time picker
            value: dateFrom,
            onChange: onDateFromChange,
            style: { width: "100%", padding: "5px", marginBottom: "5px" },
        }),
        filterType === "inRange" &&
            React.createElement("input", {
                type: "datetime-local", // HTML5 date-time picker
                value: dateTo,
                onChange: onDateToChange,
                style: { width: "100%", padding: "5px" },
            })
    );
});

So now I’m getting the intended UI for DateTime filter. It is also getting triggered as soon as I start typing. However, I’m not able to get data from that input. For reference, I want my data to be parsed/captured like this for my filterModel : {'XXX_YY': {'dateFrom': '2025-04-02 00:00:00', 'dateTo': None, 'filterType': 'date', 'type': 'equals'}} where ‘XXX_YY’ is the name of column on which filter is being applied. Instead, at the moment, it just returns this: {}.

What mistake might I be committing here? I don’t possess much working knowledge of JavaScript, so am at my wit’s end to figure out this conundrum. Or is there an easier alternative to my need - something like extending the functionality of “agDateColumnFilter” to have date-time input (instead of only date input) and the rest UI as is?

I’m using dash_ag_grid==31.2.0, plotly==5.23.0 & Python 3.12.4 and operating in Chrome browser.

Hello @Priyanshu_Mohanty,

Welcome to the community!

There are a lot of moving parts here, is it possible to provide an minimal working example?

Something with some test data for us to be able to troubleshoot with.