@yuneta/gobj-ui 7.26.39 → 7.26.41

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.
@@ -500,8 +500,16 @@ function text_node(value) {
500
500
  * Tabulator puts a STRING title in with innerHTML, so a title taken
501
501
  * from a schema (`header`, which the schema editor writes) is markup
502
502
  * unless the column carries a formatter that returns a Node.
503
+ *
504
+ * And it APPENDS that Node to the title's holder, which it never
505
+ * empties -- while it runs the formatter again on every setLocale()
506
+ * (yui_tabulator_relocalize()). Until 7.26.40 each change of language
507
+ * added one more copy of every title ("Id dispositivoId dispositivo").
508
+ * The holder (the mock cell's element) is emptied first.
503
509
  ************************************************************/
504
510
  function text_title_formatter(cell) {
511
+ const $holder = typeof cell.getElement === "function" ? cell.getElement() : null;
512
+ if ($holder) $holder.textContent = "";
505
513
  return text_node(cell.getValue());
506
514
  }
507
515
  /************************************************************
@@ -7101,6 +7109,12 @@ function register_c_yui_wizard() {
7101
7109
  * new Tabulator($el, {..., ...yui_tabulator_lang(t)});
7102
7110
  * yui_tabulator_relocalize(table, t); // on a language change
7103
7111
  *
7112
+ * A view whose COLUMN TITLES change language hands them too, in the
7113
+ * new language, by field -- Tabulator binds each column's title to its
7114
+ * locale, so they change with it and no column is rebuilt:
7115
+ *
7116
+ * yui_tabulator_relocalize(table, t, {device_id: t("device id")});
7117
+ *
7104
7118
  * Every key carries an English `defaultValue`, so an app that does not
7105
7119
  * define it renders exactly what Tabulator rendered before — the
7106
7120
  * keys are an OPPORTUNITY to translate, never a requirement.
@@ -7259,11 +7273,23 @@ function yui_tabulator_name_editor(column_def, name_of) {
7259
7273
  return Object.assign({}, params, { elementAttributes: attrs });
7260
7274
  };
7261
7275
  }
7262
- function yui_tabulator_relocalize(table, t) {
7276
+ /***************************************************************
7277
+ * yui_tabulator_relocalize() -- see "Put an EXISTING table in the
7278
+ * current language" above.
7279
+ *
7280
+ * `titles`, optional: {field: title}, the titles of the columns in
7281
+ * the new language, already translated. They go in the locale
7282
+ * (`columns`), where Tabulator binds each column's title: setLocale()
7283
+ * draws them again without rebuilding a column -- updateDefinition()
7284
+ * re-renders every row, and a row whose formatter builds something (a
7285
+ * nested table) builds it anew. A field left out keeps its title.
7286
+ ***************************************************************/
7287
+ function yui_tabulator_relocalize(table, t, titles) {
7263
7288
  if (!table) return;
7264
7289
  try {
7265
7290
  let name = next_lang_name();
7266
7291
  let strings = tabulator_strings(t);
7292
+ if (titles && typeof titles === "object") strings.columns = Object.assign({}, titles);
7267
7293
  table.options.langs = table.options.langs || {};
7268
7294
  table.options.langs[name] = strings;
7269
7295
  let localize = table.modules && table.modules.localize;
@@ -9482,7 +9508,7 @@ var attrs_table$17 = [
9482
9508
  SDATA_END()
9483
9509
  ];
9484
9510
  var PRIVATE_DATA$17 = {
9485
- rearm_timer: null,
9511
+ loading_rows: 0,
9486
9512
  tables: null,
9487
9513
  table_seq: 0,
9488
9514
  json_names_posted: false,
@@ -9538,18 +9564,12 @@ function mt_stop$17(gobj) {
9538
9564
  * Local Methods
9539
9565
  ***************************/
9540
9566
  /************************************************************
9541
- *
9542
- ************************************************************/
9543
- function isEventSet(table, event) {
9544
- let eventListeners = table.externalEvents.events;
9545
- return eventListeners && eventListeners[event] && eventListeners[event].length > 0;
9546
- }
9547
- /************************************************************
9548
- *
9567
+ * `changes` and the save/undo buttons. What the tables say is
9568
+ * not switched here: they always say it (create_tabulator).
9549
9569
  ************************************************************/
9550
9570
  function set_changed_stated(gobj, changed) {
9551
9571
  let $container = gobj_read_attr(gobj, "$container");
9552
- let $form = $container.querySelector("form");
9572
+ $container.querySelector("form");
9553
9573
  const enable = ($btn, on, color) => {
9554
9574
  if (!$btn) return;
9555
9575
  if (on) $btn.removeAttribute("disabled");
@@ -9564,35 +9584,11 @@ function set_changed_stated(gobj, changed) {
9564
9584
  gobj_write_bool_attr(gobj, "changes", true);
9565
9585
  enable($button_save, true, "MediumSeaGreen");
9566
9586
  enable($button_undo, true, "Magenta");
9567
- $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
9568
- if ($table.tabulator && isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
9569
- $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
9570
- if ($table_nested.tabulator && isEventSet($table_nested.tabulator, "dataChanged")) $table_nested.tabulator.off("dataChanged");
9571
- });
9572
- });
9573
9587
  } else {
9574
9588
  gobj_write_bool_attr(gobj, "changes", false);
9575
9589
  gobj_write_bool_attr(gobj, "edited", false);
9576
9590
  enable($button_save, false, OFF_COLOR);
9577
9591
  enable($button_undo, false, OFF_COLOR);
9578
- if (gobj.priv.rearm_timer) clearTimeout(gobj.priv.rearm_timer);
9579
- gobj.priv.rearm_timer = setTimeout(function() {
9580
- gobj.priv.rearm_timer = null;
9581
- if (gobj_is_destroying(gobj)) return;
9582
- const rearm = (tabulator) => {
9583
- if (!tabulator) return;
9584
- if (isEventSet(tabulator, "dataChanged")) tabulator.off("dataChanged");
9585
- tabulator.on("dataChanged", function(data) {
9586
- gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
9587
- });
9588
- };
9589
- $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
9590
- rearm($table.tabulator);
9591
- $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
9592
- rearm($table_nested.tabulator);
9593
- });
9594
- });
9595
- }, 200);
9596
9592
  }
9597
9593
  }
