<bmx-data-importer>
CSV, TSV, Excel and JSON in; clean, checked rows out - all in the browser. The reader drops a file (or pastes rows), matches its columns to the page's fields, fixes what is wrong in a grid, and imports.
10 properties · 3 events · 6 methods · 8 parts
Example
The sample uses semicolons, day-first dates and decimal commas, and has a few rows to fix.
Show markup
<div class="row" style="display: block">
<p style="margin: 0 0 12px">
<bmx-button id="ex-di-sample" variant="soft">Try a sample file</bmx-button>
<span class="note">or drop your own CSV, Excel or JSON file below. Nothing leaves this page.</span>
</p>
<bmx-data-importer id="ex-di" label="Import customers" style="max-inline-size: 64rem"></bmx-data-importer>
</div>
<p class="note" id="ex-di-out" role="status">The sample uses semicolons, day-first dates and decimal commas, and has a few rows to fix.</p>
<script type="module">
await customElements.whenDefined('bmx-data-importer');
const importer = document.getElementById('ex-di');
const out = document.getElementById('ex-di-out');
importer.fields = [
{ key: 'id', label: 'Customer ID', type: 'integer', required: true, unique: true, aliases: ['Cust no', 'Account'] },
{ key: 'name', label: 'Company', required: true, transform: 'trim', aliases: ['Customer', 'Organisation'] },
{ key: 'email', label: 'Email', type: 'email', unique: true },
{ key: 'country', label: 'Country', options: [{ value: 'GB', label: 'United Kingdom', aliases: ['UK', 'Britain', 'England'] }, { value: 'IE', label: 'Ireland' }, { value: 'FR', label: 'France' }, { value: 'DE', label: 'Germany' }] },
{ key: 'since', label: 'Customer since', type: 'date', aliases: ['Joined', 'Start date'] },
{ key: 'limit', label: 'Credit limit', type: 'number', min: 0, description: 'In pounds; 0 or more.' },
{ key: 'active', label: 'Active', type: 'boolean', defaultValue: true },
];
const sample = [
'Cust no;Customer;E-mail address;Country;Joined;Credit limit;Active;Notes',
'1001;Acme Engineering;sales@acme.example;UK;05/03/2024;2.500,00;yes;Key account',
'1002;Blue Harbour Ltd;info@blueharbour.example;Ireland;20/11/2023;1.250,50;yes;',
'1003;Quayside Logistics;ops@quayside;France;31/01/2022;750;no;Email bounced',
'1004; Northwind Traders ;accounts@northwind.example;Britain;14/02/2021;-50;yes;',
'1002;Blue Harbour (old);info@blueharbour.example;Ireland;01/06/2019;0;no;Duplicate',
'1005;Fenwick & Sons;hello@fenwick.example;Germany;31/02/2024;3.000;y;',
';Unnamed lead;lead@example.example;Spain;;;;',
'1006;Moorside Dairy;orders@moorside.example;England;07/07/2020;12.000;1;',
].join('\r\n');
document.getElementById('ex-di-sample').addEventListener('click', () => importer.loadText(sample, 'customers-export.csv'));
importer.addEventListener('bmxImportComplete', e => {
out.textContent = `Imported ${e.detail.rows.length} rows (skipped ${e.detail.skipped}, removed ${e.detail.removed}). First: ${JSON.stringify(e.detail.rows[0])}`;
});
</script>
<bmx-data-importer id="importer"></bmx-data-importer>
<script>
importer.fields = [
{ key: 'id', label: 'Customer ID', type: 'integer', required: true, unique: true },
{ key: 'name', label: 'Name', required: true, aliases: ['Company'] },
{ key: 'email', label: 'Email', type: 'email', unique: true },
{ key: 'country', options: [{ value: 'GB', label: 'United Kingdom', aliases: ['UK'] }] },
];
importer.importHandler = rows => api.saveCustomers(rows);
</script>
Reading: text files decoded (UTF-8, UTF-16, Windows-1252) and split by a sniffed delimiter to RFC 4180, a piece at a time; Excel workbooks with every sheet, formulas calculated and dates as dates; JSON records flattened. The heading row is found, or not.
Matching: each field is given the column whose name is nearest its key, label or aliases, helped by whether the values suit its type; the reader changes any choice. Dates are read day-first or month-first and numbers with either decimal mark, detected per column from its values.
Review: every row is read into the fields' types and checked - required,
type, range, length, pattern, list values (by label or alias), the page's
own validate, and repeats of unique fields. Problems are marked in a grid
of only the rows in view; the reader edits a cell in place, removes rows,
removes every row with problems or every duplicate, and undoes. Import
hands the page the records (importHandler, then bmxImportComplete),
with the rows that still have problems left out if the reader chooses.
Nothing is uploaded anywhere: the file never leaves the page.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
accept |
accept |
string |
'.csv,.tsv,.txt,.xlsx,.json,text/csv,text/plain,application/json,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' |
The file types offered by the file picker. |
allowSkip |
allow-skip |
boolean |
true |
May the reader import while rows still have problems, leaving those rows out. |
dateOrder |
date-order |
'auto' | BmxImportDateOrder |
'auto' |
How dates written with numbers are read: auto decides per column from its values, then the locale. |
fields |
fields |
BmxImportField[] | string |
[] |
The fields the page wants, as objects or JSON. Empty: every column is a field, named by its heading. |
importHandler |
property only | BmxImportHandler |
— | The page's own import, awaited before bmxImportComplete. |
label |
label |
string |
— | The importer's accessible name. Default: "Import data". |
locale |
locale |
string |
— | The locale for detecting date order. Default: the page's. |
maxFileSize |
max-file-size |
number |
50 * 1024 * 1024 |
The largest file read, in bytes. Default 50 MB. |
maxRows |
max-rows |
number |
1000000 |
The most rows read. Default 1,000,000. |
strings |
strings |
Partial<BmxDataImporterStrings> | string |
— | Words to show instead of the English ones, as an object or JSON. |
Events
| Event | Detail | Description |
|---|---|---|
bmxImportComplete |
BmxImportCompleteDetail |
Fired when the rows have been imported. |
bmxImportError |
BmxImportErrorDetail |
Fired when a file cannot be read or the import fails. |
bmxImportFile |
BmxImportFileDetail |
Fired before a file is read. Cancel it to refuse the file. |
Methods
| Method | Signature | Description |
|---|---|---|
getProblems |
getProblems() => Promise<{ line: number; field: string; message: string; }[]> |
The problems as they stand in review. |
getRows |
getRows() => Promise<Record<string, unknown>[]> |
The rows as they stand in review, as records. |
loadFile |
loadFile(file: Blob, name?: string) => Promise<boolean> |
Reads a file, as if dropped. Resolves whether it was read. |
loadRows |
loadRows(rows: unknown[], name?: string) => Promise<boolean> |
Takes rows already in hand: an array of records, or of arrays (the first may be the heading row). |
loadText |
loadText(text: string, name?: string) => Promise<boolean> |
Reads text: CSV, TSV or JSON. Resolves whether it was read. |
reset |
reset() => Promise<void> |
Back to the start, forgetting the file. |
CSS shadow parts
| Part | Description |
|---|---|
cell |
A review cell; also cell-problem. |
done |
The finished message. |
drop |
The drop area. |
footer |
The row of Back and the next action. |
grid |
The review grid. |
mapping |
The table matching fields to columns. |
steps |
The list of steps. |
summary |
The counts above the review grid. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-data-importer-cell-width |
The width of each field's column in review. |
--bmx-data-importer-height |
The height of the review grid. |
--bmx-data-importer-line-width |
The width of the row-number column in review. |
--bmx-data-importer-problem |
The colour that marks a problem. |