@yuneta/gobj-ui 7.23.161 → 7.23.162

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 CHANGED
@@ -805,6 +805,62 @@ Searching is a user action, so it crosses the FSM (`EV_SEARCH`) like the rest;
805
805
  it used to call `tabulator.setFilter` straight from the DOM handler, where the
806
806
  `machine` trace could not see it.
807
807
 
808
+ ### A hook opens the rows it links
809
+
810
+ A hook cell shows how many children the row has (`[5675]`). A click on it
811
+ opens the **child topic's table, filtered** to the rows whose fkey names this
812
+ row (since `7.23.162`). Above the table, a chip says which filter is on,
813
+ "filtered by device_types electric_bt", and its ✕ brings the whole topic back.
814
+
815
+ The child table already pages, searches, opens a record and exports. Until
816
+ `7.23.162` the click opened a popup that listed every child id instead:
817
+ 5,675 rows in a box with no height, no scroll and no way to close it but a
818
+ click inside, and the ids did nothing.
819
+
820
+ - The child topic and its fkey come from the hook's own mapping in the
821
+ schema, `{child_topic: fkey_col}`:
822
+
823
+ ```c
824
+ 'devices': {
825
+ 'header': 'Equipos',
826
+ 'type': 'object',
827
+ 'flag': ['hook'],
828
+ 'hook': {
829
+ 'devices': 'place'
830
+ }
831
+ }
832
+ ```
833
+
834
+ A click on this hook opens `devices`, filtered to the rows whose `place`
835
+ names the clicked row.
836
+ - A hook whose children live in more than one topic asks which one, in the
837
+ standardized dialog (Escape, Back and a click outside close it).
838
+ - The switch goes through the same entry point as a tab click, so the host
839
+ puts it in the URL and Back returns to the topic the hook was on. The
840
+ filter itself is not in the URL.
841
+ - The parent filter and the search box are **one** filter. Tabulator's
842
+ `setFilter()` replaces every programmatic filter, so as two filters each
843
+ would wipe the other.
844
+ - A child row matches when its fkey names the parent id, and also its topic
845
+ and hook when the ref carries them: one row can hang from the same parent
846
+ through two hooks.
847
+ - The filter works on the rows the table holds. With `with_remote_paging`
848
+ that is one page, which is one more reason that flag stays off.
849
+
850
+ The table asks its host to switch, because the tabs belong to the host:
851
+
852
+ ```
853
+ C_YUI_TREEDB_TOPIC_WITH_FORM C_YUI_TREEDB_TOPICS
854
+ EV_OPEN_LINKED {topic_name: "devices", fkey: "place",
855
+ parent_topic: "places", parent_id: "es", hook: "devices"} ──►
856
+ select the tab of `devices`
857
+ EV_FILTER_BY_PARENT {fkey, parent_topic, parent_id, hook} ◄── to that topic's table
858
+ ```
859
+
860
+ `EV_CLEAR_PARENT_FILTER` is the ✕, and `EV_CHOOSE_LINKED` is a pick in the
861
+ "which topic" dialog. All three are input events; `EV_OPEN_LINKED` is the one
862
+ output event, and `C_YUI_TREEDB_TOPICS` declares it.
863
+
808
864
  ### Read-only treedbs: `readonly`
809
865
 
810
866
  `C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` take a **`readonly`** attr; the
@@ -24683,6 +24683,36 @@ function ac_request_page(gobj, event, kw, src) {
24683
24683
  return 0;
24684
24684
  }
