Data

CsvTable

A table made from CSV text, with quoted fields, a header row, numbers lined up on the right and a row limit.

#data#table#file#no-js

Demo

Preview

Price list (prices.csv)

skunamepricestocknote
A-100Bolt, M6 x 200.1212,400
A-101Nut M60.089800Sold in bags of 100
B-220Washer "wide"0.0515000
C-310Bracket2.40-3Back-ordered; ships in May
Column 1Column 2Column 3
2013Lisbon1200
2014Porto1237
2015Braga1274
2016Lisbon1311
2017Porto1348

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.qwe
main.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>}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On CsvTable: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.