Auto-cleanup des filtres après restauration URL

This commit is contained in:
Colin Maudry
2026-01-29 01:41:21 +01:00
parent e4f06acc56
commit 9d9533b8a2
3 changed files with 135 additions and 3 deletions
+105
View File
@@ -0,0 +1,105 @@
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
clean_filters: function (trigger) {
if (!trigger) {
return window.dash_clientside.no_update;
}
// Helper to set value on a React text input
const setNativeValue = (element, value) => {
const valueSetter = Object.getOwnPropertyDescriptor(
element,
"value"
).set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(
prototype,
"value"
).set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
element.dispatchEvent(new Event("input", { bubbles: true }));
};
const cleanInputs = () => {
const inputs = document.querySelectorAll(
'.dash-filter input[type="text"]'
);
inputs.forEach((input) => {
let val = input.value;
let original = val;
// Remove "icontains " prefix
if (/^icontains\s+/i.test(val)) {
val = val.replace(/^icontains\s+/i, "");
// Check for surrounding quotes (single or double) and remove them
if (
(val.startsWith('"') && val.endsWith('"')) ||
(val.startsWith("'") && val.endsWith("'"))
) {
val = val.substring(1, val.length - 1);
}
}
// Handle relational operators (i<, s>, i<=, etc.)
else if (/^[is][<>]=?/i.test(val)) {
val = val.substring(1);
}
if (val !== original) {
try {
// Try setting it the React-friendly way
setNativeValue(input, val);
} catch (e) {
// Fallback to direct assignment if fancy way fails
input.value = val;
}
}
});
};
// Use MutationObserver to wait for table to appear/update
const observer = new MutationObserver((mutations) => {
cleanInputs();
});
const target = document.querySelector(".dash-table-container");
if (target) {
observer.observe(target, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["value"],
});
// Disconnect after 5 seconds
setTimeout(() => {
observer.disconnect();
}, 5000);
// Also try immediately just in case
cleanInputs();
} else {
// Poll briefly if container not found yet
const checkInterval = setInterval(() => {
const t = document.querySelector(".dash-table-container");
if (t) {
clearInterval(checkInterval);
observer.observe(t, { childList: true, subtree: true });
setTimeout(() => observer.disconnect(), 5000);
cleanInputs();
}
}, 200);
// Stop polling after 2s if still nothing
setTimeout(() => clearInterval(checkInterval), 2000);
}
return window.dash_clientside.no_update;
},
},
});
View File
+30 -3
View File
@@ -1,10 +1,22 @@
import json import json
import os import os
import urllib.parse import urllib.parse
import uuid
from datetime import datetime from datetime import datetime
import polars as pl import polars as pl
from dash import Input, Output, State, callback, dcc, html, no_update, register_page from dash import (
ClientsideFunction,
Input,
Output,
State,
callback,
clientside_callback,
dcc,
html,
no_update,
register_page,
)
from src.figures import DataTable from src.figures import DataTable
from src.utils import ( from src.utils import (
@@ -50,6 +62,7 @@ datatable = html.Div(
layout = [ layout = [
dcc.Location(id="tableau_url", refresh=False), dcc.Location(id="tableau_url", refresh=False),
dcc.Store(id="filter-cleanup-trigger"),
html.Script( html.Script(
type="application/ld+json", type="application/ld+json",
id="dataset_jsonld", id="dataset_jsonld",
@@ -272,12 +285,13 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None):
Output("table", "sort_by"), Output("table", "sort_by"),
Output("table", "hidden_columns"), Output("table", "hidden_columns"),
Output("tableau_url", "search", allow_duplicate=True), Output("tableau_url", "search", allow_duplicate=True),
Output("filter-cleanup-trigger", "data"),
Input("tableau_url", "search"), Input("tableau_url", "search"),
prevent_initial_call=True, prevent_initial_call=True,
) )
def restore_view_from_url(search): def restore_view_from_url(search):
if not search: if not search:
return no_update, no_update, no_update, no_update return no_update, no_update, no_update, no_update, no_update
params = urllib.parse.parse_qs(search.lstrip("?")) params = urllib.parse.parse_qs(search.lstrip("?"))
logger.debug("params", params) logger.debug("params", params)
@@ -285,9 +299,11 @@ def restore_view_from_url(search):
filter_query = no_update filter_query = no_update
sort_by = no_update sort_by = no_update
hidden_columns = no_update hidden_columns = no_update
trigger_cleanup = no_update
if "filtres" in params: if "filtres" in params:
filter_query = params["filtres"][0] filter_query = params["filtres"][0]
trigger_cleanup = str(uuid.uuid4())
if "tris" in params: if "tris" in params:
try: try:
@@ -300,7 +316,18 @@ def restore_view_from_url(search):
verified_columns = [column for column in columns if column in schema.names()] verified_columns = [column for column in columns if column in schema.names()]
hidden_columns = invert_columns(verified_columns) hidden_columns = invert_columns(verified_columns)
return filter_query, sort_by, hidden_columns, "" return filter_query, sort_by, hidden_columns, "", trigger_cleanup
clientside_callback(
ClientsideFunction(
namespace="clientside",
function_name="clean_filters",
),
Output("filter-cleanup-trigger", "data", allow_duplicate=True),
Input("filter-cleanup-trigger", "data"),
prevent_initial_call=True,
)
@callback( @callback(