24685
24685
  /********************************************
24686
+ * Event from formtable: open the records a hook of one row links --
24687
+ * the CHILD topic's table, filtered to the rows whose fkey names that
24688
+ * row. A hook is the question "which records hang from this one", and
24689
+ * the child's table is where that answer can be read: it pages, it
24690
+ * searches, it opens a record and it exports -- which a list of ids in
24691
+ * a popup could not do past a few dozen of them.
24692
+ * {
24693
+ * topic_name: the child topic
24694
+ * fkey: its fkey column that points back
24695
+ * parent_topic: the topic of the row the hook is on
24696
+ * parent_id: that row's id
24697
+ * hook: the hook column
24698
+ * }
24699
+ ********************************************/
24700
+ function ac_open_linked(gobj, event, kw, src) {
24701
+ let child = get_gobj_formtable(gobj, kw.topic_name);
24702
+ if (!child) {
24703
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot open '${kw.topic_name}', linked from '${kw.parent_topic}^${kw.parent_id}': that topic is not in this view`);
24704
+ return -1;
24705
+ }
24706
+ select_topic_by_id(gobj, (0, _yuneta_gobj_js.gobj_name)(child));
24707
+ (0, _yuneta_gobj_js.gobj_send_event)(child, "EV_FILTER_BY_PARENT", {
24708
+ fkey: kw.fkey,
24709
+ parent_topic: kw.parent_topic,
24710
+ parent_id: kw.parent_id,
24711
+ hook: kw.hook
24712
+ }, gobj);
24713
+ return 0;
24714
+ }
24715
+ /********************************************
24686
24716
  * Event from formtable: the bytes of one asset a `file` column names,
24687
24717
  * for the form's preview or the table's file popup.
24688
24718
  *
@@ -24915,6 +24945,11 @@ function create_gclass$6(gclass_name) {
24915
24945
  ac_request_asset,
24916
24946
  null
24917
24947
  ],
24948
+ [
24949
+ "EV_OPEN_LINKED",
24950
+ ac_open_linked,
24951
+ null
24952
+ ],
24918
24953
  [
24919
24954
  "EV_DELETE_RECORD",
24920
24955
  ac_delete_record,
@@ -24996,6 +25031,7 @@ function create_gclass$6(gclass_name) {
24996
25031
  ["EV_UPDATE_FIELD", 0],
24997
25032
  ["EV_REQUEST_PAGE", 0],
24998
25033
  ["EV_REQUEST_ASSET", 0],
25034
+ ["EV_OPEN_LINKED", 0],
24999
25035
  ["EV_DELETE_RECORD", 0],
25000
25036
  ["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
25001
25037
  ["EV_REFRESH_TOPIC", 0],
@@ -25660,6 +25696,7 @@ function plan_treedb_writes(flags) {
25660
25696
  * How many things a hook/fkey value holds.
25661
25697
  ***************************************************************/
25662
25698
  function ref_count(value) {
25699
+ if (Array.isArray(value) && value.length === 1 && value[0] && typeof value[0] === "object" && typeof value[0].size === "number") return value[0].size;
25663
25700
  if (Array.isArray(value)) return value.length;
25664
25701
  if (value && typeof value === "object") return Object.keys(value).length;
25665
25702
  if (typeof value === "string" && value.length > 0) return 1;
@@ -25806,7 +25843,11 @@ var PRIVATE_DATA$5 = {
25806
25843
  cell_file_modal: null,
25807
25844
  cell_file_win: null,
25808
25845
  _pending_assets: null,
25809
- _asset_seq: 0
25846
+ _asset_seq: 0,
25847
+ parent_filter: null,
25848
+ search_term: "",
25849
+ $parent_filter: null,
25850
+ hook_choice_modal: null
25810
25851
  };
25811
25852
  var __gclass__$5 = null;
25812
25853
  function is_mobile$1() {
@@ -25940,6 +25981,7 @@ function mt_stop$5(gobj) {
25940
25981
  close_schema_dialog(gobj);
25941
25982
  close_cell_json_dialog(gobj);
25942
25983
  close_cell_file_dialog(gobj);
25984
+ close_hook_choice(gobj);
25943
25985
  drop_pending_assets(gobj);
25944
25986
  table__destroy(gobj);
25945
25987
  }
@@ -25951,6 +25993,7 @@ function mt_destroy$5(gobj) {
25951
25993
  close_schema_dialog(gobj);
25952
25994
  close_cell_json_dialog(gobj);
25953
25995
  close_cell_file_dialog(gobj);
25996
+ close_hook_choice(gobj);
25954
25997
  drop_pending_assets(gobj);
25955
25998
  destroy_ui$2(gobj);
25956
25999
  }
@@ -26310,6 +26353,50 @@ function build_ui$5(gobj) {
26310
26353
  bar.$el.style.flex = "none";
26311
26354
  $container.insertBefore(bar.$el, $container.querySelector(`#${table_id}`));
26312
26355
  }
26356
+ let $parent_filter = (0, _yuneta_gobj_js.createElement2)([
26357
+ "div",
26358
+ {
26359
+ class: "TREEDB_PARENT_FILTER is-hidden",
26360
+ style: "flex:none;"
26361
+ },
26362
+ [
26363
+ [
26364
+ "span",
26365
+ {
26366
+ class: "PARENT_FILTER_LABEL",
26367
+ i18n: "filtered by"
26368
+ },
26369
+ "filtered by"
26370
+ ],
26371
+ [
26372
+ "span",
26373
+ { class: "PARENT_FILTER_TOPIC" },
26374
+ ""
26375
+ ],
26376
+ [
26377
+ "strong",
26378
+ { class: "PARENT_FILTER_ID" },
26379
+ ""
26380
+ ],
26381
+ [
26382
+ "button",
26383
+ {
26384
+ class: "delete PARENT_FILTER_CLEAR",
26385
+ type: "button",
26386
+ title: (0, i18next.t)("clear filter"),
26387
+ "data-i18n-title": "clear filter",
26388
+ "aria-label": (0, i18next.t)("clear filter"),
26389
+ "data-i18n-aria-label": "clear filter"
26390
+ },
26391
+ [],
26392
+ { click: () => {
26393
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CLEAR_PARENT_FILTER", {}, gobj);
26394
+ } }
26395
+ ]
26396
+ ]
26397
+ ]);
26398
+ gobj.priv.$parent_filter = $parent_filter;
26399
+ $container.insertBefore($parent_filter, $container.querySelector(`#${table_id}`));
26313
26400
  let $toolbar_slot = $container.querySelector(".toolbar_tabulator_table");
26314
26401
  if ($table_toolbar instanceof Element) $toolbar_slot.appendChild($table_toolbar);
26315
26402
  else $toolbar_slot.appendChild((0, _yuneta_gobj_js.createElement2)(["div", {}]));
@@ -26802,6 +26889,7 @@ function create_tabulator(gobj) {
26802
26889
  yui_tabulator_name_filters(tabulator, i18next.t);
26803
26890
  yui_tabulator_name_row_selects(tabulator, i18next.t);
26804
26891
  tabulator._ready = true;
26892
+ apply_row_filters(gobj);
26805
26893
  update_rowcount();
26806
26894
  yui_table_filter_clear(tabulator);
26807
26895
  if (tabulator._pendingData !== void 0) {
@@ -27009,15 +27097,18 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
27009
27097
  break;
27010
27098
  case "hook":
27011
27099
  let items = (0, _yuneta_gobj_js.treedb_hook_data_size)(value);
27012
- if (items > 0) value = [
27013
- "<a class=\"hook_cell\" ",
27014
- `data-row_id="${row.id}" `,
27015
- `data-col_id="${col.id}" > `,
27016
- "<span style=\"\" class=\"icon yi-eye\"></span>",
27017
- `<span>[&nbsp;<u>${items}</u>&nbsp;]</span>`,
27018
- "</a>"
27019
- ].join("");
27020
- else value = "";
27100
+ if (items > 0) {
27101
+ let hook_title = String((0, i18next.t)("show linked records")).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27102
+ value = [
27103
+ "<a class=\"hook_cell\" ",
27104
+ `title="${hook_title}" aria-label="${hook_title}" `,
27105
+ `data-row_id="${row.id}" `,
27106
+ `data-col_id="${col.id}" > `,
27107
+ "<span style=\"\" class=\"icon yi-eye\"></span>",
27108
+ `<span>[&nbsp;<u>${items}</u>&nbsp;]</span>`,
27109
+ "</a>"
27110
+ ].join("");
27111
+ } else value = "";
27021
27112
  break;
27022
27113
  case "file":
27023
27114
  case "fkey":
@@ -27652,6 +27743,153 @@ function close_cell_file_dialog(gobj) {
27652
27743
  teardown_cell_file(gobj);
27653
27744
  }
27654
27745
  /************************************************************
27746
+ * Ask the host to open the rows of `topic_name` whose `fkey`
27747
+ * names this row -- what a hook of this row links.
27748
+ ************************************************************/
27749
+ function publish_open_linked(gobj, topic_name, fkey, parent_id, hook) {
27750
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_OPEN_LINKED", {
27751
+ topic_name,
27752
+ fkey,
27753
+ parent_topic: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
27754
+ parent_id,
27755
+ hook
27756
+ });
27757
+ }
27758
+ /************************************************************
27759
+ * "Which child topic?" -- for a hook whose children live in
27760
+ * more than one. The standardized dialog, so Escape, Back and
27761
+ * a click outside close it like every other one.
27762
+ ************************************************************/
27763
+ function open_hook_choice_dialog(gobj, targets, parent_id, hook) {
27764
+ close_hook_choice(gobj);
27765
+ let priv = gobj.priv;
27766
+ let shell = yui_shell_of(gobj);
27767
+ if (!shell) {
27768
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no shell, cannot ask which topic to open`);
27769
+ return;
27770
+ }
27771
+ priv.hook_choice_modal = yui_shell_show_modal(shell, (0, _yuneta_gobj_js.createElement2)([
27772
+ "div",
27773
+ { class: "TREEDB_HOOK_CHOICE" },
27774
+ targets.map((tg) => {
27775
+ return [
27776
+ "button",
27777
+ {
27778
+ class: "button HOOK_CHOICE_TOPIC",
27779
+ type: "button",
27780
+ title: (0, i18next.t)(tg.topic_name),
27781
+ "data-i18n-title": tg.topic_name,
27782
+ "aria-label": (0, i18next.t)(tg.topic_name),
27783
+ "data-i18n-aria-label": tg.topic_name
27784
+ },
27785
+ [[
27786
+ "span",
27787
+ { i18n: tg.topic_name },
27788
+ tg.topic_name
27789
+ ]],
27790
+ { click: () => {
27791
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CHOOSE_LINKED", {
27792
+ topic_name: tg.topic_name,
27793
+ fkey: tg.fkey,
27794
+ parent_id,
27795
+ hook
27796
+ }, gobj);
27797
+ } }
27798
+ ];
27799
+ })
27800
+ ]), {
27801
+ dialog: true,
27802
+ logical_class: "TREEDB_HOOK_CHOICE_SHEET",
27803
+ title: "choose a topic",
27804
+ t: i18next.t,
27805
+ on_close: function() {
27806
+ priv.hook_choice_modal = null;
27807
+ }
27808
+ });
27809
+ }
27810
+ /************************************************************
27811
+ * Close "which child topic?" if it is open.
27812
+ ************************************************************/
27813
+ function close_hook_choice(gobj) {
27814
+ let priv = gobj.priv;
27815
+ if (priv.hook_choice_modal) {
27816
+ let modal = priv.hook_choice_modal;
27817
+ priv.hook_choice_modal = null;
27818
+ modal.close();
27819
+ }
27820
+ }
27821
+ /************************************************************
27822
+ * Does this row's `pf.fkey` name the parent record? The fkey
27823
+ * arrives as one ref, a list of them or a dict keyed by ref,
27824
+ * each in any of the shapes treedb_decoder_fkey() reads. The
27825
+ * topic and the hook are compared when the ref carries them:
27826
+ * one row can hang from the same parent through two hooks.
27827
+ ************************************************************/
27828
+ function row_links_to(gobj, data, pf) {
27829
+ let col = get_schema_col(gobj, pf.fkey);
27830
+ let v = data ? data[pf.fkey] : null;
27831
+ if (!col || v === null || v === void 0 || v === "") return false;
27832
+ let refs;
27833
+ if ((0, _yuneta_gobj_js.is_array)(v)) refs = v;
27834
+ else if ((0, _yuneta_gobj_js.is_object)(v) && v.id === void 0) refs = Object.keys(v);
27835
+ else refs = [v];
27836
+ for (let ref of refs) {
27837
+ if (ref === null || ref === void 0 || ref === "") continue;
27838
+ let fk = (0, _yuneta_gobj_js.treedb_decoder_fkey)(col, ref);
27839
+ if (!fk || String(fk.id) !== pf.parent_id) continue;
27840
+ if (fk.topic_name && pf.parent_topic && fk.topic_name !== pf.parent_topic) continue;
27841
+ if (fk.hook_name && pf.hook && fk.hook_name !== pf.hook) continue;
27842
+ return true;
27843
+ }
27844
+ return false;
27845
+ }
27846
+ /************************************************************
27847
+ * The ONE programmatic filter of the table: the parent filter
27848
+ * AND the search term. Kept as state and applied here, because
27849
+ * Tabulator's setFilter() replaces every programmatic filter.
27850
+ * Before `tableBuilt` the state waits; tableBuilt applies it.
27851
+ ************************************************************/
27852
+ function apply_row_filters(gobj) {
27853
+ let priv = gobj.priv;
27854
+ let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
27855
+ if (!tabulator) {
27856
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no table to filter`);
27857
+ return;
27858
+ }
27859
+ if (!tabulator._ready) return;
27860
+ let term = priv.search_term || "";
27861
+ let pf = priv.parent_filter;
27862
+ if (!term && !pf) {
27863
+ tabulator.clearFilter();
27864
+ return;
27865
+ }
27866
+ tabulator.setFilter(function(data) {
27867
+ if (pf && !row_links_to(gobj, data, pf)) return false;
27868
+ return !term || row_matches(data, term);
27869
+ });
27870
+ }
27871
+ /************************************************************
27872
+ * Show or hide the chip that says the parent filter is on.
27873
+ ************************************************************/
27874
+ function render_parent_filter(gobj) {
27875
+ let priv = gobj.priv;
27876
+ let $chip = priv.$parent_filter;
27877
+ if (!$chip) {
27878
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no parent-filter chip to paint`);
27879
+ return;
27880
+ }
27881
+ let pf = priv.parent_filter;
27882
+ if (!pf) {
27883
+ $chip.classList.add("is-hidden");
27884
+ return;
27885
+ }
27886
+ let $topic = $chip.querySelector(".PARENT_FILTER_TOPIC");
27887
+ $topic.setAttribute("data-i18n", pf.parent_topic);
27888
+ $topic.textContent = (0, i18next.t)(pf.parent_topic);
27889
+ $chip.querySelector(".PARENT_FILTER_ID").textContent = pf.parent_id;
27890
+ $chip.classList.remove("is-hidden");
27891
+ }
27892
+ /************************************************************
27655
27893
  * True if the topic's pkey col carries the "rowid" flag.
27656
27894
  ************************************************************/
27657
27895
  function pkey_is_rowid(gobj) {
@@ -27908,74 +28146,6 @@ function build_fkey_ref(gobj, col, value) {
27908
28146
  }
27909
28147
  return refs;
27910
28148
  }
27911
- /***************************************************************************
27912
- * TODO con límite máximo o máximo height o con scroll
27913
- * en un gobj propio para gestionar los datos en "page"s
27914
- * que los hook no vengan rellenos si son muchos y que se puedan gestionar
27915
- * con un gobj
27916
- ***************************************************************************/
27917
- function show_dropdown_popup_menu(gobj, x, y, items, callback) {
27918
- let $element = (0, _yuneta_gobj_js.createElement2)([
27919
- "div",
27920
- { class: "dropdown popup" },
27921
- [[
27922
- "div",
27923
- {
27924
- class: "dropdown-menu",
27925
- role: "menu",
27926
- style: "min-width:4rem; border: 2px solid var(--bulma-border); padding: 0px;"
27927
- },
27928
- [[
27929
- "div",
27930
- {
27931
- class: "dropdown-content",
27932
- style: "padding: 0;"
27933
- },
27934
- []
27935
- ]]
27936
- ]],
27937
- { "click": (evt) => {
27938
- evt.stopPropagation();
27939
- destroyModal();
27940
- if (callback) callback(evt);
27941
- } }
27942
- ]);
27943
- const destroyModal = () => {
27944
- $element.classList.remove("is-active");
27945
- $element.parentNode.removeChild($element);
27946
- };
27947
- let $dropdown_content = $element.querySelector(".dropdown-content");
27948
- let ids = (0, _yuneta_gobj_js.kwid_get_ids)(gobj, items);
27949
- for (let id of ids) {
27950
- let $item = (0, _yuneta_gobj_js.createElement2)([
27951
- "a",
27952
- {
27953
- class: "dropdown-item flex-horizontal-section",
27954
- "data-value": `${id}`,
27955
- style: "margin:0px;"
27956
- },
27957
- [[
27958
- "span",
27959
- { i18n: `${id}` },
27960
- `${id}`
27961
- ]],
27962
- { "click": (evt) => {
27963
- evt.stopPropagation();
27964
- destroyModal();
27965
- if (callback) callback(evt, this.dataset.value);
27966
- } }
27967
- ]);
27968
- $dropdown_content.appendChild($item);
27969
- }
27970
- (0, _yuneta_gobj_js.refresh_language)($element, i18next.t);
27971
- popup_mount_layer(gobj).appendChild($element);
27972
- $element.style.position = "absolute";
27973
- $element.style.top = y + "px";
27974
- $element.style.left = x + "px";
27975
- $element.classList.add("is-active");
27976
- let $with_focus = $element.querySelector(".with-focus");
27977
- if ($with_focus) $with_focus.focus();
27978
- }
27979
28149
  /***************************************************************
27980
28150
  * A read-only topic refuses the write EVENTS too, not only their
27981
28151
  * buttons. The buttons are gone, but an event can still arrive: a
@@ -28415,12 +28585,69 @@ function ac_change_locale(gobj, event, kw, src) {
28415
28585
  * };
28416
28586
  ************************************************************/
28417
28587
  function ac_show_hook_data$1(gobj, event, kw, src) {
28418
- let webix = (0, _yuneta_gobj_js.gobj_command)((0, _yuneta_gobj_js.gobj_parent)(gobj), "get_topic_data", kw, gobj);
28419
- let row = (0, _yuneta_gobj_js.kwid_find_one_record)(gobj, webix.data, kw.row_id, null);
28420
- if (row) {
28421
- let cell = row[kw.col_id];
28422
- show_dropdown_popup_menu(gobj, kw.click_x, kw.click_y, cell);
28588
+ let col = get_schema_col(gobj, kw.col_id);
28589
+ let hook = col && (0, _yuneta_gobj_js.is_object)(col.hook) ? col.hook : null;
28590
+ if (!hook || kw.row_id === void 0 || kw.row_id === null) {
28591
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: hook '${kw.col_id}' names no child topic to open`);
28592
+ return -1;
28593
+ }
28594
+ let targets = Object.keys(hook).map((topic) => {
28595
+ return {
28596
+ topic_name: topic,
28597
+ fkey: hook[topic]
28598
+ };
28599
+ });
28600
+ if (targets.length === 1) {
28601
+ publish_open_linked(gobj, targets[0].topic_name, targets[0].fkey, String(kw.row_id), kw.col_id);
28602
+ return 0;
28603
+ }
28604
+ open_hook_choice_dialog(gobj, targets, String(kw.row_id), kw.col_id);
28605
+ return 0;
28606
+ }
28607
+ /************************************************************
28608
+ * A hook with several child topics: pick one.
28609
+ * {topic_name, fkey, parent_id, hook}
28610
+ ************************************************************/
28611
+ function ac_choose_linked(gobj, event, kw, src) {
28612
+ close_hook_choice(gobj);
28613
+ publish_open_linked(gobj, kw.topic_name, kw.fkey, kw.parent_id, kw.hook);
28614
+ return 0;
28615
+ }
28616
+ /************************************************************
28617
+ * Show only the rows linked to one record of another topic.
28618
+ * Sent by the host when a hook of that record was clicked.
28619
+ * {fkey, parent_topic, parent_id, hook}
28620
+ *
28621
+ * Over the rows the table HOLDS: with `with_remote_paging` that
28622
+ * is one page, which is one of the reasons that flag stays off.
28623
+ ************************************************************/
28624
+ function ac_filter_by_parent(gobj, event, kw, src) {
28625
+ let priv = gobj.priv;
28626
+ if (!kw || !kw.fkey || kw.parent_id === void 0 || kw.parent_id === null) {
28627
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a parent filter with no fkey or no parent`);
28628
+ return -1;
28629
+ }
28630
+ if (!get_schema_col(gobj, kw.fkey)) {
28631
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: parent filter on '${kw.fkey}', which is not a column of '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name")}'`);
28632
+ return -1;
28423
28633
  }
