demo D280
Bring rows in from a CSV
Paste a block or drop a file, each column typed and mapped onto your fields, shown as a preview so nothing lands until you confirm; a spreadsheet block copied from Excel pastes the same way
grid.import.preview() · apply()
Loading a live grid…
The configuration
'csv-import': () => ({
rows: [],
config: {},
foot: [
'paste a block of comma or tab separated text, or drop a CSV file straight onto the grid',
'each column’s type is inferred and mapped onto the grid’s own fields, and the parse is shown as a preview first',
'nothing changes until you confirm, so a ragged or mismatched file is caught before it lands',
'a spreadsheet block copied from Excel pastes the same way, tabs and all',
],
mount: (el: HTMLElement, LG: any) => {
const columns = [
{ field: 'sku', title: 'SKU', layout: { width: 110, pin: 'start' } },
{ field: 'name', title: 'Product', layout: { flex: 1, min: 160 } },
{ field: 'category', title: 'Category', filter: { type: 'set' } },
{ field: 'price', title: 'Price', type: 'number', format: { style: 'currency', currency: 'GBP' }, total: 'sum' },
{ field: 'stock', title: 'In stock', type: 'number', total: 'sum' },
];
const rows = [
{ id: 'A-100', sku: 'A-100', name: 'Oak desk', category: 'Furniture', price: 240, stock: 12 },
{ id: 'A-101', sku: 'A-101', name: 'Task chair', category: 'Furniture', price: 130, stock: 40 },
{ id: 'B-200', sku: 'B-200', name: 'Desk lamp', category: 'Lighting', price: 35, stock: 88 },
];
const sample =
'sku,name,category,price,stock\n' +
'B-201,Floor lamp,Lighting,72,24\n' +
'C-300,Bookshelf,Furniture,180,9\n' +
'C-301,Filing cabinet,Furniture,150,15\n' +
'D-400,Whiteboard,Office,60,30\n';
const wrap = document.createElement('div');
wrap.style.cssText = 'display:flex;flex-direction:column;gap:12px;height:560px';
const controls = document.createElement('div');
controls.style.cssText = 'display:flex;flex-direction:column;gap:8px';
const area = document.createElement('textarea');
area.value = sample;
area.setAttribute('aria-label', 'CSV to import');
area.spellcheck = false;
area.style.cssText = 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px 10px;border:1px solid var(--rule);border-radius:7px;background:var(--paper);color:var(--ink);resize:vertical;min-height:78px';
const bar = document.createElement('div');
bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;align-items:center';
const mkBtn = (label: string) => {
const b = document.createElement('button');
b.type = 'button';
b.textContent = label;
b.style.cssText = 'font:inherit;padding:6px 12px;border:1px solid var(--rule);background:var(--paper);color:var(--ink);border-radius:7px;cursor:pointer';
return b;
};
const previewBtn = mkBtn('Preview');
const confirmBtn = mkBtn('Confirm and add');
const cancelBtn = mkBtn('Cancel');
confirmBtn.disabled = true;
cancelBtn.disabled = true;
const readoutEl = document.createElement('span');
readoutEl.style.cssText = 'font-size:.8125rem;color:var(--ink-2)';
bar.append(previewBtn, confirmBtn, cancelBtn, readoutEl);
controls.append(area, bar);
const gridEl = document.createElement('div');
gridEl.style.cssText = 'flex:1;min-height:0;border:1px solid var(--rule);border-radius:9px;background:var(--paper)';
wrap.append(gridTitle('Paste or drop a CSV, preview, then confirm'), controls, gridEl);
el.append(wrap);
const grid = LG.createGrid(gridEl, {
rowKey: 'id',
columns,
rows,
import: true,
});
let pending: any = null;
const resetPreview = () => {
pending = null;
confirmBtn.disabled = true;
cancelBtn.disabled = true;
};
previewBtn.addEventListener('click', () => {
try {
const preview = grid.import.preview(area.value);
pending = preview;
const n = preview.rowCount ?? (preview.records?.length ?? 0);
const cols = preview.columns?.length ?? 0;
const warn = preview.warnings?.length ?? 0;
readoutEl.textContent =
`Ready to add ${n} row${n === 1 ? '' : 's'} across ${cols} columns` +
(warn ? `, ${warn} warning${warn === 1 ? '' : 's'}` : '') + '.';
readoutEl.style.color = warn ? '#c0392b' : 'var(--ink-2)';
confirmBtn.disabled = n === 0;
cancelBtn.disabled = false;
} catch (err) {
readoutEl.textContent = `Could not read that text: ${(err as Error)?.message ?? err}`;
readoutEl.style.color = '#c0392b';
resetPreview();
}
});
confirmBtn.addEventListener('click', () => {
if (!pending) return;
grid.import.apply(pending, { mode: 'append' });
readoutEl.textContent = 'Rows added.';
readoutEl.style.color = 'var(--ink-2)';
resetPreview();
});
cancelBtn.addEventListener('click', () => {
readoutEl.textContent = 'Preview discarded.';
readoutEl.style.color = 'var(--ink-2)';
resetPreview();
});
return () => grid?.destroy?.();
},
})