|
| 1 | +--- |
| 2 | +description: A complete, minimal HTML Page example — initialize the SDK, read rows from the base, render them, and write new linked rows back on submit. |
| 3 | +--- |
| 4 | + |
| 5 | +# Example: a form that reads and writes |
| 6 | + |
| 7 | +This page ties the SDK together into one working flow: load data on start, render it, and write new rows back on submit. It is a distilled version of the [simple form template](https://github.com/seatable/seatable-html-page-template-simple-form) — a form that lists products from the base, lets the user pick quantities, and stores the result as an order. |
| 8 | + |
| 9 | +The data flow is always the same four steps: |
| 10 | + |
| 11 | +```mermaid |
| 12 | +flowchart LR |
| 13 | + A[init SDK] --> B[listRows: load data] |
| 14 | + B --> C[render UI] |
| 15 | + C --> D[on submit: batchAddRows + addRow] |
| 16 | +``` |
| 17 | + |
| 18 | +It assumes the three tables described in [Getting Started](getting-started.md): **Products**, **OrderItems** (link to Products), and **Orders** (link to OrderItems). |
| 19 | + |
| 20 | +## Single-file version (non-modular) |
| 21 | + |
| 22 | +This is the complete page. It loads the SDK from a CDN, so everything lives in one `index.html` — the non-modular style. Styling is omitted for clarity. |
| 23 | + |
| 24 | +```html |
| 25 | +<!DOCTYPE html> |
| 26 | +<html lang="en"> |
| 27 | + <head> |
| 28 | + <meta charset="UTF-8" /> |
| 29 | + <title>Product Order</title> |
| 30 | + </head> |
| 31 | + <body> |
| 32 | + <div id="products"></div> |
| 33 | + <button id="submit">Submit order</button> |
| 34 | + <p id="status"></p> |
| 35 | + |
| 36 | + <script src="https://unpkg.com/seatable-html-page-sdk@latest/dist/index.min.js"></script> |
| 37 | + <script> |
| 38 | + // Quantities chosen by the user: { productRowId: quantity } |
| 39 | + const order = {}; |
| 40 | + let sdk; |
| 41 | +
|
| 42 | + async function init() { |
| 43 | + // In production __HTML_PAGE_DEV_CONFIG__ is undefined and the SDK |
| 44 | + // derives its context from the host app. In development the Vite |
| 45 | + // server injects it from src/setting.js. The same line works in both. |
| 46 | + sdk = new HTMLPageSDK(window.__HTML_PAGE_DEV_CONFIG__ || null); |
| 47 | + await sdk.init(); |
| 48 | +
|
| 49 | + // Read rows. The payload is under res.data (see SDK Reference > Rows). |
| 50 | + const res = await sdk.listRows({ tableName: "Products" }); |
| 51 | + renderProducts(res.data.results); |
| 52 | + } |
| 53 | +
|
| 54 | + function renderProducts(products) { |
| 55 | + const container = document.getElementById("products"); |
| 56 | + container.innerHTML = ""; |
| 57 | +
|
| 58 | + products.forEach((product) => { |
| 59 | + const row = document.createElement("div"); |
| 60 | + row.innerHTML = ` |
| 61 | + <span>${product.Product_name} (${product.Unit_price})</span> |
| 62 | + <input type="number" min="0" value="0" /> |
| 63 | + `; |
| 64 | + // product._id is the row ID — we need it to create the link later. |
| 65 | + row.querySelector("input").addEventListener("input", (e) => { |
| 66 | + order[product._id] = parseInt(e.target.value, 10) || 0; |
| 67 | + }); |
| 68 | + container.appendChild(row); |
| 69 | + }); |
| 70 | + } |
| 71 | +
|
| 72 | + async function submitOrder() { |
| 73 | + const status = document.getElementById("status"); |
| 74 | +
|
| 75 | + // Build one OrderItems row per chosen product. |
| 76 | + const orderItemsData = Object.keys(order) |
| 77 | + .filter((productId) => order[productId] > 0) |
| 78 | + .map((productId) => ({ |
| 79 | + Quantity: order[productId], |
| 80 | + // Link columns take an array of linked row IDs. |
| 81 | + Product: [productId], |
| 82 | + })); |
| 83 | +
|
| 84 | + if (orderItemsData.length === 0) { |
| 85 | + status.textContent = "Please choose at least one product."; |
| 86 | + return; |
| 87 | + } |
| 88 | +
|
| 89 | + try { |
| 90 | + // 1. Create the order items in one batch call. |
| 91 | + const itemsRes = await sdk.batchAddRows({ |
| 92 | + tableName: "OrderItems", |
| 93 | + rowsData: orderItemsData, |
| 94 | + }); |
| 95 | +
|
| 96 | + // batchAddRows returns the created rows under res.data.rows. |
| 97 | + // Collect their new IDs to link them to the order. |
| 98 | + const orderItemIds = itemsRes.data.rows.map((row) => row._id); |
| 99 | +
|
| 100 | + // 2. Create the order, linking the items we just created. |
| 101 | + await sdk.addRow({ |
| 102 | + tableName: "Orders", |
| 103 | + rowData: { OrderItems: orderItemIds }, |
| 104 | + }); |
| 105 | +
|
| 106 | + status.textContent = "Order submitted."; |
| 107 | + } catch (error) { |
| 108 | + // A 403 here usually means the API token lacks write permission. |
| 109 | + status.textContent = "Submit failed: " + error.message; |
| 110 | + } |
| 111 | + } |
| 112 | +
|
| 113 | + document.getElementById("submit").addEventListener("click", submitOrder); |
| 114 | + document.addEventListener("DOMContentLoaded", init); |
| 115 | + </script> |
| 116 | + </body> |
| 117 | +</html> |
| 118 | +``` |
| 119 | + |
| 120 | +!!! tip "Linking rows" |
| 121 | + |
| 122 | + Notice the two-step write: first create the `OrderItems` rows with `batchAddRows`, then read their `_id` values from `res.data.rows` and pass them as an array to the `Orders` row's link column. Link columns always take an array of linked row IDs — see [`addRow`](sdk/rows.md#add-rows). |
| 123 | + |
| 124 | +## Modular version |
| 125 | + |
| 126 | +For anything beyond a simple page, split the logic into modules and import the SDK from the npm package. The template's `src/esm` directory does exactly this. A common pattern is to wrap all base access in a single `Context` class, keeping SDK calls out of your UI code: |
| 127 | + |
| 128 | +```js |
| 129 | +import { HTMLPageSDK } from "seatable-html-page-sdk"; |
| 130 | + |
| 131 | +export default class Context { |
| 132 | + async init(options) { |
| 133 | + this.sdk = new HTMLPageSDK(options); |
| 134 | + await this.sdk.init(); |
| 135 | + } |
| 136 | + |
| 137 | + async loadProducts() { |
| 138 | + const res = await this.sdk.listRows({ tableName: "Products" }); |
| 139 | + return { |
| 140 | + columns: res.data.metadata, |
| 141 | + rows: res.data.results, |
| 142 | + }; |
| 143 | + } |
| 144 | + |
| 145 | + async submitOrder(orderItemsData) { |
| 146 | + const itemsRes = await this.sdk.batchAddRows({ |
| 147 | + tableName: "OrderItems", |
| 148 | + rowsData: orderItemsData, |
| 149 | + }); |
| 150 | + const orderItemIds = itemsRes.data.rows.map((row) => row._id); |
| 151 | + |
| 152 | + await this.sdk.addRow({ |
| 153 | + tableName: "Orders", |
| 154 | + rowData: { OrderItems: orderItemIds }, |
| 155 | + }); |
| 156 | + } |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +The entry point reads the injected dev config and starts the app: |
| 161 | + |
| 162 | +```js |
| 163 | +// index.js |
| 164 | +import Context from "./context"; |
| 165 | + |
| 166 | +document.addEventListener("DOMContentLoaded", async () => { |
| 167 | + const context = new Context(); |
| 168 | + await context.init(window.__HTML_PAGE_DEV_CONFIG__ || null); |
| 169 | + const { rows } = await context.loadProducts(); |
| 170 | + // ... render rows, wire up submit -> context.submitOrder(...) |
| 171 | +}); |
| 172 | +``` |
| 173 | + |
| 174 | +## Next steps |
| 175 | + |
| 176 | +- [SDK Reference: Rows](sdk/rows.md) — full parameters and return shapes for every row method. |
| 177 | +- [SDK Reference: Files & Images](sdk/files.md) — add file and image uploads to your form. |
0 commit comments