@yuneta/gobj-ui 7.23.50 → 7.23.52

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
@@ -642,16 +642,32 @@ url is what gives way, cut with an ellipsis, and the whole value stays in the
642
642
 
643
643
  ### Reading a topic a page at a time
644
644
 
645
+ > ⚠️ **`with_remote_paging` is OFF in every consumer, and must stay off until
646
+ > linking works with a partial topic.** A record's link picker is built from
647
+ > the rows the PARENT topic's table holds — `build_fkey_options()` asks it
648
+ > with `get_topic_data`, which answers `tabulator.getData()` — and with paging
649
+ > that is one page. So the picker offers 200 of the 5891 possible parents (the
650
+ > right one is usually not among them), and a record whose parent is not on
651
+ > the loaded page opens with an fkey the form cannot match, which saving then
652
+ > drops. A treedb is a **memory database** — the backend holds the topic in
653
+ > RAM either way — so paging buys little and costs the links. The machinery
654
+ > below stays, working and tested, for the day linking learns to ask the
655
+ > BACKEND for a parent instead of the sibling table.
656
+ >
657
+ > **Local pagination is not affected and stays on.** `getData()` answers the
658
+ > WHOLE dataset whatever page is on screen, so paginating the DISPLAY is safe;
659
+ > only paginating the FETCH is not. `page_size` sets the display page for both
660
+ > paths.
661
+
645
662
  `C_YUI_TREEDB_TOPICS` takes **`with_remote_paging`** (off by default) and
646
663
  forwards it to every topic table: the table pulls the page it is showing
647
664
  instead of the host pushing the whole topic down. It needs the SDK's `nodes`
648
665
  with `from` / `limit` (see `YUNO_TREEDB.md` §5.3).
649
666
 
650
- **The page size is generous on purpose** (`page_size`, 200). A treedb that
651
- fits in one page behaves exactly as it did — paginator hidden, every filter
652
- seeing every row — so nothing that exists today changes. Only a topic that
653
- does NOT fit pays for paging, and for that one loading it whole was never an
654
- option.
667
+ **The page size is generous on purpose** (`page_size`, 200 when paging). A
668
+ treedb that fits in one page behaves exactly as it did — paginator hidden,
669
+ every filter seeing every row. Only a topic that does NOT fit pays for paging,
670
+ and for that one loading it whole was never an option.
655
671
 
656
672
  **Safe against a backend that cannot page:** it answers the whole list, which
657
673
  `nodes_answer()` reads as one page. That is the truth, and it is why the table
@@ -20534,7 +20534,8 @@ var attrs_table$6 = [
20534
20534
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote yuno for data fetching"),
20535
20535
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote TreeDB service name"),
20536
20536
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "descs", 0, null, "Description of topics"),
20537
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. Safe against a backend that cannot page: it answers the whole list, which the table reads as one page"),
20537
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. ⚠️ DO NOT TURN THIS ON: a partial topic BREAKS LINKING -- the link picker of a record is built from the rows the PARENT topic's table holds, which with paging is one page, so the right parent is usually not offered and an existing link the form cannot match is dropped on save. Off in every consumer until linking asks the backend instead of the sibling table"),
20538
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page of each topic table (the DISPLAY; the fetch is whole). 0 leaves the table's own default"),
20538
20539
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_selection_bar", 0, false, "Each topic table shows the shared selection bar (\"N selected\" + a way out) above it while in edition mode. OFF by default: the bar takes its words from the HOST's i18n, so a host that turns it on must define \"{{n}} selected\" and \"clear selection\""),
20539
20540
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
20540
20541
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
@@ -21351,6 +21352,7 @@ function process_treedb_descs$1(gobj) {
21351
21352
  topic_name: key,
21352
21353
  desc,
21353
21354
  with_remote_paging: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_remote_paging"),
21355
+ page_size: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "page_size"),
21354
21356
  with_selection_bar: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_selection_bar")
21355
21357
  };
21356
21358
  let id = `${(0, _yuneta_gobj_js.gobj_name)(gobj)}?${desc.topic_name}`;
