Land.·Listings · Sortable Table
9 parcels · live edit WHOLETECH NETWORK
Land · /listings/
Working data · click headers to sort · click cells to edit

Nine parcels, one table.

The same 9 ACTRIS parcels from the parent rubric tool, presented as a focused editable + sortable table with a sticky top row. Click any column header to sort; click any cell to edit. Edits save to your browser. The composite Score is the rubric-weighted average and updates as you change any of the 12 score columns. The Table Component Pattern below documents the allowable column parameters and the rule for building new tables like this one across the network.

№ 01 · The Data

9 parcels · sortable + editable.

Sticky header · click any column to sort · click any cell to edit · localStorage
Showing 9 of 9
№ 02 · The Pattern

Table component spec & rule.

For every editable+sortable table built across the WholeTech network

What this is.

A single-file, dependency-free editable + sortable HTML table that runs entirely in the browser, persists row data to localStorage, and follows the same paper-and-ink design system used across land.wholetech.com, realhotsprings.com, and the rest of the network.

Every table is built from two arrays:

  • COLUMNS — the schema (what columns, what types)
  • DATA — the seed rows shipped with the page

The renderer reads COLUMNS to draw the header (with sort affordance) and reads DATA (or localStorage, if present) to draw the body. Every input/select writes back to the row object and persists. No frameworks, no build step, no fetch — just open the file.

The rule.

When you build a new table page on the network, you copy this file, replace the two arrays at the top of the <script> block, and ship. Don't introduce a framework, don't add a server round-trip, don't change the design language. If you need a column type that isn't in the spec at right, add it to the spec first, then implement it once, here — so the next table inherits it.

Allowable column types · 8
text
Free-form string. <input type=text>. Sort: alphabetic, case-insensitive. Used for addresses, names, notes.
number
Float or integer. <input type=number>. Sort: numeric. Used for acres, counts, sizes. Optional min / max / step.
currency
Number stored raw; displayed as $1.86M / $295K only in derived columns. Sort: numeric. Treats 0 as "no price".
select
Fixed enum from options:[…]. <select>. Sort: by option order, not alphabetic. Used for water (yes/no/tbd), county, status flags.
score
Integer 1–5, color-coded (low=crimson, mid=amber, high=green). <select>. Sort: numeric. Feeds into computed roll-ups.
computed
Read-only. Value comes from a compute(row) function on the column. Re-runs every render. Used for Score (weighted avg) and Verdict (GO / MAYBE / PASS).
link
Text input plus a derived & clickable URL (Google Maps, HAR, CAD). <a> rendered next to the input. Sort: alphabetic on the underlying string.
action
Row-action button (delete, duplicate). Not sortable. Always pinned to the right edge of the table.
The 10-rule table component contract
1
One file. The page is self-contained. No external JS framework, no CSS framework. Fonts via Google Fonts only.
2
Two arrays at the top. COLUMNS declares schema, DATA declares seed rows. Change those, redeploy.
3
Sticky header. thead th { position:sticky; top:0; z-index:5 }. The wrap is the scroll container, not the page.
4
Sortable headers. Click sorts asc; second click sorts desc; third click clears sort. Arrow indicator in the header.
5
Inline edits. Every non-computed cell is editable in place. No modals. input / change events both write through.
6
Persistence. localStorage[STORAGE_KEY], namespaced per-page (e.g. land_listings_v1). Bump the version when the schema changes.
7
Reset always available. A "Reset" button restores the seed DATA. Confirm before destroying edits.
8
Add & delete. "+ Add" appends a row with defaults; per-row "✕" deletes after confirm. Never leave the table empty — fall back to defaults if all rows deleted.
9
Filter, don't paginate. A free-text search filters by regex across text/select/number columns. Hidden rows still exist (and still persist).
10
Export. CSV + JSON download buttons. Both reflect the current order (post-sort) and the full row set (not the filter).
// New table? Copy /listings/index.html, replace the two arrays:
const COLUMNS = [
  { key:"addr",  label:"Address",    type:"text",                       width:"col-addr"  },
  { key:"acres", label:"Acres",      type:"number", step:0.01,           width:"col-num"   },
  { key:"price", label:"Price",      type:"currency",                   width:"col-price" },
  { key:"water", label:"H₂O",        type:"select", options:["yes","no","tbd"], width:"col-water", css:"water-col" },
  { key:"c0",    label:"C1 Zon",     type:"score",                      width:"col-score" },
  { key:"score", label:"Score",      type:"computed",                   width:"col-scoresum",
    compute:r => weightedAvg(r), tone:scoreTone },
  { key:"_del",  label:"",           type:"action", action:"delete",    width:"col-del"   },
];
const DATA = [ { addr:"…", acres:10.7, price:315000, water:"yes", c0:4, … }, … ];
const STORAGE_KEY = "land_listings_v1";
land.wholetech.com.private investor working doc.noindex.part of the WholeTech network