9598
9594
  /************************************************************
@@ -9829,10 +9825,6 @@ function build_ui$15(gobj) {
9829
9825
  * Destroy UI
9830
9826
  ************************************************************/
9831
9827
  function destroy_ui$10(gobj) {
9832
- if (gobj.priv && gobj.priv.rearm_timer) {
9833
- clearTimeout(gobj.priv.rearm_timer);
9834
- gobj.priv.rearm_timer = null;
9835
- }
9836
9828
  let $container = gobj_read_attr(gobj, "$container");
9837
9829
  if ($container) {
9838
9830
  destroy_widgets(gobj, $container);
@@ -11226,6 +11218,10 @@ function create_tabulator$1(gobj, $extend, name, template) {
11226
11218
  hozAlign: "center"
11227
11219
  };
11228
11220
  let tabulator = new TabulatorFull($extend, tabulator_settings);
11221
+ tabulator.on("dataChanged", function() {
11222
+ if (gobj.priv.loading_rows > 0) return;
11223
+ gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
11224
+ });
11229
11225
  tabulator.on("renderComplete", function() {
11230
11226
  yui_tabulator_name_row_selects(tabulator, t);
11231
11227
  });
@@ -11243,6 +11239,24 @@ function create_tabulator$1(gobj, $extend, name, template) {
11243
11239
  return tabulator;
11244
11240
  }
11245
11241
  /******************************************************************
11242
+ * The title of a table's column: the schema's header, or the
11243
+ * field's name, translated.
11244
+ ******************************************************************/
11245
+ function column_title(field_desc) {
11246
+ return t(field_desc.header || field_desc.name);
11247
+ }
11248
+ /******************************************************************
11249
+ * {field: title} of a table's template, in the current language.
11250
+ ******************************************************************/
11251
+ function table_column_titles(template) {
11252
+ let titles = {};
11253
+ Object.entries(template || {}).forEach(([key, value]) => {
11254
+ const field_desc = template_get_field_desc(key, value);
11255
+ titles[field_desc.name] = column_title(field_desc);
11256
+ });
11257
+ return titles;
11258
+ }
11259
+ /******************************************************************
11246
11260
  * Convert a template's items in tabulator columns
11247
11261
  ******************************************************************/
11248
11262
  function template2columns(gobj, columns, template, sub_elements) {
@@ -11255,7 +11269,7 @@ function template2columns(gobj, columns, template, sub_elements) {
11255
11269
  let validator = [];
11256
11270
  let column = {
11257
11271
  field: field_desc.name,
11258
- title: t(field_desc.header || field_desc.name),
11272
+ title: column_title(field_desc),
11259
11273
  titleFormatter: text_title_formatter,
11260
11274
  editable: get_editable_state,
11261
11275
  editor: false,
@@ -12110,7 +12124,14 @@ function ac_add_table_row(gobj, event, kw, src) {
12110
12124
  }
12111
12125
  let record = kw.record;
12112
12126
  let loading = kw.loading;
12113
- tabulator.addRow(record).then(function(row) {
12127
+ let added;
12128
+ if (loading) gobj.priv.loading_rows++;
12129
+ try {
12130
+ added = tabulator.addRow(record);
12131
+ } finally {
12132
+ if (loading) gobj.priv.loading_rows--;
12133
+ }
12134
+ added.then(function(row) {
12114
12135
  if (loading) {
12115
12136
  let template = $table.template;
12116
12137
  Object.entries(template).forEach(([key, template_value]) => {
@@ -12217,6 +12238,10 @@ function ac_language_changed$10(gobj, event, kw, src) {
12217
12238
  let editor = $editor.jsoneditor;
12218
12239
  if (editor && typeof editor.updateProps === "function") editor.updateProps({ onRenderMenu: json_editor_render_menu(gobj) });
12219
12240
  }
12241
+ for (let $table of $container.querySelectorAll(".yui-tabulator-table, .yui-tabulator-table-nested")) {
12242
+ let tabulator = $table.tabulator;
12243
+ if (tabulator && tabulator.initialized && $table.template) yui_tabulator_relocalize(tabulator, t, table_column_titles($table.template));
12244
+ }
12220
12245
  return 0;
12221
12246
  }
12222
12247
  /***************************
@@ -13391,41 +13416,58 @@ function register_c_yui_map() {
13391
13416
  return create_gclass$16(GCLASS_NAME$16);
13392
13417
  }
13393
13418
  //#endregion
13394
- //#region src/c_yui_uplot.js
13419
+ //#region src/yui_uplot_ink.js
13395
13420
  /***********************************************************************
13396
- * c_yui_uplot.js
13421
+ * yui_uplot_ink.js
13397
13422
  *
13398
- * Graphics, Charts with uPlot
13423
+ * The ink of a uPlot chart, on both schemes, and its palette.
13399
13424
  *
13400
- * Copyright (c) 2025-2026, ArtGins.
13425
+ * uPlot is built for a white page: its axis labels are `#000`
13426
+ * and its grid `rgba(0,0,0,0.07)`, which on the dark scheme are
13427
+ * 1.16:1 and 1.04 -- an axis nobody can read and a grid that is
13428
+ * not there. The inks here are handed to uPlot as FUNCTIONS: it
13429
+ * calls them when it draws, so the value can never be stale --
13430
+ * what goes stale is the canvas, and a change of the theme has to
13431
+ * ask for a redraw (yui_watch_theme() turns it into an event).
13432
+ *
13433
+ * uPlot ALWAYS draws a Y axis: its setDefaults() makes axes[1]
13434
+ * whether it was given or not, with its own black. So the axes are
13435
+ * padded to two before they are inked. C_YUI_UPLOT's default is
13436
+ * `[{}]`, the X axis alone, and until 7.26.40 every chart of the
13437
+ * library had a Y axis whose values could not be read in the dark.
13438
+ *
13439
+ * C_YUI_UPLOT uses it, and so can an app that builds a uPlot of its
13440
+ * own:
13441
+ *
13442
+ * import {yui_uplot_axes} from "@yuneta/gobj-ui/src/yui_uplot_ink.js";
13443
+ *
13444
+ * const chart = new uPlot({
13445
+ * width: 600, height: 300,
13446
+ * series: [{}, {label: "temperature"}],
13447
+ * axes: yui_uplot_axes([{}]), // X and Y, with the ink of the theme
13448
+ * }, data, $el);
13449
+ *
13450
+ * The palette is six colours that clear 3:1 on BOTH grounds
13451
+ * (worst 3.39 on white, 3.71 on near-black), ordered so the first
13452
+ * two are the pair that survives colour blindness -- blue and
13453
+ * orange, never red and green as the opening pair.
13454
+ *
13455
+ * Copyright (c) 2026, ArtGins.
13401
13456
  * All Rights Reserved.
13402
13457
  ***********************************************************************/
13403
- /***************************************************************
13404
- * The ink of a chart, and its palette.
13405
- *
13406
- * uPlot is built for a white page: its axis labels are `#000`
13407
- * and its grid `rgba(0,0,0,0.07)`, which on the dark scheme are
13408
- * 1.16:1 and 1.04 -- an axis nobody can read and a grid that is
13409
- * not there. And the series were CSS colour names: `blue` is
13410
- * 2.11:1 on the dark ground, `orange` 1.97 on the light one, so
13411
- * whichever scheme you were in, one of the lines was a rumour.
13412
- *
13413
- * These are given to uPlot as FUNCTIONS: it calls them when it
13414
- * draws, so the value can never be stale -- what goes stale is
13415
- * the canvas, and that is what EV_THEME redraws.
13416
- *
13417
- * The palette is six colours that clear 3:1 on BOTH grounds
13418
- * (worst 3.39 on white, 3.71 on near-black), ordered so the
13419
- * first two are the pair that survives colour blindness -- blue
13420
- * and orange, never red and green as the opening pair.
13421
- ***************************************************************/
13422
- function axis_ink() {
13458
+ /************************************************************
13459
+ * The ink of the labels of an axis.
13460
+ ************************************************************/
13461
+ function yui_uplot_axis_ink() {
13423
13462
  return yui_is_dark() ? "#c7cdd8" : "#2e333d";
13424
13463
  }
13425
- function grid_ink() {
13464
+ /************************************************************
13465
+ * The ink of the grid and of the ticks.
13466
+ ************************************************************/
13467
+ function yui_uplot_grid_ink() {
13426
13468
  return yui_is_dark() ? "rgba(199,205,216,0.16)" : "rgba(46,51,61,0.12)";
13427
13469
  }
13428
- var CHART_PALETTE = [
13470
+ var YUI_UPLOT_PALETTE = [
13429
13471
  {
13430
13472
  stroke: "#2f6fd0",
13431
13473
  fill: "rgba(47,111,208,0.10)"
@@ -13451,15 +13493,33 @@ var CHART_PALETTE = [
13451
13493
  fill: "rgba(209,52,75,0.10)"
13452
13494
  }
13453
13495
  ];
13454
- function themed_axes(axes) {
13455
- return (Array.isArray(axes) ? axes : [axes || {}]).map((ax) => {
13496
+ /************************************************************
13497
+ * The axes the host asked for -- at least the X and the Y,
13498
+ * the two uPlot always draws -- with the ink it did not ask
13499
+ * about. A stroke, a grid or a ticks the host gave is kept.
13500
+ * Answers new objects: the host's are not touched.
13501
+ ************************************************************/
13502
+ function yui_uplot_axes(axes) {
13503
+ let list = Array.isArray(axes) ? axes.slice() : [axes || {}];
13504
+ while (list.length < 2) list.push({});
13505
+ return list.map((ax) => {
13456
13506
  let a = Object.assign({}, ax || {});
13457
- if (a.stroke === void 0) a.stroke = axis_ink;
13458
- a.grid = Object.assign({ stroke: grid_ink }, a.grid || {});
13459
- a.ticks = Object.assign({ stroke: grid_ink }, a.ticks || {});
13507
+ if (a.stroke === void 0) a.stroke = yui_uplot_axis_ink;
13508
+ a.grid = Object.assign({ stroke: yui_uplot_grid_ink }, a.grid || {});
13509
+ a.ticks = Object.assign({ stroke: yui_uplot_grid_ink }, a.ticks || {});
13460
13510
  return a;
13461
13511
  });
13462
13512
  }
13513
+ //#endregion
13514
+ //#region src/c_yui_uplot.js
13515
+ /***********************************************************************
13516
+ * c_yui_uplot.js
13517
+ *
13518
+ * Graphics, Charts with uPlot
13519
+ *
13520
+ * Copyright (c) 2025-2026, ArtGins.
13521
+ * All Rights Reserved.
13522
+ ***********************************************************************/
13463
13523
  /***************************************************************
13464
13524
  * Constants
13465
13525
  ***************************************************************/
@@ -13477,7 +13537,7 @@ var attrs_table$15 = [
13477
13537
  SDATA(data_type_t.DTP_JSON, "axes", 0, [{}], "Chart axes"),
13478
13538
  SDATA(data_type_t.DTP_JSON, "scales", 0, {}, "Chart scales"),
13479
13539
  SDATA(data_type_t.DTP_JSON, "data", 0, [[]], "Chart data"),
13480
- SDATA(data_type_t.DTP_JSON, "colors", 0, CHART_PALETTE, "Series colours, in order. The default palette clears 3:1 on both schemes and opens with the colour-blind-safe pair (blue, orange)"),
13540
+ SDATA(data_type_t.DTP_JSON, "colors", 0, YUI_UPLOT_PALETTE, "Series colours, in order. The default palette clears 3:1 on both schemes and opens with the colour-blind-safe pair (blue, orange)"),
13481
13541
  SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTML container for the chart"),
13482
13542
  SDATA(data_type_t.DTP_BOOLEAN, "own_container", 0, false, "If true, container is gobj-managed"),
13483
13543
  SDATA(data_type_t.DTP_POINTER, "uplot", 0, null, "uPlot instance"),
@@ -13544,7 +13604,7 @@ function build_ui$14(gobj) {
13544
13604
  width: gobj_read_attr(gobj, "width"),
13545
13605
  height: gobj_read_attr(gobj, "height"),
13546
13606
  series: gobj_read_attr(gobj, "series"),
13547
- axes: themed_axes(gobj_read_attr(gobj, "axes")),
13607
+ axes: yui_uplot_axes(gobj_read_attr(gobj, "axes")),
13548
13608
  scales: gobj_read_attr(gobj, "scales")
13549
13609
  };
13550
13610
  let uplot = new uPlot(uPlot_options, gobj_read_attr(gobj, "data"), $container);
@@ -13645,7 +13705,7 @@ function ac_add_serie(gobj, event, kw, src) {
13645
13705
  }
13646
13706
  }
13647
13707
  let colors = gobj_read_attr(gobj, "colors");
13648
- if (!Array.isArray(colors) || !colors.length) colors = CHART_PALETTE;
13708
+ if (!Array.isArray(colors) || !colors.length) colors = YUI_UPLOT_PALETTE;
13649
13709
  let color = colors[Math.max(0, idx - 1) % colors.length];
13650
13710
  if (kw.stroke === void 0) kw.stroke = color.stroke;
13651
13711
  if (kw.fill === void 0) kw.fill = color.fill;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.26.39",
3
+ "version": "7.26.41",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
package/src/c_yui_form.js CHANGED
@@ -55,7 +55,11 @@ import {
55
55
 
56
56
  import {t} from "i18next";
57
57
 
58
- import {yui_tabulator_name_row_selects, yui_tabulator_name_editor} from "./yui_tabulator_i18n.js";
58
+ import {
59
+ yui_tabulator_name_row_selects,
60
+ yui_tabulator_name_editor,
61
+ yui_tabulator_relocalize
62
+ } from "./yui_tabulator_i18n.js";
59
63
  import "tom-select/dist/css/tom-select.css"; // Import Tom-Select CSS
60
64
  import TomSelect from "tom-select"; // Import Tom-Select JS
61
65
 
@@ -172,9 +176,10 @@ SDATA_END()
172
176
  ];
173
177
 
174
178
  let PRIVATE_DATA = {
175
- /* The deferred re-wiring of the tables' dataChanged after a
176
- * save/undo (see set_changed_stated): cancelled in destroy_ui. */
177
- rearm_timer: null,
179
+ /* > 0 while the FORM adds rows (a record loaded, a nested table
180
+ * filled): what a table says then is no change of the reader's
181
+ * (see create_tabulator's dataChanged). */
182
+ loading_rows: 0,
178
183
  /* table_id -> WeakRef of its element: EV_ADD_TABLE_ROW names a
179
184
  * table by id (a kw is plain json), see table_identity(). */
180
185
  tables: null,
@@ -281,17 +286,8 @@ function mt_stop(gobj)
281
286
 
282
287
 
283
288
  /************************************************************
284
- *
285
- ************************************************************/
286
- function isEventSet(table, event)
287
- {
288
- // Access the event listeners directly from the Tabulator instance
289
- let eventListeners = table.externalEvents.events;
290
- return eventListeners && eventListeners[event] && eventListeners[event].length > 0;
291
- }
292
-
293
- /************************************************************
294
- *
289
+ * `changes` and the save/undo buttons. What the tables say is
290
+ * not switched here: they always say it (create_tabulator).
295
291
  ************************************************************/
296
292
  function set_changed_stated(gobj, changed)
297
293
  {
@@ -333,18 +329,6 @@ function set_changed_stated(gobj, changed)
333
329
  enable($button_save, true, "MediumSeaGreen");
334
330
  enable($button_undo, true, "Magenta");
335
331
 
336
- $form.querySelectorAll('.yui-tabulator-table').forEach($table => {
337
- if($table.tabulator && isEventSet($table.tabulator, "dataChanged")) {
338
- $table.tabulator.off("dataChanged");
339
- }
340
-
341
- $table.querySelectorAll('.yui-tabulator-table-nested').forEach($table_nested => {
342
- if($table_nested.tabulator && isEventSet($table_nested.tabulator, "dataChanged")) {
343
- $table_nested.tabulator.off("dataChanged");
344
- }
345
- });
346
- });
347
-
348
332
  } else {
349
333
  /*
350
334
  * Data not changed, disable buttons to save/undo
@@ -354,41 +338,6 @@ function set_changed_stated(gobj, changed)
354
338
 
355
339
  enable($button_save, false, OFF_COLOR);
356
340
  enable($button_undo, false, OFF_COLOR);
357
-
358
- /*
359
- * TODO improve. The tables are re-wired a moment later, once the
360
- * data just loaded has settled. The timer is kept so that
361
- * destroy_ui() cancels it: a form closed right after a save (what
362
- * a dialog does) destroyed its widgets first, and the timer then
363
- * called .on() on a null tabulator -- an uncaught TypeError.
364
- */
365
- if(gobj.priv.rearm_timer) {
366
- clearTimeout(gobj.priv.rearm_timer);
367
- }
368
- gobj.priv.rearm_timer = setTimeout(function() {
369
- gobj.priv.rearm_timer = null;
370
- if(gobj_is_destroying(gobj)) {
371
- return; // destroy_ui() cancels it; this is the belt
372
- }
373
- const rearm = (tabulator) => {
374
- if(!tabulator) {
375
- return; // destroyed with its form
376
- }
377
- if(isEventSet(tabulator, "dataChanged")) {
378
- tabulator.off("dataChanged");
379
- }
380
- tabulator.on("dataChanged", function(data) {
381
- //data - the updated table data
382
- gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
383
- });
384
- };
385
- $form.querySelectorAll('.yui-tabulator-table').forEach($table => {
386
- rearm($table.tabulator);
387
- $table.querySelectorAll('.yui-tabulator-table-nested').forEach($table_nested => {
388
- rearm($table_nested.tabulator);
389
- });
390
- });
391
- }, 200);
392
341
  }
393
342
  }
394
343
 
@@ -562,10 +511,6 @@ function build_ui(gobj)
562
511
  ************************************************************/
563
512
  function destroy_ui(gobj)
564
513
  {
565
- if(gobj.priv && gobj.priv.rearm_timer) {
566
- clearTimeout(gobj.priv.rearm_timer);
567
- gobj.priv.rearm_timer = null;
568
- }
569
514
  let $container = gobj_read_attr(gobj, "$container");
570
515
  if($container) {
571
516
  destroy_widgets(gobj, $container);
@@ -2332,6 +2277,22 @@ function create_tabulator(gobj, $extend, name, template)
2332
2277
  $extend,
2333
2278
  tabulator_settings
2334
2279
  );
2280
+ /* What the READER does to a table -- edits a cell, adds or deletes a
2281
+ * row -- reaches the form only through `dataChanged`. Wired here, on
2282
+ * every table, nested ones included (a row's formatter builds them
2283
+ * whenever the row is drawn), and never switched off: until 7.26.40
2284
+ * it was unwired whenever the form was CHANGED and re-wired only by a
2285
+ * save, an undo or a load of what is stored, so a form that opened
2286
+ * changed (a field its record lacks, filled with its default) never
2287
+ * heard its tables -- an edit there was not `edited`, and closing lost
2288
+ * it without a question. The rows the form adds itself say nothing
2289
+ * (`loading_rows`, ac_add_table_row). */
2290
+ tabulator.on("dataChanged", function() {
2291
+ if(gobj.priv.loading_rows > 0) {
2292
+ return;
2293
+ }
2294
+ gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
2295
+ });
2335
2296
  /* Tabulator hard-codes `aria-label="Select Row"` on the box its
2336
2297
  * rowSelection formatter draws, and it draws a fresh one on EVERY
2337
2298
  * render -- so this hangs off `renderComplete`, not `tableBuilt`:
@@ -2369,6 +2330,28 @@ function create_tabulator(gobj, $extend, name, template)
2369
2330
  return tabulator;
2370
2331
  }
2371
2332
 
2333
+ /******************************************************************
2334
+ * The title of a table's column: the schema's header, or the
2335
+ * field's name, translated.
2336
+ ******************************************************************/
2337
+ function column_title(field_desc)
2338
+ {
2339
+ return t(/* i18n: data */ field_desc.header || field_desc.name); // the host's schema
2340
+ }
2341
+
2342
+ /******************************************************************
2343
+ * {field: title} of a table's template, in the current language.
2344
+ ******************************************************************/
2345
+ function table_column_titles(template)
2346
+ {
2347
+ let titles = {};
2348
+ Object.entries(template || {}).forEach(([key, value]) => {
2349
+ const field_desc = template_get_field_desc(key, value);
2350
+ titles[field_desc.name] = column_title(field_desc);
2351
+ });
2352
+ return titles;
2353
+ }
2354
+
2372
2355
  /******************************************************************
2373
2356
  * Convert a template's items in tabulator columns
2374
2357
  ******************************************************************/
@@ -2388,7 +2371,7 @@ function template2columns(gobj, columns, template, sub_elements)
2388
2371
  let validator = [];
2389
2372
  let column = {
2390
2373
  field: field_desc.name,
2391
- title: t(/* i18n: data */ field_desc.header || field_desc.name), // the host's schema
2374
+ title: column_title(field_desc),
2392
2375
  titleFormatter: text_title_formatter,
2393
2376
  editable: get_editable_state,
2394
2377
  editor: false,
@@ -3793,7 +3776,23 @@ function ac_add_table_row(gobj, event, kw, src)
3793
3776
  let record = kw.record;
3794
3777
  let loading = kw.loading;
3795
3778
 
3796
- tabulator.addRow(record)
3779
+ /* Tabulator says `dataChanged` INSIDE addRow(), synchronously: a row
3780
+ * loaded from the record is muted there, whenever it lands -- also
3781
+ * from a `tableBuilt` that comes after the load returned, which is
3782
+ * why no timer can tell the two apart. */
3783
+ let added;
3784
+ if(loading) {
3785
+ gobj.priv.loading_rows++;
3786
+ }
3787
+ try {
3788
+ added = tabulator.addRow(record);
3789
+ } finally {
3790
+ if(loading) {
3791
+ gobj.priv.loading_rows--;
3792
+ }
3793
+ }
3794
+
3795
+ added
3797
3796
  .then(function(row) {
3798
3797
  if(loading) {
3799
3798
  /*
@@ -3955,6 +3954,19 @@ function ac_language_changed(gobj, event, kw, src)
3955
3954
  editor.updateProps({onRenderMenu: json_editor_render_menu(gobj)});
3956
3955
  }
3957
3956
  }
3957
+
3958
+ /* The titles of the columns of every table, nested ones included:
3959
+ * Tabulator draws them at build, so they stayed in the language the
3960
+ * form was built in. Through the table's LOCALE, never by
3961
+ * updateDefinition(): rebuilding a column re-renders every row, and
3962
+ * a row's formatter builds its nested tables anew -- empty. A table
3963
+ * not built yet is drawn in the current language when it is. */
3964
+ for(let $table of $container.querySelectorAll(".yui-tabulator-table, .yui-tabulator-table-nested")) {
3965
+ let tabulator = $table.tabulator;
3966
+ if(tabulator && tabulator.initialized && $table.template) {
3967
+ yui_tabulator_relocalize(tabulator, t, table_column_titles($table.template));
3968
+ }
3969
+ }
3958
3970
  return 0;
3959
3971
  }
3960
3972