@@ -22864,6 +22866,33 @@ function delete_impact(desc, records) {
22864
22866
  * Constants
22865
22867
  ***************************************************************/
22866
22868
  var GCLASS_NAME$5 = "C_YUI_TREEDB_TOPIC_WITH_FORM";
22869
+ var PAGE_SIZE_STORE_PREFIX = "yui_treedb_page_size:";
22870
+ /************************************************************
22871
+ * The size the reader left, or null when there is none.
22872
+ * `true` is Tabulator's "All" and it round-trips as the boolean:
22873
+ * JSON.parse gives it back as one, and a string "true" would be
22874
+ * read by Tabulator as a size of NaN.
22875
+ ************************************************************/
22876
+ function load_page_size(gobj) {
22877
+ try {
22878
+ let raw = window.localStorage.getItem(PAGE_SIZE_STORE_PREFIX + (0, _yuneta_gobj_js.gobj_name)(gobj));
22879
+ if (raw === null) return null;
22880
+ let size = JSON.parse(raw);
22881
+ if (size === true) return true;
22882
+ if (typeof size === "number" && size > 0) return size;
22883
+ return null;
22884
+ } catch (e) {
22885
+ return null;
22886
+ }
22887
+ }
22888
+ /************************************************************
22889
+ *
22890
+ ************************************************************/
22891
+ function save_page_size(gobj, size) {
22892
+ try {
22893
+ window.localStorage.setItem(PAGE_SIZE_STORE_PREFIX + (0, _yuneta_gobj_js.gobj_name)(gobj), JSON.stringify(size));
22894
+ } catch (e) {}
22895
+ }
22867
22896
  /***************************************************************
22868
22897
  * Data
22869
22898
  ***************************************************************/
@@ -22885,8 +22914,8 @@ var attrs_table$5 = [
22885
22914
  (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)"),
22886
22915
  (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)"),
22887
22916
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_inline_edit", 0, true, "Edit a writable scalar cell in place while in edition mode (the record form keeps the rest)"),
22888
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages"),
22889
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "page_size", 0, 200, "Rows per page when with_remote_paging. Generous on purpose: a treedb that fits in one page behaves exactly as it did, paginator hidden and every filter seeing every row"),
22917
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages. ⚠️ DO NOT TURN THIS ON: it breaks LINKS -- see the note above the tabulator settings. It is off in every consumer until linking works with a partial topic"),
22918
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page. 0 leaves the `tabulator_settings` value alone. With with_remote_paging it is also the size of each fetch (200 when unset)"),
22890
22919
  (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"),
22891
22920
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
22892
22921
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
@@ -22920,6 +22949,7 @@ var PRIVATE_DATA$5 = {
22920
22949
  _pending_pages: null,
22921
22950
  _page_seq: 0,
22922
22951
  _page_total: null,
22952
+ _page_size: null,
22923
22953
  $container: null,
22924
22954
  treedb_name: "",
22925
22955
  topic_name: "",
@@ -23806,6 +23836,22 @@ function create_tabulator(gobj) {
23806
23836
  }
23807
23837
  });
23808
23838
  }
