Examples

Virtual list

About

A virtual list: 1,000,000 rows of 10 columns, 20 on screen at a time.

quillo run virtual.qwe --watch then open http://localhost:8080

Unlike scroll.qwe, every row is reachable from the start: drag the scrollbar anywhere and that part of the table shows. The page holds only the rows on screen, and the browser only the rows around them: when the window leaves what it has, live loads the rows there.

A browser cannot make an element 32 million pixels tall (Firefox stops near 17 million), so the scrolling area is at most MAX_HEIGHT tall, and a pixel of scrolling stands for several pixels of rows when the table is taller.

Run it

$ quillo run virtual.qwe --watch

Source

virtual.qwe
db.open(":memory:")db.exec("create table people (id integer primary key, first text, last text, email text,         city text, country text, age int, plan text, balance text, joined text)")db.exec("with recursive n(i) as (select 1 union all select i + 1 from n where i < 1000000),  f(a) as (select '[\"Ada\",\"Ben\",\"Chloe\",\"Dev\",\"Elena\",\"Femi\",\"Grace\",\"Hiro\",\"Ines\",\"Jonas\",\"Kemi\",\"Liam\",\"Mei\",\"Noah\",\"Omar\",\"Priya\"]'),  l(a) as (select '[\"Okafor\",\"Smith\",\"Tanaka\",\"Garcia\",\"Novak\",\"Silva\",\"Khan\",\"Larsen\",\"Rossi\",\"Dubois\",\"Kim\",\"Moreau\",\"Haddad\"]'),  c(a) as (select '[[\"Lagos\",\"Nigeria\"],[\"Lisbon\",\"Portugal\"],[\"Osaka\",\"Japan\"],[\"Lima\",\"Peru\"],[\"Oslo\",\"Norway\"],[\"Pune\",\"India\"],[\"Quebec\",\"Canada\"],[\"Seoul\",\"Korea\"],[\"Lyon\",\"France\"],[\"Cebu\",\"Philippines\"],[\"Austin\",\"USA\"]]')  insert into people select i,    json_extract(f.a, '$[' || (i % 16) || ']'),    json_extract(l.a, '$[' || (i * 7 % 13) || ']'),    lower(json_extract(f.a, '$[' || (i % 16) || ']')) || '.' || i || '@example.com',    json_extract(c.a, '$[' || (i * 3 % 11) || '][0]'),    json_extract(c.a, '$[' || (i * 3 % 11) || '][1]'),    18 + (i * 37 % 60),    case i % 4 when 0 then 'Free' when 1 then 'Pro' when 2 then 'Team' else 'Enterprise' end,    printf('%.2f', (i * 7919 % 100000) / 10.0),    date('2015-01-01', '+' || (i * 13 % 3650) || ' days')  from n, f, l, c")let ROW = 32                // height of a row, in pxlet VISIBLE = 20            // rows on screenlet VIEW = ROW * VISIBLE    // height of the scrolling area, in pxlet CHUNK = 100             // rows the browser holds; a new chunk every CHUNK / 2 rowslet MAX_HEIGHT = 8000000    // the tallest scrolling area, in pxlet COLUMNS = ["#", "First", "Last", "Email", "City", "Country", "Age", "Plan", "Balance", "Joined"]let NUMERIC = ["#", "Age", "Balance", "Joined"]// Rows start..start + n, by primary key: as fast at row 999,000 as at row 0.server fn load_rows(start: int, n: int) -> list with db =    db.query(sql`select * from people where id > ? order by id limit ?`, start, n)fn total() with db = db.one("select count(*) as n from people").ncomponent Table(count: int) {    state top = 0    let height = min([count * ROW, MAX_HEIGHT])    // Pixels of rows per pixel of scrolling: 1 until the table is taller than MAX_HEIGHT.    let scale = (count * ROW - VIEW) / max([height - VIEW, 1])    let y = top * scale    let first = max([0, min([count - VISIBLE, floor(y / ROW)])])    let offset = y - first * ROW    let last = min([count, first + VISIBLE + 1])    // one more, for the row half in view    // The chunk holding the window: it changes every CHUNK / 2 rows, and `live`    // loads it again whenever it does.    let start = max([0, floor(first / (CHUNK / 2)) * (CHUNK / 2) - CHUNK / 4])    state rows = live(load_rows, start, CHUNK)    let held = len(rows) > 0 ? rows[0].id - 1 : 0    <section.table>        <p.stats>            Rows <b>{first + 1}</b>–<b>{min([count, first + VISIBLE])}</b> of <b>{count}</b> · <b>{last - first}</b> rows in the page · <b>{len(rows)}</b> in the browser        </p>        <div.head>{for c in COLUMNS => <span key={c} class={contains(NUMERIC, c) && "num"}>{c}</span>}</div>        <div.viewport style={`height: ${VIEW}px`} @scroll={(e) => { top = e.scrollTop }}>            <div.spacer style={`height: ${height}px`}>                <div.window style={`transform: translateY(${top - offset}px)`}>                    {for i in range(first, last) => {                        let p = i - held >= 0 && i - held < len(rows) ? rows[i - held] : null                        let cls = i % 2 == 1 ? "row odd" : "row"                        p == null                            ? <div key={i} class={cls} style={`height: ${ROW}px`}>                                <span.loading>row {i + 1} · loading…</span>                              </div>                            : <div key={i} class={cls} style={`height: ${ROW}px`}>                                <span.num>{p.id}</span>                                <span>{p.first}</span>                                <span>{p.last}</span>                                <span.email>{p.email}</span>                                <span>{p.city}</span>                                <span>{p.country}</span>                                <span.num>{p.age}</span>                                <span class={`plan ${lower(p.plan)}`}>{p.plan}</span>                                <span.num>{p.balance}</span>                                <span.num>{p.joined}</span>                              </div>                    }}                </div>            </div>        </div>    </section>}let style = css`* { box-sizing: border-box; }body { font: 14px/1.4 system-ui, sans-serif; max-width: 72rem; margin: 2rem auto; padding: 0 1rem; color: #222; }h1 { font-size: 1.4rem; margin: 0 0 .25rem; }.stats { color: #555; margin: 0 0 .75rem; } .stats b { color: #222; font-variant-numeric: tabular-nums; }.table { border: 1px solid #ddd; border-radius: 8px; overflow: hidden; }.stats { padding: .75rem 1rem 0; }.head, .row { display: grid; grid-template-columns: 5.5rem 5rem 5.5rem minmax(12rem, 2fr) 5rem 6.5rem 3rem 6.5rem 6rem 6.5rem; gap: .75rem; align-items: center; padding: 0 1rem; }.head { height: 36px; font-weight: 600; color: #555; background: #f6f7f9; border-block: 1px solid #e5e5e5; }.viewport { overflow-y: auto; overflow-x: hidden; position: relative; }.spacer { position: relative; }.window { position: absolute; left: 0; right: 0; top: 0; will-change: transform; }.row { white-space: nowrap; border-bottom: 1px solid #f0f0f0; }.row.odd { background: #fbfbfc; }.row span { overflow: hidden; text-overflow: ellipsis; }.num { font-variant-numeric: tabular-nums; text-align: right; }.email { color: #0b57d0; }.loading { grid-column: 1 / -1; color: #aaa; }.plan { justify-self: start; padding: 0 .5rem; border-radius: 999px; font-size: 12px; background: #eee; }.plan.pro { background: #e3efff; color: #0b57d0; } .plan.team { background: #e6f6ec; color: #137333; }.plan.enterprise { background: #f3e8fd; color: #8430ce; }@media (prefers-color-scheme: dark) {  body { background: #161616; color: #ddd; } .stats, .head { color: #aaa; } .stats b { color: #fff; }  .table { border-color: #2c2c2c; } .head { background: #1e1e1e; border-color: #2c2c2c; }  .row { border-color: #222; } .row.odd { background: #1a1a1a; } .email { color: #8ab4f8; }  .plan { background: #2a2a2a; } .plan.pro { background: #1c2b44; color: #8ab4f8; }  .plan.team { background: #17301f; color: #81c995; } .plan.enterprise { background: #2e2140; color: #c58af9; }}`route GET / =    <html>        <head>            <meta charset="utf-8"/>            <meta name="viewport" content="width=device-width, initial-scale=1"/>            <title>Virtual list</title>            <style>{style}</style>        </head>        <body>            <h1>Virtual list</h1>            <Table count={total()}/>        </body>    </html>

See virtual.qwe on GitHub

Thread
10Useful? Like it, or add to its thread below.

Thread 0 posts

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

No posts yet. The first one opens the thread.