133 lines
5.4 KiB
JavaScript
133 lines
5.4 KiB
JavaScript
// add-row.js — new-row creation.
|
|
//
|
|
// Two paths, chosen by the table's schema:
|
|
// - Record-tables (identity composed from required fields that aren't
|
|
// columns — e.g. the risk register's tracking-number components): "+ Add
|
|
// row" navigates to the compose form (<dir>/form.html), the only place
|
|
// those components can be supplied. See needsComposeForm().
|
|
// - Simple tables (all required fields are columns): "+ Add row" appends a
|
|
// draft row at the end of state.rows, focuses its first editable cell, and
|
|
// accumulates typing into the drafts buffer like any other row. On
|
|
// row-blur, save.js detects row.isNew and POSTs to <dir>/form.html; the
|
|
// 201 Location swaps the synthetic url and the draft becomes a saved row.
|
|
//
|
|
// Synthetic identity: each new row gets a temporary "__new-<N>" url
|
|
// so rowKey() returns something unique for selection + draft tracking.
|
|
// The temporary url is replaced after a successful POST. There is no
|
|
// "save on click" UX — the existing row-blur trigger is the save path,
|
|
// same as for edits.
|
|
(function (app) {
|
|
'use strict';
|
|
|
|
let _counter = 0;
|
|
|
|
function makeSyntheticKey() {
|
|
_counter += 1;
|
|
return '__new-' + _counter;
|
|
}
|
|
|
|
// Compute the form-create URL for the current page. Both
|
|
// /<dir>/table.html and /<dir>/ (default_tool: tables) shape work;
|
|
// /<dir>/form.html is the form handler's "create" endpoint either
|
|
// way (the form handler keys off the in-dir convention, not the
|
|
// visiting URL shape).
|
|
function formCreateUrl() {
|
|
let dir = (location.pathname || '/').replace(/\/table\.html$/, '/');
|
|
if (!dir.endsWith('/')) dir += '/';
|
|
return dir + 'form.html';
|
|
}
|
|
|
|
// True when a new record's identity is composed from required fields that
|
|
// AREN'T table columns (e.g. the risk register's project/discipline/
|
|
// sequence tracking-number components). Such rows can't be created by
|
|
// typing into the grid — they need the compose form. Server mode only
|
|
// (the form handler is server-side).
|
|
function needsComposeForm() {
|
|
const ctx = app.context || {};
|
|
if (!app.state || app.state.source !== 'server') return false;
|
|
const req = (ctx.rowSchema && Array.isArray(ctx.rowSchema.required)) ? ctx.rowSchema.required : [];
|
|
if (!req.length) return false;
|
|
const colFields = {};
|
|
(ctx.columns || []).forEach(function (c) { if (c && c.field) colFields[c.field] = true; });
|
|
return req.some(function (f) { return !colFields[f]; });
|
|
}
|
|
|
|
// Create-and-paint: the user-facing path. Record-tables (composed identity)
|
|
// open the compose form directly — the grid can't supply their
|
|
// tracking-number components; simple tables append an inline draft row.
|
|
function invoke() {
|
|
if (needsComposeForm()) {
|
|
window.location.href = formCreateUrl();
|
|
return;
|
|
}
|
|
const key = createSilent();
|
|
if (typeof app.repaint === 'function') app.repaint();
|
|
focusNewRow(key);
|
|
}
|
|
|
|
// Push a draft row WITHOUT painting or focusing. Used by multi-row
|
|
// paste (clipboard.js) to create N rows in a single batch, with one
|
|
// paint at the end. Returns the synthetic url so callers can address
|
|
// the new row in their draft writes.
|
|
function createSilent() {
|
|
const key = makeSyntheticKey();
|
|
const draftRow = {
|
|
url: key,
|
|
yamlUrl: null,
|
|
data: {},
|
|
etag: null,
|
|
editable: true,
|
|
isNew: true,
|
|
};
|
|
if (!Array.isArray(app.state.rows)) {
|
|
app.state.rows = [];
|
|
}
|
|
app.state.rows.push(draftRow);
|
|
return key;
|
|
}
|
|
|
|
function focusNewRow(key) {
|
|
// After repaint, find the tr with our synthetic data-row-id and
|
|
// tell the editor to select its first cell. Filtering may have
|
|
// hidden the new row if a default filter excludes it; we accept
|
|
// that — clearing filters surfaces it.
|
|
const tbody = document.querySelector('#table-root tbody');
|
|
if (!tbody) return;
|
|
const trs = tbody.querySelectorAll('tr');
|
|
for (let i = 0; i < trs.length; i++) {
|
|
if (trs[i].getAttribute('data-row-id') === key) {
|
|
const editor = app.modules.editor;
|
|
if (editor && typeof editor.setSelected === 'function') {
|
|
// Scroll into view so the user sees the new row.
|
|
trs[i].scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
editor.setSelected(i, 0);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cancel-new-row helper: drop the synthetic row entirely. Used when
|
|
// the user adds a row, makes no edits, and clicks Add again or
|
|
// navigates away — there's nothing to save and an empty draft just
|
|
// clutters the table. The save module calls this from row-blur when
|
|
// it sees a new row with no drafts.
|
|
function discardEmpty(rowId) {
|
|
const rows = app.state.rows || [];
|
|
for (let i = 0; i < rows.length; i++) {
|
|
if (rows[i].isNew && rows[i].url === rowId) {
|
|
rows.splice(i, 1);
|
|
if (typeof app.repaint === 'function') app.repaint();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
app.modules.addRow = {
|
|
invoke: invoke,
|
|
createSilent: createSilent,
|
|
formCreateUrl: formCreateUrl,
|
|
discardEmpty: discardEmpty,
|
|
};
|
|
})(window.tablesApp);
|