@yuneta/gobj-ui 7.23.53 → 7.23.54

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.
@@ -6356,6 +6356,7 @@ var attrs_table$16 = [
6356
6356
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
6357
6357
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
6358
6358
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
6359
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "Show EVERY field read-only, whatever its schema says: a record being LOOKED at, not edited. Independent of `editable`, which drives the TABLE rendering; this one drives the FORM"),
6359
6360
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
6360
6361
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
6361
6362
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
@@ -6759,7 +6760,7 @@ function build_form_field_conf(gobj, field_desc) {
6759
6760
  placeholder: "",
6760
6761
  options: [],
6761
6762
  extras: {},
6762
- readonly: !field_desc.is_writable,
6763
+ readonly: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly") || !field_desc.is_writable,
6763
6764
  required: field_desc.is_required,
6764
6765
  hidden: field_desc.is_hidden,
6765
6766
  default_value: field_desc.default_value
@@ -24126,6 +24127,13 @@ function create_tabulator(gobj) {
24126
24127
  tabulator.on("dataFiltered", function(filters, rows) {
24127
24128
  update_rowcount(Array.isArray(rows) ? rows.length : void 0);
24128
24129
  });
24130
+ tabulator.on("rowClick", function(e, row) {
24131
+ if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable")) return;
24132
+ let $cell = e.target ? e.target.closest(".tabulator-cell") : null;
24133
+ let field = $cell ? $cell.getAttribute("tabulator-field") : null;
24134
+ if (field && cell_has_its_own_action(gobj, field)) return;
24135
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SHOW_RECORD", { index: row.getPosition() }, gobj);
24136
+ });
24129
24137
  tabulator.on("rowSelected", function(row) {
24130
24138
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SELECT_ROWS", { rows: [row.getData()] }, gobj);
24131
24139
  });
@@ -24301,10 +24309,25 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
24301
24309
  return value;
24302
24310
  }
24303
24311
  /************************************************************
24304
- * Build the form template from desc.cols: only user-editable
24305
- * fields reach the form — writable cols, fkeys (linkable) and
24306
- * the pkey (the hosted C_YUI_FORM's form_mode drives the
24307
- * pkey's readonly/required state).
24312
+ * Build the form template from desc.cols: EVERY field of the
24313
+ * record, the ones that cannot be written included.
24314
+ *
24315
+ * They used to be dropped, and a record was then shown as the
24316
+ * handful of its fields somebody may type: an asset opened as
24317
+ * its id alone, with its type, its size, its date and where it
24318
+ * came from -- everything a person opens an asset to read --
24319
+ * nowhere on screen. What a form is for is READING a record;
24320
+ * writing it is one of the two things you can do next.
24321
+ *
24322
+ * C_YUI_FORM renders a col that is not `writable` read-only on
24323
+ * its own (`build_form_field_conf`), so this is the whole of
24324
+ * it. What must NOT follow is sending them back:
24325
+ * `transform__form_record_2_treedb_record()` drops them, and
24326
+ * the reason is written there.
24327
+ *
24328
+ * Still out: hooks (a hook is not a field of the record, it is
24329
+ * the children hanging off it), hidden cols, and the `_`
24330
+ * scaffolding the table adds.
24308
24331
  ************************************************************/
24309
24332
  function build_form_template(gobj) {
24310
24333
  let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
@@ -24314,7 +24337,6 @@ function build_form_template(gobj) {
24314
24337
  const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
24315
24338
  if (field_desc.is_hidden) continue;
24316
24339
  if (field_desc.type === "hook") continue;
24317
- if (!field_desc.is_writable && field_desc.type !== "fkey" && col.id !== desc.pkey) continue;
24318
24340
  template.push(col);
24319
24341
  }
24320
24342
  return template;
@@ -24362,6 +24384,8 @@ function open_form_dialog(gobj, mode, record) {
24362
24384
  let rid = record ? String(record[pkey] ?? "") : "";
24363
24385
  title = (0, i18next.t)(topic_name) + ((0, _yuneta_gobj_js.empty_string)(rid) ? "" : " — " + rid);
24364
24386
  }
24387
+ let view_only = mode === "view";
24388
+ let form_mode = view_only ? "update" : mode;
24365
24389
  let $body = (0, _yuneta_gobj_js.createElement2)([
24366
24390
  "div",
24367
24391
  {
@@ -24375,12 +24399,13 @@ function open_form_dialog(gobj, mode, record) {
24375
24399
  template: build_form_template(gobj),
24376
24400
  record,
24377
24401
  fkey_options: build_fkey_options(gobj),
24378
- form_mode: mode,
24379
- ...form_plan.form_toolbar ? { toolbar: form_plan.form_toolbar } : {},
24402
+ form_mode,
24403
+ readonly: view_only,
24404
+ ...(view_only ? READONLY_FORM_TOOLBAR : form_plan.form_toolbar) ? { toolbar: view_only ? READONLY_FORM_TOOLBAR.slice() : form_plan.form_toolbar } : {},
24380
24405
  render_mode: "edit",
24381
24406
  pkey: desc.pkey || "id",
24382
24407
  topic_name,
24383
- editable: !form_plan.readonly,
24408
+ editable: !form_plan.readonly && !view_only,
24384
24409
  $parent: $body
24385
24410
  }, gobj);
24386
24411
  priv.form = form;
@@ -24665,6 +24690,39 @@ function show_edit_form(gobj, row, index) {
24665
24690
  open_form_dialog(gobj, pkey_is_rowid(gobj) ? "create" : "update", row);
24666
24691
  }
24667
24692
  /************************************************************
24693
+ * Does a click on this column already mean something else?
24694
+ *
24695
+ * The `_` columns are the table's own scaffolding (the selection
24696
+ * checkbox, the Op buttons); a hook opens its children and a col
24697
+ * holding a json document opens it in a viewer. Anything else is
24698
+ * plain data, and a click on it means "show me this record".
24699
+ ************************************************************/
24700
+ var CELL_WITH_ACTION = [
24701
+ "hook",
24702
+ "object",
24703
+ "dict",
24704
+ "template",
24705
+ "array",
24706
+ "list",
24707
+ "coordinates",
24708
+ "blob",
24709
+ "gbuffer"
24710
+ ];
24711
+ function cell_has_its_own_action(gobj, field) {
24712
+ if (!field || field.charAt(0) === "_") return true;
24713
+ let col = get_schema_col(gobj, field);
24714
+ if (!col) return false;
24715
+ const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
24716
+ return CELL_WITH_ACTION.includes(field_desc.type) || CELL_WITH_ACTION.includes(field_desc.real_type);
24717
+ }
24718
+ /************************************************************
24719
+ * Show a record to be READ: every field, none of them editable.
24720
+ * What a row click opens while the table is not in edition mode.
24721
+ ************************************************************/
24722
+ function show_view_form(gobj, row) {
24723
+ open_form_dialog(gobj, "view", row);
24724
+ }
24725
+ /************************************************************
24668
24726
  * Show the edit "create" form to a new record
24669
24727
  * internally called from the top toolbar +New button
24670
24728
  ************************************************************/
@@ -24687,14 +24745,16 @@ function get_schema_col(gobj, id) {
24687
24745
  * operation: "create" "update"
24688
24746
  ************************************************************/
24689
24747
  function transform__form_record_2_treedb_record(gobj, kw, operation) {
24748
+ let pkey = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc").pkey || "id";
24690
24749
  let row = {};
24691
24750
  for (let field_name of Object.keys(kw)) {
24692
24751
  if ((0, _yuneta_gobj_js.empty_string)(field_name)) continue;
24693
24752
  let col = get_schema_col(gobj, field_name);
24694
- if (col) {
24695
- let value = kw[field_name];
24696
- row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
24697
- }
24753
+ if (!col) continue;
24754
+ const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
24755
+ if (!field_desc.is_writable && field_desc.type !== "fkey" && col.id !== pkey) continue;
24756
+ let value = kw[field_name];
24757
+ row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
24698
24758
  }
24699
24759
  return row;
24700
24760
  }
@@ -25593,6 +25653,24 @@ function ac_open_columns(gobj, event, kw, src) {
25593
25653
  return 0;
25594
25654
  }
25595
25655
  /************************************************************
25656
+ * A row was clicked outside edition mode: open the record to be read.
25657
+ ************************************************************/
25658
+ function ac_show_record(gobj, event, kw, src) {
25659
+ let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
25660
+ let index = kw ? kw.index : void 0;
25661
+ if (!tabulator || typeof index !== "number") {
25662
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no row ${index} to show`);
25663
+ return -1;
25664
+ }
25665
+ let row = tabulator.getRowFromPosition(index);
25666
+ if (!row) {
25667
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: row ${index} is gone`);
25668
+ return -1;
25669
+ }
25670
+ show_view_form(gobj, row.getData());
25671
+ return 0;
25672
+ }
25673
+ /************************************************************
25596
25674
  * The reader picked another page size: remember it, so coming back to
25597
25675
  * this topic opens where they left it instead of at 200.
25598
25676
  ************************************************************/
@@ -25817,6 +25895,11 @@ function create_gclass$5(gclass_name) {
25817
25895
  ac_page_size_changed,
25818
25896
  null
25819
25897
  ],
25898
+ [
25899
+ "EV_SHOW_RECORD",
25900
+ ac_show_record,
25901
+ null
25902
+ ],
25820
25903
  [
25821
25904
  "EV_TOGGLE_COLUMN",
25822
25905
  ac_toggle_column,
@@ -25868,6 +25951,7 @@ function create_gclass$5(gclass_name) {
25868
25951
  ["EV_SEARCH", 0],
25869
25952
  ["EV_OPEN_COLUMNS", 0],
25870
25953
  ["EV_PAGE_SIZE_CHANGED", 0],
25954
+ ["EV_SHOW_RECORD", 0],
25871
25955
  ["EV_TOGGLE_COLUMN", 0],
25872
25956
  ["EV_EXPORT_TABLE", 0],
25873
25957
  ["EV_CREATE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
@@ -6351,6 +6351,7 @@ var attrs_table$16 = [
6351
6351
  SDATA(data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
6352
6352
  SDATA(data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
6353
6353
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
6354
+ SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "Show EVERY field read-only, whatever its schema says: a record being LOOKED at, not edited. Independent of `editable`, which drives the TABLE rendering; this one drives the FORM"),
6354
6355
  SDATA(data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
6355
6356
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
6356
6357
  SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
@@ -6754,7 +6755,7 @@ function build_form_field_conf(gobj, field_desc) {
6754
6755
  placeholder: "",
6755
6756
  options: [],
6756
6757
  extras: {},
6757
- readonly: !field_desc.is_writable,
6758
+ readonly: gobj_read_bool_attr(gobj, "readonly") || !field_desc.is_writable,
6758
6759
  required: field_desc.is_required,
6759
6760
  hidden: field_desc.is_hidden,
6760
6761
  default_value: field_desc.default_value
@@ -24121,6 +24122,13 @@ function create_tabulator(gobj) {
24121
24122
  tabulator.on("dataFiltered", function(filters, rows) {
24122
24123
  update_rowcount(Array.isArray(rows) ? rows.length : void 0);
24123
24124
  });
24125
+ tabulator.on("rowClick", function(e, row) {
24126
+ if (gobj_read_bool_attr(gobj, "editable")) return;
24127
+ let $cell = e.target ? e.target.closest(".tabulator-cell") : null;
24128
+ let field = $cell ? $cell.getAttribute("tabulator-field") : null;
24129
+ if (field && cell_has_its_own_action(gobj, field)) return;
24130
+ gobj_send_event(gobj, "EV_SHOW_RECORD", { index: row.getPosition() }, gobj);
24131
+ });
24124
24132
  tabulator.on("rowSelected", function(row) {
24125
24133
  gobj_send_event(gobj, "EV_SELECT_ROWS", { rows: [row.getData()] }, gobj);
24126
24134
  });
@@ -24296,10 +24304,25 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
24296
24304
  return value;
24297
24305
  }
24298
24306
  /************************************************************
24299
- * Build the form template from desc.cols: only user-editable
24300
- * fields reach the form — writable cols, fkeys (linkable) and
24301
- * the pkey (the hosted C_YUI_FORM's form_mode drives the
24302
- * pkey's readonly/required state).
24307
+ * Build the form template from desc.cols: EVERY field of the
24308
+ * record, the ones that cannot be written included.
24309
+ *
24310
+ * They used to be dropped, and a record was then shown as the
24311
+ * handful of its fields somebody may type: an asset opened as
24312
+ * its id alone, with its type, its size, its date and where it
24313
+ * came from -- everything a person opens an asset to read --
24314
+ * nowhere on screen. What a form is for is READING a record;
24315
+ * writing it is one of the two things you can do next.
24316
+ *
24317
+ * C_YUI_FORM renders a col that is not `writable` read-only on
24318
+ * its own (`build_form_field_conf`), so this is the whole of
24319
+ * it. What must NOT follow is sending them back:
24320
+ * `transform__form_record_2_treedb_record()` drops them, and
24321
+ * the reason is written there.
24322
+ *
24323
+ * Still out: hooks (a hook is not a field of the record, it is
24324
+ * the children hanging off it), hidden cols, and the `_`
24325
+ * scaffolding the table adds.
24303
24326
  ************************************************************/
24304
24327
  function build_form_template(gobj) {
24305
24328
  let desc = gobj_read_attr(gobj, "desc");
@@ -24309,7 +24332,6 @@ function build_form_template(gobj) {
24309
24332
  const field_desc = treedb_get_field_desc(col);
24310
24333
  if (field_desc.is_hidden) continue;
24311
24334
  if (field_desc.type === "hook") continue;
24312
- if (!field_desc.is_writable && field_desc.type !== "fkey" && col.id !== desc.pkey) continue;
24313
24335
  template.push(col);
24314
24336
  }
24315
24337
  return template;
@@ -24357,6 +24379,8 @@ function open_form_dialog(gobj, mode, record) {
24357
24379
  let rid = record ? String(record[pkey] ?? "") : "";
24358
24380
  title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
24359
24381
  }
24382
+ let view_only = mode === "view";
24383
+ let form_mode = view_only ? "update" : mode;
24360
24384
  let $body = createElement2([
24361
24385
  "div",
24362
24386
  {
@@ -24370,12 +24394,13 @@ function open_form_dialog(gobj, mode, record) {
24370
24394
  template: build_form_template(gobj),
24371
24395
  record,
24372
24396
  fkey_options: build_fkey_options(gobj),
24373
- form_mode: mode,
24374
- ...form_plan.form_toolbar ? { toolbar: form_plan.form_toolbar } : {},
24397
+ form_mode,
24398
+ readonly: view_only,
24399
+ ...(view_only ? READONLY_FORM_TOOLBAR : form_plan.form_toolbar) ? { toolbar: view_only ? READONLY_FORM_TOOLBAR.slice() : form_plan.form_toolbar } : {},
24375
24400
  render_mode: "edit",
24376
24401
  pkey: desc.pkey || "id",
24377
24402
  topic_name,
24378
- editable: !form_plan.readonly,
24403
+ editable: !form_plan.readonly && !view_only,
24379
24404
  $parent: $body
24380
24405
  }, gobj);
24381
24406
  priv.form = form;
@@ -24660,6 +24685,39 @@ function show_edit_form(gobj, row, index) {
24660
24685
  open_form_dialog(gobj, pkey_is_rowid(gobj) ? "create" : "update", row);
24661
24686
  }
24662
24687
  /************************************************************
24688
+ * Does a click on this column already mean something else?
24689
+ *
24690
+ * The `_` columns are the table's own scaffolding (the selection
24691
+ * checkbox, the Op buttons); a hook opens its children and a col
24692
+ * holding a json document opens it in a viewer. Anything else is
24693
+ * plain data, and a click on it means "show me this record".
24694
+ ************************************************************/
24695
+ var CELL_WITH_ACTION = [
24696
+ "hook",
24697
+ "object",
24698
+ "dict",
24699
+ "template",
24700
+ "array",
24701
+ "list",
24702
+ "coordinates",
24703
+ "blob",
24704
+ "gbuffer"
24705
+ ];
24706
+ function cell_has_its_own_action(gobj, field) {
24707
+ if (!field || field.charAt(0) === "_") return true;
24708
+ let col = get_schema_col(gobj, field);
24709
+ if (!col) return false;
24710
+ const field_desc = treedb_get_field_desc(col);
24711
+ return CELL_WITH_ACTION.includes(field_desc.type) || CELL_WITH_ACTION.includes(field_desc.real_type);
24712
+ }
24713
+ /************************************************************
24714
+ * Show a record to be READ: every field, none of them editable.
24715
+ * What a row click opens while the table is not in edition mode.
24716
+ ************************************************************/
24717
+ function show_view_form(gobj, row) {
24718
+ open_form_dialog(gobj, "view", row);
24719
+ }
24720
+ /************************************************************
24663
24721
  * Show the edit "create" form to a new record
24664
24722
  * internally called from the top toolbar +New button
24665
24723
  ************************************************************/
@@ -24682,14 +24740,16 @@ function get_schema_col(gobj, id) {
24682
24740
  * operation: "create" "update"
24683
24741
  ************************************************************/
24684
24742
  function transform__form_record_2_treedb_record(gobj, kw, operation) {
24743
+ let pkey = gobj_read_attr(gobj, "desc").pkey || "id";
24685
24744
  let row = {};
24686
24745
  for (let field_name of Object.keys(kw)) {
24687
24746
  if (empty_string(field_name)) continue;
24688
24747
  let col = get_schema_col(gobj, field_name);
24689
- if (col) {
24690
- let value = kw[field_name];
24691
- row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
24692
- }
24748
+ if (!col) continue;
24749
+ const field_desc = treedb_get_field_desc(col);
24750
+ if (!field_desc.is_writable && field_desc.type !== "fkey" && col.id !== pkey) continue;
24751
+ let value = kw[field_name];
24752
+ row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
24693
24753
  }
24694
24754
  return row;
24695
24755
  }
@@ -25588,6 +25648,24 @@ function ac_open_columns(gobj, event, kw, src) {
25588
25648
  return 0;
25589
25649
  }
25590
25650
  /************************************************************
25651
+ * A row was clicked outside edition mode: open the record to be read.
25652
+ ************************************************************/
25653
+ function ac_show_record(gobj, event, kw, src) {
25654
+ let tabulator = gobj_read_attr(gobj, "tabulator");
25655
+ let index = kw ? kw.index : void 0;
25656
+ if (!tabulator || typeof index !== "number") {
25657
+ log_error(`${gobj_short_name(gobj)}: no row ${index} to show`);
25658
+ return -1;
25659
+ }
25660
+ let row = tabulator.getRowFromPosition(index);
25661
+ if (!row) {
25662
+ log_error(`${gobj_short_name(gobj)}: row ${index} is gone`);
25663
+ return -1;
25664
+ }
25665
+ show_view_form(gobj, row.getData());
25666
+ return 0;
25667
+ }
25668
+ /************************************************************
25591
25669
  * The reader picked another page size: remember it, so coming back to
25592
25670
  * this topic opens where they left it instead of at 200.
25593
25671
  ************************************************************/
@@ -25812,6 +25890,11 @@ function create_gclass$5(gclass_name) {
25812
25890
  ac_page_size_changed,
25813
25891
  null
25814
25892
  ],
25893
+ [
25894
+ "EV_SHOW_RECORD",
25895
+ ac_show_record,
25896
+ null
25897
+ ],
25815
25898
  [
25816
25899
  "EV_TOGGLE_COLUMN",
25817
25900
  ac_toggle_column,
@@ -25863,6 +25946,7 @@ function create_gclass$5(gclass_name) {
25863
25946
  ["EV_SEARCH", 0],
25864
25947
  ["EV_OPEN_COLUMNS", 0],
25865
25948
  ["EV_PAGE_SIZE_CHANGED", 0],
25949
+ ["EV_SHOW_RECORD", 0],
25866
25950
  ["EV_TOGGLE_COLUMN", 0],
25867
25951
  ["EV_EXPORT_TABLE", 0],
25868
25952
  ["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.53",
3
+ "version": "7.23.54",
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
@@ -99,6 +99,7 @@ SDATA(data_type_t.DTP_STRING, "render_mode", 0, "exec", "'exec' interpre
99
99
  SDATA(data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
100
100
  SDATA(data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
101
101
  SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
102
+ SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "Show EVERY field read-only, whatever its schema says: a record being LOOKED at, not edited. Independent of `editable`, which drives the TABLE rendering; this one drives the FORM"),
102
103
  SDATA(data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
103
104
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
104
105
  SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
@@ -586,7 +587,10 @@ function build_form_field_conf(gobj, field_desc)
586
587
 
587
588
  options: [], // select,select2,radio options
588
589
  extras: {}, // extra html input/textarea attributes
589
- readonly: !field_desc.is_writable,
590
+ /* A form opened to LOOK at a record has no editable field, and
591
+ * that is a property of the opening, not of the schema: the same
592
+ * col is writable when the same form is opened to edit. */
593
+ readonly: gobj_read_bool_attr(gobj, "readonly") || !field_desc.is_writable,
590
594
  required: field_desc.is_required,
591
595
  hidden: field_desc.is_hidden,
592
596
  default_value: field_desc.default_value,
@@ -91,7 +91,7 @@ import {row_matches} from "./yui_row_search.js";
91
91
 
92
92
  import {t} from "i18next";
93
93
 
94
- import {plan_treedb_writes} from "./treedb_write_plan.js";
94
+ import {plan_treedb_writes, READONLY_FORM_TOOLBAR} from "./treedb_write_plan.js";
95
95
  import {delete_impact} from "./delete_impact.js";
96
96
 
97
97
  import "./c_yui_treedb_topic_with_form.css";
@@ -1680,6 +1680,31 @@ function create_tabulator(gobj)
1680
1680
  tabulator.on("dataFiltered", function(filters, rows) {
1681
1681
  update_rowcount(Array.isArray(rows)? rows.length : undefined);
1682
1682
  });
1683
+ /* A click on a row, while the table is NOT in edition mode, OPENS the
1684
+ * record to be read. It used to do nothing at all: selection is driven
1685
+ * by the checkbox column and editing by the pencil, so the biggest
1686
+ * target on screen was the one thing that answered nothing.
1687
+ *
1688
+ * Only outside edition mode: in it a click starts editing a cell, and
1689
+ * the pencil already opens the same record to be written.
1690
+ *
1691
+ * Cells that carry an action of their own are left alone -- a json
1692
+ * preview opens its viewer, a hook opens its children, the Op buttons
1693
+ * edit and delete -- and the question is asked of the SCHEMA rather
1694
+ * than of the markup, so a new formatter does not silently start
1695
+ * opening two things at once. */
1696
+ tabulator.on("rowClick", function(e, row) {
1697
+ if(gobj_read_bool_attr(gobj, "editable")) {
1698
+ return;
1699
+ }
1700
+ let $cell = e.target? e.target.closest(".tabulator-cell") : null;
1701
+ let field = $cell? $cell.getAttribute("tabulator-field") : null;
1702
+ if(field && cell_has_its_own_action(gobj, field)) {
1703
+ return;
1704
+ }
1705
+ gobj_send_event(gobj, "EV_SHOW_RECORD", {index: row.getPosition()}, gobj);
1706
+ });
1707
+
1683
1708
  tabulator.on("rowSelected", function(row) {
1684
1709
  gobj_send_event(gobj, "EV_SELECT_ROWS", {rows: [row.getData()]}, gobj);
1685
1710
  });
@@ -1930,10 +1955,25 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
1930
1955
  }
1931
1956
 
1932
1957
  /************************************************************
1933
- * Build the form template from desc.cols: only user-editable
1934
- * fields reach the form — writable cols, fkeys (linkable) and
1935
- * the pkey (the hosted C_YUI_FORM's form_mode drives the
1936
- * pkey's readonly/required state).
1958
+ * Build the form template from desc.cols: EVERY field of the
1959
+ * record, the ones that cannot be written included.
1960
+ *
1961
+ * They used to be dropped, and a record was then shown as the
1962
+ * handful of its fields somebody may type: an asset opened as
1963
+ * its id alone, with its type, its size, its date and where it
1964
+ * came from -- everything a person opens an asset to read --
1965
+ * nowhere on screen. What a form is for is READING a record;
1966
+ * writing it is one of the two things you can do next.
1967
+ *
1968
+ * C_YUI_FORM renders a col that is not `writable` read-only on
1969
+ * its own (`build_form_field_conf`), so this is the whole of
1970
+ * it. What must NOT follow is sending them back:
1971
+ * `transform__form_record_2_treedb_record()` drops them, and
1972
+ * the reason is written there.
1973
+ *
1974
+ * Still out: hooks (a hook is not a field of the record, it is
1975
+ * the children hanging off it), hidden cols, and the `_`
1976
+ * scaffolding the table adds.
1937
1977
  ************************************************************/
1938
1978
  function build_form_template(gobj)
1939
1979
  {
@@ -1950,10 +1990,6 @@ function build_form_template(gobj)
1950
1990
  if(field_desc.type === "hook") {
1951
1991
  continue; // hooks don't appear in forms
1952
1992
  }
1953
- if(!field_desc.is_writable &&
1954
- field_desc.type !== "fkey" && col.id !== desc.pkey) {
1955
- continue;
1956
- }
1957
1993
  template.push(col);
1958
1994
  }
1959
1995
  return template;
@@ -2023,6 +2059,14 @@ function open_form_dialog(gobj, mode, record)
2023
2059
  title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
2024
2060
  }
2025
2061
 
2062
+ /* "view" is an "update" that writes nothing: same title, same pkey
2063
+ * handling (readonly), every field read-only and the write half of
2064
+ * the toolbar gone. It is what a row click opens when the table is
2065
+ * NOT in edition mode -- reading a record is the ordinary thing to
2066
+ * want, and until now a click on a row did nothing at all. */
2067
+ let view_only = (mode === "view");
2068
+ let form_mode = view_only? "update" : mode;
2069
+
2026
2070
  /* A flex column so the hosted form takes the dialog height: fields
2027
2071
  * scroll internally and the form's bottom toolbar stays visible. */
2028
2072
  let $body = createElement2(
@@ -2038,19 +2082,22 @@ function open_form_dialog(gobj, mode, record)
2038
2082
  template: build_form_template(gobj),
2039
2083
  record: record,
2040
2084
  fkey_options: build_fkey_options(gobj),
2041
- form_mode: mode,
2085
+ form_mode: form_mode,
2086
+ readonly: view_only,
2042
2087
  /* A read-only topic still OPENS its form -- looking at a record
2043
2088
  * is the point of a replica -- with the cells not editable and
2044
2089
  * the write half of the toolbar gone. A null plan leaves the
2045
2090
  * form's own default alone. */
2046
- ...(form_plan.form_toolbar ? {toolbar: form_plan.form_toolbar} : {}),
2091
+ ...((view_only? READONLY_FORM_TOOLBAR : form_plan.form_toolbar)?
2092
+ {toolbar: view_only? READONLY_FORM_TOOLBAR.slice()
2093
+ : form_plan.form_toolbar} : {}),
2047
2094
  /* Editing the raw topic record: structured cols
2048
2095
  * (template / table / coordinates) are raw JSON editors,
2049
2096
  * not interpreted into sub-widgets (the pre-merge behaviour). */
2050
2097
  render_mode: "edit",
2051
2098
  pkey: desc.pkey || "id",
2052
2099
  topic_name: topic_name,
2053
- editable: !form_plan.readonly,
2100
+ editable: !form_plan.readonly && !view_only,
2054
2101
  $parent: $body
2055
2102
  },
2056
2103
  gobj
@@ -2446,6 +2493,42 @@ function show_edit_form(gobj, row, index)
2446
2493
  open_form_dialog(gobj, mode, row);
2447
2494
  }
2448
2495
 
2496
+ /************************************************************
2497
+ * Does a click on this column already mean something else?
2498
+ *
2499
+ * The `_` columns are the table's own scaffolding (the selection
2500
+ * checkbox, the Op buttons); a hook opens its children and a col
2501
+ * holding a json document opens it in a viewer. Anything else is
2502
+ * plain data, and a click on it means "show me this record".
2503
+ ************************************************************/
2504
+ const CELL_WITH_ACTION = [
2505
+ "hook", "object", "dict", "template", "array", "list",
2506
+ "coordinates", "blob", "gbuffer"
2507
+ ];
2508
+
2509
+ function cell_has_its_own_action(gobj, field)
2510
+ {
2511
+ if(!field || field.charAt(0) === '_') {
2512
+ return true;
2513
+ }
2514
+ let col = get_schema_col(gobj, field);
2515
+ if(!col) {
2516
+ return false;
2517
+ }
2518
+ const field_desc = treedb_get_field_desc(col);
2519
+ return CELL_WITH_ACTION.includes(field_desc.type) ||
2520
+ CELL_WITH_ACTION.includes(field_desc.real_type);
2521
+ }
2522
+
2523
+ /************************************************************
2524
+ * Show a record to be READ: every field, none of them editable.
2525
+ * What a row click opens while the table is not in edition mode.
2526
+ ************************************************************/
2527
+ function show_view_form(gobj, row)
2528
+ {
2529
+ open_form_dialog(gobj, "view", row);
2530
+ }
2531
+
2449
2532
  /************************************************************
2450
2533
  * Show the edit "create" form to a new record
2451
2534
  * internally called from the top toolbar +New button
@@ -2476,6 +2559,8 @@ function get_schema_col(gobj, id)
2476
2559
  ************************************************************/
2477
2560
  function transform__form_record_2_treedb_record(gobj, kw, operation)
2478
2561
  {
2562
+ let desc = gobj_read_attr(gobj, "desc");
2563
+ let pkey = desc.pkey || "id";
2479
2564
  let row = {};
2480
2565
 
2481
2566
  for(let field_name of Object.keys(kw)) {
@@ -2483,12 +2568,33 @@ function transform__form_record_2_treedb_record(gobj, kw, operation)
2483
2568
  continue;
2484
2569
  }
2485
2570
  let col = get_schema_col(gobj, field_name);
2486
- if(col) {
2487
- let value = kw[field_name];
2488
- row[field_name] = transform__form_value_2_treedb_value(
2489
- gobj, col, value, operation
2490
- );
2571
+ if(!col) {
2572
+ continue;
2573
+ }
2574
+
2575
+ /* The form SHOWS every field and sends back only what the topic
2576
+ * accepts: the writable cols, the fkeys (a link is edited by
2577
+ * linking) and the pkey, which is not written but is what
2578
+ * ADDRESSES the record.
2579
+ *
2580
+ * It matters because `treedb_update_node()` does not check
2581
+ * `writable` -- it writes any col it is handed -- so a read-only
2582
+ * field travelling back is written with whatever the form made of
2583
+ * it, and the fields that describe a record are exactly the ones
2584
+ * that do not survive a round trip through a widget: `t` is an
2585
+ * integer rendered as a date. Nothing would look wrong until the
2586
+ * stored timestamp had moved.
2587
+ */
2588
+ const field_desc = treedb_get_field_desc(col);
2589
+ if(!field_desc.is_writable &&
2590
+ field_desc.type !== "fkey" && col.id !== pkey) {
2591
+ continue;
2491
2592
  }
2593
+
2594
+ let value = kw[field_name];
2595
+ row[field_name] = transform__form_value_2_treedb_value(
2596
+ gobj, col, value, operation
2597
+ );
2492
2598
  }
2493
2599
  return row;
2494
2600
  }
@@ -3773,6 +3879,29 @@ function ac_open_columns(gobj, event, kw, src)
3773
3879
  return 0;
3774
3880
  }
3775
3881
 
3882
+ /************************************************************
3883
+ * A row was clicked outside edition mode: open the record to be read.
3884
+ ************************************************************/
3885
+ function ac_show_record(gobj, event, kw, src)
3886
+ {
3887
+ let tabulator = gobj_read_attr(gobj, "tabulator");
3888
+ let index = kw? kw.index : undefined;
3889
+ if(!tabulator || typeof index !== "number") {
3890
+ log_error(`${gobj_short_name(gobj)}: no row ${index} to show`);
3891
+ return -1;
3892
+ }
3893
+
3894
+ let row = tabulator.getRowFromPosition(index);
3895
+ if(!row) {
3896
+ log_error(`${gobj_short_name(gobj)}: row ${index} is gone`);
3897
+ return -1;
3898
+ }
3899
+
3900
+ show_view_form(gobj, row.getData());
3901
+
3902
+ return 0;
3903
+ }
3904
+
3776
3905
  /************************************************************
3777
3906
  * The reader picked another page size: remember it, so coming back to
3778
3907
  * this topic opens where they left it instead of at 200.
@@ -3933,6 +4062,7 @@ function create_gclass(gclass_name)
3933
4062
  ["EV_SEARCH", ac_search, null],
3934
4063
  ["EV_OPEN_COLUMNS", ac_open_columns, null],
3935
4064
  ["EV_PAGE_SIZE_CHANGED", ac_page_size_changed, null],
4065
+ ["EV_SHOW_RECORD", ac_show_record, null],
3936
4066
  ["EV_TOGGLE_COLUMN", ac_toggle_column, null],
3937
4067
  ["EV_EXPORT_TABLE", ac_export_table, null],
3938
4068
  ["EV_SHOW", ac_show, null],
@@ -3974,6 +4104,7 @@ function create_gclass(gclass_name)
3974
4104
  ["EV_SEARCH", 0],
3975
4105
  ["EV_OPEN_COLUMNS", 0],
3976
4106
  ["EV_PAGE_SIZE_CHANGED", 0],
4107
+ ["EV_SHOW_RECORD", 0],
3977
4108
  ["EV_TOGGLE_COLUMN", 0],
3978
4109
  ["EV_EXPORT_TABLE", 0],
3979
4110