Auto-cleanup des filtres après restauration URL
This commit is contained in:
@@ -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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
+30
-3
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user