CsvTable
A table made from CSV text, with quoted fields, a header row, numbers lined up on the right and a row limit.
Demo
Price list (prices.csv)
| sku | name | price | stock | note |
|---|---|---|---|---|
| A-100 | Bolt, M6 x 20 | 0.12 | 12,400 | |
| A-101 | Nut M6 | 0.08 | 9800 | Sold in bags of 100 |
| B-220 | Washer "wide" | 0.05 | 15000 | |
| C-310 | Bracket | 2.40 | -3 | Back-ordered; ships in May |
| Column 1 | Column 2 | Column 3 |
|---|---|---|
| 2013 | Lisbon | 1200 |
| 2014 | Porto | 1237 |
| 2015 | Braga | 1274 |
| 2016 | Lisbon | 1311 |
| 2017 | Porto | 1348 |
and 9 more rows
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/csv_table.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/csv_table.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/csv_table.qwe
let csv_table_demo_prices = "sku,name,price,stock,noteA-100,\"Bolt, M6 x 20\",0.12,\"12,400\",A-101,Nut M6,0.08,9800,\"Sold in bags of 100\"B-220,\"Washer \"\"wide\"\"\",0.05,15000,C-310,Bracket,2.40,-3,\"Back-ordered;ships in May\""let csv_table_demo_tsv = for i in range(0, 14) => `${2013 + i}\t${["Lisbon", "Porto", "Braga"][i % 3]}\t${1200 + i * 37}`view CsvTableDemo() = <div.demo-stack> <CsvTable id="csv-table-demo-prices" caption="Price list (prices.csv)" text={csv_table_demo_prices}/> <CsvTable id="csv-table-demo-tsv" label="Visitors by year" delimiter={"\t"} header={false} limit={5} zebra={true} text={join(csv_table_demo_tsv, "\n")}/> </div>Source
ui/csv_table.qwe
// CsvTable: a table made from CSV text: the first row is the header, quoted// fields may hold commas, quotes ("") and line breaks, and columns of numbers// line up on the right. For a report pasted in, an export to preview, a fixture.//// <CsvTable label="Prices" text={"sku,name,price\nA-1,\"Bolt, M6\",0.12\nA-2,Nut,0.08"}/>// <CsvTable text={read_file("data/cities.tsv")} delimiter="\t" limit={20}/>//// text: the CSV. delimiter: the character between fields ("," by default; ";"// or "\t"). header: the first row names the columns (true); false numbers them// (Column 1, Column 2...). limit: show only the first so many rows, with "and 120// more rows" under them. caption: a line above the table (also its name for// screen readers, unless label is given). label: the table's name. zebra: shades// every other row. empty: what it says for text with no rows. id: the element's// id. Rows shorter than the header get empty cells; a column whose cells are all// numbers (1,234.5 or -3) is right-aligned.import ui/theme.qwe use ui_clslet csv_table_css = css`.ui-csvtable { display: grid; gap: 6px; min-width: 0; font: 14px/1.45 var(--ui-font); color: var(--ui-fg); }.ui-csvtable-cap { margin: 0; color: var(--ui-muted); font-size: 13px; font-weight: 500; }.ui-csvtable-scroll { overflow: auto; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); }.ui-csvtable-scroll:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-csvtable table { width: 100%; border-collapse: separate; border-spacing: 0; }.ui-csvtable th, .ui-csvtable td { padding: 8px 14px; text-align: left; white-space: pre-line; border-bottom: 1px solid var(--ui-border); }.ui-csvtable th { position: sticky; top: 0; background: var(--ui-subtle); color: var(--ui-muted); font-size: 12px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; white-space: nowrap; }.ui-csvtable tbody tr:last-child td { border-bottom: 0; }.ui-csvtable .ui-csvtable-num { text-align: right; font-variant-numeric: tabular-nums; }.ui-csvtable-zebra tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--ui-subtle) 55%, transparent); }.ui-csvtable-more { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-csvtable-empty { padding: 24px 14px; text-align: center; color: var(--ui-muted); }`// The CSV's rows, each a list of fields, as RFC 4180 reads them.fn csv_table_parse(text: str, sep: str) -> list { let rows = [] let row = [] let field = "" let quoted = false let i = 0 let n = len(text) while i < n { let c = text[i] if quoted { if c == "\"" { if i + 1 < n && text[i + 1] == "\"" { field += "\"" i += 1 } else { quoted = false } } else { field += c } } else if c == "\"" { quoted = true } else if c == sep { row = push(row, field) field = "" } else if c == "\n" { row = push(row, field) rows = push(rows, row) row = [] field = "" } else if c != "\r" { field += c } i += 1 } if field != "" || len(row) > 0 { rows = push(rows, push(row, field)) } // A blank line is no row. rows | filter((r) => !(len(r) == 1 && trim(r[0]) == ""))}fn csv_table_is_num(s: str) -> bool = re.test(trim(s), "^[-+]?(\\d{1,3}(,\\d{3})+|\\d+)?(\\.\\d+)?%?$") && re.test(s, "\\d")view CsvTable(text: str, delimiter: str = ",", header: bool = true, limit: int? = null, caption: str? = null, label: str? = null, zebra: bool = false, empty: str = "No rows.", id: str? = null, class: str? = null) { style global csv_table_css let rows = csv_table_parse(text, delimiter) let width = max(rows | map((r) => len(r))) ?? 0 let head = header && len(rows) > 0 ? rows[0] : for i in range(0, width) => `Column ${i + 1}` let body = header ? drop(rows, 1) : rows let cols = max([width, len(head)]) ?? 0 let shown = limit != null ? take(body, limit) : body let more = len(body) - len(shown) let numeric = for c in range(0, cols) => { let cells = body | map((r) => r[c] ?? "") | filter((v) => trim(v) != "") len(cells) > 0 && all(cells, (v) => csv_table_is_num(v)) } <div class={ui_cls(["ui-csvtable", zebra && "ui-csvtable-zebra", class])} id={id}> {caption != null && <p.ui-csvtable-cap>{caption}</p>} <div.ui-csvtable-scroll tabindex="0" role="region" aria-label={label ?? caption ?? "Table"}> <table aria-label={label ?? caption}> <thead> <tr>{for c in range(0, cols) => <th scope="col" class={numeric[c] ? "ui-csvtable-num" : null}>{head[c] ?? ""}</th>}</tr> </thead> <tbody> {len(body) == 0 && <tr><td.ui-csvtable-empty colspan={str(max([cols, 1]))}>{empty}</td></tr>} {for r in shown => <tr> {for c in range(0, cols) => <td class={numeric[c] ? "ui-csvtable-num" : null}>{r[c] ?? ""}</td>} </tr>} </tbody> </table> </div> {more > 0 && <p.ui-csvtable-more>and {format_number(more, "en-US")} more {more == 1 ? "row" : "rows"}</p>} </div>}Related
AuditLogWho did what to what and when, newest first, filtered by person and action, searched, and paged.5 views · 7 downloadsComparisonTableFeatures down the side and options across, with ticks, crosses and values in the cells, and one option picked out.5 views · 6 downloadsDataGridSort, search, filter by a column's values, pick rows and page through them, in one table.5 views · 8 downloadsDataTableRows of data sorted by their headers, with search, pages, number, money, percent and date columns.5 views · 9 downloadsEditableTableA grid edited in place: text, number, select, checkbox and date cells, checked, sorted, with rows added and deleted.7 views · 5 downloadsExpandableTableRows with a button that opens a panel of details under them: labelled fields, a note, or your own markup.5 views · 6 downloads
Thread
Thread 0 posts
On CsvTable: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.