@yuneta/gobj-ui 7.23.167 → 7.23.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/gobj-ui.cjs.js +448 -104
- package/dist/gobj-ui.es.js +448 -104
- package/package.json +1 -1
- package/src/broadcast_attrs.test.js +115 -0
- package/src/c_g6_nodes_tree.js +208 -19
- package/src/c_yui_treedb_graph.js +4 -1
- package/src/c_yui_treedb_topic_with_form.js +276 -98
- package/src/c_yui_treedb_topics.js +4 -1
- package/src/yui_row_search.js +56 -27
- package/src/yui_row_search.test.js +51 -1
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
json_deep_copy,
|
|
24
24
|
createElement2,
|
|
25
25
|
gobj_send_event,
|
|
26
|
+
gobj_post_event,
|
|
26
27
|
clean_name,
|
|
27
28
|
is_array,
|
|
28
29
|
is_string,
|
|
@@ -209,8 +210,8 @@ SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Edit state"
|
|
|
209
210
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
210
211
|
SDATA(data_type_t.DTP_BOOLEAN, "with_radio", 0, false, "Auxiliary first column to select one row"),
|
|
211
212
|
SDATA(data_type_t.DTP_BOOLEAN, "with_selection_bar", 0, false, "Show the shared selection bar (\"N selected\" + a way out) above the table while in edition mode. OFF by default: the bar takes its words from the HOST's i18n, and a host that has not defined \"{{n}} selected\" and \"clear selection\" would render the keys"),
|
|
212
|
-
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_select_rows_event", 0, false, "
|
|
213
|
-
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_unselect_rows_event", 0, false, "
|
|
213
|
+
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_select_rows_event", 0, false, "Publish EV_SELECT_ROWS to the subscriber. OFF by default: the event is an OUTPUT event, so a host that turns it on must declare EV_SELECT_ROWS in its own FSM or it answers \"Event NOT DEFINED in state\" on every selected row"),
|
|
214
|
+
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_unselect_rows_event", 0, false, "Publish EV_UNSELECT_ROWS to the subscriber. Same contract as broadcast_select_rows_event, and INDEPENDENT of it: a host that wants both sets both"),
|
|
214
215
|
|
|
215
216
|
/*---------------- Tabulator Defaults ----------------*/
|
|
216
217
|
SDATA(data_type_t.DTP_JSON, "tabulator_settings", 0, {
|
|
@@ -605,7 +606,9 @@ function build_ui(gobj)
|
|
|
605
606
|
* instead of being pushed away from the harmless ones. Do not
|
|
606
607
|
* reorder this group as if they were peer actions. */
|
|
607
608
|
let $edit_button = createElement2(
|
|
608
|
-
['button', {id: ``, class: 'button button-edit-record mr-1'
|
|
609
|
+
['button', {id: ``, class: 'button button-edit-record mr-1',
|
|
610
|
+
title: t('edit'), 'data-i18n-title': 'edit',
|
|
611
|
+
'aria-label': t('edit'), 'data-i18n-aria-label': 'edit'}, [
|
|
609
612
|
yui_toolbar_icon('yi-pen'),
|
|
610
613
|
['span',
|
|
611
614
|
{
|
|
@@ -631,7 +634,9 @@ function build_ui(gobj)
|
|
|
631
634
|
|
|
632
635
|
if(with_new_button) {
|
|
633
636
|
let $new_button = createElement2(
|
|
634
|
-
['button', {id: ``, class: 'button button-new-record mr-1',
|
|
637
|
+
['button', {id: ``, class: 'button button-new-record mr-1',
|
|
638
|
+
title: t('new'), 'data-i18n-title': 'new',
|
|
639
|
+
'aria-label': t('new'), 'data-i18n-aria-label': 'new', disabled: true}, [
|
|
635
640
|
yui_toolbar_icon('yi-plus'),
|
|
636
641
|
['span',
|
|
637
642
|
{
|
|
@@ -651,7 +656,9 @@ function build_ui(gobj)
|
|
|
651
656
|
|
|
652
657
|
if(with_delete_button) {
|
|
653
658
|
let $delete_button = createElement2(
|
|
654
|
-
['button', {id: ``, class: 'button button-delete-record mr-1',
|
|
659
|
+
['button', {id: ``, class: 'button button-delete-record mr-1',
|
|
660
|
+
title: t('delete'), 'data-i18n-title': 'delete',
|
|
661
|
+
'aria-label': t('delete'), 'data-i18n-aria-label': 'delete', disabled: true}, [
|
|
655
662
|
yui_toolbar_icon('yi-trash'),
|
|
656
663
|
['span',
|
|
657
664
|
{
|
|
@@ -672,7 +679,9 @@ function build_ui(gobj)
|
|
|
672
679
|
|
|
673
680
|
if(with_copy_button) {
|
|
674
681
|
let $copy_button = createElement2(
|
|
675
|
-
['button', {id: ``, class: 'button button-copy-record mr-1',
|
|
682
|
+
['button', {id: ``, class: 'button button-copy-record mr-1',
|
|
683
|
+
title: t('copy'), 'data-i18n-title': 'copy',
|
|
684
|
+
'aria-label': t('copy'), 'data-i18n-aria-label': 'copy', disabled: true}, [
|
|
676
685
|
yui_toolbar_icon('yi-copy'),
|
|
677
686
|
['span',
|
|
678
687
|
{
|
|
@@ -693,7 +702,9 @@ function build_ui(gobj)
|
|
|
693
702
|
|
|
694
703
|
if(with_paste_button) {
|
|
695
704
|
let $paste_button = createElement2(
|
|
696
|
-
['button', {id: ``, class: 'button button-paste-record mr-1',
|
|
705
|
+
['button', {id: ``, class: 'button button-paste-record mr-1',
|
|
706
|
+
title: t('paste'), 'data-i18n-title': 'paste',
|
|
707
|
+
'aria-label': t('paste'), 'data-i18n-aria-label': 'paste', disabled: true}, [
|
|
697
708
|
yui_toolbar_icon('yi-paste'),
|
|
698
709
|
['span',
|
|
699
710
|
{
|
|
@@ -745,9 +756,17 @@ function build_ui(gobj)
|
|
|
745
756
|
type: 'text',
|
|
746
757
|
/* The placeholder is not a text node, so the
|
|
747
758
|
* data-i18n walk cannot reach it — it needs its own
|
|
748
|
-
* key or it stays English for good.
|
|
759
|
+
* key or it stays English for good.
|
|
760
|
+
*
|
|
761
|
+
* And a placeholder is not a NAME: it goes away the
|
|
762
|
+
* moment something is typed, and a reader is not
|
|
763
|
+
* obliged to announce it. The box carries no <label>
|
|
764
|
+
* either, so without the two below it is an unnamed
|
|
765
|
+
* text field. */
|
|
749
766
|
placeholder: t('search'),
|
|
750
|
-
'data-i18n-placeholder': 'search'
|
|
767
|
+
'data-i18n-placeholder': 'search',
|
|
768
|
+
title: t('search'), 'data-i18n-title': 'search',
|
|
769
|
+
'aria-label': t('search'), 'data-i18n-aria-label': 'search'
|
|
751
770
|
}],
|
|
752
771
|
['span', {class: 'icon is-left'}, [
|
|
753
772
|
yui_toolbar_icon('yi-magnifying-glass')
|
|
@@ -776,7 +795,9 @@ function build_ui(gobj)
|
|
|
776
795
|
|
|
777
796
|
if(with_refresh_button) {
|
|
778
797
|
let $refresh = createElement2(
|
|
779
|
-
['button', {class: 'button mr-1',
|
|
798
|
+
['button', {class: 'button mr-1',
|
|
799
|
+
title: t('refresh'), 'data-i18n-title': 'refresh',
|
|
800
|
+
'aria-label': t('refresh'), 'data-i18n-aria-label': 'refresh'}, [
|
|
780
801
|
yui_toolbar_icon('yi-arrows-rotate'),
|
|
781
802
|
['span', {class: 'is-hidden-mobile', i18n: 'refresh', style: 'padding-left:5px;'}, 'refresh']
|
|
782
803
|
], {
|
|
@@ -1485,7 +1506,7 @@ function create_tabulator(gobj)
|
|
|
1485
1506
|
let index = cell.getRow().getPosition();
|
|
1486
1507
|
if(e.target.closest('.edit')) {
|
|
1487
1508
|
e.stopPropagation();
|
|
1488
|
-
|
|
1509
|
+
gobj_send_event(gobj, "EV_EDIT_RECORD", {index: index}, gobj);
|
|
1489
1510
|
} else if(e.target.closest('.remove')) {
|
|
1490
1511
|
e.stopPropagation();
|
|
1491
1512
|
gobj_send_event(gobj, "EV_DELETE_ROWS", {index: index, row: row}, gobj);
|
|
@@ -2182,8 +2203,9 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
|
|
|
2182
2203
|
* C_YUI_FORM renders a col that is not `writable` read-only on
|
|
2183
2204
|
* its own (`build_form_field_conf`), so this is the whole of
|
|
2184
2205
|
* it. What must NOT follow is sending them back:
|
|
2185
|
-
* `
|
|
2186
|
-
* the reason is written
|
|
2206
|
+
* `strip_read_only_cols()` drops them from every write of the
|
|
2207
|
+
* form (`publish_treedb_write()`), and the reason is written at
|
|
2208
|
+
* `col_goes_back_to_treedb()`.
|
|
2187
2209
|
*
|
|
2188
2210
|
* Still out: hooks (a hook is not a field of the record, it is
|
|
2189
2211
|
* the children hanging off it), hidden cols, and the `_`
|
|
@@ -2262,15 +2284,31 @@ function open_form_dialog(gobj, mode, record)
|
|
|
2262
2284
|
return;
|
|
2263
2285
|
}
|
|
2264
2286
|
|
|
2265
|
-
/*
|
|
2266
|
-
*
|
|
2287
|
+
/* The title says WHAT YOU ARE DOING, and it has to survive a
|
|
2288
|
+
* language change.
|
|
2289
|
+
*
|
|
2290
|
+
* It used to be composed here -- `t("new") + " " + t(topic_name)`
|
|
2291
|
+
* -- and handed to the dialog as its `title`, which the header
|
|
2292
|
+
* renders as an i18n KEY. So the composed sentence was looked up
|
|
2293
|
+
* as a key, found nothing, and rendered as itself: right the first
|
|
2294
|
+
* time, because both halves had been translated already, and
|
|
2295
|
+
* FROZEN in that language for the life of the dialog. Switching
|
|
2296
|
+
* language left "Nuevo usuarios" on an English page.
|
|
2297
|
+
*
|
|
2298
|
+
* The header carries two halves and only one of them can hold a
|
|
2299
|
+
* key: `title_prefix` is DATA and is never translated, `title` is
|
|
2300
|
+
* the KIND and re-translates. So the record id goes in the prefix
|
|
2301
|
+
* -- it is the one part no language touches -- and the kind takes
|
|
2302
|
+
* the key. The topic name leaves the title: it is what the TAB
|
|
2303
|
+
* behind the dialog says, and the form's own fields say it again,
|
|
2304
|
+
* while "new"/"edit"/"view" is what only this header can tell you.
|
|
2305
|
+
* All three are keys this library already uses. */
|
|
2267
2306
|
let pkey = desc.pkey || "id";
|
|
2268
|
-
let title;
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
} else {
|
|
2307
|
+
let title = (mode === "create")? "new" : ((mode === "view")? "view" : "edit");
|
|
2308
|
+
let title_prefix = "";
|
|
2309
|
+
if(mode !== "create") {
|
|
2272
2310
|
let rid = record ? String(record[pkey] ?? "") : "";
|
|
2273
|
-
|
|
2311
|
+
title_prefix = empty_string(rid)? "" : rid;
|
|
2274
2312
|
}
|
|
2275
2313
|
|
|
2276
2314
|
/* "view" is an "update" that writes nothing: same title, same pkey
|
|
@@ -2333,6 +2371,7 @@ function open_form_dialog(gobj, mode, record)
|
|
|
2333
2371
|
priv.form_modal = yui_shell_show_modal(shell, $body, {
|
|
2334
2372
|
dialog: true,
|
|
2335
2373
|
logical_class: "TREEDB_FORM_SHEET",
|
|
2374
|
+
title_prefix: title_prefix,
|
|
2336
2375
|
title: title,
|
|
2337
2376
|
t: t,
|
|
2338
2377
|
before_close: function() {
|
|
@@ -2381,15 +2420,8 @@ function form_may_close(gobj)
|
|
|
2381
2420
|
* a second key to define — and the one that was here had never
|
|
2382
2421
|
* been defined in any locale, which renders as the English
|
|
2383
2422
|
* sentence itself, in every language, for ever. */
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
kw.warning || "all changes will be lost",
|
|
2387
|
-
{t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
|
|
2388
|
-
).then(function(answer) {
|
|
2389
|
-
if(answer === "yes" && priv.form_modal) {
|
|
2390
|
-
priv.form_modal.close();
|
|
2391
|
-
}
|
|
2392
|
-
});
|
|
2423
|
+
confirm_then(gobj, kw.warning || "all changes will be lost",
|
|
2424
|
+
{what: "close_form"});
|
|
2393
2425
|
return false;
|
|
2394
2426
|
}
|
|
2395
2427
|
}
|
|
@@ -2415,6 +2447,33 @@ function teardown_form_child(gobj)
|
|
|
2415
2447
|
priv.form_modal = null;
|
|
2416
2448
|
}
|
|
2417
2449
|
|
|
2450
|
+
/************************************************************
|
|
2451
|
+
* Ask, and turn the answer into an EVENT.
|
|
2452
|
+
*
|
|
2453
|
+
* A dialog's promise settles outside the machine: acting from its
|
|
2454
|
+
* `.then` puts the decision -- delete these records, throw these
|
|
2455
|
+
* edits away -- in a callback the `machine` trace never sees, and
|
|
2456
|
+
* in a view that may be gone by the time the person answers.
|
|
2457
|
+
*
|
|
2458
|
+
* `kw` carries an IDENTITY and never an object: the trace dumps
|
|
2459
|
+
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
2460
|
+
* circular structure that breaks the very trace the FSM feeds.
|
|
2461
|
+
*
|
|
2462
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
2463
|
+
************************************************************/
|
|
2464
|
+
function confirm_then(gobj, message, kw)
|
|
2465
|
+
{
|
|
2466
|
+
yui_shell_confirm_yesnocancel(
|
|
2467
|
+
yui_shell_of(gobj), message,
|
|
2468
|
+
{t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
|
|
2469
|
+
).then(function(answer) {
|
|
2470
|
+
if(answer !== "yes" || gobj_is_destroying(gobj)) {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
gobj_send_event(gobj, "EV_CONFIRMED", kw, gobj);
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2418
2477
|
/************************************************************
|
|
2419
2478
|
* Force the dialog down (bypasses the unsaved guard): used after a
|
|
2420
2479
|
* successful save and on teardown. Closing the modal runs its
|
|
@@ -3129,7 +3188,7 @@ function pkey_is_rowid(gobj)
|
|
|
3129
3188
|
* Show the edit "update" form of a row (record)
|
|
3130
3189
|
* internally called from the icon edit at Op column
|
|
3131
3190
|
************************************************************/
|
|
3132
|
-
function show_edit_form(gobj, row
|
|
3191
|
+
function show_edit_form(gobj, row)
|
|
3133
3192
|
{
|
|
3134
3193
|
/* a rowid pkey has no "update": every save appends a new
|
|
3135
3194
|
* instance (timeranger semantics) — open in create mode */
|
|
@@ -3203,6 +3262,61 @@ function get_schema_col(gobj, id)
|
|
|
3203
3262
|
return null;
|
|
3204
3263
|
}
|
|
3205
3264
|
|
|
3265
|
+
/************************************************************
|
|
3266
|
+
* Does column `col` travel back to treedb on a write?
|
|
3267
|
+
*
|
|
3268
|
+
* The form SHOWS every field and sends back only what the topic
|
|
3269
|
+
* accepts: the writable cols, the fkeys (a link is edited by
|
|
3270
|
+
* linking) and the pkey, which is not written but is what
|
|
3271
|
+
* ADDRESSES the record.
|
|
3272
|
+
*
|
|
3273
|
+
* It matters because `treedb_update_node()` does not check
|
|
3274
|
+
* `writable` -- it writes any col it is handed -- so a read-only
|
|
3275
|
+
* field travelling back is written with whatever the form made of
|
|
3276
|
+
* it, and the fields that describe a record are exactly the ones
|
|
3277
|
+
* that do not survive a round trip through a widget: a `time` is an
|
|
3278
|
+
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
3279
|
+
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
3280
|
+
* stored timestamp had moved.
|
|
3281
|
+
*
|
|
3282
|
+
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
3283
|
+
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
3284
|
+
* goes out with `autolink`, which rebuilds the links from what the
|
|
3285
|
+
* record carries. Dropped, a read-only `file` column -- the one only
|
|
3286
|
+
* a load fills -- was UNLINKED by every save of any other field.
|
|
3287
|
+
************************************************************/
|
|
3288
|
+
function col_goes_back_to_treedb(gobj, col)
|
|
3289
|
+
{
|
|
3290
|
+
let desc = gobj_read_attr(gobj, "desc");
|
|
3291
|
+
let pkey = desc.pkey || "id";
|
|
3292
|
+
const field_desc = treedb_get_field_desc(col);
|
|
3293
|
+
|
|
3294
|
+
if(field_desc.is_writable || field_desc.type === "fkey" ||
|
|
3295
|
+
field_desc.is_file || col.id === pkey) {
|
|
3296
|
+
return true;
|
|
3297
|
+
}
|
|
3298
|
+
return false;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
/************************************************************
|
|
3302
|
+
* The record the FORM hands over, without the columns that do
|
|
3303
|
+
* not go back to treedb (see col_goes_back_to_treedb()). The
|
|
3304
|
+
* form has already encoded the values, so only the columns are
|
|
3305
|
+
* taken out; keys that are not columns are left as they are.
|
|
3306
|
+
************************************************************/
|
|
3307
|
+
function strip_read_only_cols(gobj, record)
|
|
3308
|
+
{
|
|
3309
|
+
let row = {};
|
|
3310
|
+
for(let field_name of Object.keys(record || {})) {
|
|
3311
|
+
let col = get_schema_col(gobj, field_name);
|
|
3312
|
+
if(col && !col_goes_back_to_treedb(gobj, col)) {
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
row[field_name] = record[field_name];
|
|
3316
|
+
}
|
|
3317
|
+
return row;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3206
3320
|
/************************************************************
|
|
3207
3321
|
* Convert from frontend to backend
|
|
3208
3322
|
* operation: "create" "update"
|
|
@@ -3222,29 +3336,7 @@ function transform__form_record_2_treedb_record(gobj, kw, operation)
|
|
|
3222
3336
|
continue;
|
|
3223
3337
|
}
|
|
3224
3338
|
|
|
3225
|
-
|
|
3226
|
-
* accepts: the writable cols, the fkeys (a link is edited by
|
|
3227
|
-
* linking) and the pkey, which is not written but is what
|
|
3228
|
-
* ADDRESSES the record.
|
|
3229
|
-
*
|
|
3230
|
-
* It matters because `treedb_update_node()` does not check
|
|
3231
|
-
* `writable` -- it writes any col it is handed -- so a read-only
|
|
3232
|
-
* field travelling back is written with whatever the form made of
|
|
3233
|
-
* it, and the fields that describe a record are exactly the ones
|
|
3234
|
-
* that do not survive a round trip through a widget: `t` is an
|
|
3235
|
-
* integer rendered as a date. Nothing would look wrong until the
|
|
3236
|
-
* stored timestamp had moved.
|
|
3237
|
-
*/
|
|
3238
|
-
const field_desc = treedb_get_field_desc(col);
|
|
3239
|
-
if(!field_desc.is_writable &&
|
|
3240
|
-
field_desc.type !== "fkey" && !field_desc.is_file &&
|
|
3241
|
-
col.id !== pkey) {
|
|
3242
|
-
/* `is_file` and not the type: a `file` column IS an fkey (it
|
|
3243
|
-
* is flagged ['fkey','file']) but answers `type: "file"`, and
|
|
3244
|
-
* the write goes out with `autolink`, which rebuilds the links
|
|
3245
|
-
* from what the record carries. Dropped here, a read-only
|
|
3246
|
-
* `file` column -- the one only a load fills -- was UNLINKED
|
|
3247
|
-
* by every save of any other field of its record. */
|
|
3339
|
+
if(!col_goes_back_to_treedb(gobj, col)) {
|
|
3248
3340
|
continue;
|
|
3249
3341
|
}
|
|
3250
3342
|
|
|
@@ -3792,12 +3884,17 @@ function ac_edition_mode(gobj, event, kw, src)
|
|
|
3792
3884
|
tabulator.showColumn('_check_box_state_');
|
|
3793
3885
|
|
|
3794
3886
|
$button_new_record.removeAttribute("disabled");
|
|
3795
|
-
|
|
3887
|
+
/* with_copy_button / with_paste_button = false build no button */
|
|
3888
|
+
if($button_paste_record) {
|
|
3889
|
+
$button_paste_record.removeAttribute("disabled");
|
|
3890
|
+
}
|
|
3796
3891
|
|
|
3797
3892
|
let rows = yui_selected_rows(tabulator);
|
|
3798
3893
|
if (rows.length) {
|
|
3799
3894
|
$button_delete_record.removeAttribute("disabled");
|
|
3800
|
-
$button_copy_record
|
|
3895
|
+
if($button_copy_record) {
|
|
3896
|
+
$button_copy_record.removeAttribute("disabled");
|
|
3897
|
+
}
|
|
3801
3898
|
}
|
|
3802
3899
|
|
|
3803
3900
|
} else {
|
|
@@ -3813,8 +3910,12 @@ function ac_edition_mode(gobj, event, kw, src)
|
|
|
3813
3910
|
|
|
3814
3911
|
$button_new_record.setAttribute("disabled", true);
|
|
3815
3912
|
$button_delete_record.setAttribute("disabled", true);
|
|
3816
|
-
$button_copy_record
|
|
3817
|
-
|
|
3913
|
+
if($button_copy_record) {
|
|
3914
|
+
$button_copy_record.setAttribute("disabled", true);
|
|
3915
|
+
}
|
|
3916
|
+
if($button_paste_record) {
|
|
3917
|
+
$button_paste_record.setAttribute("disabled", true);
|
|
3918
|
+
}
|
|
3818
3919
|
}
|
|
3819
3920
|
|
|
3820
3921
|
render_selection_bar(gobj);
|
|
@@ -3897,50 +3998,81 @@ function ac_delete_rows(gobj, event, kw, src)
|
|
|
3897
3998
|
return 0;
|
|
3898
3999
|
}
|
|
3899
4000
|
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
{t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
|
|
3903
|
-
).then(function(answer) {
|
|
3904
|
-
if(answer === "yes") {
|
|
3905
|
-
for(let row of rows) {
|
|
3906
|
-
// TODO why don't send once EV_DELETE_RECORD(S)
|
|
3907
|
-
gobj_publish_event(
|
|
3908
|
-
gobj,
|
|
3909
|
-
"EV_DELETE_RECORD",
|
|
3910
|
-
{
|
|
3911
|
-
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
3912
|
-
record: row
|
|
3913
|
-
}
|
|
3914
|
-
);
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
});
|
|
4001
|
+
confirm_then(gobj, build_delete_question(gobj, rows),
|
|
4002
|
+
{what: "delete_selection"});
|
|
3918
4003
|
|
|
3919
4004
|
} else {
|
|
3920
4005
|
/*----------------------------*
|
|
3921
4006
|
* Delete one row
|
|
3922
4007
|
* {index: , row: }
|
|
3923
4008
|
*----------------------------*/
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
{t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
|
|
3927
|
-
).then(function(answer) {
|
|
3928
|
-
if(answer === "yes") {
|
|
3929
|
-
gobj_publish_event(
|
|
3930
|
-
gobj,
|
|
3931
|
-
"EV_DELETE_RECORD",
|
|
3932
|
-
{
|
|
3933
|
-
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
3934
|
-
record: kw.row
|
|
3935
|
-
}
|
|
3936
|
-
);
|
|
3937
|
-
}
|
|
3938
|
-
});
|
|
4009
|
+
confirm_then(gobj, build_delete_question(gobj, kw.row),
|
|
4010
|
+
{what: "delete_row", index: kw.index});
|
|
3939
4011
|
}
|
|
3940
4012
|
|
|
3941
4013
|
return 0;
|
|
3942
4014
|
}
|
|
3943
4015
|
|
|
4016
|
+
/************************************************************
|
|
4017
|
+
* {what, ...} -- the person said yes to what confirm_then()
|
|
4018
|
+
* asked. The rows are read again HERE and not carried in the
|
|
4019
|
+
* kw: the kw is plain json, and a Tabulator row is not.
|
|
4020
|
+
************************************************************/
|
|
4021
|
+
function ac_confirmed(gobj, event, kw, src)
|
|
4022
|
+
{
|
|
4023
|
+
let priv = gobj.priv;
|
|
4024
|
+
let what = kw? kw.what : "";
|
|
4025
|
+
|
|
4026
|
+
if(what === "close_form") {
|
|
4027
|
+
if(priv.form_modal) {
|
|
4028
|
+
priv.form_modal.close();
|
|
4029
|
+
}
|
|
4030
|
+
return 0;
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
if(refuse_if_readonly(gobj, event)) {
|
|
4034
|
+
return -1; /* Error already logged */
|
|
4035
|
+
}
|
|
4036
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
4037
|
+
let topic_name = gobj_read_str_attr(gobj, "topic_name");
|
|
4038
|
+
|
|
4039
|
+
if(what === "delete_selection") {
|
|
4040
|
+
let rows = yui_selected_rows(tabulator);
|
|
4041
|
+
if(!rows.length) {
|
|
4042
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with the selection gone`);
|
|
4043
|
+
return -1;
|
|
4044
|
+
}
|
|
4045
|
+
for(let row of rows) {
|
|
4046
|
+
// TODO why don't send once EV_DELETE_RECORD(S)
|
|
4047
|
+
gobj_publish_event(
|
|
4048
|
+
gobj,
|
|
4049
|
+
"EV_DELETE_RECORD",
|
|
4050
|
+
{topic_name: topic_name, record: row}
|
|
4051
|
+
);
|
|
4052
|
+
}
|
|
4053
|
+
return 0;
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
if(what === "delete_row") {
|
|
4057
|
+
let index = kw.index;
|
|
4058
|
+
let row = (tabulator && typeof index === "number")?
|
|
4059
|
+
tabulator.getRowFromPosition(index) : null;
|
|
4060
|
+
if(!row) {
|
|
4061
|
+
log_error(`${gobj_short_name(gobj)}: ${event} names no row: ${index}`);
|
|
4062
|
+
return -1;
|
|
4063
|
+
}
|
|
4064
|
+
gobj_publish_event(
|
|
4065
|
+
gobj,
|
|
4066
|
+
"EV_DELETE_RECORD",
|
|
4067
|
+
{topic_name: topic_name, record: row.getData()}
|
|
4068
|
+
);
|
|
4069
|
+
return 0;
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
4073
|
+
return -1;
|
|
4074
|
+
}
|
|
4075
|
+
|
|
3944
4076
|
/************************************************************
|
|
3945
4077
|
* From internal
|
|
3946
4078
|
************************************************************/
|
|
@@ -4042,12 +4174,15 @@ function ac_form_save_record(gobj, event, kw, src)
|
|
|
4042
4174
|
if(cols.length === 0) {
|
|
4043
4175
|
publish_treedb_write(gobj, mode, kw);
|
|
4044
4176
|
|
|
4045
|
-
/*
|
|
4046
|
-
*
|
|
4047
|
-
* callback
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4177
|
+
/* We are INSIDE the form child's gobj_publish_event stack, and
|
|
4178
|
+
* closing the dialog tears that child down -- never destroy the
|
|
4179
|
+
* publisher synchronously from a subscriber's callback. So the
|
|
4180
|
+
* close is DEFERRED, and a deferral is not a time:
|
|
4181
|
+
* gobj_post_event() delivers EV_CLOSE_FORM to us on the next
|
|
4182
|
+
* turn of the loop, it drops it if we are being destroyed
|
|
4183
|
+
* meanwhile, and it says so in the `machine` trace. A bare
|
|
4184
|
+
* setTimeout(…, 0) did none of the three. */
|
|
4185
|
+
gobj_post_event(gobj, "EV_CLOSE_FORM", {}, gobj);
|
|
4051
4186
|
return 0;
|
|
4052
4187
|
}
|
|
4053
4188
|
|
|
@@ -4136,9 +4271,11 @@ function read_is_current(gobj, kw)
|
|
|
4136
4271
|
************************************************************/
|
|
4137
4272
|
function publish_treedb_write(gobj, mode, record, jn_files)
|
|
4138
4273
|
{
|
|
4274
|
+
/* Both writes of the form pass here, with and without files: the
|
|
4275
|
+
* read-only columns it shows stay on screen and off the wire. */
|
|
4139
4276
|
const kw_write = {
|
|
4140
4277
|
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
4141
|
-
record: record
|
|
4278
|
+
record: strip_read_only_cols(gobj, record)
|
|
4142
4279
|
};
|
|
4143
4280
|
if(jn_files && Object.keys(jn_files).length > 0) {
|
|
4144
4281
|
/* Not a column: an instruction to the treedb write path,
|
|
@@ -4152,6 +4289,16 @@ function publish_treedb_write(gobj, mode, record, jn_files)
|
|
|
4152
4289
|
);
|
|
4153
4290
|
}
|
|
4154
4291
|
|
|
4292
|
+
/************************************************************
|
|
4293
|
+
* The deferred close of the form dialog, posted by the save
|
|
4294
|
+
* from inside the form child's own publish stack.
|
|
4295
|
+
************************************************************/
|
|
4296
|
+
function ac_close_form(gobj, event, kw, src)
|
|
4297
|
+
{
|
|
4298
|
+
close_form_dialog(gobj);
|
|
4299
|
+
return 0;
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4155
4302
|
/************************************************************
|
|
4156
4303
|
* {mode, record, picks} -- the bytes are here now
|
|
4157
4304
|
************************************************************/
|
|
@@ -4215,7 +4362,7 @@ function ac_unselect_rows(gobj, event, kw, src)
|
|
|
4215
4362
|
|
|
4216
4363
|
// WARNING with radio, there is no unselect event.
|
|
4217
4364
|
|
|
4218
|
-
if(gobj_read_bool_attr(gobj, "
|
|
4365
|
+
if(gobj_read_bool_attr(gobj, "broadcast_unselect_rows_event")) {
|
|
4219
4366
|
gobj_publish_event(gobj, event, kw);
|
|
4220
4367
|
}
|
|
4221
4368
|
|
|
@@ -4764,6 +4911,31 @@ function ac_show_record(gobj, event, kw, src)
|
|
|
4764
4911
|
return 0;
|
|
4765
4912
|
}
|
|
4766
4913
|
|
|
4914
|
+
/************************************************************
|
|
4915
|
+
* {index} -- the pencil of the Op column. Same shape as
|
|
4916
|
+
* EV_SHOW_RECORD: the kw carries WHICH row, and the row itself
|
|
4917
|
+
* is resolved here, where the table is.
|
|
4918
|
+
************************************************************/
|
|
4919
|
+
function ac_edit_record(gobj, event, kw, src)
|
|
4920
|
+
{
|
|
4921
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
4922
|
+
let index = kw? kw.index : undefined;
|
|
4923
|
+
if(!tabulator || typeof index !== "number") {
|
|
4924
|
+
log_error(`${gobj_short_name(gobj)}: no row ${index} to edit`);
|
|
4925
|
+
return -1;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
let row = tabulator.getRowFromPosition(index);
|
|
4929
|
+
if(!row) {
|
|
4930
|
+
log_error(`${gobj_short_name(gobj)}: row ${index} is gone`);
|
|
4931
|
+
return -1;
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4934
|
+
show_edit_form(gobj, row.getData());
|
|
4935
|
+
|
|
4936
|
+
return 0;
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4767
4939
|
/************************************************************
|
|
4768
4940
|
* The reader picked another page size: remember it, so coming back to
|
|
4769
4941
|
* this topic opens where they left it instead of at 200.
|
|
@@ -4903,11 +5075,13 @@ function create_gclass(gclass_name)
|
|
|
4903
5075
|
|
|
4904
5076
|
["EV_EDITION_MODE", ac_edition_mode, null],
|
|
4905
5077
|
["EV_SAVE_RECORD", ac_form_save_record, null],
|
|
5078
|
+
["EV_CLOSE_FORM", ac_close_form, null],
|
|
4906
5079
|
["EV_FILES_READ", ac_files_read, null],
|
|
4907
5080
|
["EV_FILES_FAILED", ac_files_failed, null],
|
|
4908
5081
|
|
|
4909
5082
|
["EV_NEW_ROW", ac_new_row, null],
|
|
4910
5083
|
["EV_DELETE_ROWS", ac_delete_rows, null],
|
|
5084
|
+
["EV_CONFIRMED", ac_confirmed, null],
|
|
4911
5085
|
["EV_SELECT_ROWS", ac_select_rows, null],
|
|
4912
5086
|
["EV_CLEAR_SELECTION", ac_clear_selection, null],
|
|
4913
5087
|
["EV_UNSELECT_ROWS", ac_unselect_rows, null],
|
|
@@ -4933,6 +5107,7 @@ function create_gclass(gclass_name)
|
|
|
4933
5107
|
["EV_OPEN_COLUMNS", ac_open_columns, null],
|
|
4934
5108
|
["EV_PAGE_SIZE_CHANGED", ac_page_size_changed, null],
|
|
4935
5109
|
["EV_SHOW_RECORD", ac_show_record, null],
|
|
5110
|
+
["EV_EDIT_RECORD", ac_edit_record, null],
|
|
4936
5111
|
["EV_TOGGLE_COLUMN", ac_toggle_column, null],
|
|
4937
5112
|
["EV_EXPORT_TABLE", ac_export_table, null],
|
|
4938
5113
|
["EV_SHOW", ac_show, null],
|
|
@@ -4952,10 +5127,12 @@ function create_gclass(gclass_name)
|
|
|
4952
5127
|
|
|
4953
5128
|
["EV_EDITION_MODE", 0],
|
|
4954
5129
|
["EV_SAVE_RECORD", 0],
|
|
5130
|
+
["EV_CLOSE_FORM", 0],
|
|
4955
5131
|
["EV_FILES_READ", 0],
|
|
4956
5132
|
["EV_FILES_FAILED", 0],
|
|
4957
5133
|
["EV_NEW_ROW", 0],
|
|
4958
5134
|
["EV_DELETE_ROWS", 0],
|
|
5135
|
+
["EV_CONFIRMED", 0],
|
|
4959
5136
|
["EV_COPY_ROWS", 0],
|
|
4960
5137
|
["EV_PASTE_ROWS", 0],
|
|
4961
5138
|
["EV_CLEAR_SELECTION", 0],
|
|
@@ -4985,6 +5162,7 @@ function create_gclass(gclass_name)
|
|
|
4985
5162
|
["EV_OPEN_COLUMNS", 0],
|
|
4986
5163
|
["EV_PAGE_SIZE_CHANGED", 0],
|
|
4987
5164
|
["EV_SHOW_RECORD", 0],
|
|
5165
|
+
["EV_EDIT_RECORD", 0],
|
|
4988
5166
|
["EV_TOGGLE_COLUMN", 0],
|
|
4989
5167
|
["EV_EXPORT_TABLE", 0],
|
|
4990
5168
|
|
|
@@ -1806,7 +1806,10 @@ function request_print_tranger(gobj, path)
|
|
|
1806
1806
|
expanded: 1,
|
|
1807
1807
|
lists_limit: 100,
|
|
1808
1808
|
dicts_limit: 100,
|
|
1809
|
-
path: path || ""
|
|
1809
|
+
path: path || "",
|
|
1810
|
+
/* What comes BACK: the answer carries only this frame, and
|
|
1811
|
+
* without the path a drill replaced the whole document. */
|
|
1812
|
+
__md_command__: {path: path || ""}
|
|
1810
1813
|
}, gobj);
|
|
1811
1814
|
if(ret) {
|
|
1812
1815
|
log_error(ret);
|