23839
+ if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_remote_paging")) {
23840
+ let page_size = (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "page_size");
23841
+ if (page_size > 0 && tabulator_settings.pagination) {
23842
+ tabulator_settings.paginationSize = page_size;
23843
+ tabulator_settings.paginationSizeSelector = [
23844
+ 50,
23845
+ 100,
23846
+ 200,
23847
+ 500,
23848
+ true
23849
+ ];
23850
+ }
23851
+ }
23852
+ let remembered_size = load_page_size(gobj);
23853
+ if (remembered_size !== null && tabulator_settings.pagination) tabulator_settings.paginationSize = remembered_size;
23854
+ gobj.priv._page_size = tabulator_settings.paginationSize;
23809
23855
  Object.assign(tabulator_settings, {
23810
23856
  index: pkey,
23811
23857
  columns,
@@ -23874,6 +23920,9 @@ function create_tabulator(gobj) {
23874
23920
  });
23875
23921
  tabulator.on("dataProcessed", update_rowcount);
23876
23922
  tabulator.on("dataChanged", update_rowcount);
23923
+ tabulator.on("pageSizeChanged", function(size) {
23924
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_PAGE_SIZE_CHANGED", { size }, gobj);
23925
+ });
23877
23926
  tabulator.on("cellEdited", function(cell) {
23878
23927
  let pkey = ((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc") || {}).pkey || "id";
23879
23928
  let data = cell.getRow().getData();
@@ -25325,6 +25374,21 @@ function ac_open_columns(gobj, event, kw, src) {
25325
25374
  return 0;
25326
25375
  }
25327
25376
  /************************************************************
25377
+ * The reader picked another page size: remember it, so coming back to
25378
+ * this topic opens where they left it instead of at 200.
25379
+ ************************************************************/
25380
+ function ac_page_size_changed(gobj, event, kw, src) {
25381
+ let size = kw ? kw.size : void 0;
25382
+ if (size !== true && !(typeof size === "number" && size > 0)) {
25383
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: bad page size: ${size}`);
25384
+ return -1;
25385
+ }
25386
+ if (size === gobj.priv._page_size) return 0;
25387
+ gobj.priv._page_size = size;
25388
+ save_page_size(gobj, size);
25389
+ return 0;
25390
+ }
25391
+ /************************************************************
25328
25392
  * Show / hide one column.
25329
25393
  ************************************************************/
25330
25394
  function ac_toggle_column(gobj, event, kw, src) {
@@ -25529,6 +25593,11 @@ function create_gclass$5(gclass_name) {
25529
25593
  ac_open_columns,
25530
25594
  null
25531
25595
  ],
25596
+ [
25597
+ "EV_PAGE_SIZE_CHANGED",
25598
+ ac_page_size_changed,
25599
+ null
25600
+ ],
25532
25601
  [
25533
25602
  "EV_TOGGLE_COLUMN",
25534
25603
  ac_toggle_column,
@@ -25579,6 +25648,7 @@ function create_gclass$5(gclass_name) {
25579
25648
  ["EV_REQUEST_PAGE", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
25580
25649
  ["EV_SEARCH", 0],
25581
25650
  ["EV_OPEN_COLUMNS", 0],
25651
+ ["EV_PAGE_SIZE_CHANGED", 0],
25582
25652
  ["EV_TOGGLE_COLUMN", 0],
25583
25653
  ["EV_EXPORT_TABLE", 0],
25584
25654
  ["EV_CREATE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
@@ -20529,7 +20529,8 @@ var attrs_table$6 = [
20529
20529
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote yuno for data fetching"),
20530
20530
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote TreeDB service name"),
20531
20531
  SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Description of topics"),
20532
- SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. Safe against a backend that cannot page: it answers the whole list, which the table reads as one page"),
20532
+ SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. ⚠️ DO NOT TURN THIS ON: a partial topic BREAKS LINKING -- the link picker of a record is built from the rows the PARENT topic's table holds, which with paging is one page, so the right parent is usually not offered and an existing link the form cannot match is dropped on save. Off in every consumer until linking asks the backend instead of the sibling table"),
20533
+ SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page of each topic table (the DISPLAY; the fetch is whole). 0 leaves the table's own default"),
20533
20534
  SDATA(data_type_t.DTP_BOOLEAN, "with_selection_bar", 0, false, "Each topic table shows the shared selection bar (\"N selected\" + a way out) above it while in edition mode. OFF by default: the bar takes its words from the HOST's i18n, so a host that turns it on must define \"{{n}} selected\" and \"clear selection\""),
20534
20535
  SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
20535
20536
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
@@ -21346,6 +21347,7 @@ function process_treedb_descs$1(gobj) {
21346
21347
  topic_name: key,
21347
21348
  desc,
21348
21349
  with_remote_paging: gobj_read_bool_attr(gobj, "with_remote_paging"),
21350
+ page_size: gobj_read_integer_attr(gobj, "page_size"),
21349
21351
  with_selection_bar: gobj_read_bool_attr(gobj, "with_selection_bar")
21350
21352
  };
21351
21353
  let id = `${gobj_name(gobj)}?${desc.topic_name}`;
@@ -22859,6 +22861,33 @@ function delete_impact(desc, records) {
22859
22861
  * Constants
22860
22862
  ***************************************************************/
22861
22863
  var GCLASS_NAME$5 = "C_YUI_TREEDB_TOPIC_WITH_FORM";
22864
+ var PAGE_SIZE_STORE_PREFIX = "yui_treedb_page_size:";
22865
+ /************************************************************
22866
+ * The size the reader left, or null when there is none.
22867
+ * `true` is Tabulator's "All" and it round-trips as the boolean:
22868
+ * JSON.parse gives it back as one, and a string "true" would be
22869
+ * read by Tabulator as a size of NaN.
22870
+ ************************************************************/
22871
+ function load_page_size(gobj) {
22872
+ try {
22873
+ let raw = window.localStorage.getItem(PAGE_SIZE_STORE_PREFIX + gobj_name(gobj));
22874
+ if (raw === null) return null;
22875
+ let size = JSON.parse(raw);
22876
+ if (size === true) return true;
22877
+ if (typeof size === "number" && size > 0) return size;
22878
+ return null;
22879
+ } catch (e) {
22880
+ return null;
22881
+ }
22882
+ }
22883
+ /************************************************************
22884
+ *
22885
+ ************************************************************/
22886
+ function save_page_size(gobj, size) {
22887
+ try {
22888
+ window.localStorage.setItem(PAGE_SIZE_STORE_PREFIX + gobj_name(gobj), JSON.stringify(size));
22889
+ } catch (e) {}
22890
+ }
22862
22891
  /***************************************************************
22863
22892
  * Data
22864
22893
  ***************************************************************/
@@ -22880,8 +22909,8 @@ var attrs_table$5 = [
22880
22909
  SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
22881
22910
  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)"),
22882
22911
  SDATA(data_type_t.DTP_BOOLEAN, "with_inline_edit", 0, true, "Edit a writable scalar cell in place while in edition mode (the record form keeps the rest)"),
22883
- SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages"),
22884
- SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 200, "Rows per page when with_remote_paging. Generous on purpose: a treedb that fits in one page behaves exactly as it did, paginator hidden and every filter seeing every row"),
22912
+ SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages. ⚠️ DO NOT TURN THIS ON: it breaks LINKS -- see the note above the tabulator settings. It is off in every consumer until linking works with a partial topic"),
22913
+ SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page. 0 leaves the `tabulator_settings` value alone. With with_remote_paging it is also the size of each fetch (200 when unset)"),
22885
22914
  SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
22886
22915
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
22887
22916
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
@@ -22915,6 +22944,7 @@ var PRIVATE_DATA$5 = {
22915
22944
  _pending_pages: null,
22916
22945
  _page_seq: 0,
22917
22946
  _page_total: null,
22947
+ _page_size: null,
22918
22948
  $container: null,
22919
22949
  treedb_name: "",
22920
22950
  topic_name: "",
@@ -23801,6 +23831,22 @@ function create_tabulator(gobj) {
23801
23831
  }
23802
23832
  });
23803
23833
  }
23834
+ if (!gobj_read_bool_attr(gobj, "with_remote_paging")) {
23835
+ let page_size = gobj_read_integer_attr(gobj, "page_size");
23836
+ if (page_size > 0 && tabulator_settings.pagination) {
23837
+ tabulator_settings.paginationSize = page_size;
23838
+ tabulator_settings.paginationSizeSelector = [
23839
+ 50,
23840
+ 100,
23841
+ 200,
23842
+ 500,
23843
+ true
23844
+ ];
23845
+ }
23846
+ }
23847
+ let remembered_size = load_page_size(gobj);
23848
+ if (remembered_size !== null && tabulator_settings.pagination) tabulator_settings.paginationSize = remembered_size;
23849
+ gobj.priv._page_size = tabulator_settings.paginationSize;
23804
23850
  Object.assign(tabulator_settings, {
23805
23851
  index: pkey,
23806
23852
  columns,
@@ -23869,6 +23915,9 @@ function create_tabulator(gobj) {
23869
23915
  });
23870
23916
  tabulator.on("dataProcessed", update_rowcount);
23871
23917
  tabulator.on("dataChanged", update_rowcount);
23918
+ tabulator.on("pageSizeChanged", function(size) {
23919
+ gobj_send_event(gobj, "EV_PAGE_SIZE_CHANGED", { size }, gobj);
23920
+ });
23872
23921
  tabulator.on("cellEdited", function(cell) {
23873
23922
  let pkey = (gobj_read_attr(gobj, "desc") || {}).pkey || "id";
23874
23923
  let data = cell.getRow().getData();
@@ -25320,6 +25369,21 @@ function ac_open_columns(gobj, event, kw, src) {
25320
25369
  return 0;
25321
25370
  }
25322
25371
  /************************************************************
25372
+ * The reader picked another page size: remember it, so coming back to
25373
+ * this topic opens where they left it instead of at 200.
25374
+ ************************************************************/
25375
+ function ac_page_size_changed(gobj, event, kw, src) {
25376
+ let size = kw ? kw.size : void 0;
25377
+ if (size !== true && !(typeof size === "number" && size > 0)) {
25378
+ log_error(`${gobj_short_name(gobj)}: bad page size: ${size}`);
25379
+ return -1;
25380
+ }
25381
+ if (size === gobj.priv._page_size) return 0;
25382
+ gobj.priv._page_size = size;
25383
+ save_page_size(gobj, size);
25384
+ return 0;
25385
+ }
25386
+ /************************************************************
25323
25387
  * Show / hide one column.
25324
25388
  ************************************************************/
25325
25389
  function ac_toggle_column(gobj, event, kw, src) {
@@ -25524,6 +25588,11 @@ function create_gclass$5(gclass_name) {
25524
25588
  ac_open_columns,
25525
25589
  null
25526
25590
  ],
25591
+ [
25592
+ "EV_PAGE_SIZE_CHANGED",
25593
+ ac_page_size_changed,
25594
+ null
25595
+ ],
25527
25596
  [
25528
25597
  "EV_TOGGLE_COLUMN",
25529
25598
  ac_toggle_column,
@@ -25574,6 +25643,7 @@ function create_gclass$5(gclass_name) {
25574
25643
  ["EV_REQUEST_PAGE", event_flag_t.EVF_OUTPUT_EVENT],
25575
25644
  ["EV_SEARCH", 0],
25576
25645
  ["EV_OPEN_COLUMNS", 0],
25646
+ ["EV_PAGE_SIZE_CHANGED", 0],
25577
25647
  ["EV_TOGGLE_COLUMN", 0],
25578
25648
  ["EV_EXPORT_TABLE", 0],
25579
25649
  ["EV_CREATE_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.23.50",
3
+ "version": "7.23.52",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -103,6 +103,67 @@ import { TabulatorFull as Tabulator } from "tabulator-tables";
103
103
  ***************************************************************/
104
104
  const GCLASS_NAME = "C_YUI_TREEDB_TOPIC_WITH_FORM";
105
105
 
106
+ /*
107
+ * Where the reader's page size is remembered.
108
+ *
109
+ * In localStorage and not in a persistent attr: how many rows somebody
110
+ * wants in front of them is a fact about THAT browser, not about the
111
+ * yuno, and a library gclass cannot assume the host app wired the
112
+ * persistence functions. Same reasoning as `C_YUI_GOBJ_TREE_JS` and
113
+ * `C_YUI_JSON`.
114
+ *
115
+ * Keyed by the gobj name, which the host builds as `<view>?<topic>`, so
116
+ * the size is remembered PER TOPIC. That is the useful grain: a topic of
117
+ * 5891 devices and one of 22 do not want the same page.
118
+ */
119
+ const PAGE_SIZE_STORE_PREFIX = "yui_treedb_page_size:";
120
+
121
+ /************************************************************
122
+ * The size the reader left, or null when there is none.
123
+ * `true` is Tabulator's "All" and it round-trips as the boolean:
124
+ * JSON.parse gives it back as one, and a string "true" would be
125
+ * read by Tabulator as a size of NaN.
126
+ ************************************************************/
127
+ function load_page_size(gobj)
128
+ {
129
+ try {
130
+ let raw = window.localStorage.getItem(
131
+ PAGE_SIZE_STORE_PREFIX + gobj_name(gobj)
132
+ );
133
+ if(raw === null) {
134
+ return null;
135
+ }
136
+ let size = JSON.parse(raw);
137
+ if(size === true) {
138
+ return true;
139
+ }
140
+ if(typeof size === "number" && size > 0) {
141
+ return size;
142
+ }
143
+ return null;
144
+ } catch(e) {
145
+ /* localStorage unavailable (private mode) or a value somebody
146
+ * else wrote: the table opens at its default size, which is
147
+ * the same thing that happened before it was remembered. */
148
+ return null;
149
+ }
150
+ }
151
+
152
+ /************************************************************
153
+ *
154
+ ************************************************************/
155
+ function save_page_size(gobj, size)
156
+ {
157
+ try {
158
+ window.localStorage.setItem(
159
+ PAGE_SIZE_STORE_PREFIX + gobj_name(gobj),
160
+ JSON.stringify(size)
161
+ );
162
+ } catch(e) {
163
+ // localStorage unavailable (private mode) — non-fatal
164
+ }
165
+ }
166
+
106
167
  /***************************************************************
107
168
  * Data
108
169
  ***************************************************************/
@@ -127,8 +188,8 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button
127
188
  SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
128
189
  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)"),
129
190
  SDATA(data_type_t.DTP_BOOLEAN, "with_inline_edit", 0, true, "Edit a writable scalar cell in place while in edition mode (the record form keeps the rest)"),
130
- SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages"),
131
- SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 200, "Rows per page when with_remote_paging. Generous on purpose: a treedb that fits in one page behaves exactly as it did, paginator hidden and every filter seeing every row"),
191
+ SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Pull the topic a PAGE at a time from the backend (`nodes` from/limit) instead of loading it whole. Needs a backend that pages. ⚠️ DO NOT TURN THIS ON: it breaks LINKS -- see the note above the tabulator settings. It is off in every consumer until linking works with a partial topic"),
192
+ SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page. 0 leaves the `tabulator_settings` value alone. With with_remote_paging it is also the size of each fetch (200 when unset)"),
132
193
  SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
133
194
 
134
195
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
@@ -168,6 +229,7 @@ let PRIVATE_DATA = {
168
229
  _pending_pages: null, // req_id -> {resolve, reject, timer}
169
230
  _page_seq: 0, // correlation id of a page request
170
231
  _page_total: null, // rows the topic HAS, from the last page answer
232
+ _page_size: null, // rows per page in effect (remembered across visits)
171
233
  $container: null,
172
234
  treedb_name: "",
173
235
  topic_name: "",
@@ -1331,16 +1393,29 @@ function create_tabulator(gobj)
1331
1393
  let tabulator_settings = json_deep_copy(gobj_read_attr(gobj, "tabulator_settings"));
1332
1394
 
1333
1395
  /* Remote paging: the TABLE pulls, instead of the host pushing the whole
1334
- * topic down. The page size is generous on purpose — a treedb that fits
1335
- * in one page behaves exactly as it did before, with the paginator
1336
- * hidden and every filter seeing every row. Only a topic that does NOT
1337
- * fit pays for paging, and for that one loading it whole was never an
1338
- * option anyway.
1396
+ * topic down. Only a topic that does NOT fit pays for it, and for that
1397
+ * one loading it whole was never an option anyway.
1339
1398
  *
1340
1399
  * `filterMode: "local"` says the plain truth: the header filters and the
1341
1400
  * search box work on the page that is loaded. Same as the tranger
1342
1401
  * browser's Rows card, and for the same reason — the alternative is
1343
1402
  * pushing every filter to the backend and changing what "search" means.
1403
+ *
1404
+ * ⚠️ AND IT IS OFF IN EVERY CONSUMER, because it BREAKS LINKING.
1405
+ * `build_fkey_options()` asks the PARENT topic's table for the rows it
1406
+ * holds (`get_topic_data` -> `tabulator.getData()`), and with paging
1407
+ * that is one page. So the link picker offers 200 of the 5891 possible
1408
+ * parents -- the right one is usually not among them -- and a record
1409
+ * whose parent is not on the loaded page opens with an fkey the form
1410
+ * cannot match, which saving then drops. Local pagination has none of
1411
+ * this: `getData()` answers the WHOLE dataset whatever page is on
1412
+ * screen, so paginating the DISPLAY is safe and only paginating the
1413
+ * FETCH is not.
1414
+ *
1415
+ * A treedb is a memory database -- the backend holds the topic in RAM
1416
+ * either way -- so paging buys little and costs the links. It stays
1417
+ * here, working and tested, for the day linking learns to ask the
1418
+ * BACKEND for a parent instead of the sibling table.
1344
1419
  */
1345
1420
  if(gobj_read_bool_attr(gobj, "with_remote_paging")) {
1346
1421
  let page_size = gobj_read_integer_attr(gobj, "page_size") || 200;
@@ -1366,6 +1441,36 @@ function create_tabulator(gobj)
1366
1441
  });
1367
1442
  }
1368
1443
 
1444
+ /* Rows per page for the LOCAL paginator too, when the host asks for
1445
+ * one: with the fetch no longer paged, `page_size` is the only thing
1446
+ * left that says how many rows a reader wants in front of them, and a
1447
+ * host that had 200 should not drop to 25 for having stopped paging the
1448
+ * fetch. The sizes offered go up with it -- a selector that cannot name
1449
+ * the size in use is a selector Tabulator has to patch itself. */
1450
+ if(!gobj_read_bool_attr(gobj, "with_remote_paging")) {
1451
+ let page_size = gobj_read_integer_attr(gobj, "page_size");
1452
+ if(page_size > 0 && tabulator_settings.pagination) {
1453
+ tabulator_settings.paginationSize = page_size;
1454
+ tabulator_settings.paginationSizeSelector = [50, 100, 200, 500, true];
1455
+ }
1456
+ }
1457
+
1458
+ /* The size the reader picked last time, which outranks both defaults
1459
+ * above. Applied here, once the two paths have set theirs, so it is
1460
+ * written in ONE place -- and applied to `paginationSize` only: the
1461
+ * SELECTOR keeps offering the same sizes, and a remembered value that
1462
+ * is not one of them Tabulator adds to the list itself. */
1463
+ let remembered_size = load_page_size(gobj);
1464
+ if(remembered_size !== null && tabulator_settings.pagination) {
1465
+ tabulator_settings.paginationSize = remembered_size;
1466
+ }
1467
+ /* What the table OPENS at, remembered or default. Tabulator announces
1468
+ * `pageSizeChanged` at build time too, with the size it starts at, and
1469
+ * saving that one would freeze today's default in the reader's browser
1470
+ * for ever: a later change of the default would never reach anybody who
1471
+ * had once opened the table. Only a real change is written. */
1472
+ gobj.priv._page_size = tabulator_settings.paginationSize;
1473
+
1369
1474
  Object.assign(tabulator_settings, {
1370
1475
  index: pkey,
1371
1476
  columns: columns,
@@ -1529,6 +1634,13 @@ function create_tabulator(gobj)
1529
1634
  * box; a filter per column made it a claim you read on every keystroke. */
1530
1635
  tabulator.on("dataProcessed", update_rowcount);
1531
1636
  tabulator.on("dataChanged", update_rowcount);
1637
+ /* The reader changed the page size. The widget has already done the
1638
+ * work -- it re-pages itself -- so what crosses the machine is the
1639
+ * DECISION, which is the half worth remembering and the half that
1640
+ * shows up in the trace. */
1641
+ tabulator.on("pageSizeChanged", function(size) {
1642
+ gobj_send_event(gobj, "EV_PAGE_SIZE_CHANGED", {size: size}, gobj);
1643
+ });
1532
1644
  /* A cell edited in place. Announced as ONE FIELD of one record — not
1533
1645
  * as the row — and that is the whole safety of it: the host writes it
1534
1646
  * with a partial update and no `autolink`, so nothing but that field
@@ -3606,6 +3718,27 @@ function ac_open_columns(gobj, event, kw, src)
3606
3718
  return 0;
3607
3719
  }
3608
3720
 
3721
+ /************************************************************
3722
+ * The reader picked another page size: remember it, so coming back to
3723
+ * this topic opens where they left it instead of at 200.
3724
+ ************************************************************/
3725
+ function ac_page_size_changed(gobj, event, kw, src)
3726
+ {
3727
+ let size = kw? kw.size : undefined;
3728
+ if(size !== true && !(typeof size === "number" && size > 0)) {
3729
+ log_error(`${gobj_short_name(gobj)}: bad page size: ${size}`);
3730
+ return -1;
3731
+ }
3732
+
3733
+ if(size === gobj.priv._page_size) {
3734
+ return 0; /* the build-time announcement, not a decision */
3735
+ }
3736
+ gobj.priv._page_size = size;
3737
+ save_page_size(gobj, size);
3738
+
3739
+ return 0;
3740
+ }
3741
+
3609
3742
  /************************************************************
3610
3743
  * Show / hide one column.
3611
3744
  ************************************************************/
@@ -3744,6 +3877,7 @@ function create_gclass(gclass_name)
3744
3877
  ["EV_PAGE_TIMEOUT", ac_page_timeout, null],
3745
3878
  ["EV_SEARCH", ac_search, null],
3746
3879
  ["EV_OPEN_COLUMNS", ac_open_columns, null],
3880
+ ["EV_PAGE_SIZE_CHANGED", ac_page_size_changed, null],
3747
3881
  ["EV_TOGGLE_COLUMN", ac_toggle_column, null],
3748
3882
  ["EV_EXPORT_TABLE", ac_export_table, null],
3749
3883
  ["EV_SHOW", ac_show, null],
@@ -3784,6 +3918,7 @@ function create_gclass(gclass_name)
3784
3918
  ["EV_REQUEST_PAGE", event_flag_t.EVF_OUTPUT_EVENT],
3785
3919
  ["EV_SEARCH", 0],
3786
3920
  ["EV_OPEN_COLUMNS", 0],
3921
+ ["EV_PAGE_SIZE_CHANGED", 0],
3787
3922
  ["EV_TOGGLE_COLUMN", 0],
3788
3923
  ["EV_EXPORT_TABLE", 0],
3789
3924
 
@@ -32,6 +32,7 @@ import {
32
32
  gobj_short_name,
33
33
  gobj_read_str_attr,
34
34
  gobj_read_bool_attr,
35
+ gobj_read_integer_attr,
35
36
  gobj_start,
36
37
  gobj_stop,
37
38
  gobj_is_running,
@@ -75,7 +76,8 @@ SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of o
75
76
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote yuno for data fetching"),
76
77
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote TreeDB service name"),
77
78
  SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Description of topics"),
78
- SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. Safe against a backend that cannot page: it answers the whole list, which the table reads as one page"),
79
+ SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "Each topic table pulls its rows a PAGE at a time (`nodes` from/limit) instead of loading the topic whole. ⚠️ DO NOT TURN THIS ON: a partial topic BREAKS LINKING -- the link picker of a record is built from the rows the PARENT topic's table holds, which with paging is one page, so the right parent is usually not offered and an existing link the form cannot match is dropped on save. Off in every consumer until linking asks the backend instead of the sibling table"),
80
+ SDATA(data_type_t.DTP_INTEGER, "page_size", 0, 0, "Rows per page of each topic table (the DISPLAY; the fetch is whole). 0 leaves the table's own default"),
79
81
  SDATA(data_type_t.DTP_BOOLEAN, "with_selection_bar", 0, false, "Each topic table shows the shared selection bar (\"N selected\" + a way out) above it while in edition mode. OFF by default: the bar takes its words from the HOST's i18n, so a host that turns it on must define \"{{n}} selected\" and \"clear selection\""),
80
82
  SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
81
83
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
@@ -1041,6 +1043,7 @@ function process_treedb_descs(gobj)
1041
1043
  topic_name: key,
1042
1044
  desc: desc,
1043
1045
  with_remote_paging: gobj_read_bool_attr(gobj, "with_remote_paging"),
1046
+ page_size: gobj_read_integer_attr(gobj, "page_size"),
1044
1047
  with_selection_bar: gobj_read_bool_attr(gobj, "with_selection_bar")
1045
1048
  };
1046
1049
 
package/src/tabulator.css CHANGED
@@ -304,6 +304,23 @@
304
304
  color: var(--bulma-link-invert);
305
305
  }
306
306
 
307
+ /* The paginator paints its OWN colour -- tabulator_bulma.css sets
308
+ * `color: hsl(0, 0%, 21%)` on `.tabulator-paginator` itself -- so the
309
+ * footer's colour never reaches the "Rows per page" label: near-black
310
+ * text on the dark footer, legible only if you knew it was there. Say
311
+ * it on the element that sets it, and on the native <select>, which
312
+ * otherwise keeps the browser's light chrome. */
313
+ [data-theme=dark] .tabulator .tabulator-footer .tabulator-paginator,
314
+ [data-theme=dark] .tabulator .tabulator-footer .tabulator-paginator label {
315
+ color: var(--bulma-text);
316
+ }
317
+
318
+ [data-theme=dark] .tabulator .tabulator-footer .tabulator-page-size {
319
+ background-color: var(--bulma-background);
320
+ color: var(--bulma-text);
321
+ border-color: var(--bulma-border);
322
+ }
323
+
307
324
  [data-theme=dark] .tabulator-row .tabulator-cell.tabulator-frozen {
308
325
  background-color: var(--bulma-background) !important;
309
326
  }
@@ -386,5 +403,15 @@
386
403
  background-color: var(--bulma-link);
387
404
  color: var(--bulma-link-invert);
388
405
  }
406
+ /* same paginator-paints-its-own-colour rule as [data-theme=dark] */
407
+ [data-theme=system] .tabulator .tabulator-footer .tabulator-paginator,
408
+ [data-theme=system] .tabulator .tabulator-footer .tabulator-paginator label {
409
+ color: var(--bulma-text);
410
+ }
411
+ [data-theme=system] .tabulator .tabulator-footer .tabulator-page-size {
412
+ background-color: var(--bulma-background);
413
+ color: var(--bulma-text);
414
+ border-color: var(--bulma-border);
415
+ }
389
416
  }
390
417