28634
+ priv.parent_filter = {
28635
+ fkey: kw.fkey,
28636
+ parent_topic: kw.parent_topic || "",
28637
+ parent_id: String(kw.parent_id),
28638
+ hook: kw.hook || ""
28639
+ };
28640
+ render_parent_filter(gobj);
28641
+ apply_row_filters(gobj);
28642
+ return 0;
28643
+ }
28644
+ /************************************************************
28645
+ * The ✕ of the parent filter: back to the whole topic.
28646
+ ************************************************************/
28647
+ function ac_clear_parent_filter(gobj, event, kw, src) {
28648
+ gobj.priv.parent_filter = null;
28649
+ render_parent_filter(gobj);
28650
+ apply_row_filters(gobj);
28424
28651
  return 0;
28425
28652
  }
28426
28653
  /************************************************************
@@ -28731,19 +28958,12 @@ function ac_page_timeout(gobj, event, kw, src) {
28731
28958
  * cleared search box does not silently undo them.
28732
28959
  ************************************************************/
28733
28960
  function ac_search(gobj, event, kw, src) {
28734
- let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
28735
- if (!tabulator) {
28961
+ if (!(0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator")) {
28736
28962
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no table to search`);
28737
28963
  return -1;
28738
28964
  }
28739
- let term = (kw && kw.text || "").toLowerCase();
28740
- if (!term) {
28741
- tabulator.clearFilter();
28742
- return 0;
28743
- }
28744
- tabulator.setFilter(function(data) {
28745
- return row_matches(data, term);
28746
- });
28965
+ gobj.priv.search_term = (kw && kw.text || "").toLowerCase();
28966
+ apply_row_filters(gobj);
28747
28967
  return 0;
28748
28968
  }
28749
28969
  /************************************************************
@@ -28991,6 +29211,21 @@ function create_gclass$5(gclass_name) {
28991
29211
  ac_show_hook_data$1,
28992
29212
  null
28993
29213
  ],
29214
+ [
29215
+ "EV_CHOOSE_LINKED",
29216
+ ac_choose_linked,
29217
+ null
29218
+ ],
29219
+ [
29220
+ "EV_FILTER_BY_PARENT",
29221
+ ac_filter_by_parent,
29222
+ null
29223
+ ],
29224
+ [
29225
+ "EV_CLEAR_PARENT_FILTER",
29226
+ ac_clear_parent_filter,
29227
+ null
29228
+ ],
28994
29229
  [
28995
29230
  "EV_SHOW_CELL_JSON",
28996
29231
  ac_show_cell_json,
@@ -29109,7 +29344,11 @@ function create_gclass$5(gclass_name) {
29109
29344
  ["EV_CLEAR_SELECTION", 0],
29110
29345
  ["EV_SELECT_ROWS", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
29111
29346
  ["EV_UNSELECT_ROWS", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
29112
- ["EV_SHOW_HOOK_DATA", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
29347
+ ["EV_SHOW_HOOK_DATA", 0],
29348
+ ["EV_CHOOSE_LINKED", 0],
29349
+ ["EV_FILTER_BY_PARENT", 0],
29350
+ ["EV_CLEAR_PARENT_FILTER", 0],
29351
+ ["EV_OPEN_LINKED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
29113
29352
  ["EV_SHOW_CELL_JSON", 0],
29114
29353
  ["EV_SHOW_CELL_FILE", 0],
29115
29354
  ["EV_ASSET_LOADED", 0],