@yuneta/gobj-ui 7.7.0 → 7.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gobj-ui.cjs.js +119 -8
- package/dist/gobj-ui.es.js +119 -8
- package/package.json +1 -1
- package/src/c_yui_form.js +10 -6
- package/src/c_yui_treedb_topic_with_form.js +113 -0
- package/src/c_yui_treedb_topics.js +57 -0
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -7493,13 +7493,8 @@ function field_validation_message($input) {
|
|
|
7493
7493
|
************************************************************/
|
|
7494
7494
|
function mark_field_validity($input) {
|
|
7495
7495
|
let $h = $input.parentNode ? $input.parentNode.querySelector(".help") : null;
|
|
7496
|
-
let
|
|
7497
|
-
|
|
7498
|
-
ok = $input.checkValidity();
|
|
7499
|
-
} catch (e) {
|
|
7500
|
-
ok = true;
|
|
7501
|
-
}
|
|
7502
|
-
if (ok) {
|
|
7496
|
+
let v = $input.validity;
|
|
7497
|
+
if (!v || v.valid) {
|
|
7503
7498
|
$input.classList.remove("is-danger");
|
|
7504
7499
|
if ($h) {
|
|
7505
7500
|
$h.textContent = "";
|
|
@@ -15614,7 +15609,13 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
15614
15609
|
}
|
|
15615
15610
|
if (result < 0) {
|
|
15616
15611
|
if (command === "descs") show_load_error$1(gobj, (0, i18next.t)(comment));
|
|
15617
|
-
else
|
|
15612
|
+
else {
|
|
15613
|
+
yui_shell_show_error(yui_shell_of(gobj), comment, { t: i18next.t });
|
|
15614
|
+
if (command === "update-node" || command === "create-node") {
|
|
15615
|
+
let failed_topic = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0);
|
|
15616
|
+
if (failed_topic) treedb_nodes$1(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name"), failed_topic, { list_dict: true });
|
|
15617
|
+
}
|
|
15618
|
+
}
|
|
15618
15619
|
return 0;
|
|
15619
15620
|
}
|
|
15620
15621
|
switch (command) {
|
|
@@ -15854,6 +15855,35 @@ function ac_create_record(gobj, event, kw, src) {
|
|
|
15854
15855
|
});
|
|
15855
15856
|
}
|
|
15856
15857
|
/********************************************
|
|
15858
|
+
* Event from formtable: ONE field of one record, edited in place.
|
|
15859
|
+
*
|
|
15860
|
+
* Written as a PARTIAL update and, deliberately, with NO `autolink`.
|
|
15861
|
+
* `treedb_update_node()` merges (`json_object_update`), so the fields
|
|
15862
|
+
* it does not carry are left alone; and `autolink` is what wipes a
|
|
15863
|
+
* node's links to rebuild them from the fkeys the record carries — on
|
|
15864
|
+
* a partial record that reads as "no parents" and DETACHES the node,
|
|
15865
|
+
* answering success. A cell edit changes a scalar and must not be able
|
|
15866
|
+
* to touch a link at all.
|
|
15867
|
+
*
|
|
15868
|
+
* This is why it is its own event and not EV_UPDATE_RECORD: that one
|
|
15869
|
+
* DOES send autolink, and may, because the form hands it the whole
|
|
15870
|
+
* record with its fkeys in it.
|
|
15871
|
+
********************************************/
|
|
15872
|
+
function ac_update_field(gobj, event, kw, src) {
|
|
15873
|
+
if (refuse_if_readonly$3(gobj, event)) return -1;
|
|
15874
|
+
let topic_name = kw.topic_name || (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
15875
|
+
let desc = ((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "descs") || {})[topic_name];
|
|
15876
|
+
let pkey = desc && desc.pkey || "id";
|
|
15877
|
+
if (kw.id === void 0 || kw.id === null || !kw.field) {
|
|
15878
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a field write with no id or no field`);
|
|
15879
|
+
return -1;
|
|
15880
|
+
}
|
|
15881
|
+
let record = {};
|
|
15882
|
+
record[pkey] = kw.id;
|
|
15883
|
+
record[kw.field] = kw.value;
|
|
15884
|
+
return treedb_update_node$1(gobj, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name"), topic_name, record, { list_dict: true });
|
|
15885
|
+
}
|
|
15886
|
+
/********************************************
|
|
15857
15887
|
* Event from formtable
|
|
15858
15888
|
* kw: {
|
|
15859
15889
|
* topic_name,
|
|
@@ -15975,6 +16005,11 @@ function create_gclass$6(gclass_name) {
|
|
|
15975
16005
|
ac_update_record,
|
|
15976
16006
|
null
|
|
15977
16007
|
],
|
|
16008
|
+
[
|
|
16009
|
+
"EV_UPDATE_FIELD",
|
|
16010
|
+
ac_update_field,
|
|
16011
|
+
null
|
|
16012
|
+
],
|
|
15978
16013
|
[
|
|
15979
16014
|
"EV_DELETE_RECORD",
|
|
15980
16015
|
ac_delete_record,
|
|
@@ -16048,6 +16083,7 @@ function create_gclass$6(gclass_name) {
|
|
|
16048
16083
|
["EV_TREEDB_NODE_DELETED", _yuneta_gobj_js.event_flag_t.EVF_PUBLIC_EVENT],
|
|
16049
16084
|
["EV_CREATE_RECORD", 0],
|
|
16050
16085
|
["EV_UPDATE_RECORD", 0],
|
|
16086
|
+
["EV_UPDATE_FIELD", 0],
|
|
16051
16087
|
["EV_DELETE_RECORD", 0],
|
|
16052
16088
|
["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
16053
16089
|
["EV_REFRESH_TOPIC", 0],
|
|
@@ -16261,6 +16297,7 @@ var attrs_table$5 = [
|
|
|
16261
16297
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
16262
16298
|
(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)"),
|
|
16263
16299
|
(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)"),
|
|
16300
|
+
(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)"),
|
|
16264
16301
|
(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"),
|
|
16265
16302
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
16266
16303
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
@@ -16770,6 +16807,7 @@ function create_tabulator(gobj) {
|
|
|
16770
16807
|
let table_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "table_id");
|
|
16771
16808
|
let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
|
|
16772
16809
|
let with_header_filters = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_header_filters");
|
|
16810
|
+
let with_inline_edit = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_inline_edit");
|
|
16773
16811
|
let columns = [];
|
|
16774
16812
|
let with_checkbox = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_checkbox");
|
|
16775
16813
|
let with_radio = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_radio");
|
|
@@ -16813,6 +16851,42 @@ function create_tabulator(gobj) {
|
|
|
16813
16851
|
col_id: cell.getField()
|
|
16814
16852
|
}, gobj);
|
|
16815
16853
|
}
|
|
16854
|
+
const INLINE_EDITABLE = [
|
|
16855
|
+
"string",
|
|
16856
|
+
"integer",
|
|
16857
|
+
"real",
|
|
16858
|
+
"email",
|
|
16859
|
+
"url",
|
|
16860
|
+
"tel",
|
|
16861
|
+
"id",
|
|
16862
|
+
"hex",
|
|
16863
|
+
"currency",
|
|
16864
|
+
"percent",
|
|
16865
|
+
"uuid"
|
|
16866
|
+
];
|
|
16867
|
+
function inline_editor_for(colDef, col, field_desc) {
|
|
16868
|
+
if (!field_desc.is_writable) return;
|
|
16869
|
+
if (col.id === (desc.pkey || "id")) return;
|
|
16870
|
+
switch (field_desc.type) {
|
|
16871
|
+
case "boolean":
|
|
16872
|
+
colDef.editor = "tickCross";
|
|
16873
|
+
break;
|
|
16874
|
+
case "enum":
|
|
16875
|
+
colDef.editor = "list";
|
|
16876
|
+
colDef.editorParams = { values: field_desc.enum_list || [] };
|
|
16877
|
+
break;
|
|
16878
|
+
case "integer":
|
|
16879
|
+
case "real":
|
|
16880
|
+
colDef.editor = "number";
|
|
16881
|
+
break;
|
|
16882
|
+
default:
|
|
16883
|
+
if (!INLINE_EDITABLE.includes(field_desc.type)) return;
|
|
16884
|
+
colDef.editor = "input";
|
|
16885
|
+
}
|
|
16886
|
+
colDef.editable = function() {
|
|
16887
|
+
return (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable") && !(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly");
|
|
16888
|
+
};
|
|
16889
|
+
}
|
|
16816
16890
|
const FILTERABLE = [
|
|
16817
16891
|
"string",
|
|
16818
16892
|
"integer",
|
|
@@ -16935,6 +17009,7 @@ function create_tabulator(gobj) {
|
|
|
16935
17009
|
formatter: colFormatter
|
|
16936
17010
|
};
|
|
16937
17011
|
if (with_header_filters) apply_header_filter(colDef, field_desc);
|
|
17012
|
+
if (with_inline_edit) inline_editor_for(colDef, col, field_desc);
|
|
16938
17013
|
if (vertAlign) colDef.vertAlign = vertAlign;
|
|
16939
17014
|
if (formatterParams) colDef.formatterParams = formatterParams;
|
|
16940
17015
|
if (cellClick) colDef.cellClick = cellClick;
|
|
@@ -17021,6 +17096,21 @@ function create_tabulator(gobj) {
|
|
|
17021
17096
|
});
|
|
17022
17097
|
tabulator.on("dataProcessed", update_rowcount);
|
|
17023
17098
|
tabulator.on("dataChanged", update_rowcount);
|
|
17099
|
+
tabulator.on("cellEdited", function(cell) {
|
|
17100
|
+
let pkey = ((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc") || {}).pkey || "id";
|
|
17101
|
+
let data = cell.getRow().getData();
|
|
17102
|
+
let id = data ? data[pkey] : void 0;
|
|
17103
|
+
if (id === void 0 || id === null) {
|
|
17104
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: edited a row with no '${pkey}'`);
|
|
17105
|
+
cell.restoreOldValue();
|
|
17106
|
+
return;
|
|
17107
|
+
}
|
|
17108
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CELL_EDITED", {
|
|
17109
|
+
id,
|
|
17110
|
+
field: cell.getField(),
|
|
17111
|
+
value: cell.getValue()
|
|
17112
|
+
}, gobj);
|
|
17113
|
+
});
|
|
17024
17114
|
tabulator.on("dataFiltered", function(filters, rows) {
|
|
17025
17115
|
update_rowcount(Array.isArray(rows) ? rows.length : void 0);
|
|
17026
17116
|
});
|
|
@@ -18141,6 +18231,20 @@ function ac_show_cell_json(gobj, event, kw, src) {
|
|
|
18141
18231
|
return 0;
|
|
18142
18232
|
}
|
|
18143
18233
|
/************************************************************
|
|
18234
|
+
* One cell of one record changed. Publish it UP as a field write;
|
|
18235
|
+
* the host owns the treedb command and its options.
|
|
18236
|
+
************************************************************/
|
|
18237
|
+
function ac_cell_edited(gobj, event, kw, src) {
|
|
18238
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
18239
|
+
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_UPDATE_FIELD", {
|
|
18240
|
+
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
18241
|
+
id: kw.id,
|
|
18242
|
+
field: kw.field,
|
|
18243
|
+
value: kw.value
|
|
18244
|
+
});
|
|
18245
|
+
return 0;
|
|
18246
|
+
}
|
|
18247
|
+
/************************************************************
|
|
18144
18248
|
* Show the schema (desc) of this topic
|
|
18145
18249
|
************************************************************/
|
|
18146
18250
|
function ac_show_schema(gobj, event, kw, src) {
|
|
@@ -18390,6 +18494,11 @@ function create_gclass$5(gclass_name) {
|
|
|
18390
18494
|
ac_show_schema,
|
|
18391
18495
|
null
|
|
18392
18496
|
],
|
|
18497
|
+
[
|
|
18498
|
+
"EV_CELL_EDITED",
|
|
18499
|
+
ac_cell_edited,
|
|
18500
|
+
null
|
|
18501
|
+
],
|
|
18393
18502
|
[
|
|
18394
18503
|
"EV_SEARCH",
|
|
18395
18504
|
ac_search,
|
|
@@ -18440,6 +18549,8 @@ function create_gclass$5(gclass_name) {
|
|
|
18440
18549
|
["EV_CHANGE_LOCALE", 0],
|
|
18441
18550
|
["EV_REFRESH", 0],
|
|
18442
18551
|
["EV_SHOW_SCHEMA", 0],
|
|
18552
|
+
["EV_CELL_EDITED", 0],
|
|
18553
|
+
["EV_UPDATE_FIELD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
18443
18554
|
["EV_SEARCH", 0],
|
|
18444
18555
|
["EV_OPEN_COLUMNS", 0],
|
|
18445
18556
|
["EV_TOGGLE_COLUMN", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -7488,13 +7488,8 @@ function field_validation_message($input) {
|
|
|
7488
7488
|
************************************************************/
|
|
7489
7489
|
function mark_field_validity($input) {
|
|
7490
7490
|
let $h = $input.parentNode ? $input.parentNode.querySelector(".help") : null;
|
|
7491
|
-
let
|
|
7492
|
-
|
|
7493
|
-
ok = $input.checkValidity();
|
|
7494
|
-
} catch (e) {
|
|
7495
|
-
ok = true;
|
|
7496
|
-
}
|
|
7497
|
-
if (ok) {
|
|
7491
|
+
let v = $input.validity;
|
|
7492
|
+
if (!v || v.valid) {
|
|
7498
7493
|
$input.classList.remove("is-danger");
|
|
7499
7494
|
if ($h) {
|
|
7500
7495
|
$h.textContent = "";
|
|
@@ -15609,7 +15604,13 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
15609
15604
|
}
|
|
15610
15605
|
if (result < 0) {
|
|
15611
15606
|
if (command === "descs") show_load_error$1(gobj, t(comment));
|
|
15612
|
-
else
|
|
15607
|
+
else {
|
|
15608
|
+
yui_shell_show_error(yui_shell_of(gobj), comment, { t });
|
|
15609
|
+
if (command === "update-node" || command === "create-node") {
|
|
15610
|
+
let failed_topic = kw_get_str(gobj, kw_command, "topic_name", "", 0);
|
|
15611
|
+
if (failed_topic) treedb_nodes$1(gobj, gobj_read_str_attr(gobj, "treedb_name"), failed_topic, { list_dict: true });
|
|
15612
|
+
}
|
|
15613
|
+
}
|
|
15613
15614
|
return 0;
|
|
15614
15615
|
}
|
|
15615
15616
|
switch (command) {
|
|
@@ -15849,6 +15850,35 @@ function ac_create_record(gobj, event, kw, src) {
|
|
|
15849
15850
|
});
|
|
15850
15851
|
}
|
|
15851
15852
|
/********************************************
|
|
15853
|
+
* Event from formtable: ONE field of one record, edited in place.
|
|
15854
|
+
*
|
|
15855
|
+
* Written as a PARTIAL update and, deliberately, with NO `autolink`.
|
|
15856
|
+
* `treedb_update_node()` merges (`json_object_update`), so the fields
|
|
15857
|
+
* it does not carry are left alone; and `autolink` is what wipes a
|
|
15858
|
+
* node's links to rebuild them from the fkeys the record carries — on
|
|
15859
|
+
* a partial record that reads as "no parents" and DETACHES the node,
|
|
15860
|
+
* answering success. A cell edit changes a scalar and must not be able
|
|
15861
|
+
* to touch a link at all.
|
|
15862
|
+
*
|
|
15863
|
+
* This is why it is its own event and not EV_UPDATE_RECORD: that one
|
|
15864
|
+
* DOES send autolink, and may, because the form hands it the whole
|
|
15865
|
+
* record with its fkeys in it.
|
|
15866
|
+
********************************************/
|
|
15867
|
+
function ac_update_field(gobj, event, kw, src) {
|
|
15868
|
+
if (refuse_if_readonly$3(gobj, event)) return -1;
|
|
15869
|
+
let topic_name = kw.topic_name || gobj_read_attr(src, "topic_name");
|
|
15870
|
+
let desc = (gobj_read_attr(gobj, "descs") || {})[topic_name];
|
|
15871
|
+
let pkey = desc && desc.pkey || "id";
|
|
15872
|
+
if (kw.id === void 0 || kw.id === null || !kw.field) {
|
|
15873
|
+
log_error(`${gobj_short_name(gobj)}: a field write with no id or no field`);
|
|
15874
|
+
return -1;
|
|
15875
|
+
}
|
|
15876
|
+
let record = {};
|
|
15877
|
+
record[pkey] = kw.id;
|
|
15878
|
+
record[kw.field] = kw.value;
|
|
15879
|
+
return treedb_update_node$1(gobj, gobj_read_str_attr(gobj, "treedb_name"), topic_name, record, { list_dict: true });
|
|
15880
|
+
}
|
|
15881
|
+
/********************************************
|
|
15852
15882
|
* Event from formtable
|
|
15853
15883
|
* kw: {
|
|
15854
15884
|
* topic_name,
|
|
@@ -15970,6 +16000,11 @@ function create_gclass$6(gclass_name) {
|
|
|
15970
16000
|
ac_update_record,
|
|
15971
16001
|
null
|
|
15972
16002
|
],
|
|
16003
|
+
[
|
|
16004
|
+
"EV_UPDATE_FIELD",
|
|
16005
|
+
ac_update_field,
|
|
16006
|
+
null
|
|
16007
|
+
],
|
|
15973
16008
|
[
|
|
15974
16009
|
"EV_DELETE_RECORD",
|
|
15975
16010
|
ac_delete_record,
|
|
@@ -16043,6 +16078,7 @@ function create_gclass$6(gclass_name) {
|
|
|
16043
16078
|
["EV_TREEDB_NODE_DELETED", event_flag_t.EVF_PUBLIC_EVENT],
|
|
16044
16079
|
["EV_CREATE_RECORD", 0],
|
|
16045
16080
|
["EV_UPDATE_RECORD", 0],
|
|
16081
|
+
["EV_UPDATE_FIELD", 0],
|
|
16046
16082
|
["EV_DELETE_RECORD", 0],
|
|
16047
16083
|
["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
16048
16084
|
["EV_REFRESH_TOPIC", 0],
|
|
@@ -16256,6 +16292,7 @@ var attrs_table$5 = [
|
|
|
16256
16292
|
SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
16257
16293
|
SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
|
|
16258
16294
|
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)"),
|
|
16295
|
+
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)"),
|
|
16259
16296
|
SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
|
|
16260
16297
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
16261
16298
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
@@ -16765,6 +16802,7 @@ function create_tabulator(gobj) {
|
|
|
16765
16802
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
16766
16803
|
let desc = gobj_read_attr(gobj, "desc");
|
|
16767
16804
|
let with_header_filters = gobj_read_bool_attr(gobj, "with_header_filters");
|
|
16805
|
+
let with_inline_edit = gobj_read_bool_attr(gobj, "with_inline_edit");
|
|
16768
16806
|
let columns = [];
|
|
16769
16807
|
let with_checkbox = gobj_read_bool_attr(gobj, "with_checkbox");
|
|
16770
16808
|
let with_radio = gobj_read_bool_attr(gobj, "with_radio");
|
|
@@ -16808,6 +16846,42 @@ function create_tabulator(gobj) {
|
|
|
16808
16846
|
col_id: cell.getField()
|
|
16809
16847
|
}, gobj);
|
|
16810
16848
|
}
|
|
16849
|
+
const INLINE_EDITABLE = [
|
|
16850
|
+
"string",
|
|
16851
|
+
"integer",
|
|
16852
|
+
"real",
|
|
16853
|
+
"email",
|
|
16854
|
+
"url",
|
|
16855
|
+
"tel",
|
|
16856
|
+
"id",
|
|
16857
|
+
"hex",
|
|
16858
|
+
"currency",
|
|
16859
|
+
"percent",
|
|
16860
|
+
"uuid"
|
|
16861
|
+
];
|
|
16862
|
+
function inline_editor_for(colDef, col, field_desc) {
|
|
16863
|
+
if (!field_desc.is_writable) return;
|
|
16864
|
+
if (col.id === (desc.pkey || "id")) return;
|
|
16865
|
+
switch (field_desc.type) {
|
|
16866
|
+
case "boolean":
|
|
16867
|
+
colDef.editor = "tickCross";
|
|
16868
|
+
break;
|
|
16869
|
+
case "enum":
|
|
16870
|
+
colDef.editor = "list";
|
|
16871
|
+
colDef.editorParams = { values: field_desc.enum_list || [] };
|
|
16872
|
+
break;
|
|
16873
|
+
case "integer":
|
|
16874
|
+
case "real":
|
|
16875
|
+
colDef.editor = "number";
|
|
16876
|
+
break;
|
|
16877
|
+
default:
|
|
16878
|
+
if (!INLINE_EDITABLE.includes(field_desc.type)) return;
|
|
16879
|
+
colDef.editor = "input";
|
|
16880
|
+
}
|
|
16881
|
+
colDef.editable = function() {
|
|
16882
|
+
return gobj_read_bool_attr(gobj, "editable") && !gobj_read_bool_attr(gobj, "readonly");
|
|
16883
|
+
};
|
|
16884
|
+
}
|
|
16811
16885
|
const FILTERABLE = [
|
|
16812
16886
|
"string",
|
|
16813
16887
|
"integer",
|
|
@@ -16930,6 +17004,7 @@ function create_tabulator(gobj) {
|
|
|
16930
17004
|
formatter: colFormatter
|
|
16931
17005
|
};
|
|
16932
17006
|
if (with_header_filters) apply_header_filter(colDef, field_desc);
|
|
17007
|
+
if (with_inline_edit) inline_editor_for(colDef, col, field_desc);
|
|
16933
17008
|
if (vertAlign) colDef.vertAlign = vertAlign;
|
|
16934
17009
|
if (formatterParams) colDef.formatterParams = formatterParams;
|
|
16935
17010
|
if (cellClick) colDef.cellClick = cellClick;
|
|
@@ -17016,6 +17091,21 @@ function create_tabulator(gobj) {
|
|
|
17016
17091
|
});
|
|
17017
17092
|
tabulator.on("dataProcessed", update_rowcount);
|
|
17018
17093
|
tabulator.on("dataChanged", update_rowcount);
|
|
17094
|
+
tabulator.on("cellEdited", function(cell) {
|
|
17095
|
+
let pkey = (gobj_read_attr(gobj, "desc") || {}).pkey || "id";
|
|
17096
|
+
let data = cell.getRow().getData();
|
|
17097
|
+
let id = data ? data[pkey] : void 0;
|
|
17098
|
+
if (id === void 0 || id === null) {
|
|
17099
|
+
log_error(`${gobj_short_name(gobj)}: edited a row with no '${pkey}'`);
|
|
17100
|
+
cell.restoreOldValue();
|
|
17101
|
+
return;
|
|
17102
|
+
}
|
|
17103
|
+
gobj_send_event(gobj, "EV_CELL_EDITED", {
|
|
17104
|
+
id,
|
|
17105
|
+
field: cell.getField(),
|
|
17106
|
+
value: cell.getValue()
|
|
17107
|
+
}, gobj);
|
|
17108
|
+
});
|
|
17019
17109
|
tabulator.on("dataFiltered", function(filters, rows) {
|
|
17020
17110
|
update_rowcount(Array.isArray(rows) ? rows.length : void 0);
|
|
17021
17111
|
});
|
|
@@ -18136,6 +18226,20 @@ function ac_show_cell_json(gobj, event, kw, src) {
|
|
|
18136
18226
|
return 0;
|
|
18137
18227
|
}
|
|
18138
18228
|
/************************************************************
|
|
18229
|
+
* One cell of one record changed. Publish it UP as a field write;
|
|
18230
|
+
* the host owns the treedb command and its options.
|
|
18231
|
+
************************************************************/
|
|
18232
|
+
function ac_cell_edited(gobj, event, kw, src) {
|
|
18233
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
18234
|
+
gobj_publish_event(gobj, "EV_UPDATE_FIELD", {
|
|
18235
|
+
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
18236
|
+
id: kw.id,
|
|
18237
|
+
field: kw.field,
|
|
18238
|
+
value: kw.value
|
|
18239
|
+
});
|
|
18240
|
+
return 0;
|
|
18241
|
+
}
|
|
18242
|
+
/************************************************************
|
|
18139
18243
|
* Show the schema (desc) of this topic
|
|
18140
18244
|
************************************************************/
|
|
18141
18245
|
function ac_show_schema(gobj, event, kw, src) {
|
|
@@ -18385,6 +18489,11 @@ function create_gclass$5(gclass_name) {
|
|
|
18385
18489
|
ac_show_schema,
|
|
18386
18490
|
null
|
|
18387
18491
|
],
|
|
18492
|
+
[
|
|
18493
|
+
"EV_CELL_EDITED",
|
|
18494
|
+
ac_cell_edited,
|
|
18495
|
+
null
|
|
18496
|
+
],
|
|
18388
18497
|
[
|
|
18389
18498
|
"EV_SEARCH",
|
|
18390
18499
|
ac_search,
|
|
@@ -18435,6 +18544,8 @@ function create_gclass$5(gclass_name) {
|
|
|
18435
18544
|
["EV_CHANGE_LOCALE", 0],
|
|
18436
18545
|
["EV_REFRESH", 0],
|
|
18437
18546
|
["EV_SHOW_SCHEMA", 0],
|
|
18547
|
+
["EV_CELL_EDITED", 0],
|
|
18548
|
+
["EV_UPDATE_FIELD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
18438
18549
|
["EV_SEARCH", 0],
|
|
18439
18550
|
["EV_OPEN_COLUMNS", 0],
|
|
18440
18551
|
["EV_TOGGLE_COLUMN", 0],
|
package/package.json
CHANGED
package/src/c_yui_form.js
CHANGED
|
@@ -1990,12 +1990,16 @@ function field_validation_message($input)
|
|
|
1990
1990
|
function mark_field_validity($input)
|
|
1991
1991
|
{
|
|
1992
1992
|
let $h = $input.parentNode? $input.parentNode.querySelector('.help') : null;
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1993
|
+
|
|
1994
|
+
/* `validity.valid`, NEVER `checkValidity()`.
|
|
1995
|
+
*
|
|
1996
|
+
* checkValidity() FIRES the `invalid` event, and this function is what
|
|
1997
|
+
* that event's handler calls: one asks the other for ever. It cost a
|
|
1998
|
+
* page full of "too much recursion" the first time Save reported a bad
|
|
1999
|
+
* field. Reading `validity` asks the same question and fires nothing.
|
|
2000
|
+
* A control with no `validity` (a wrapper div) is not invalid. */
|
|
2001
|
+
let v = $input.validity;
|
|
2002
|
+
let ok = !v || v.valid;
|
|
1999
2003
|
|
|
2000
2004
|
if(ok) {
|
|
2001
2005
|
$input.classList.remove('is-danger');
|
|
@@ -111,6 +111,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button
|
|
|
111
111
|
SDATA(data_type_t.DTP_BOOLEAN, "with_columns_button", 0, true, "Button toolbar COLUMNS (choose which columns the table shows)"),
|
|
112
112
|
SDATA(data_type_t.DTP_BOOLEAN, "with_export_button", 0, true, "Button toolbar EXPORT (download as CSV what the table holds)"),
|
|
113
113
|
SDATA(data_type_t.DTP_BOOLEAN, "with_header_filters", 0, true, "Per-column filter box in the table header, on the columns a text/number match means something (not hooks, fkeys or json)"),
|
|
114
|
+
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)"),
|
|
114
115
|
SDATA(data_type_t.DTP_BOOLEAN, "with_in_row_edit_icons", 0, true, "Add a last column with internal EDIT/DELETE icon"),
|
|
115
116
|
|
|
116
117
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"),
|
|
@@ -730,6 +731,7 @@ function create_tabulator(gobj)
|
|
|
730
731
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
731
732
|
let desc = gobj_read_attr(gobj, "desc");
|
|
732
733
|
let with_header_filters = gobj_read_bool_attr(gobj, "with_header_filters");
|
|
734
|
+
let with_inline_edit = gobj_read_bool_attr(gobj, "with_inline_edit");
|
|
733
735
|
|
|
734
736
|
let columns = [];
|
|
735
737
|
|
|
@@ -819,6 +821,62 @@ function create_tabulator(gobj)
|
|
|
819
821
|
* value is stringified first because a fkey can arrive as a ref string,
|
|
820
822
|
* a list of them or a dict.
|
|
821
823
|
*/
|
|
824
|
+
/* A cell you can type into, and the ones you cannot.
|
|
825
|
+
*
|
|
826
|
+
* Editing in place is for a SCALAR the table can round-trip. A hook
|
|
827
|
+
* holds children and an fkey IS a link — both are edited by linking,
|
|
828
|
+
* not by typing — a dict or a list is a document (the form has an
|
|
829
|
+
* editor for it), and a date cell shows a formatted string over an
|
|
830
|
+
* epoch, so typing into it would write the string. Those stay with
|
|
831
|
+
* the form, which is one click away on the same row.
|
|
832
|
+
*
|
|
833
|
+
* The schema decides the rest: only a column flagged `writable` is
|
|
834
|
+
* offered, and never the pkey — renaming what a record is KEYED by
|
|
835
|
+
* is not a field edit.
|
|
836
|
+
*/
|
|
837
|
+
const INLINE_EDITABLE = [
|
|
838
|
+
"string", "integer", "real",
|
|
839
|
+
"email", "url", "tel", "id", "hex", "currency", "percent", "uuid"
|
|
840
|
+
];
|
|
841
|
+
|
|
842
|
+
function inline_editor_for(colDef, col, field_desc)
|
|
843
|
+
{
|
|
844
|
+
if(!field_desc.is_writable) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if(col.id === (desc.pkey || "id")) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
switch(field_desc.type) {
|
|
852
|
+
case "boolean":
|
|
853
|
+
colDef.editor = "tickCross";
|
|
854
|
+
break;
|
|
855
|
+
case "enum":
|
|
856
|
+
colDef.editor = "list";
|
|
857
|
+
colDef.editorParams = {values: field_desc.enum_list || []};
|
|
858
|
+
break;
|
|
859
|
+
case "integer":
|
|
860
|
+
case "real":
|
|
861
|
+
colDef.editor = "number";
|
|
862
|
+
break;
|
|
863
|
+
default:
|
|
864
|
+
if(!INLINE_EDITABLE.includes(field_desc.type)) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
colDef.editor = "input";
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* A FUNCTION, not a flag: edition mode is toggled on a table that
|
|
872
|
+
* is already built (ac_edition_mode only shows and hides columns),
|
|
873
|
+
* so the answer has to be asked for at the moment of the click. */
|
|
874
|
+
colDef.editable = function() {
|
|
875
|
+
return gobj_read_bool_attr(gobj, "editable") &&
|
|
876
|
+
!gobj_read_bool_attr(gobj, "readonly");
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
822
880
|
const FILTERABLE = [
|
|
823
881
|
"string", "integer", "real", "rowid", "uuid", "qualified",
|
|
824
882
|
"email", "url", "tel", "id", "hex", "currency", "percent"
|
|
@@ -974,6 +1032,9 @@ function create_tabulator(gobj)
|
|
|
974
1032
|
if(with_header_filters) {
|
|
975
1033
|
apply_header_filter(colDef, field_desc);
|
|
976
1034
|
}
|
|
1035
|
+
if(with_inline_edit) {
|
|
1036
|
+
inline_editor_for(colDef, col, field_desc);
|
|
1037
|
+
}
|
|
977
1038
|
if(vertAlign) {
|
|
978
1039
|
colDef.vertAlign = vertAlign;
|
|
979
1040
|
}
|
|
@@ -1139,6 +1200,33 @@ function create_tabulator(gobj)
|
|
|
1139
1200
|
* box; a filter per column made it a claim you read on every keystroke. */
|
|
1140
1201
|
tabulator.on("dataProcessed", update_rowcount);
|
|
1141
1202
|
tabulator.on("dataChanged", update_rowcount);
|
|
1203
|
+
/* A cell edited in place. Announced as ONE FIELD of one record — not
|
|
1204
|
+
* as the row — and that is the whole safety of it: the host writes it
|
|
1205
|
+
* with a partial update and no `autolink`, so nothing but that field
|
|
1206
|
+
* moves. `autolink` wipes a node's links and rebuilds them from the
|
|
1207
|
+
* fkeys the record carries, so sending a row that the table shaped for
|
|
1208
|
+
* DISPLAY would unlink the node and answer success. */
|
|
1209
|
+
tabulator.on("cellEdited", function(cell) {
|
|
1210
|
+
let pkey = (gobj_read_attr(gobj, "desc") || {}).pkey || "id";
|
|
1211
|
+
let data = cell.getRow().getData();
|
|
1212
|
+
let id = data ? data[pkey] : undefined;
|
|
1213
|
+
if(id === undefined || id === null) {
|
|
1214
|
+
log_error(`${gobj_short_name(gobj)}: edited a row with no '${pkey}'`);
|
|
1215
|
+
cell.restoreOldValue();
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
gobj_send_event(
|
|
1219
|
+
gobj,
|
|
1220
|
+
"EV_CELL_EDITED",
|
|
1221
|
+
{
|
|
1222
|
+
id: id,
|
|
1223
|
+
field: cell.getField(),
|
|
1224
|
+
value: cell.getValue()
|
|
1225
|
+
},
|
|
1226
|
+
gobj
|
|
1227
|
+
);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1142
1230
|
tabulator.on("dataFiltered", function(filters, rows) {
|
|
1143
1231
|
update_rowcount(Array.isArray(rows)? rows.length : undefined);
|
|
1144
1232
|
});
|
|
@@ -2845,6 +2933,28 @@ function ac_show_cell_json(gobj, event, kw, src)
|
|
|
2845
2933
|
return 0;
|
|
2846
2934
|
}
|
|
2847
2935
|
|
|
2936
|
+
/************************************************************
|
|
2937
|
+
* One cell of one record changed. Publish it UP as a field write;
|
|
2938
|
+
* the host owns the treedb command and its options.
|
|
2939
|
+
************************************************************/
|
|
2940
|
+
function ac_cell_edited(gobj, event, kw, src)
|
|
2941
|
+
{
|
|
2942
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2943
|
+
return -1; /* Error already logged */
|
|
2944
|
+
}
|
|
2945
|
+
gobj_publish_event(
|
|
2946
|
+
gobj,
|
|
2947
|
+
"EV_UPDATE_FIELD",
|
|
2948
|
+
{
|
|
2949
|
+
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
2950
|
+
id: kw.id,
|
|
2951
|
+
field: kw.field,
|
|
2952
|
+
value: kw.value
|
|
2953
|
+
}
|
|
2954
|
+
);
|
|
2955
|
+
return 0;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2848
2958
|
/************************************************************
|
|
2849
2959
|
* Show the schema (desc) of this topic
|
|
2850
2960
|
************************************************************/
|
|
@@ -3076,6 +3186,7 @@ function create_gclass(gclass_name)
|
|
|
3076
3186
|
["EV_CHANGE_LOCALE", ac_change_locale, null],
|
|
3077
3187
|
["EV_REFRESH", ac_refresh, null],
|
|
3078
3188
|
["EV_SHOW_SCHEMA", ac_show_schema, null],
|
|
3189
|
+
["EV_CELL_EDITED", ac_cell_edited, null],
|
|
3079
3190
|
["EV_SEARCH", ac_search, null],
|
|
3080
3191
|
["EV_OPEN_COLUMNS", ac_open_columns, null],
|
|
3081
3192
|
["EV_TOGGLE_COLUMN", ac_toggle_column, null],
|
|
@@ -3108,6 +3219,8 @@ function create_gclass(gclass_name)
|
|
|
3108
3219
|
["EV_CHANGE_LOCALE", 0],
|
|
3109
3220
|
["EV_REFRESH", 0],
|
|
3110
3221
|
["EV_SHOW_SCHEMA", 0],
|
|
3222
|
+
["EV_CELL_EDITED", 0],
|
|
3223
|
+
["EV_UPDATE_FIELD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
3111
3224
|
["EV_SEARCH", 0],
|
|
3112
3225
|
["EV_OPEN_COLUMNS", 0],
|
|
3113
3226
|
["EV_TOGGLE_COLUMN", 0],
|
|
@@ -1740,6 +1740,18 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
1740
1740
|
show_load_error(gobj, t(comment));
|
|
1741
1741
|
} else {
|
|
1742
1742
|
yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
|
|
1743
|
+
/* A refused write leaves the TABLE showing what the operator
|
|
1744
|
+
* typed and the store holding what it had. That is tolerable
|
|
1745
|
+
* for a form (it stays open on the values it failed with) and
|
|
1746
|
+
* is not for a cell edited in place, which looks saved. Put the
|
|
1747
|
+
* topic back to what the treedb actually has. */
|
|
1748
|
+
if(command === "update-node" || command === "create-node") {
|
|
1749
|
+
let failed_topic = kw_get_str(gobj, kw_command, "topic_name", "", 0);
|
|
1750
|
+
if(failed_topic) {
|
|
1751
|
+
treedb_nodes(gobj, gobj_read_str_attr(gobj, "treedb_name"),
|
|
1752
|
+
failed_topic, {list_dict: true});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1743
1755
|
}
|
|
1744
1756
|
return 0;
|
|
1745
1757
|
}
|
|
@@ -2126,6 +2138,49 @@ function ac_create_record(gobj, event, kw, src)
|
|
|
2126
2138
|
);
|
|
2127
2139
|
}
|
|
2128
2140
|
|
|
2141
|
+
/********************************************
|
|
2142
|
+
* Event from formtable: ONE field of one record, edited in place.
|
|
2143
|
+
*
|
|
2144
|
+
* Written as a PARTIAL update and, deliberately, with NO `autolink`.
|
|
2145
|
+
* `treedb_update_node()` merges (`json_object_update`), so the fields
|
|
2146
|
+
* it does not carry are left alone; and `autolink` is what wipes a
|
|
2147
|
+
* node's links to rebuild them from the fkeys the record carries — on
|
|
2148
|
+
* a partial record that reads as "no parents" and DETACHES the node,
|
|
2149
|
+
* answering success. A cell edit changes a scalar and must not be able
|
|
2150
|
+
* to touch a link at all.
|
|
2151
|
+
*
|
|
2152
|
+
* This is why it is its own event and not EV_UPDATE_RECORD: that one
|
|
2153
|
+
* DOES send autolink, and may, because the form hands it the whole
|
|
2154
|
+
* record with its fkeys in it.
|
|
2155
|
+
********************************************/
|
|
2156
|
+
function ac_update_field(gobj, event, kw, src)
|
|
2157
|
+
{
|
|
2158
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
2159
|
+
return -1; /* Error already logged */
|
|
2160
|
+
}
|
|
2161
|
+
let topic_name = kw.topic_name || gobj_read_attr(src, "topic_name");
|
|
2162
|
+
let descs = gobj_read_attr(gobj, "descs") || {};
|
|
2163
|
+
let desc = descs[topic_name];
|
|
2164
|
+
let pkey = (desc && desc.pkey) || "id";
|
|
2165
|
+
|
|
2166
|
+
if(kw.id === undefined || kw.id === null || !kw.field) {
|
|
2167
|
+
log_error(`${gobj_short_name(gobj)}: a field write with no id or no field`);
|
|
2168
|
+
return -1;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
let record = {};
|
|
2172
|
+
record[pkey] = kw.id;
|
|
2173
|
+
record[kw.field] = kw.value;
|
|
2174
|
+
|
|
2175
|
+
return treedb_update_node(
|
|
2176
|
+
gobj,
|
|
2177
|
+
gobj_read_str_attr(gobj, "treedb_name"),
|
|
2178
|
+
topic_name,
|
|
2179
|
+
record,
|
|
2180
|
+
{list_dict: true}
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2129
2184
|
/********************************************
|
|
2130
2185
|
* Event from formtable
|
|
2131
2186
|
* kw: {
|
|
@@ -2292,6 +2347,7 @@ function create_gclass(gclass_name)
|
|
|
2292
2347
|
["EV_TREEDB_NODE_DELETED", ac_treedb_node_deleted, null],
|
|
2293
2348
|
["EV_CREATE_RECORD", ac_create_record, null],
|
|
2294
2349
|
["EV_UPDATE_RECORD", ac_update_record, null],
|
|
2350
|
+
["EV_UPDATE_FIELD", ac_update_field, null],
|
|
2295
2351
|
["EV_DELETE_RECORD", ac_delete_record, null],
|
|
2296
2352
|
["EV_REFRESH_TOPIC", ac_refresh_topic, null],
|
|
2297
2353
|
["EV_OPEN_JSON", ac_open_json, null],
|
|
@@ -2318,6 +2374,7 @@ function create_gclass(gclass_name)
|
|
|
2318
2374
|
["EV_TREEDB_NODE_DELETED", event_flag_t.EVF_PUBLIC_EVENT],
|
|
2319
2375
|
["EV_CREATE_RECORD", 0],
|
|
2320
2376
|
["EV_UPDATE_RECORD", 0],
|
|
2377
|
+
["EV_UPDATE_FIELD", 0],
|
|
2321
2378
|
["EV_DELETE_RECORD", 0],
|
|
2322
2379
|
["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT|
|
|
2323
2380
|
event_flag_t.EVF_NO_WARN_SUBS],
|