@yuneta/gobj-ui 7.0.1 → 7.1.0
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/README.md +31 -0
- package/dist/gobj-ui.cjs.js +245 -13
- package/dist/gobj-ui.es.js +245 -13
- package/package.json +2 -2
- package/src/c_yui_treedb_topic_with_form.js +265 -20
package/README.md
CHANGED
|
@@ -491,6 +491,37 @@ The buttons of that toolbar never shrink. When the row runs out of room, the
|
|
|
491
491
|
url is what gives way, cut with an ellipsis, and the whole value stays in the
|
|
492
492
|
`title` and the `aria-label`.
|
|
493
493
|
|
|
494
|
+
### Reading a topic table: filters, columns, CSV
|
|
495
|
+
|
|
496
|
+
`C_YUI_TREEDB_TOPIC_WITH_FORM` had one global search box over the loaded rows.
|
|
497
|
+
Three tools join it, each behind its own flag, all **on** by default:
|
|
498
|
+
|
|
499
|
+
| attr | what it adds |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `with_header_filters` | a filter box in the header of each column a match means something on |
|
|
502
|
+
| `with_columns_button` | a dialog that ticks which columns the table shows |
|
|
503
|
+
| `with_export_button` | downloads what the table holds as CSV |
|
|
504
|
+
|
|
505
|
+
**Not every column gets a filter box, on purpose.** A hook holds children, a
|
|
506
|
+
dict holds a subtree, and a date cell shows a formatted string over an epoch
|
|
507
|
+
number — a text match against the raw value there answers a question nobody
|
|
508
|
+
asked, so those columns get no box rather than a box that lies. A `boolean`
|
|
509
|
+
gets a tristate tick, an `enum` gets a list of its own values, and an `fkey`
|
|
510
|
+
gets a box whose match stringifies the value first, because *which rows point
|
|
511
|
+
at X* is the question fkey columns exist to answer and a fkey arrives as a ref
|
|
512
|
+
string, a list of them or a dict.
|
|
513
|
+
|
|
514
|
+
The search box and the header filters are **separate layers**: clearing the
|
|
515
|
+
search does not silently drop the column filters. The CSV carries what the
|
|
516
|
+
table HOLDS — the loaded rows, the visible columns, both filters applied, which
|
|
517
|
+
is what the reader is looking at. It is not the topic: a server-side dump of
|
|
518
|
+
every node is not something this view can stream, and the button's title says
|
|
519
|
+
so.
|
|
520
|
+
|
|
521
|
+
Searching is a user action, so it crosses the FSM (`EV_SEARCH`) like the rest;
|
|
522
|
+
it used to call `tabulator.setFilter` straight from the DOM handler, where the
|
|
523
|
+
`machine` trace could not see it.
|
|
524
|
+
|
|
494
525
|
### Read-only treedbs: `readonly`
|
|
495
526
|
|
|
496
527
|
`C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` take a **`readonly`** attr; the
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -11017,7 +11017,7 @@ function ac_expand_collapsed(gobj, event, kw, src) {
|
|
|
11017
11017
|
/************************************************************
|
|
11018
11018
|
* EV_SEARCH { text }
|
|
11019
11019
|
************************************************************/
|
|
11020
|
-
function ac_search(gobj, event, kw, src) {
|
|
11020
|
+
function ac_search$1(gobj, event, kw, src) {
|
|
11021
11021
|
let priv = gobj.priv;
|
|
11022
11022
|
priv.search = (kw.text || "").trim().toLowerCase();
|
|
11023
11023
|
render_tree(gobj);
|
|
@@ -11166,7 +11166,7 @@ function create_gclass$10(gclass_name) {
|
|
|
11166
11166
|
],
|
|
11167
11167
|
[
|
|
11168
11168
|
"EV_SEARCH",
|
|
11169
|
-
ac_search,
|
|
11169
|
+
ac_search$1,
|
|
11170
11170
|
null
|
|
11171
11171
|
],
|
|
11172
11172
|
[
|
|
@@ -16222,6 +16222,9 @@ var attrs_table$5 = [
|
|
|
16222
16222
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
|
|
16223
16223
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
|
|
16224
16224
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
|
|
16225
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
16226
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
|
|
16227
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_header_filters", 0, true, "Per-column filter box in the table header, on the columns a text/number match means something (not hooks, fkeys or json)"),
|
|
16225
16228
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
|
|
16226
16229
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
16227
16230
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
@@ -16514,6 +16517,8 @@ function build_ui$5(gobj) {
|
|
|
16514
16517
|
let with_refresh_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_refresh_button");
|
|
16515
16518
|
let with_search_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_search_button");
|
|
16516
16519
|
let with_schema_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_schema_button");
|
|
16520
|
+
let with_columns_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_columns_button");
|
|
16521
|
+
let with_export_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_export_button");
|
|
16517
16522
|
if (with_search_button) {
|
|
16518
16523
|
let search_id = `${toolbar_id}_search`;
|
|
16519
16524
|
let $search_box = (0, _yuneta_gobj_js.createElement2)([
|
|
@@ -16534,17 +16539,7 @@ function build_ui$5(gobj) {
|
|
|
16534
16539
|
let $search_input = $search_box.querySelector(`#${search_id}`);
|
|
16535
16540
|
attach_clear($search_box, $search_input);
|
|
16536
16541
|
if ($search_input) $search_input.addEventListener("input", (event) => {
|
|
16537
|
-
|
|
16538
|
-
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
16539
|
-
if (!tabulator) return;
|
|
16540
|
-
if (searchText) tabulator.setFilter(function(data) {
|
|
16541
|
-
return Object.entries(data).some(([key, val]) => {
|
|
16542
|
-
if (key.startsWith("_")) return false;
|
|
16543
|
-
if (val === null || val === void 0) return false;
|
|
16544
|
-
return String(val).toLowerCase().includes(searchText);
|
|
16545
|
-
});
|
|
16546
|
-
});
|
|
16547
|
-
else tabulator.clearFilter();
|
|
16542
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SEARCH", { text: event.target.value.trim() }, gobj);
|
|
16548
16543
|
});
|
|
16549
16544
|
$view_toolbar.appendChild($search_box);
|
|
16550
16545
|
}
|
|
@@ -16598,6 +16593,58 @@ function build_ui$5(gobj) {
|
|
|
16598
16593
|
]);
|
|
16599
16594
|
$view_toolbar.appendChild($schema);
|
|
16600
16595
|
}
|
|
16596
|
+
if (with_columns_button) {
|
|
16597
|
+
let $columns = (0, _yuneta_gobj_js.createElement2)([
|
|
16598
|
+
"button",
|
|
16599
|
+
{
|
|
16600
|
+
class: "TREEDB_TABLE_COLUMNS button mr-1",
|
|
16601
|
+
title: (0, i18next.t)("choose the columns to show"),
|
|
16602
|
+
"data-i18n-title": "choose the columns to show",
|
|
16603
|
+
"aria-label": (0, i18next.t)("columns"),
|
|
16604
|
+
"data-i18n-aria-label": "columns"
|
|
16605
|
+
},
|
|
16606
|
+
[["i", { class: "yi-table" }], [
|
|
16607
|
+
"span",
|
|
16608
|
+
{
|
|
16609
|
+
class: "is-hidden-mobile",
|
|
16610
|
+
i18n: "columns",
|
|
16611
|
+
style: "padding-left:5px;"
|
|
16612
|
+
},
|
|
16613
|
+
"columns"
|
|
16614
|
+
]],
|
|
16615
|
+
{ "click": (event) => {
|
|
16616
|
+
event.stopPropagation();
|
|
16617
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_COLUMNS", {}, gobj);
|
|
16618
|
+
} }
|
|
16619
|
+
]);
|
|
16620
|
+
$view_toolbar.appendChild($columns);
|
|
16621
|
+
}
|
|
16622
|
+
if (with_export_button) {
|
|
16623
|
+
let $export = (0, _yuneta_gobj_js.createElement2)([
|
|
16624
|
+
"button",
|
|
16625
|
+
{
|
|
16626
|
+
class: "TREEDB_TABLE_EXPORT button mr-1",
|
|
16627
|
+
title: (0, i18next.t)("download the rows loaded in this table as csv"),
|
|
16628
|
+
"data-i18n-title": "download the rows loaded in this table as csv",
|
|
16629
|
+
"aria-label": (0, i18next.t)("export"),
|
|
16630
|
+
"data-i18n-aria-label": "export"
|
|
16631
|
+
},
|
|
16632
|
+
[["i", { class: "yi-download" }], [
|
|
16633
|
+
"span",
|
|
16634
|
+
{
|
|
16635
|
+
class: "is-hidden-mobile",
|
|
16636
|
+
i18n: "export",
|
|
16637
|
+
style: "padding-left:5px;"
|
|
16638
|
+
},
|
|
16639
|
+
"export"
|
|
16640
|
+
]],
|
|
16641
|
+
{ "click": (event) => {
|
|
16642
|
+
event.stopPropagation();
|
|
16643
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EXPORT_TABLE", {}, gobj);
|
|
16644
|
+
} }
|
|
16645
|
+
]);
|
|
16646
|
+
$view_toolbar.appendChild($export);
|
|
16647
|
+
}
|
|
16601
16648
|
let table_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "table_id");
|
|
16602
16649
|
let $container = (0, _yuneta_gobj_js.createElement2)([
|
|
16603
16650
|
"div",
|
|
@@ -16686,6 +16733,7 @@ function col_title_formatter(gobj, col) {
|
|
|
16686
16733
|
function create_tabulator(gobj) {
|
|
16687
16734
|
let table_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "table_id");
|
|
16688
16735
|
let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
|
|
16736
|
+
let with_header_filters = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_header_filters");
|
|
16689
16737
|
let columns = [];
|
|
16690
16738
|
let with_checkbox = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_checkbox");
|
|
16691
16739
|
let with_radio = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_radio");
|
|
@@ -16729,6 +16777,46 @@ function create_tabulator(gobj) {
|
|
|
16729
16777
|
col_id: cell.getField()
|
|
16730
16778
|
}, gobj);
|
|
16731
16779
|
}
|
|
16780
|
+
const FILTERABLE = [
|
|
16781
|
+
"string",
|
|
16782
|
+
"integer",
|
|
16783
|
+
"real",
|
|
16784
|
+
"rowid",
|
|
16785
|
+
"uuid",
|
|
16786
|
+
"qualified",
|
|
16787
|
+
"email",
|
|
16788
|
+
"url",
|
|
16789
|
+
"tel",
|
|
16790
|
+
"id",
|
|
16791
|
+
"hex",
|
|
16792
|
+
"currency",
|
|
16793
|
+
"percent"
|
|
16794
|
+
];
|
|
16795
|
+
function fkey_matches(term, value) {
|
|
16796
|
+
if (term === "" || term === null || term === void 0) return true;
|
|
16797
|
+
if (value === null || value === void 0) return false;
|
|
16798
|
+
return (typeof value === "string" ? value : JSON.stringify(value)).toLowerCase().includes(String(term).toLowerCase());
|
|
16799
|
+
}
|
|
16800
|
+
function apply_header_filter(colDef, field_desc) {
|
|
16801
|
+
switch (field_desc.type) {
|
|
16802
|
+
case "boolean":
|
|
16803
|
+
colDef.headerFilter = "tickCross";
|
|
16804
|
+
colDef.headerFilterParams = { tristate: true };
|
|
16805
|
+
break;
|
|
16806
|
+
case "enum":
|
|
16807
|
+
colDef.headerFilter = "list";
|
|
16808
|
+
colDef.headerFilterParams = {
|
|
16809
|
+
values: field_desc.enum_list || [],
|
|
16810
|
+
clearable: true
|
|
16811
|
+
};
|
|
16812
|
+
break;
|
|
16813
|
+
case "fkey":
|
|
16814
|
+
colDef.headerFilter = "input";
|
|
16815
|
+
colDef.headerFilterFunc = fkey_matches;
|
|
16816
|
+
break;
|
|
16817
|
+
default: if (FILTERABLE.includes(field_desc.type)) colDef.headerFilter = "input";
|
|
16818
|
+
}
|
|
16819
|
+
}
|
|
16732
16820
|
for (let i = 0; i < desc.cols.length; i++) {
|
|
16733
16821
|
let col = desc.cols[i];
|
|
16734
16822
|
if (!col.id || col.id[0] === "_") continue;
|
|
@@ -16809,6 +16897,7 @@ function create_tabulator(gobj) {
|
|
|
16809
16897
|
hozAlign,
|
|
16810
16898
|
formatter: colFormatter
|
|
16811
16899
|
};
|
|
16900
|
+
if (with_header_filters) apply_header_filter(colDef, field_desc);
|
|
16812
16901
|
if (vertAlign) colDef.vertAlign = vertAlign;
|
|
16813
16902
|
if (formatterParams) colDef.formatterParams = formatterParams;
|
|
16814
16903
|
if (cellClick) colDef.cellClick = cellClick;
|
|
@@ -18018,6 +18107,125 @@ function ac_show_schema(gobj, event, kw, src) {
|
|
|
18018
18107
|
return 0;
|
|
18019
18108
|
}
|
|
18020
18109
|
/************************************************************
|
|
18110
|
+
* Filter the loaded rows by a term matched against every non-internal
|
|
18111
|
+
* field. `clearFilter()` with no argument drops only THIS filter: the
|
|
18112
|
+
* per-column header filters are a separate layer and survive, so a
|
|
18113
|
+
* cleared search box does not silently undo them.
|
|
18114
|
+
************************************************************/
|
|
18115
|
+
function ac_search(gobj, event, kw, src) {
|
|
18116
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
18117
|
+
if (!tabulator) {
|
|
18118
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no table to search`);
|
|
18119
|
+
return -1;
|
|
18120
|
+
}
|
|
18121
|
+
let term = (kw && kw.text || "").toLowerCase();
|
|
18122
|
+
if (!term) {
|
|
18123
|
+
tabulator.clearFilter();
|
|
18124
|
+
return 0;
|
|
18125
|
+
}
|
|
18126
|
+
tabulator.setFilter(function(data) {
|
|
18127
|
+
return Object.entries(data).some(([key, val]) => {
|
|
18128
|
+
if (key.startsWith("_")) return false;
|
|
18129
|
+
if (val === null || val === void 0) return false;
|
|
18130
|
+
return String(val).toLowerCase().includes(term);
|
|
18131
|
+
});
|
|
18132
|
+
});
|
|
18133
|
+
return 0;
|
|
18134
|
+
}
|
|
18135
|
+
/************************************************************
|
|
18136
|
+
* Which columns the table shows: one checkbox per column, the current
|
|
18137
|
+
* visibility ticked. The internal columns (`_check_box_state_`,
|
|
18138
|
+
* `_operation`) are not offered — they are chrome, not data.
|
|
18139
|
+
************************************************************/
|
|
18140
|
+
function ac_open_columns(gobj, event, kw, src) {
|
|
18141
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
18142
|
+
if (!tabulator) {
|
|
18143
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no table, no columns to choose`);
|
|
18144
|
+
return -1;
|
|
18145
|
+
}
|
|
18146
|
+
let shell = yui_shell_of(gobj);
|
|
18147
|
+
if (!shell) {
|
|
18148
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no shell, cannot open the column chooser`);
|
|
18149
|
+
return -1;
|
|
18150
|
+
}
|
|
18151
|
+
let $list = (0, _yuneta_gobj_js.createElement2)(["div", { class: "TREEDB_COLUMNS_LIST" }]);
|
|
18152
|
+
let columns = tabulator.getColumns();
|
|
18153
|
+
for (let i = 0; i < columns.length; i++) {
|
|
18154
|
+
let column = columns[i];
|
|
18155
|
+
let field = column.getField();
|
|
18156
|
+
if (!field || field[0] === "_") continue;
|
|
18157
|
+
let $cb = (0, _yuneta_gobj_js.createElement2)(["input", {
|
|
18158
|
+
type: "checkbox",
|
|
18159
|
+
class: "mr-2"
|
|
18160
|
+
}]);
|
|
18161
|
+
$cb.checked = column.isVisible();
|
|
18162
|
+
$cb.addEventListener("change", () => {
|
|
18163
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_COLUMN", {
|
|
18164
|
+
field,
|
|
18165
|
+
visible: $cb.checked
|
|
18166
|
+
}, gobj);
|
|
18167
|
+
});
|
|
18168
|
+
$list.appendChild((0, _yuneta_gobj_js.createElement2)([
|
|
18169
|
+
"label",
|
|
18170
|
+
{ class: "checkbox is-block mb-1 TREEDB_COLUMN_ITEM" },
|
|
18171
|
+
[$cb, [
|
|
18172
|
+
"span",
|
|
18173
|
+
{ class: "ml-2" },
|
|
18174
|
+
column.getDefinition().title || field
|
|
18175
|
+
]]
|
|
18176
|
+
]));
|
|
18177
|
+
}
|
|
18178
|
+
yui_shell_show_modal(shell, $list, {
|
|
18179
|
+
dialog: true,
|
|
18180
|
+
logical_class: "TREEDB_COLUMNS_DIALOG",
|
|
18181
|
+
title_prefix: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
18182
|
+
title: "columns",
|
|
18183
|
+
t: i18next.t
|
|
18184
|
+
});
|
|
18185
|
+
return 0;
|
|
18186
|
+
}
|
|
18187
|
+
/************************************************************
|
|
18188
|
+
* Show / hide one column.
|
|
18189
|
+
************************************************************/
|
|
18190
|
+
function ac_toggle_column(gobj, event, kw, src) {
|
|
18191
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
18192
|
+
let field = kw && kw.field || "";
|
|
18193
|
+
if (!tabulator || !field) {
|
|
18194
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no column '${field}' to toggle`);
|
|
18195
|
+
return -1;
|
|
18196
|
+
}
|
|
18197
|
+
try {
|
|
18198
|
+
if (kw.visible) tabulator.showColumn(field);
|
|
18199
|
+
else tabulator.hideColumn(field);
|
|
18200
|
+
} catch (e) {
|
|
18201
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot toggle column '${field}': ${e}`);
|
|
18202
|
+
return -1;
|
|
18203
|
+
}
|
|
18204
|
+
return 0;
|
|
18205
|
+
}
|
|
18206
|
+
/************************************************************
|
|
18207
|
+
* Download what the table HOLDS as CSV: the loaded rows, the visible
|
|
18208
|
+
* columns, search and header filters applied — which is what the reader
|
|
18209
|
+
* is looking at. Not the topic: a server-side dump of every node is not
|
|
18210
|
+
* something this view can stream.
|
|
18211
|
+
************************************************************/
|
|
18212
|
+
function ac_export_table(gobj, event, kw, src) {
|
|
18213
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
18214
|
+
if (!tabulator) {
|
|
18215
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no table to export`);
|
|
18216
|
+
return -1;
|
|
18217
|
+
}
|
|
18218
|
+
let name = `${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}-${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name")}.csv`;
|
|
18219
|
+
name = name.replace(/[^\w.\-]+/g, "_");
|
|
18220
|
+
try {
|
|
18221
|
+
tabulator.download("csv", name);
|
|
18222
|
+
} catch (e) {
|
|
18223
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: CSV export failed: ${e}`);
|
|
18224
|
+
return -1;
|
|
18225
|
+
}
|
|
18226
|
+
return 0;
|
|
18227
|
+
}
|
|
18228
|
+
/************************************************************
|
|
18021
18229
|
* {
|
|
18022
18230
|
* href: href
|
|
18023
18231
|
* }
|
|
@@ -18141,6 +18349,26 @@ function create_gclass$5(gclass_name) {
|
|
|
18141
18349
|
ac_show_schema,
|
|
18142
18350
|
null
|
|
18143
18351
|
],
|
|
18352
|
+
[
|
|
18353
|
+
"EV_SEARCH",
|
|
18354
|
+
ac_search,
|
|
18355
|
+
null
|
|
18356
|
+
],
|
|
18357
|
+
[
|
|
18358
|
+
"EV_OPEN_COLUMNS",
|
|
18359
|
+
ac_open_columns,
|
|
18360
|
+
null
|
|
18361
|
+
],
|
|
18362
|
+
[
|
|
18363
|
+
"EV_TOGGLE_COLUMN",
|
|
18364
|
+
ac_toggle_column,
|
|
18365
|
+
null
|
|
18366
|
+
],
|
|
18367
|
+
[
|
|
18368
|
+
"EV_EXPORT_TABLE",
|
|
18369
|
+
ac_export_table,
|
|
18370
|
+
null
|
|
18371
|
+
],
|
|
18144
18372
|
[
|
|
18145
18373
|
"EV_SHOW",
|
|
18146
18374
|
ac_show$4,
|
|
@@ -18171,6 +18399,10 @@ function create_gclass$5(gclass_name) {
|
|
|
18171
18399
|
["EV_CHANGE_LOCALE", 0],
|
|
18172
18400
|
["EV_REFRESH", 0],
|
|
18173
18401
|
["EV_SHOW_SCHEMA", 0],
|
|
18402
|
+
["EV_SEARCH", 0],
|
|
18403
|
+
["EV_OPEN_COLUMNS", 0],
|
|
18404
|
+
["EV_TOGGLE_COLUMN", 0],
|
|
18405
|
+
["EV_EXPORT_TABLE", 0],
|
|
18174
18406
|
["EV_CREATE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
18175
18407
|
["EV_UPDATE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
18176
18408
|
["EV_DELETE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -11012,7 +11012,7 @@ function ac_expand_collapsed(gobj, event, kw, src) {
|
|
|
11012
11012
|
/************************************************************
|
|
11013
11013
|
* EV_SEARCH { text }
|
|
11014
11014
|
************************************************************/
|
|
11015
|
-
function ac_search(gobj, event, kw, src) {
|
|
11015
|
+
function ac_search$1(gobj, event, kw, src) {
|
|
11016
11016
|
let priv = gobj.priv;
|
|
11017
11017
|
priv.search = (kw.text || "").trim().toLowerCase();
|
|
11018
11018
|
render_tree(gobj);
|
|
@@ -11161,7 +11161,7 @@ function create_gclass$10(gclass_name) {
|
|
|
11161
11161
|
],
|
|
11162
11162
|
[
|
|
11163
11163
|
"EV_SEARCH",
|
|
11164
|
-
ac_search,
|
|
11164
|
+
ac_search$1,
|
|
11165
11165
|
null
|
|
11166
11166
|
],
|
|
11167
11167
|
[
|
|
@@ -16217,6 +16217,9 @@ var attrs_table$5 = [
|
|
|
16217
16217
|
SDATA(data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
|
|
16218
16218
|
SDATA(data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
|
|
16219
16219
|
SDATA(data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
|
|
16220
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
16221
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
|
|
16222
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_header_filters", 0, true, "Per-column filter box in the table header, on the columns a text/number match means something (not hooks, fkeys or json)"),
|
|
16220
16223
|
SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
|
|
16221
16224
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
16222
16225
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
@@ -16509,6 +16512,8 @@ function build_ui$5(gobj) {
|
|
|
16509
16512
|
let with_refresh_button = gobj_read_bool_attr(gobj, "with_refresh_button");
|
|
16510
16513
|
let with_search_button = gobj_read_bool_attr(gobj, "with_search_button");
|
|
16511
16514
|
let with_schema_button = gobj_read_bool_attr(gobj, "with_schema_button");
|
|
16515
|
+
let with_columns_button = gobj_read_bool_attr(gobj, "with_columns_button");
|
|
16516
|
+
let with_export_button = gobj_read_bool_attr(gobj, "with_export_button");
|
|
16512
16517
|
if (with_search_button) {
|
|
16513
16518
|
let search_id = `${toolbar_id}_search`;
|
|
16514
16519
|
let $search_box = createElement2([
|
|
@@ -16529,17 +16534,7 @@ function build_ui$5(gobj) {
|
|
|
16529
16534
|
let $search_input = $search_box.querySelector(`#${search_id}`);
|
|
16530
16535
|
attach_clear($search_box, $search_input);
|
|
16531
16536
|
if ($search_input) $search_input.addEventListener("input", (event) => {
|
|
16532
|
-
|
|
16533
|
-
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
16534
|
-
if (!tabulator) return;
|
|
16535
|
-
if (searchText) tabulator.setFilter(function(data) {
|
|
16536
|
-
return Object.entries(data).some(([key, val]) => {
|
|
16537
|
-
if (key.startsWith("_")) return false;
|
|
16538
|
-
if (val === null || val === void 0) return false;
|
|
16539
|
-
return String(val).toLowerCase().includes(searchText);
|
|
16540
|
-
});
|
|
16541
|
-
});
|
|
16542
|
-
else tabulator.clearFilter();
|
|
16537
|
+
gobj_send_event(gobj, "EV_SEARCH", { text: event.target.value.trim() }, gobj);
|
|
16543
16538
|
});
|
|
16544
16539
|
$view_toolbar.appendChild($search_box);
|
|
16545
16540
|
}
|
|
@@ -16593,6 +16588,58 @@ function build_ui$5(gobj) {
|
|
|
16593
16588
|
]);
|
|
16594
16589
|
$view_toolbar.appendChild($schema);
|
|
16595
16590
|
}
|
|
16591
|
+
if (with_columns_button) {
|
|
16592
|
+
let $columns = createElement2([
|
|
16593
|
+
"button",
|
|
16594
|
+
{
|
|
16595
|
+
class: "TREEDB_TABLE_COLUMNS button mr-1",
|
|
16596
|
+
title: t("choose the columns to show"),
|
|
16597
|
+
"data-i18n-title": "choose the columns to show",
|
|
16598
|
+
"aria-label": t("columns"),
|
|
16599
|
+
"data-i18n-aria-label": "columns"
|
|
16600
|
+
},
|
|
16601
|
+
[["i", { class: "yi-table" }], [
|
|
16602
|
+
"span",
|
|
16603
|
+
{
|
|
16604
|
+
class: "is-hidden-mobile",
|
|
16605
|
+
i18n: "columns",
|
|
16606
|
+
style: "padding-left:5px;"
|
|
16607
|
+
},
|
|
16608
|
+
"columns"
|
|
16609
|
+
]],
|
|
16610
|
+
{ "click": (event) => {
|
|
16611
|
+
event.stopPropagation();
|
|
16612
|
+
gobj_send_event(gobj, "EV_OPEN_COLUMNS", {}, gobj);
|
|
16613
|
+
} }
|
|
16614
|
+
]);
|
|
16615
|
+
$view_toolbar.appendChild($columns);
|
|
16616
|
+
}
|
|
16617
|
+
if (with_export_button) {
|
|
16618
|
+
let $export = createElement2([
|
|
16619
|
+
"button",
|
|
16620
|
+
{
|
|
16621
|
+
class: "TREEDB_TABLE_EXPORT button mr-1",
|
|
16622
|
+
title: t("download the rows loaded in this table as csv"),
|
|
16623
|
+
"data-i18n-title": "download the rows loaded in this table as csv",
|
|
16624
|
+
"aria-label": t("export"),
|
|
16625
|
+
"data-i18n-aria-label": "export"
|
|
16626
|
+
},
|
|
16627
|
+
[["i", { class: "yi-download" }], [
|
|
16628
|
+
"span",
|
|
16629
|
+
{
|
|
16630
|
+
class: "is-hidden-mobile",
|
|
16631
|
+
i18n: "export",
|
|
16632
|
+
style: "padding-left:5px;"
|
|
16633
|
+
},
|
|
16634
|
+
"export"
|
|
16635
|
+
]],
|
|
16636
|
+
{ "click": (event) => {
|
|
16637
|
+
event.stopPropagation();
|
|
16638
|
+
gobj_send_event(gobj, "EV_EXPORT_TABLE", {}, gobj);
|
|
16639
|
+
} }
|
|
16640
|
+
]);
|
|
16641
|
+
$view_toolbar.appendChild($export);
|
|
16642
|
+
}
|
|
16596
16643
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
16597
16644
|
let $container = createElement2([
|
|
16598
16645
|
"div",
|
|
@@ -16681,6 +16728,7 @@ function col_title_formatter(gobj, col) {
|
|
|
16681
16728
|
function create_tabulator(gobj) {
|
|
16682
16729
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
16683
16730
|
let desc = gobj_read_attr(gobj, "desc");
|
|
16731
|
+
let with_header_filters = gobj_read_bool_attr(gobj, "with_header_filters");
|
|
16684
16732
|
let columns = [];
|
|
16685
16733
|
let with_checkbox = gobj_read_bool_attr(gobj, "with_checkbox");
|
|
16686
16734
|
let with_radio = gobj_read_bool_attr(gobj, "with_radio");
|
|
@@ -16724,6 +16772,46 @@ function create_tabulator(gobj) {
|
|
|
16724
16772
|
col_id: cell.getField()
|
|
16725
16773
|
}, gobj);
|
|
16726
16774
|
}
|
|
16775
|
+
const FILTERABLE = [
|
|
16776
|
+
"string",
|
|
16777
|
+
"integer",
|
|
16778
|
+
"real",
|
|
16779
|
+
"rowid",
|
|
16780
|
+
"uuid",
|
|
16781
|
+
"qualified",
|
|
16782
|
+
"email",
|
|
16783
|
+
"url",
|
|
16784
|
+
"tel",
|
|
16785
|
+
"id",
|
|
16786
|
+
"hex",
|
|
16787
|
+
"currency",
|
|
16788
|
+
"percent"
|
|
16789
|
+
];
|
|
16790
|
+
function fkey_matches(term, value) {
|
|
16791
|
+
if (term === "" || term === null || term === void 0) return true;
|
|
16792
|
+
if (value === null || value === void 0) return false;
|
|
16793
|
+
return (typeof value === "string" ? value : JSON.stringify(value)).toLowerCase().includes(String(term).toLowerCase());
|
|
16794
|
+
}
|
|
16795
|
+
function apply_header_filter(colDef, field_desc) {
|
|
16796
|
+
switch (field_desc.type) {
|
|
16797
|
+
case "boolean":
|
|
16798
|
+
colDef.headerFilter = "tickCross";
|
|
16799
|
+
colDef.headerFilterParams = { tristate: true };
|
|
16800
|
+
break;
|
|
16801
|
+
case "enum":
|
|
16802
|
+
colDef.headerFilter = "list";
|
|
16803
|
+
colDef.headerFilterParams = {
|
|
16804
|
+
values: field_desc.enum_list || [],
|
|
16805
|
+
clearable: true
|
|
16806
|
+
};
|
|
16807
|
+
break;
|
|
16808
|
+
case "fkey":
|
|
16809
|
+
colDef.headerFilter = "input";
|
|
16810
|
+
colDef.headerFilterFunc = fkey_matches;
|
|
16811
|
+
break;
|
|
16812
|
+
default: if (FILTERABLE.includes(field_desc.type)) colDef.headerFilter = "input";
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16727
16815
|
for (let i = 0; i < desc.cols.length; i++) {
|
|
16728
16816
|
let col = desc.cols[i];
|
|
16729
16817
|
if (!col.id || col.id[0] === "_") continue;
|
|
@@ -16804,6 +16892,7 @@ function create_tabulator(gobj) {
|
|
|
16804
16892
|
hozAlign,
|
|
16805
16893
|
formatter: colFormatter
|
|
16806
16894
|
};
|
|
16895
|
+
if (with_header_filters) apply_header_filter(colDef, field_desc);
|
|
16807
16896
|
if (vertAlign) colDef.vertAlign = vertAlign;
|
|
16808
16897
|
if (formatterParams) colDef.formatterParams = formatterParams;
|
|
16809
16898
|
if (cellClick) colDef.cellClick = cellClick;
|
|
@@ -18013,6 +18102,125 @@ function ac_show_schema(gobj, event, kw, src) {
|
|
|
18013
18102
|
return 0;
|
|
18014
18103
|
}
|
|
18015
18104
|
/************************************************************
|
|
18105
|
+
* Filter the loaded rows by a term matched against every non-internal
|
|
18106
|
+
* field. `clearFilter()` with no argument drops only THIS filter: the
|
|
18107
|
+
* per-column header filters are a separate layer and survive, so a
|
|
18108
|
+
* cleared search box does not silently undo them.
|
|
18109
|
+
************************************************************/
|
|
18110
|
+
function ac_search(gobj, event, kw, src) {
|
|
18111
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
18112
|
+
if (!tabulator) {
|
|
18113
|
+
log_error(`${gobj_short_name(gobj)}: no table to search`);
|
|
18114
|
+
return -1;
|
|
18115
|
+
}
|
|
18116
|
+
let term = (kw && kw.text || "").toLowerCase();
|
|
18117
|
+
if (!term) {
|
|
18118
|
+
tabulator.clearFilter();
|
|
18119
|
+
return 0;
|
|
18120
|
+
}
|
|
18121
|
+
tabulator.setFilter(function(data) {
|
|
18122
|
+
return Object.entries(data).some(([key, val]) => {
|
|
18123
|
+
if (key.startsWith("_")) return false;
|
|
18124
|
+
if (val === null || val === void 0) return false;
|
|
18125
|
+
return String(val).toLowerCase().includes(term);
|
|
18126
|
+
});
|
|
18127
|
+
});
|
|
18128
|
+
return 0;
|
|
18129
|
+
}
|
|
18130
|
+
/************************************************************
|
|
18131
|
+
* Which columns the table shows: one checkbox per column, the current
|
|
18132
|
+
* visibility ticked. The internal columns (`_check_box_state_`,
|
|
18133
|
+
* `_operation`) are not offered — they are chrome, not data.
|
|
18134
|
+
************************************************************/
|
|
18135
|
+
function ac_open_columns(gobj, event, kw, src) {
|
|
18136
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
18137
|
+
if (!tabulator) {
|
|
18138
|
+
log_error(`${gobj_short_name(gobj)}: no table, no columns to choose`);
|
|
18139
|
+
return -1;
|
|
18140
|
+
}
|
|
18141
|
+
let shell = yui_shell_of(gobj);
|
|
18142
|
+
if (!shell) {
|
|
18143
|
+
log_error(`${gobj_short_name(gobj)}: no shell, cannot open the column chooser`);
|
|
18144
|
+
return -1;
|
|
18145
|
+
}
|
|
18146
|
+
let $list = createElement2(["div", { class: "TREEDB_COLUMNS_LIST" }]);
|
|
18147
|
+
let columns = tabulator.getColumns();
|
|
18148
|
+
for (let i = 0; i < columns.length; i++) {
|
|
18149
|
+
let column = columns[i];
|
|
18150
|
+
let field = column.getField();
|
|
18151
|
+
if (!field || field[0] === "_") continue;
|
|
18152
|
+
let $cb = createElement2(["input", {
|
|
18153
|
+
type: "checkbox",
|
|
18154
|
+
class: "mr-2"
|
|
18155
|
+
}]);
|
|
18156
|
+
$cb.checked = column.isVisible();
|
|
18157
|
+
$cb.addEventListener("change", () => {
|
|
18158
|
+
gobj_send_event(gobj, "EV_TOGGLE_COLUMN", {
|
|
18159
|
+
field,
|
|
18160
|
+
visible: $cb.checked
|
|
18161
|
+
}, gobj);
|
|
18162
|
+
});
|
|
18163
|
+
$list.appendChild(createElement2([
|
|
18164
|
+
"label",
|
|
18165
|
+
{ class: "checkbox is-block mb-1 TREEDB_COLUMN_ITEM" },
|
|
18166
|
+
[$cb, [
|
|
18167
|
+
"span",
|
|
18168
|
+
{ class: "ml-2" },
|
|
18169
|
+
column.getDefinition().title || field
|
|
18170
|
+
]]
|
|
18171
|
+
]));
|
|
18172
|
+
}
|
|
18173
|
+
yui_shell_show_modal(shell, $list, {
|
|
18174
|
+
dialog: true,
|
|
18175
|
+
logical_class: "TREEDB_COLUMNS_DIALOG",
|
|
18176
|
+
title_prefix: gobj_read_str_attr(gobj, "topic_name"),
|
|
18177
|
+
title: "columns",
|
|
18178
|
+
t
|
|
18179
|
+
});
|
|
18180
|
+
return 0;
|
|
18181
|
+
}
|
|
18182
|
+
/************************************************************
|
|
18183
|
+
* Show / hide one column.
|
|
18184
|
+
************************************************************/
|
|
18185
|
+
function ac_toggle_column(gobj, event, kw, src) {
|
|
18186
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
18187
|
+
let field = kw && kw.field || "";
|
|
18188
|
+
if (!tabulator || !field) {
|
|
18189
|
+
log_error(`${gobj_short_name(gobj)}: no column '${field}' to toggle`);
|
|
18190
|
+
return -1;
|
|
18191
|
+
}
|
|
18192
|
+
try {
|
|
18193
|
+
if (kw.visible) tabulator.showColumn(field);
|
|
18194
|
+
else tabulator.hideColumn(field);
|
|
18195
|
+
} catch (e) {
|
|
18196
|
+
log_error(`${gobj_short_name(gobj)}: cannot toggle column '${field}': ${e}`);
|
|
18197
|
+
return -1;
|
|
18198
|
+
}
|
|
18199
|
+
return 0;
|
|
18200
|
+
}
|
|
18201
|
+
/************************************************************
|
|
18202
|
+
* Download what the table HOLDS as CSV: the loaded rows, the visible
|
|
18203
|
+
* columns, search and header filters applied — which is what the reader
|
|
18204
|
+
* is looking at. Not the topic: a server-side dump of every node is not
|
|
18205
|
+
* something this view can stream.
|
|
18206
|
+
************************************************************/
|
|
18207
|
+
function ac_export_table(gobj, event, kw, src) {
|
|
18208
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
18209
|
+
if (!tabulator) {
|
|
18210
|
+
log_error(`${gobj_short_name(gobj)}: no table to export`);
|
|
18211
|
+
return -1;
|
|
18212
|
+
}
|
|
18213
|
+
let name = `${gobj_read_str_attr(gobj, "treedb_name")}-${gobj_read_str_attr(gobj, "topic_name")}.csv`;
|
|
18214
|
+
name = name.replace(/[^\w.\-]+/g, "_");
|
|
18215
|
+
try {
|
|
18216
|
+
tabulator.download("csv", name);
|
|
18217
|
+
} catch (e) {
|
|
18218
|
+
log_error(`${gobj_short_name(gobj)}: CSV export failed: ${e}`);
|
|
18219
|
+
return -1;
|
|
18220
|
+
}
|
|
18221
|
+
return 0;
|
|
18222
|
+
}
|
|
18223
|
+
/************************************************************
|
|
18016
18224
|
* {
|
|
18017
18225
|
* href: href
|
|
18018
18226
|
* }
|
|
@@ -18136,6 +18344,26 @@ function create_gclass$5(gclass_name) {
|
|
|
18136
18344
|
ac_show_schema,
|
|
18137
18345
|
null
|
|
18138
18346
|
],
|
|
18347
|
+
[
|
|
18348
|
+
"EV_SEARCH",
|
|
18349
|
+
ac_search,
|
|
18350
|
+
null
|
|
18351
|
+
],
|
|
18352
|
+
[
|
|
18353
|
+
"EV_OPEN_COLUMNS",
|
|
18354
|
+
ac_open_columns,
|
|
18355
|
+
null
|
|
18356
|
+
],
|
|
18357
|
+
[
|
|
18358
|
+
"EV_TOGGLE_COLUMN",
|
|
18359
|
+
ac_toggle_column,
|
|
18360
|
+
null
|
|
18361
|
+
],
|
|
18362
|
+
[
|
|
18363
|
+
"EV_EXPORT_TABLE",
|
|
18364
|
+
ac_export_table,
|
|
18365
|
+
null
|
|
18366
|
+
],
|
|
18139
18367
|
[
|
|
18140
18368
|
"EV_SHOW",
|
|
18141
18369
|
ac_show$4,
|
|
@@ -18166,6 +18394,10 @@ function create_gclass$5(gclass_name) {
|
|
|
18166
18394
|
["EV_CHANGE_LOCALE", 0],
|
|
18167
18395
|
["EV_REFRESH", 0],
|
|
18168
18396
|
["EV_SHOW_SCHEMA", 0],
|
|
18397
|
+
["EV_SEARCH", 0],
|
|
18398
|
+
["EV_OPEN_COLUMNS", 0],
|
|
18399
|
+
["EV_TOGGLE_COLUMN", 0],
|
|
18400
|
+
["EV_EXPORT_TABLE", 0],
|
|
18169
18401
|
["EV_CREATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
18170
18402
|
["EV_UPDATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
18171
18403
|
["EV_DELETE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-ui",
|
|
3
|
-
"version": "7.0
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/gobj-ui.cjs.js",
|
|
6
6
|
"module": "dist/gobj-ui.es.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"src/",
|
|
21
21
|
"vite-plugin-yuneta-html.js"
|
|
22
22
|
],
|
|
23
|
-
"description": "Yuneta UI Library
|
|
23
|
+
"description": "Yuneta UI Library — the active line: declarative shell (C_YUI_SHELL+NAV+PAGER+WIZARD) + windows, TreeDB views, forms, charts and maps. Consumed by wattyzer and the yunetas JS yunos (gui_agent, gui_treedb). The frozen legacy GClass GUI stack (C_YUI_MAIN/TABS/ROUTING) lives on the v1 branch, npm dist-tag 'legacy' (consumed by estadodelaire and hidraulia).",
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "vite build",
|
|
26
26
|
"test": "vitest run",
|
|
@@ -108,6 +108,9 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_paste_button", 0, true, "Button
|
|
|
108
108
|
SDATA(data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
|
|
109
109
|
SDATA(data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
|
|
110
110
|
SDATA(data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
|
|
111
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
112
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
|
|
113
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_header_filters", 0, true, "Per-column filter box in the table header, on the columns a text/number match means something (not hooks, fkeys or json)"),
|
|
111
114
|
SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
|
|
112
115
|
|
|
113
116
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
@@ -496,6 +499,8 @@ function build_ui(gobj)
|
|
|
496
499
|
let with_refresh_button = gobj_read_bool_attr(gobj, "with_refresh_button");
|
|
497
500
|
let with_search_button = gobj_read_bool_attr(gobj, "with_search_button");
|
|
498
501
|
let with_schema_button = gobj_read_bool_attr(gobj, "with_schema_button");
|
|
502
|
+
let with_columns_button = gobj_read_bool_attr(gobj, "with_columns_button");
|
|
503
|
+
let with_export_button = gobj_read_bool_attr(gobj, "with_export_button");
|
|
499
504
|
|
|
500
505
|
if(with_search_button) {
|
|
501
506
|
let search_id = `${toolbar_id}_search`;
|
|
@@ -521,27 +526,16 @@ function build_ui(gobj)
|
|
|
521
526
|
* filter below re-runs (empty term → clearFilter). */
|
|
522
527
|
attach_clear($search_box, $search_input);
|
|
523
528
|
if($search_input) {
|
|
529
|
+
/* The DOM handler's only job is to turn the keystroke into an
|
|
530
|
+
* event: searching is a user action, so it crosses the FSM and
|
|
531
|
+
* shows up in the `machine` trace like every other one. */
|
|
524
532
|
$search_input.addEventListener('input', (event) => {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
tabulator.setFilter(function(data) {
|
|
532
|
-
return Object.entries(data).some(([key, val]) => {
|
|
533
|
-
if(key.startsWith('_')) {
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
if(val === null || val === undefined) {
|
|
537
|
-
return false;
|
|
538
|
-
}
|
|
539
|
-
return String(val).toLowerCase().includes(searchText);
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
} else {
|
|
543
|
-
tabulator.clearFilter();
|
|
544
|
-
}
|
|
533
|
+
gobj_send_event(
|
|
534
|
+
gobj,
|
|
535
|
+
"EV_SEARCH",
|
|
536
|
+
{text: event.target.value.trim()},
|
|
537
|
+
gobj
|
|
538
|
+
);
|
|
545
539
|
});
|
|
546
540
|
}
|
|
547
541
|
$view_toolbar.appendChild($search_box);
|
|
@@ -583,6 +577,48 @@ function build_ui(gobj)
|
|
|
583
577
|
$view_toolbar.appendChild($schema);
|
|
584
578
|
}
|
|
585
579
|
|
|
580
|
+
if(with_columns_button) {
|
|
581
|
+
/* A topic with a dozen columns is wider than the screen, and until
|
|
582
|
+
* now the reader could not decide WHICH of them to keep: the table
|
|
583
|
+
* simply scrolled sideways for ever. */
|
|
584
|
+
let $columns = createElement2(
|
|
585
|
+
['button', {class: 'TREEDB_TABLE_COLUMNS button mr-1',
|
|
586
|
+
title: t('choose the columns to show'),
|
|
587
|
+
'data-i18n-title': 'choose the columns to show',
|
|
588
|
+
'aria-label': t('columns'), 'data-i18n-aria-label': 'columns'}, [
|
|
589
|
+
['i', {class: 'yi-table'}],
|
|
590
|
+
['span', {class: 'is-hidden-mobile', i18n: 'columns', style: 'padding-left:5px;'}, 'columns']
|
|
591
|
+
], {
|
|
592
|
+
'click': (event) => {
|
|
593
|
+
event.stopPropagation();
|
|
594
|
+
gobj_send_event(gobj, "EV_OPEN_COLUMNS", {}, gobj);
|
|
595
|
+
}
|
|
596
|
+
}]
|
|
597
|
+
);
|
|
598
|
+
$view_toolbar.appendChild($columns);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if(with_export_button) {
|
|
602
|
+
/* What the TABLE holds, filters and column choice applied — not the
|
|
603
|
+
* topic. A server-side dump of every node is not something this view
|
|
604
|
+
* can stream, and the title says so. */
|
|
605
|
+
let $export = createElement2(
|
|
606
|
+
['button', {class: 'TREEDB_TABLE_EXPORT button mr-1',
|
|
607
|
+
title: t('download the rows loaded in this table as csv'),
|
|
608
|
+
'data-i18n-title': 'download the rows loaded in this table as csv',
|
|
609
|
+
'aria-label': t('export'), 'data-i18n-aria-label': 'export'}, [
|
|
610
|
+
['i', {class: 'yi-download'}],
|
|
611
|
+
['span', {class: 'is-hidden-mobile', i18n: 'export', style: 'padding-left:5px;'}, 'export']
|
|
612
|
+
], {
|
|
613
|
+
'click': (event) => {
|
|
614
|
+
event.stopPropagation();
|
|
615
|
+
gobj_send_event(gobj, "EV_EXPORT_TABLE", {}, gobj);
|
|
616
|
+
}
|
|
617
|
+
}]
|
|
618
|
+
);
|
|
619
|
+
$view_toolbar.appendChild($export);
|
|
620
|
+
}
|
|
621
|
+
|
|
586
622
|
/*----------------------------------------------*
|
|
587
623
|
* Layout Schema
|
|
588
624
|
*----------------------------------------------*/
|
|
@@ -693,6 +729,7 @@ function create_tabulator(gobj)
|
|
|
693
729
|
{
|
|
694
730
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
695
731
|
let desc = gobj_read_attr(gobj, "desc");
|
|
732
|
+
let with_header_filters = gobj_read_bool_attr(gobj, "with_header_filters");
|
|
696
733
|
|
|
697
734
|
let columns = [];
|
|
698
735
|
|
|
@@ -773,6 +810,58 @@ function create_tabulator(gobj)
|
|
|
773
810
|
);
|
|
774
811
|
}
|
|
775
812
|
|
|
813
|
+
/* A filter box per column, on the columns where a text/number match
|
|
814
|
+
* MEANS something. A hook holds children, a dict holds a subtree and a
|
|
815
|
+
* date cell shows a formatted string over an epoch number — matching
|
|
816
|
+
* the raw value there answers a question nobody asked, so those columns
|
|
817
|
+
* get no box rather than a box that lies. An fkey does get one: "which
|
|
818
|
+
* rows point at X" is the question fkey columns exist to answer, and its
|
|
819
|
+
* value is stringified first because a fkey can arrive as a ref string,
|
|
820
|
+
* a list of them or a dict.
|
|
821
|
+
*/
|
|
822
|
+
const FILTERABLE = [
|
|
823
|
+
"string", "integer", "real", "rowid", "uuid", "qualified",
|
|
824
|
+
"email", "url", "tel", "id", "hex", "currency", "percent"
|
|
825
|
+
];
|
|
826
|
+
|
|
827
|
+
function fkey_matches(term, value)
|
|
828
|
+
{
|
|
829
|
+
if(term === "" || term === null || term === undefined) {
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
if(value === null || value === undefined) {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
let text = (typeof value === "string")? value : JSON.stringify(value);
|
|
836
|
+
return text.toLowerCase().includes(String(term).toLowerCase());
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function apply_header_filter(colDef, field_desc)
|
|
840
|
+
{
|
|
841
|
+
switch(field_desc.type) {
|
|
842
|
+
case "boolean":
|
|
843
|
+
colDef.headerFilter = "tickCross";
|
|
844
|
+
colDef.headerFilterParams = {tristate: true};
|
|
845
|
+
break;
|
|
846
|
+
case "enum":
|
|
847
|
+
colDef.headerFilter = "list";
|
|
848
|
+
colDef.headerFilterParams = {
|
|
849
|
+
values: field_desc.enum_list || [],
|
|
850
|
+
clearable: true
|
|
851
|
+
};
|
|
852
|
+
break;
|
|
853
|
+
case "fkey":
|
|
854
|
+
colDef.headerFilter = "input";
|
|
855
|
+
colDef.headerFilterFunc = fkey_matches;
|
|
856
|
+
break;
|
|
857
|
+
default:
|
|
858
|
+
if(FILTERABLE.includes(field_desc.type)) {
|
|
859
|
+
colDef.headerFilter = "input";
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
776
865
|
for (let i = 0; i < desc.cols.length; i++) {
|
|
777
866
|
let col = desc.cols[i];
|
|
778
867
|
if(!col.id || col.id[0]==='_') {
|
|
@@ -866,6 +955,9 @@ function create_tabulator(gobj)
|
|
|
866
955
|
hozAlign: hozAlign,
|
|
867
956
|
formatter: colFormatter,
|
|
868
957
|
};
|
|
958
|
+
if(with_header_filters) {
|
|
959
|
+
apply_header_filter(colDef, field_desc);
|
|
960
|
+
}
|
|
869
961
|
if(vertAlign) {
|
|
870
962
|
colDef.vertAlign = vertAlign;
|
|
871
963
|
}
|
|
@@ -2719,6 +2811,151 @@ function ac_show_schema(gobj, event, kw, src)
|
|
|
2719
2811
|
return 0;
|
|
2720
2812
|
}
|
|
2721
2813
|
|
|
2814
|
+
/************************************************************
|
|
2815
|
+
* Filter the loaded rows by a term matched against every non-internal
|
|
2816
|
+
* field. `clearFilter()` with no argument drops only THIS filter: the
|
|
2817
|
+
* per-column header filters are a separate layer and survive, so a
|
|
2818
|
+
* cleared search box does not silently undo them.
|
|
2819
|
+
************************************************************/
|
|
2820
|
+
function ac_search(gobj, event, kw, src)
|
|
2821
|
+
{
|
|
2822
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2823
|
+
if(!tabulator) {
|
|
2824
|
+
log_error(`${gobj_short_name(gobj)}: no table to search`);
|
|
2825
|
+
return -1;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
let term = ((kw && kw.text) || "").toLowerCase();
|
|
2829
|
+
if(!term) {
|
|
2830
|
+
tabulator.clearFilter();
|
|
2831
|
+
return 0;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
tabulator.setFilter(function(data) {
|
|
2835
|
+
return Object.entries(data).some(([key, val]) => {
|
|
2836
|
+
if(key.startsWith('_')) {
|
|
2837
|
+
return false;
|
|
2838
|
+
}
|
|
2839
|
+
if(val === null || val === undefined) {
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
return String(val).toLowerCase().includes(term);
|
|
2843
|
+
});
|
|
2844
|
+
});
|
|
2845
|
+
|
|
2846
|
+
return 0;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
/************************************************************
|
|
2850
|
+
* Which columns the table shows: one checkbox per column, the current
|
|
2851
|
+
* visibility ticked. The internal columns (`_check_box_state_`,
|
|
2852
|
+
* `_operation`) are not offered — they are chrome, not data.
|
|
2853
|
+
************************************************************/
|
|
2854
|
+
function ac_open_columns(gobj, event, kw, src)
|
|
2855
|
+
{
|
|
2856
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2857
|
+
if(!tabulator) {
|
|
2858
|
+
log_error(`${gobj_short_name(gobj)}: no table, no columns to choose`);
|
|
2859
|
+
return -1;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
let shell = yui_shell_of(gobj);
|
|
2863
|
+
if(!shell) {
|
|
2864
|
+
log_error(`${gobj_short_name(gobj)}: no shell, cannot open the column chooser`);
|
|
2865
|
+
return -1;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
let $list = createElement2(['div', {class: 'TREEDB_COLUMNS_LIST'}]);
|
|
2869
|
+
let columns = tabulator.getColumns();
|
|
2870
|
+
for(let i = 0; i < columns.length; i++) {
|
|
2871
|
+
let column = columns[i];
|
|
2872
|
+
let field = column.getField();
|
|
2873
|
+
if(!field || field[0] === '_') {
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
let $cb = createElement2(['input', {type: 'checkbox', class: 'mr-2'}]);
|
|
2877
|
+
$cb.checked = column.isVisible();
|
|
2878
|
+
$cb.addEventListener('change', () => {
|
|
2879
|
+
gobj_send_event(
|
|
2880
|
+
gobj,
|
|
2881
|
+
"EV_TOGGLE_COLUMN",
|
|
2882
|
+
{field: field, visible: $cb.checked},
|
|
2883
|
+
gobj
|
|
2884
|
+
);
|
|
2885
|
+
});
|
|
2886
|
+
$list.appendChild(createElement2(
|
|
2887
|
+
['label', {class: 'checkbox is-block mb-1 TREEDB_COLUMN_ITEM'}, [
|
|
2888
|
+
$cb,
|
|
2889
|
+
['span', {class: 'ml-2'}, column.getDefinition().title || field]
|
|
2890
|
+
]]
|
|
2891
|
+
));
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
yui_shell_show_modal(shell, $list, {
|
|
2895
|
+
dialog: true,
|
|
2896
|
+
logical_class: "TREEDB_COLUMNS_DIALOG",
|
|
2897
|
+
title_prefix: gobj_read_str_attr(gobj, "topic_name"),
|
|
2898
|
+
title: "columns",
|
|
2899
|
+
t: t
|
|
2900
|
+
});
|
|
2901
|
+
|
|
2902
|
+
return 0;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
/************************************************************
|
|
2906
|
+
* Show / hide one column.
|
|
2907
|
+
************************************************************/
|
|
2908
|
+
function ac_toggle_column(gobj, event, kw, src)
|
|
2909
|
+
{
|
|
2910
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2911
|
+
let field = (kw && kw.field) || "";
|
|
2912
|
+
if(!tabulator || !field) {
|
|
2913
|
+
log_error(`${gobj_short_name(gobj)}: no column '${field}' to toggle`);
|
|
2914
|
+
return -1;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
try {
|
|
2918
|
+
if(kw.visible) {
|
|
2919
|
+
tabulator.showColumn(field);
|
|
2920
|
+
} else {
|
|
2921
|
+
tabulator.hideColumn(field);
|
|
2922
|
+
}
|
|
2923
|
+
} catch(e) {
|
|
2924
|
+
log_error(`${gobj_short_name(gobj)}: cannot toggle column '${field}': ${e}`);
|
|
2925
|
+
return -1;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
return 0;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
/************************************************************
|
|
2932
|
+
* Download what the table HOLDS as CSV: the loaded rows, the visible
|
|
2933
|
+
* columns, search and header filters applied — which is what the reader
|
|
2934
|
+
* is looking at. Not the topic: a server-side dump of every node is not
|
|
2935
|
+
* something this view can stream.
|
|
2936
|
+
************************************************************/
|
|
2937
|
+
function ac_export_table(gobj, event, kw, src)
|
|
2938
|
+
{
|
|
2939
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
2940
|
+
if(!tabulator) {
|
|
2941
|
+
log_error(`${gobj_short_name(gobj)}: no table to export`);
|
|
2942
|
+
return -1;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
let name = `${gobj_read_str_attr(gobj, "treedb_name")}-` +
|
|
2946
|
+
`${gobj_read_str_attr(gobj, "topic_name")}.csv`;
|
|
2947
|
+
name = name.replace(/[^\w.\-]+/g, "_");
|
|
2948
|
+
|
|
2949
|
+
try {
|
|
2950
|
+
tabulator.download("csv", name);
|
|
2951
|
+
} catch(e) {
|
|
2952
|
+
log_error(`${gobj_short_name(gobj)}: CSV export failed: ${e}`);
|
|
2953
|
+
return -1;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
return 0;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2722
2959
|
/************************************************************
|
|
2723
2960
|
* {
|
|
2724
2961
|
* href: href
|
|
@@ -2795,6 +3032,10 @@ function create_gclass(gclass_name)
|
|
|
2795
3032
|
["EV_CHANGE_LOCALE", ac_change_locale, null],
|
|
2796
3033
|
["EV_REFRESH", ac_refresh, null],
|
|
2797
3034
|
["EV_SHOW_SCHEMA", ac_show_schema, null],
|
|
3035
|
+
["EV_SEARCH", ac_search, null],
|
|
3036
|
+
["EV_OPEN_COLUMNS", ac_open_columns, null],
|
|
3037
|
+
["EV_TOGGLE_COLUMN", ac_toggle_column, null],
|
|
3038
|
+
["EV_EXPORT_TABLE", ac_export_table, null],
|
|
2798
3039
|
["EV_SHOW", ac_show, null],
|
|
2799
3040
|
["EV_HIDE", ac_hide, null]
|
|
2800
3041
|
]]
|
|
@@ -2823,6 +3064,10 @@ function create_gclass(gclass_name)
|
|
|
2823
3064
|
["EV_CHANGE_LOCALE", 0],
|
|
2824
3065
|
["EV_REFRESH", 0],
|
|
2825
3066
|
["EV_SHOW_SCHEMA", 0],
|
|
3067
|
+
["EV_SEARCH", 0],
|
|
3068
|
+
["EV_OPEN_COLUMNS", 0],
|
|
3069
|
+
["EV_TOGGLE_COLUMN", 0],
|
|
3070
|
+
["EV_EXPORT_TABLE", 0],
|
|
2826
3071
|
|
|
2827
3072
|
["EV_CREATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
2828
3073
|
["EV_UPDATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|