@yuneta/gobj-ui 7.23.48 → 7.23.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gobj-ui.cjs.js +173 -8
- package/dist/gobj-ui.es.js +173 -8
- package/package.json +1 -1
- package/src/c_yui_treedb_topic_with_form.js +29 -12
- package/src/yui_row_search.js +96 -0
- package/src/yui_row_search.test.js +62 -0
- package/src/yui_table_filter_clear.css +37 -0
- package/src/yui_table_filter_clear.js +151 -0
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -22558,6 +22558,172 @@ function yui_selection_bar(t, opts) {
|
|
|
22558
22558
|
};
|
|
22559
22559
|
}
|
|
22560
22560
|
//#endregion
|
|
22561
|
+
//#region src/yui_table_filter_clear.js
|
|
22562
|
+
/***********************************************************************
|
|
22563
|
+
* yui_table_filter_clear.js
|
|
22564
|
+
*
|
|
22565
|
+
* LA ✕ DE LOS FILTROS DE CABECERA DE UNA TABULATOR.
|
|
22566
|
+
*
|
|
22567
|
+
* Un filtro de columna se pone escribiendo y se quita BORRANDO lo
|
|
22568
|
+
* escrito, letra a letra, y no hay nada en la cabecera que diga
|
|
22569
|
+
* que se puede quitar. Con varias columnas filtradas, volver a
|
|
22570
|
+
* ver la tabla entera es un ejercicio de memoria: cuáles toqué.
|
|
22571
|
+
* La ✕ lo dice y lo hace.
|
|
22572
|
+
*
|
|
22573
|
+
* TRES DECISIONES QUE NO SON OBVIAS:
|
|
22574
|
+
*
|
|
22575
|
+
* - **Se ve mientras el filtro tenga contenido, con foco o sin
|
|
22576
|
+
* él.** Es al revés que la ✕ de un formulario
|
|
22577
|
+
* (`yui_inputs.js`), que sólo aparece en el campo que se está
|
|
22578
|
+
* editando para no encender un aspa en cada campo relleno. Aquí
|
|
22579
|
+
* el caso de uso es justo el contrario: el filtro ya está puesto,
|
|
22580
|
+
* el foco está en otra parte, y lo que se quiere es verlo y
|
|
22581
|
+
* quitarlo. Escondida tras el foco harían falta dos clics.
|
|
22582
|
+
*
|
|
22583
|
+
* - **Se borra con la API, no simulando teclas.**
|
|
22584
|
+
* `setHeaderFilterValue(field, "")` es lo que Tabulator ofrece, y
|
|
22585
|
+
* deja el valor y el filtro consistentes. Sintetizar un `input`
|
|
22586
|
+
* sobre el `<input>` depende de a qué escuche el editor de ese
|
|
22587
|
+
* filtro, que cambia con el tipo de columna.
|
|
22588
|
+
*
|
|
22589
|
+
* - **Se recorre la cabecera, no se envuelve el editor.** Las
|
|
22590
|
+
* columnas se construyen del esquema del topic, así que no hay un
|
|
22591
|
+
* sitio único donde envolver el editor; y la cabecera se rehace
|
|
22592
|
+
* sola cuando cambian las columnas. El recorrido es idempotente
|
|
22593
|
+
* -- marca lo que ya tiene aspa -- y se dispara con un
|
|
22594
|
+
* `querySelector` de una sola pasada, así que repetirlo en cada
|
|
22595
|
+
* render no cuesta nada.
|
|
22596
|
+
*
|
|
22597
|
+
* Copyright (c) 2026, ArtGins.
|
|
22598
|
+
* All Rights Reserved.
|
|
22599
|
+
***********************************************************************/
|
|
22600
|
+
var MARK = "data-yui-filter-clear";
|
|
22601
|
+
/***************************************************************
|
|
22602
|
+
* Pon (o repón) la ✕ de cada filtro de cabecera de `table`.
|
|
22603
|
+
*
|
|
22604
|
+
* Idempotente: los que ya la tienen se saltan.
|
|
22605
|
+
***************************************************************/
|
|
22606
|
+
function decorate(table) {
|
|
22607
|
+
if (!table || !table.element) return;
|
|
22608
|
+
if (!table.element.querySelector(`.tabulator-header-filter:not([${MARK}])`)) return;
|
|
22609
|
+
let list = table.element.querySelectorAll(`.tabulator-header-filter:not([${MARK}])`);
|
|
22610
|
+
for (const $filter of list) {
|
|
22611
|
+
$filter.setAttribute(MARK, "1");
|
|
22612
|
+
let $input = $filter.querySelector("input");
|
|
22613
|
+
if (!$input) continue;
|
|
22614
|
+
let $col = $filter.closest(".tabulator-col");
|
|
22615
|
+
let field = $col ? $col.getAttribute("tabulator-field") : "";
|
|
22616
|
+
if (!field) continue;
|
|
22617
|
+
$filter.classList.add("yui-filter-has-clear");
|
|
22618
|
+
let $btn = (0, _yuneta_gobj_js.createElement2)(["button", {
|
|
22619
|
+
type: "button",
|
|
22620
|
+
class: "delete is-small yui-filter-clear",
|
|
22621
|
+
tabindex: "-1",
|
|
22622
|
+
title: i18next.default.t("clear"),
|
|
22623
|
+
"aria-label": i18next.default.t("clear"),
|
|
22624
|
+
"data-i18n-title": "clear",
|
|
22625
|
+
"data-i18n-aria-label": "clear"
|
|
22626
|
+
}]);
|
|
22627
|
+
const sync = () => {
|
|
22628
|
+
$btn.classList.toggle("is-visible", !!$input.value);
|
|
22629
|
+
};
|
|
22630
|
+
$input.addEventListener("input", sync);
|
|
22631
|
+
$input.addEventListener("change", sync);
|
|
22632
|
+
$btn.addEventListener("click", (event) => {
|
|
22633
|
+
event.stopPropagation();
|
|
22634
|
+
table.setHeaderFilterValue(field, "");
|
|
22635
|
+
sync();
|
|
22636
|
+
});
|
|
22637
|
+
$filter.appendChild($btn);
|
|
22638
|
+
sync();
|
|
22639
|
+
}
|
|
22640
|
+
}
|
|
22641
|
+
/***************************************************************
|
|
22642
|
+
* Engancha la ✕ a una tabla ya creada.
|
|
22643
|
+
*
|
|
22644
|
+
* Se llama una vez, con la tabla construida. Los enganches
|
|
22645
|
+
* cubren los dos momentos en que la cabecera aparece o se
|
|
22646
|
+
* rehace: cuando se construye y cuando cambian las columnas.
|
|
22647
|
+
***************************************************************/
|
|
22648
|
+
function yui_table_filter_clear(table) {
|
|
22649
|
+
if (!table) return;
|
|
22650
|
+
decorate(table);
|
|
22651
|
+
table.on("renderComplete", () => {
|
|
22652
|
+
decorate(table);
|
|
22653
|
+
});
|
|
22654
|
+
table.on("columnVisibilityChanged", () => {
|
|
22655
|
+
decorate(table);
|
|
22656
|
+
});
|
|
22657
|
+
}
|
|
22658
|
+
//#endregion
|
|
22659
|
+
//#region src/yui_row_search.js
|
|
22660
|
+
/***********************************************************************
|
|
22661
|
+
* yui_row_search.js
|
|
22662
|
+
*
|
|
22663
|
+
* ¿ESTÁ ESTE TÉRMINO EN ESTA FILA?
|
|
22664
|
+
*
|
|
22665
|
+
* La caja de búsqueda de una tabla de treedb miraba los valores de
|
|
22666
|
+
* la fila con `String(val)`, y una fila de treedb no es plana: un
|
|
22667
|
+
* fkey llega como una LISTA DE OBJETOS
|
|
22668
|
+
* `[{id, topic_name, hook_name}]`, y `String()` de eso es
|
|
22669
|
+
* `"[object Object]"`. O sea que buscar el taller de un equipo --
|
|
22670
|
+
* que es donde vive el dato que un operador tiene en la cabeza --
|
|
22671
|
+
* no encontraba nunca nada, y la caja no daba ninguna pista de que
|
|
22672
|
+
* había mirado en otro sitio.
|
|
22673
|
+
*
|
|
22674
|
+
* DOS REGLAS, Y LA SEGUNDA ES LA QUE HACE QUE SIRVA:
|
|
22675
|
+
*
|
|
22676
|
+
* - Se baja por listas y objetos hasta una profundidad corta: un
|
|
22677
|
+
* fkey está a dos niveles y nada de lo que se busca está más
|
|
22678
|
+
* hondo.
|
|
22679
|
+
* - **De un fkey se mira sólo el `id`.** `topic_name` y
|
|
22680
|
+
* `hook_name` son las MISMAS dos palabras en todas las filas, así
|
|
22681
|
+
* que mirarlas convierte el término en un comodín: buscar
|
|
22682
|
+
* "devices" traería el topic entero. El `id` es lo único que
|
|
22683
|
+
* nombra la cosa enlazada.
|
|
22684
|
+
*
|
|
22685
|
+
* Las claves que empiezan por `_` no se miran en ningún nivel: son
|
|
22686
|
+
* del armazón (`_check_box_state_`, `_operation`) o metadatos
|
|
22687
|
+
* (`__md_treedb__`), y nadie busca por ellas.
|
|
22688
|
+
*
|
|
22689
|
+
* Copyright (c) 2026, ArtGins.
|
|
22690
|
+
* All Rights Reserved.
|
|
22691
|
+
***********************************************************************/
|
|
22692
|
+
var MAX_DEPTH = 4;
|
|
22693
|
+
/***************************************************************
|
|
22694
|
+
* Un fkey, tal y como `nodes` lo contesta con `list_dict`.
|
|
22695
|
+
***************************************************************/
|
|
22696
|
+
function is_fkey_ref(value) {
|
|
22697
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value && "hook_name" in value;
|
|
22698
|
+
}
|
|
22699
|
+
/***************************************************************
|
|
22700
|
+
* ¿Contiene `value` el término? El término viene YA en
|
|
22701
|
+
* minúsculas: quien busca lo baja una vez y no una por celda.
|
|
22702
|
+
***************************************************************/
|
|
22703
|
+
function value_matches(value, term, depth) {
|
|
22704
|
+
if (value === null || value === void 0) return false;
|
|
22705
|
+
if (depth > MAX_DEPTH) return false;
|
|
22706
|
+
if (Array.isArray(value)) return value.some((v) => value_matches(v, term, depth + 1));
|
|
22707
|
+
if (typeof value === "object") {
|
|
22708
|
+
if (is_fkey_ref(value)) return value_matches(value.id, term, depth + 1);
|
|
22709
|
+
return Object.entries(value).some(([key, v]) => {
|
|
22710
|
+
if (key.startsWith("_")) return false;
|
|
22711
|
+
return value_matches(v, term, depth + 1);
|
|
22712
|
+
});
|
|
22713
|
+
}
|
|
22714
|
+
return String(value).toLowerCase().includes(term);
|
|
22715
|
+
}
|
|
22716
|
+
/***************************************************************
|
|
22717
|
+
* ¿Está el término en alguna parte de esta fila?
|
|
22718
|
+
***************************************************************/
|
|
22719
|
+
function row_matches(row, term) {
|
|
22720
|
+
if (!row || typeof row !== "object" || !term) return false;
|
|
22721
|
+
return Object.entries(row).some(([key, value]) => {
|
|
22722
|
+
if (key.startsWith("_")) return false;
|
|
22723
|
+
return value_matches(value, term, 0);
|
|
22724
|
+
});
|
|
22725
|
+
}
|
|
22726
|
+
//#endregion
|
|
22561
22727
|
//#region src/treedb_write_plan.js
|
|
22562
22728
|
/***********************************************************************
|
|
22563
22729
|
* treedb_write_plan.js
|
|
@@ -23631,7 +23797,8 @@ function create_tabulator(gobj) {
|
|
|
23631
23797
|
50,
|
|
23632
23798
|
100,
|
|
23633
23799
|
200,
|
|
23634
|
-
500
|
|
23800
|
+
500,
|
|
23801
|
+
true
|
|
23635
23802
|
],
|
|
23636
23803
|
ajaxURL: "nodes",
|
|
23637
23804
|
ajaxRequestFunc: function(url, config, params) {
|
|
@@ -23685,6 +23852,7 @@ function create_tabulator(gobj) {
|
|
|
23685
23852
|
tabulator.on("tableBuilt", function() {
|
|
23686
23853
|
tabulator._ready = true;
|
|
23687
23854
|
update_rowcount();
|
|
23855
|
+
yui_table_filter_clear(tabulator);
|
|
23688
23856
|
if (tabulator._pendingData !== void 0) {
|
|
23689
23857
|
tabulator.setData(tabulator._pendingData);
|
|
23690
23858
|
delete tabulator._pendingData;
|
|
@@ -24999,11 +25167,12 @@ function request_page(gobj, page, size) {
|
|
|
24999
25167
|
reject,
|
|
25000
25168
|
timer
|
|
25001
25169
|
};
|
|
25170
|
+
const all = size === true;
|
|
25002
25171
|
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_REQUEST_PAGE", {
|
|
25003
25172
|
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
25004
25173
|
req_id,
|
|
25005
|
-
from: (page - 1) * size + 1,
|
|
25006
|
-
limit: size
|
|
25174
|
+
from: all ? 1 : (page - 1) * size + 1,
|
|
25175
|
+
limit: all ? 0 : size
|
|
25007
25176
|
});
|
|
25008
25177
|
});
|
|
25009
25178
|
}
|
|
@@ -25085,11 +25254,7 @@ function ac_search(gobj, event, kw, src) {
|
|
|
25085
25254
|
return 0;
|
|
25086
25255
|
}
|
|
25087
25256
|
tabulator.setFilter(function(data) {
|
|
25088
|
-
return
|
|
25089
|
-
if (key.startsWith("_")) return false;
|
|
25090
|
-
if (val === null || val === void 0) return false;
|
|
25091
|
-
return String(val).toLowerCase().includes(term);
|
|
25092
|
-
});
|
|
25257
|
+
return row_matches(data, term);
|
|
25093
25258
|
});
|
|
25094
25259
|
return 0;
|
|
25095
25260
|
}
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -22553,6 +22553,172 @@ function yui_selection_bar(t, opts) {
|
|
|
22553
22553
|
};
|
|
22554
22554
|
}
|
|
22555
22555
|
//#endregion
|
|
22556
|
+
//#region src/yui_table_filter_clear.js
|
|
22557
|
+
/***********************************************************************
|
|
22558
|
+
* yui_table_filter_clear.js
|
|
22559
|
+
*
|
|
22560
|
+
* LA ✕ DE LOS FILTROS DE CABECERA DE UNA TABULATOR.
|
|
22561
|
+
*
|
|
22562
|
+
* Un filtro de columna se pone escribiendo y se quita BORRANDO lo
|
|
22563
|
+
* escrito, letra a letra, y no hay nada en la cabecera que diga
|
|
22564
|
+
* que se puede quitar. Con varias columnas filtradas, volver a
|
|
22565
|
+
* ver la tabla entera es un ejercicio de memoria: cuáles toqué.
|
|
22566
|
+
* La ✕ lo dice y lo hace.
|
|
22567
|
+
*
|
|
22568
|
+
* TRES DECISIONES QUE NO SON OBVIAS:
|
|
22569
|
+
*
|
|
22570
|
+
* - **Se ve mientras el filtro tenga contenido, con foco o sin
|
|
22571
|
+
* él.** Es al revés que la ✕ de un formulario
|
|
22572
|
+
* (`yui_inputs.js`), que sólo aparece en el campo que se está
|
|
22573
|
+
* editando para no encender un aspa en cada campo relleno. Aquí
|
|
22574
|
+
* el caso de uso es justo el contrario: el filtro ya está puesto,
|
|
22575
|
+
* el foco está en otra parte, y lo que se quiere es verlo y
|
|
22576
|
+
* quitarlo. Escondida tras el foco harían falta dos clics.
|
|
22577
|
+
*
|
|
22578
|
+
* - **Se borra con la API, no simulando teclas.**
|
|
22579
|
+
* `setHeaderFilterValue(field, "")` es lo que Tabulator ofrece, y
|
|
22580
|
+
* deja el valor y el filtro consistentes. Sintetizar un `input`
|
|
22581
|
+
* sobre el `<input>` depende de a qué escuche el editor de ese
|
|
22582
|
+
* filtro, que cambia con el tipo de columna.
|
|
22583
|
+
*
|
|
22584
|
+
* - **Se recorre la cabecera, no se envuelve el editor.** Las
|
|
22585
|
+
* columnas se construyen del esquema del topic, así que no hay un
|
|
22586
|
+
* sitio único donde envolver el editor; y la cabecera se rehace
|
|
22587
|
+
* sola cuando cambian las columnas. El recorrido es idempotente
|
|
22588
|
+
* -- marca lo que ya tiene aspa -- y se dispara con un
|
|
22589
|
+
* `querySelector` de una sola pasada, así que repetirlo en cada
|
|
22590
|
+
* render no cuesta nada.
|
|
22591
|
+
*
|
|
22592
|
+
* Copyright (c) 2026, ArtGins.
|
|
22593
|
+
* All Rights Reserved.
|
|
22594
|
+
***********************************************************************/
|
|
22595
|
+
var MARK = "data-yui-filter-clear";
|
|
22596
|
+
/***************************************************************
|
|
22597
|
+
* Pon (o repón) la ✕ de cada filtro de cabecera de `table`.
|
|
22598
|
+
*
|
|
22599
|
+
* Idempotente: los que ya la tienen se saltan.
|
|
22600
|
+
***************************************************************/
|
|
22601
|
+
function decorate(table) {
|
|
22602
|
+
if (!table || !table.element) return;
|
|
22603
|
+
if (!table.element.querySelector(`.tabulator-header-filter:not([${MARK}])`)) return;
|
|
22604
|
+
let list = table.element.querySelectorAll(`.tabulator-header-filter:not([${MARK}])`);
|
|
22605
|
+
for (const $filter of list) {
|
|
22606
|
+
$filter.setAttribute(MARK, "1");
|
|
22607
|
+
let $input = $filter.querySelector("input");
|
|
22608
|
+
if (!$input) continue;
|
|
22609
|
+
let $col = $filter.closest(".tabulator-col");
|
|
22610
|
+
let field = $col ? $col.getAttribute("tabulator-field") : "";
|
|
22611
|
+
if (!field) continue;
|
|
22612
|
+
$filter.classList.add("yui-filter-has-clear");
|
|
22613
|
+
let $btn = createElement2(["button", {
|
|
22614
|
+
type: "button",
|
|
22615
|
+
class: "delete is-small yui-filter-clear",
|
|
22616
|
+
tabindex: "-1",
|
|
22617
|
+
title: i18next.t("clear"),
|
|
22618
|
+
"aria-label": i18next.t("clear"),
|
|
22619
|
+
"data-i18n-title": "clear",
|
|
22620
|
+
"data-i18n-aria-label": "clear"
|
|
22621
|
+
}]);
|
|
22622
|
+
const sync = () => {
|
|
22623
|
+
$btn.classList.toggle("is-visible", !!$input.value);
|
|
22624
|
+
};
|
|
22625
|
+
$input.addEventListener("input", sync);
|
|
22626
|
+
$input.addEventListener("change", sync);
|
|
22627
|
+
$btn.addEventListener("click", (event) => {
|
|
22628
|
+
event.stopPropagation();
|
|
22629
|
+
table.setHeaderFilterValue(field, "");
|
|
22630
|
+
sync();
|
|
22631
|
+
});
|
|
22632
|
+
$filter.appendChild($btn);
|
|
22633
|
+
sync();
|
|
22634
|
+
}
|
|
22635
|
+
}
|
|
22636
|
+
/***************************************************************
|
|
22637
|
+
* Engancha la ✕ a una tabla ya creada.
|
|
22638
|
+
*
|
|
22639
|
+
* Se llama una vez, con la tabla construida. Los enganches
|
|
22640
|
+
* cubren los dos momentos en que la cabecera aparece o se
|
|
22641
|
+
* rehace: cuando se construye y cuando cambian las columnas.
|
|
22642
|
+
***************************************************************/
|
|
22643
|
+
function yui_table_filter_clear(table) {
|
|
22644
|
+
if (!table) return;
|
|
22645
|
+
decorate(table);
|
|
22646
|
+
table.on("renderComplete", () => {
|
|
22647
|
+
decorate(table);
|
|
22648
|
+
});
|
|
22649
|
+
table.on("columnVisibilityChanged", () => {
|
|
22650
|
+
decorate(table);
|
|
22651
|
+
});
|
|
22652
|
+
}
|
|
22653
|
+
//#endregion
|
|
22654
|
+
//#region src/yui_row_search.js
|
|
22655
|
+
/***********************************************************************
|
|
22656
|
+
* yui_row_search.js
|
|
22657
|
+
*
|
|
22658
|
+
* ¿ESTÁ ESTE TÉRMINO EN ESTA FILA?
|
|
22659
|
+
*
|
|
22660
|
+
* La caja de búsqueda de una tabla de treedb miraba los valores de
|
|
22661
|
+
* la fila con `String(val)`, y una fila de treedb no es plana: un
|
|
22662
|
+
* fkey llega como una LISTA DE OBJETOS
|
|
22663
|
+
* `[{id, topic_name, hook_name}]`, y `String()` de eso es
|
|
22664
|
+
* `"[object Object]"`. O sea que buscar el taller de un equipo --
|
|
22665
|
+
* que es donde vive el dato que un operador tiene en la cabeza --
|
|
22666
|
+
* no encontraba nunca nada, y la caja no daba ninguna pista de que
|
|
22667
|
+
* había mirado en otro sitio.
|
|
22668
|
+
*
|
|
22669
|
+
* DOS REGLAS, Y LA SEGUNDA ES LA QUE HACE QUE SIRVA:
|
|
22670
|
+
*
|
|
22671
|
+
* - Se baja por listas y objetos hasta una profundidad corta: un
|
|
22672
|
+
* fkey está a dos niveles y nada de lo que se busca está más
|
|
22673
|
+
* hondo.
|
|
22674
|
+
* - **De un fkey se mira sólo el `id`.** `topic_name` y
|
|
22675
|
+
* `hook_name` son las MISMAS dos palabras en todas las filas, así
|
|
22676
|
+
* que mirarlas convierte el término en un comodín: buscar
|
|
22677
|
+
* "devices" traería el topic entero. El `id` es lo único que
|
|
22678
|
+
* nombra la cosa enlazada.
|
|
22679
|
+
*
|
|
22680
|
+
* Las claves que empiezan por `_` no se miran en ningún nivel: son
|
|
22681
|
+
* del armazón (`_check_box_state_`, `_operation`) o metadatos
|
|
22682
|
+
* (`__md_treedb__`), y nadie busca por ellas.
|
|
22683
|
+
*
|
|
22684
|
+
* Copyright (c) 2026, ArtGins.
|
|
22685
|
+
* All Rights Reserved.
|
|
22686
|
+
***********************************************************************/
|
|
22687
|
+
var MAX_DEPTH = 4;
|
|
22688
|
+
/***************************************************************
|
|
22689
|
+
* Un fkey, tal y como `nodes` lo contesta con `list_dict`.
|
|
22690
|
+
***************************************************************/
|
|
22691
|
+
function is_fkey_ref(value) {
|
|
22692
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value && "hook_name" in value;
|
|
22693
|
+
}
|
|
22694
|
+
/***************************************************************
|
|
22695
|
+
* ¿Contiene `value` el término? El término viene YA en
|
|
22696
|
+
* minúsculas: quien busca lo baja una vez y no una por celda.
|
|
22697
|
+
***************************************************************/
|
|
22698
|
+
function value_matches(value, term, depth) {
|
|
22699
|
+
if (value === null || value === void 0) return false;
|
|
22700
|
+
if (depth > MAX_DEPTH) return false;
|
|
22701
|
+
if (Array.isArray(value)) return value.some((v) => value_matches(v, term, depth + 1));
|
|
22702
|
+
if (typeof value === "object") {
|
|
22703
|
+
if (is_fkey_ref(value)) return value_matches(value.id, term, depth + 1);
|
|
22704
|
+
return Object.entries(value).some(([key, v]) => {
|
|
22705
|
+
if (key.startsWith("_")) return false;
|
|
22706
|
+
return value_matches(v, term, depth + 1);
|
|
22707
|
+
});
|
|
22708
|
+
}
|
|
22709
|
+
return String(value).toLowerCase().includes(term);
|
|
22710
|
+
}
|
|
22711
|
+
/***************************************************************
|
|
22712
|
+
* ¿Está el término en alguna parte de esta fila?
|
|
22713
|
+
***************************************************************/
|
|
22714
|
+
function row_matches(row, term) {
|
|
22715
|
+
if (!row || typeof row !== "object" || !term) return false;
|
|
22716
|
+
return Object.entries(row).some(([key, value]) => {
|
|
22717
|
+
if (key.startsWith("_")) return false;
|
|
22718
|
+
return value_matches(value, term, 0);
|
|
22719
|
+
});
|
|
22720
|
+
}
|
|
22721
|
+
//#endregion
|
|
22556
22722
|
//#region src/treedb_write_plan.js
|
|
22557
22723
|
/***********************************************************************
|
|
22558
22724
|
* treedb_write_plan.js
|
|
@@ -23626,7 +23792,8 @@ function create_tabulator(gobj) {
|
|
|
23626
23792
|
50,
|
|
23627
23793
|
100,
|
|
23628
23794
|
200,
|
|
23629
|
-
500
|
|
23795
|
+
500,
|
|
23796
|
+
true
|
|
23630
23797
|
],
|
|
23631
23798
|
ajaxURL: "nodes",
|
|
23632
23799
|
ajaxRequestFunc: function(url, config, params) {
|
|
@@ -23680,6 +23847,7 @@ function create_tabulator(gobj) {
|
|
|
23680
23847
|
tabulator.on("tableBuilt", function() {
|
|
23681
23848
|
tabulator._ready = true;
|
|
23682
23849
|
update_rowcount();
|
|
23850
|
+
yui_table_filter_clear(tabulator);
|
|
23683
23851
|
if (tabulator._pendingData !== void 0) {
|
|
23684
23852
|
tabulator.setData(tabulator._pendingData);
|
|
23685
23853
|
delete tabulator._pendingData;
|
|
@@ -24994,11 +25162,12 @@ function request_page(gobj, page, size) {
|
|
|
24994
25162
|
reject,
|
|
24995
25163
|
timer
|
|
24996
25164
|
};
|
|
25165
|
+
const all = size === true;
|
|
24997
25166
|
gobj_publish_event(gobj, "EV_REQUEST_PAGE", {
|
|
24998
25167
|
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
24999
25168
|
req_id,
|
|
25000
|
-
from: (page - 1) * size + 1,
|
|
25001
|
-
limit: size
|
|
25169
|
+
from: all ? 1 : (page - 1) * size + 1,
|
|
25170
|
+
limit: all ? 0 : size
|
|
25002
25171
|
});
|
|
25003
25172
|
});
|
|
25004
25173
|
}
|
|
@@ -25080,11 +25249,7 @@ function ac_search(gobj, event, kw, src) {
|
|
|
25080
25249
|
return 0;
|
|
25081
25250
|
}
|
|
25082
25251
|
tabulator.setFilter(function(data) {
|
|
25083
|
-
return
|
|
25084
|
-
if (key.startsWith("_")) return false;
|
|
25085
|
-
if (val === null || val === void 0) return false;
|
|
25086
|
-
return String(val).toLowerCase().includes(term);
|
|
25087
|
-
});
|
|
25252
|
+
return row_matches(data, term);
|
|
25088
25253
|
});
|
|
25089
25254
|
return 0;
|
|
25090
25255
|
}
|
package/package.json
CHANGED
|
@@ -85,6 +85,9 @@ import {
|
|
|
85
85
|
yui_clear_selection,
|
|
86
86
|
} from "./yui_table_select.js";
|
|
87
87
|
|
|
88
|
+
import {yui_table_filter_clear} from "./yui_table_filter_clear.js";
|
|
89
|
+
import {row_matches} from "./yui_row_search.js";
|
|
90
|
+
|
|
88
91
|
import {t} from "i18next";
|
|
89
92
|
|
|
90
93
|
import {plan_treedb_writes} from "./treedb_write_plan.js";
|
|
@@ -1346,7 +1349,13 @@ function create_tabulator(gobj)
|
|
|
1346
1349
|
paginationMode: "remote",
|
|
1347
1350
|
filterMode: "local",
|
|
1348
1351
|
paginationSize: page_size,
|
|
1349
|
-
|
|
1352
|
+
/* `true` is Tabulator's "All", and here it is not a courtesy:
|
|
1353
|
+
* `filterMode: "local"` means the header filters and the search
|
|
1354
|
+
* box only see the page that is loaded, so on a topic that does
|
|
1355
|
+
* not fit, searching for something on page 17 finds nothing.
|
|
1356
|
+
* "All" is the way out, and the backend has it -- `nodes` takes
|
|
1357
|
+
* `limit: 0` for "every one", answering the plain list. */
|
|
1358
|
+
paginationSizeSelector: [50, 100, 200, 500, true],
|
|
1350
1359
|
ajaxURL: "nodes", /* dummy: only fires the func */
|
|
1351
1360
|
ajaxRequestFunc: function(url, config, params) {
|
|
1352
1361
|
/* Widget plumbing, not an action: Tabulator wants a PROMISE
|
|
@@ -1467,6 +1476,10 @@ function create_tabulator(gobj)
|
|
|
1467
1476
|
tabulator.on("tableBuilt", function() {
|
|
1468
1477
|
tabulator._ready = true;
|
|
1469
1478
|
update_rowcount();
|
|
1479
|
+
/* La ✕ de cada filtro de cabecera. Un filtro se quita borrando lo
|
|
1480
|
+
* escrito, y con varias columnas filtradas volver a la tabla entera
|
|
1481
|
+
* era acordarse de cuáles se tocaron. */
|
|
1482
|
+
yui_table_filter_clear(tabulator);
|
|
1470
1483
|
if(tabulator._pendingData !== undefined) {
|
|
1471
1484
|
tabulator.setData(tabulator._pendingData);
|
|
1472
1485
|
delete tabulator._pendingData;
|
|
@@ -3370,14 +3383,21 @@ function request_page(gobj, page, size)
|
|
|
3370
3383
|
|
|
3371
3384
|
priv._pending_pages[req_id] = {resolve: resolve, reject: reject, timer: timer};
|
|
3372
3385
|
|
|
3386
|
+
/* "All" (Tabulator sends the size as `true`): `nodes` says every
|
|
3387
|
+
* one with `limit: 0`, and then it answers the plain list -- which
|
|
3388
|
+
* nodes_answer() reads as a single page, so the paginator hides and
|
|
3389
|
+
* every filter sees every row. Arithmetic on `true` would send
|
|
3390
|
+
* `limit: true` to a backend that wants an integer. */
|
|
3391
|
+
const all = (size === true);
|
|
3392
|
+
|
|
3373
3393
|
gobj_publish_event(
|
|
3374
3394
|
gobj,
|
|
3375
3395
|
"EV_REQUEST_PAGE",
|
|
3376
3396
|
{
|
|
3377
3397
|
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
3378
3398
|
req_id: req_id,
|
|
3379
|
-
from: (page - 1) * size + 1,
|
|
3380
|
-
limit: size
|
|
3399
|
+
from: all? 1: (page - 1) * size + 1, /* `nodes` counts from 1 */
|
|
3400
|
+
limit: all? 0: size
|
|
3381
3401
|
}
|
|
3382
3402
|
);
|
|
3383
3403
|
});
|
|
@@ -3482,16 +3502,13 @@ function ac_search(gobj, event, kw, src)
|
|
|
3482
3502
|
return 0;
|
|
3483
3503
|
}
|
|
3484
3504
|
|
|
3505
|
+
/* A treedb row is not flat: an fkey arrives as a list of objects, and
|
|
3506
|
+
* `String()` of that is "[object Object]" -- so searching for the
|
|
3507
|
+
* workshop of a meter, which is where the value a person has in mind
|
|
3508
|
+
* lives, never found anything. `row_matches()` walks into them, and
|
|
3509
|
+
* reads only the `id` of an fkey. See yui_row_search.js. */
|
|
3485
3510
|
tabulator.setFilter(function(data) {
|
|
3486
|
-
return
|
|
3487
|
-
if(key.startsWith('_')) {
|
|
3488
|
-
return false;
|
|
3489
|
-
}
|
|
3490
|
-
if(val === null || val === undefined) {
|
|
3491
|
-
return false;
|
|
3492
|
-
}
|
|
3493
|
-
return String(val).toLowerCase().includes(term);
|
|
3494
|
-
});
|
|
3511
|
+
return row_matches(data, term);
|
|
3495
3512
|
});
|
|
3496
3513
|
|
|
3497
3514
|
return 0;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_row_search.js
|
|
3
|
+
*
|
|
4
|
+
* ¿ESTÁ ESTE TÉRMINO EN ESTA FILA?
|
|
5
|
+
*
|
|
6
|
+
* La caja de búsqueda de una tabla de treedb miraba los valores de
|
|
7
|
+
* la fila con `String(val)`, y una fila de treedb no es plana: un
|
|
8
|
+
* fkey llega como una LISTA DE OBJETOS
|
|
9
|
+
* `[{id, topic_name, hook_name}]`, y `String()` de eso es
|
|
10
|
+
* `"[object Object]"`. O sea que buscar el taller de un equipo --
|
|
11
|
+
* que es donde vive el dato que un operador tiene en la cabeza --
|
|
12
|
+
* no encontraba nunca nada, y la caja no daba ninguna pista de que
|
|
13
|
+
* había mirado en otro sitio.
|
|
14
|
+
*
|
|
15
|
+
* DOS REGLAS, Y LA SEGUNDA ES LA QUE HACE QUE SIRVA:
|
|
16
|
+
*
|
|
17
|
+
* - Se baja por listas y objetos hasta una profundidad corta: un
|
|
18
|
+
* fkey está a dos niveles y nada de lo que se busca está más
|
|
19
|
+
* hondo.
|
|
20
|
+
* - **De un fkey se mira sólo el `id`.** `topic_name` y
|
|
21
|
+
* `hook_name` son las MISMAS dos palabras en todas las filas, así
|
|
22
|
+
* que mirarlas convierte el término en un comodín: buscar
|
|
23
|
+
* "devices" traería el topic entero. El `id` es lo único que
|
|
24
|
+
* nombra la cosa enlazada.
|
|
25
|
+
*
|
|
26
|
+
* Las claves que empiezan por `_` no se miran en ningún nivel: son
|
|
27
|
+
* del armazón (`_check_box_state_`, `_operation`) o metadatos
|
|
28
|
+
* (`__md_treedb__`), y nadie busca por ellas.
|
|
29
|
+
*
|
|
30
|
+
* Copyright (c) 2026, ArtGins.
|
|
31
|
+
* All Rights Reserved.
|
|
32
|
+
***********************************************************************/
|
|
33
|
+
|
|
34
|
+
const MAX_DEPTH = 4;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/***************************************************************
|
|
38
|
+
* Un fkey, tal y como `nodes` lo contesta con `list_dict`.
|
|
39
|
+
***************************************************************/
|
|
40
|
+
function is_fkey_ref(value)
|
|
41
|
+
{
|
|
42
|
+
return !!value
|
|
43
|
+
&& typeof value === "object"
|
|
44
|
+
&& !Array.isArray(value)
|
|
45
|
+
&& ("id" in value)
|
|
46
|
+
&& ("topic_name" in value)
|
|
47
|
+
&& ("hook_name" in value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/***************************************************************
|
|
51
|
+
* ¿Contiene `value` el término? El término viene YA en
|
|
52
|
+
* minúsculas: quien busca lo baja una vez y no una por celda.
|
|
53
|
+
***************************************************************/
|
|
54
|
+
function value_matches(value, term, depth)
|
|
55
|
+
{
|
|
56
|
+
if(value === null || value === undefined) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if(depth > MAX_DEPTH) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if(Array.isArray(value)) {
|
|
63
|
+
return value.some((v) => value_matches(v, term, depth + 1));
|
|
64
|
+
}
|
|
65
|
+
if(typeof value === "object") {
|
|
66
|
+
if(is_fkey_ref(value)) {
|
|
67
|
+
return value_matches(value.id, term, depth + 1);
|
|
68
|
+
}
|
|
69
|
+
return Object.entries(value).some(([key, v]) => {
|
|
70
|
+
if(key.startsWith("_")) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return value_matches(v, term, depth + 1);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return String(value).toLowerCase().includes(term);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/***************************************************************
|
|
80
|
+
* ¿Está el término en alguna parte de esta fila?
|
|
81
|
+
***************************************************************/
|
|
82
|
+
function row_matches(row, term)
|
|
83
|
+
{
|
|
84
|
+
if(!row || typeof row !== "object" || !term) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return Object.entries(row).some(([key, value]) => {
|
|
88
|
+
if(key.startsWith("_")) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return value_matches(value, term, 0);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export {row_matches, value_matches, is_fkey_ref};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {describe, it, expect} from "vitest";
|
|
2
|
+
|
|
3
|
+
import {row_matches, is_fkey_ref} from "./yui_row_search.js";
|
|
4
|
+
|
|
5
|
+
describe("row_matches", () => {
|
|
6
|
+
const row = {
|
|
7
|
+
id: "E22003089",
|
|
8
|
+
name: "",
|
|
9
|
+
place: [
|
|
10
|
+
{
|
|
11
|
+
id: "es.madrid.bm-madrid-fuencarral-m-av.local-13-torno",
|
|
12
|
+
topic_name: "places",
|
|
13
|
+
hook_name: "devices"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
tension: 400,
|
|
17
|
+
enabled: true,
|
|
18
|
+
_operation: "hidden",
|
|
19
|
+
__md_treedb__: {topic_name: "devices"}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it("finds a plain field", () => {
|
|
23
|
+
expect(row_matches(row, "e22003089")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("finds a number", () => {
|
|
27
|
+
expect(row_matches(row, "400")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("finds a value INSIDE an fkey, which is the whole point", () => {
|
|
31
|
+
expect(row_matches(row, "local-13-torno")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("does NOT match the structural halves of an fkey", () => {
|
|
35
|
+
/* `topic_name`/`hook_name` are the same two words on every row:
|
|
36
|
+
* matching them turns the term into a wildcard. */
|
|
37
|
+
expect(row_matches(row, "places")).toBe(false);
|
|
38
|
+
expect(row_matches(row, "hook")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("ignores keys of the scaffolding", () => {
|
|
42
|
+
expect(row_matches(row, "hidden")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("says no when nothing matches", () => {
|
|
46
|
+
expect(row_matches(row, "zzz")).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("says no for an empty term or a missing row", () => {
|
|
50
|
+
expect(row_matches(row, "")).toBe(false);
|
|
51
|
+
expect(row_matches(null, "a")).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("is_fkey_ref", () => {
|
|
56
|
+
it("wants the three keys", () => {
|
|
57
|
+
expect(is_fkey_ref({id: "a", topic_name: "t", hook_name: "h"})).toBe(true);
|
|
58
|
+
expect(is_fkey_ref({id: "a"})).toBe(false);
|
|
59
|
+
expect(is_fkey_ref(["a"])).toBe(false);
|
|
60
|
+
expect(is_fkey_ref(null)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_table_filter_clear.css
|
|
3
|
+
*
|
|
4
|
+
* La ✕ de un filtro de cabecera (yui_table_filter_clear.js).
|
|
5
|
+
*
|
|
6
|
+
* Al reves que la ✕ de un formulario (yui_inputs.css), esta se ve
|
|
7
|
+
* SIN foco: el filtro ya esta puesto, el foco esta en otra parte,
|
|
8
|
+
* y lo que se quiere es verlo y quitarlo.
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2026, ArtGins.
|
|
11
|
+
* All Rights Reserved.
|
|
12
|
+
***********************************************************************/
|
|
13
|
+
.tabulator-header-filter.yui-filter-has-clear {
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Sitio para el aspa dentro del propio input, para que no se coma
|
|
18
|
+
* ancho de columna. */
|
|
19
|
+
.tabulator-header-filter.yui-filter-has-clear input {
|
|
20
|
+
padding-right: 1.6em;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Dos clases sobre el elemento para ganarle a las reglas propias de
|
|
24
|
+
* Bulma para `.delete` (position:relative; display:inline-block) sin
|
|
25
|
+
* depender de que hoja carga la ultima. */
|
|
26
|
+
.tabulator-header-filter .yui-filter-clear {
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 50%;
|
|
29
|
+
right: 0.25em;
|
|
30
|
+
transform: translateY(-50%);
|
|
31
|
+
display: none;
|
|
32
|
+
z-index: 4;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tabulator-header-filter .yui-filter-clear.is-visible {
|
|
36
|
+
display: inline-block;
|
|
37
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_table_filter_clear.js
|
|
3
|
+
*
|
|
4
|
+
* LA ✕ DE LOS FILTROS DE CABECERA DE UNA TABULATOR.
|
|
5
|
+
*
|
|
6
|
+
* Un filtro de columna se pone escribiendo y se quita BORRANDO lo
|
|
7
|
+
* escrito, letra a letra, y no hay nada en la cabecera que diga
|
|
8
|
+
* que se puede quitar. Con varias columnas filtradas, volver a
|
|
9
|
+
* ver la tabla entera es un ejercicio de memoria: cuáles toqué.
|
|
10
|
+
* La ✕ lo dice y lo hace.
|
|
11
|
+
*
|
|
12
|
+
* TRES DECISIONES QUE NO SON OBVIAS:
|
|
13
|
+
*
|
|
14
|
+
* - **Se ve mientras el filtro tenga contenido, con foco o sin
|
|
15
|
+
* él.** Es al revés que la ✕ de un formulario
|
|
16
|
+
* (`yui_inputs.js`), que sólo aparece en el campo que se está
|
|
17
|
+
* editando para no encender un aspa en cada campo relleno. Aquí
|
|
18
|
+
* el caso de uso es justo el contrario: el filtro ya está puesto,
|
|
19
|
+
* el foco está en otra parte, y lo que se quiere es verlo y
|
|
20
|
+
* quitarlo. Escondida tras el foco harían falta dos clics.
|
|
21
|
+
*
|
|
22
|
+
* - **Se borra con la API, no simulando teclas.**
|
|
23
|
+
* `setHeaderFilterValue(field, "")` es lo que Tabulator ofrece, y
|
|
24
|
+
* deja el valor y el filtro consistentes. Sintetizar un `input`
|
|
25
|
+
* sobre el `<input>` depende de a qué escuche el editor de ese
|
|
26
|
+
* filtro, que cambia con el tipo de columna.
|
|
27
|
+
*
|
|
28
|
+
* - **Se recorre la cabecera, no se envuelve el editor.** Las
|
|
29
|
+
* columnas se construyen del esquema del topic, así que no hay un
|
|
30
|
+
* sitio único donde envolver el editor; y la cabecera se rehace
|
|
31
|
+
* sola cuando cambian las columnas. El recorrido es idempotente
|
|
32
|
+
* -- marca lo que ya tiene aspa -- y se dispara con un
|
|
33
|
+
* `querySelector` de una sola pasada, así que repetirlo en cada
|
|
34
|
+
* render no cuesta nada.
|
|
35
|
+
*
|
|
36
|
+
* Copyright (c) 2026, ArtGins.
|
|
37
|
+
* All Rights Reserved.
|
|
38
|
+
***********************************************************************/
|
|
39
|
+
import i18next from "i18next";
|
|
40
|
+
|
|
41
|
+
import {createElement2} from "@yuneta/gobj-js";
|
|
42
|
+
|
|
43
|
+
import "./yui_table_filter_clear.css";
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const MARK = "data-yui-filter-clear";
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/***************************************************************
|
|
50
|
+
* Pon (o repón) la ✕ de cada filtro de cabecera de `table`.
|
|
51
|
+
*
|
|
52
|
+
* Idempotente: los que ya la tienen se saltan.
|
|
53
|
+
***************************************************************/
|
|
54
|
+
function decorate(table)
|
|
55
|
+
{
|
|
56
|
+
if(!table || !table.element) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let $pending = table.element.querySelector(`.tabulator-header-filter:not([${MARK}])`);
|
|
60
|
+
if(!$pending) {
|
|
61
|
+
return; /* nada nuevo en la cabecera */
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let list = table.element.querySelectorAll(`.tabulator-header-filter:not([${MARK}])`);
|
|
65
|
+
for(const $filter of list) {
|
|
66
|
+
$filter.setAttribute(MARK, "1");
|
|
67
|
+
|
|
68
|
+
let $input = $filter.querySelector("input");
|
|
69
|
+
if(!$input) {
|
|
70
|
+
continue; /* un filtro que no se escribe: un select, un rango */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* El campo se lee del `.tabulator-col` que lo contiene, que es
|
|
74
|
+
* quien lo lleva; el `<input>` no sabe de qué columna es. */
|
|
75
|
+
let $col = $filter.closest(".tabulator-col");
|
|
76
|
+
let field = $col? $col.getAttribute("tabulator-field"): "";
|
|
77
|
+
if(!field) {
|
|
78
|
+
continue; /* sin campo no hay a quién decirle que se limpie */
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
$filter.classList.add("yui-filter-has-clear");
|
|
82
|
+
|
|
83
|
+
/* La clave viaja con el botón: un `title` puesto con t() al
|
|
84
|
+
* construir es invisible para refresh_language(), y se queda en
|
|
85
|
+
* el idioma de aquel momento para siempre. */
|
|
86
|
+
let $btn = createElement2(["button", {
|
|
87
|
+
type: "button",
|
|
88
|
+
class: "delete is-small yui-filter-clear",
|
|
89
|
+
tabindex: "-1",
|
|
90
|
+
title: i18next.t("clear"),
|
|
91
|
+
"aria-label": i18next.t("clear"),
|
|
92
|
+
"data-i18n-title": "clear",
|
|
93
|
+
"data-i18n-aria-label": "clear"
|
|
94
|
+
}]);
|
|
95
|
+
|
|
96
|
+
const sync = () => {
|
|
97
|
+
$btn.classList.toggle("is-visible", !!$input.value);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
$input.addEventListener("input", sync);
|
|
101
|
+
$input.addEventListener("change", sync);
|
|
102
|
+
$btn.addEventListener("click", (event) => {
|
|
103
|
+
event.stopPropagation(); /* no ordenar la columna al pulsar */
|
|
104
|
+
table.setHeaderFilterValue(field, "");
|
|
105
|
+
sync();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
$filter.appendChild($btn);
|
|
109
|
+
sync();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/***************************************************************
|
|
114
|
+
* Engancha la ✕ a una tabla ya creada.
|
|
115
|
+
*
|
|
116
|
+
* Se llama una vez, con la tabla construida. Los enganches
|
|
117
|
+
* cubren los dos momentos en que la cabecera aparece o se
|
|
118
|
+
* rehace: cuando se construye y cuando cambian las columnas.
|
|
119
|
+
***************************************************************/
|
|
120
|
+
export function yui_table_filter_clear(table)
|
|
121
|
+
{
|
|
122
|
+
if(!table) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
decorate(table);
|
|
126
|
+
table.on("renderComplete", () => {
|
|
127
|
+
decorate(table);
|
|
128
|
+
});
|
|
129
|
+
table.on("columnVisibilityChanged", () => {
|
|
130
|
+
decorate(table);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/***************************************************************
|
|
135
|
+
* Repasa el estado de las ✕ tras un cambio que no vino del
|
|
136
|
+
* teclado -- limpiar todos los filtros, cargar una vista
|
|
137
|
+
* guardada -- porque eso no dispara `input`.
|
|
138
|
+
***************************************************************/
|
|
139
|
+
export function yui_table_filter_clear_refresh(table)
|
|
140
|
+
{
|
|
141
|
+
if(!table || !table.element) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
for(const $filter of table.element.querySelectorAll(".yui-filter-has-clear")) {
|
|
145
|
+
let $input = $filter.querySelector("input");
|
|
146
|
+
let $btn = $filter.querySelector(".yui-filter-clear");
|
|
147
|
+
if($input && $btn) {
|
|
148
|
+
$btn.classList.toggle("is-visible", !!$input.value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|