Client Side Ux Snippets
Custom Code Snippets — Client-Side UX
Copy-paste examples for the user-facing helpers — toasts, modals, redirects, clipboard — that only work in client-side workflows. These run in the user's browser; on a server-side workflow they're silently ignored.
A workflow is "client-side" when it's triggered from the user's browser (typically by a Trigger Workflow button or page event). Record-rule and scheduled workflows run server-side, so client UX helpers do nothing in those.
Available client-side helpers
| Helper | What it does |
|---|---|
userAlert(msg, opts) |
Native-style alert dialog. |
userNotify(msg, type, opts) |
Toast notification (info / success / warning / error). |
showModal(html, opts) |
Open a modal with custom HTML. |
closeModal() |
Close any open modal. |
redirectTo(url, newTab) |
Navigate to a URL. |
openUrl(url, target) |
Open a URL in a new tab/window. |
setClientStorage(key, value, scope) |
Save to localStorage or sessionStorage. |
removeClientStorage(key, scope) |
Remove a key from client storage. |
downloadFile(url, filename) |
Trigger a file download. |
copyToClipboard(text) |
Copy a string to the clipboard. |
triggerEvent(name, payload) |
Dispatch a custom DOM event. |
reloadPage(force) |
Reload the current page. |
playSound(name) |
Play a built-in sound. |
updateUI(action, payload) |
Refresh / re-render parts of the page. |
Toast + redirect after success
userNotify("Saved!", "success", { duration: 2500, position: "top-right" });
redirectTo("/dashboard", false);
Confirm-then-act with a modal
showModal(`<p>You have ${params["unsaved_count"]} unsaved changes.</p>`, {
title: "Heads up",
size: "sm"
});
Stash a value in client storage
setClientStorage("last_seen_record_id", record.id, "local");
Copy a generated link to the clipboard
const link = `${getAppDetails("base_url", "")}/r/${record.id}`;
copyToClipboard(link);
userNotify("Link copied", "info");
returnData("link", link);
Error toast after a failed API call
const r = await fetch("https://api.example.com/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: record.id })
});
if (!r.ok) {
userAlert(`Save failed (status ${r.status}). Please try again.`, { type: "error" });
returnData("ok", false);
return;
}
userNotify("Saved", "success");
returnData("ok", true);
Show a modal with a table of items
// Inputs: items (array of {name, qty})
const items = Array.isArray(params["items"]) ? params["items"] : [];
const rows = items.map(i =>
`<tr><td>${Str.capitalize(i.name)}</td><td>${i.qty}</td></tr>`
).join("");
showModal(`
<table style="width:100%;border-collapse:collapse">
<thead><tr><th align="left">Item</th><th align="left">Qty</th></tr></thead>
<tbody>${rows || "<tr><td colspan='2'>(empty)</td></tr>"}</tbody>
</table>
`, { title: "Order summary", size: "md" });
Progress messages around a long step
// Tell the user "we're working" before the await, then update them after.
userNotify("Generating PDF…", "info", { duration: 4000 });
const r = await fetch("https://api.example.com/pdf", {
method: "POST",
body: JSON.stringify({ id: record.id }),
headers: { "Content-Type": "application/json" }
});
if (r.ok) {
const data = await r.json();
userNotify("PDF ready — opening download.", "success");
downloadFile(data.url, `receipt-${record.id}.pdf`);
} else {
userAlert("PDF generation failed.", { type: "error" });
}
Conditional redirect by status
// Send the user to different pages based on a status field.
const status = String(record.status || "").toLowerCase();
const target = {
draft: "/edit",
submitted: "/submitted",
approved: "/dashboard"
}[status] || "/";
userNotify(`Status: ${status}`, "info");
redirectTo(target, false);
Save a UI preference and confirm
// Inputs: theme (custom_val "dark"/"light")
const theme = String(params["theme"] || "light");
setClientStorage("ui_theme", theme, "local");
userNotify(`Theme set to ${theme}`, "success");
updateUI("refresh", {});
Play a sound on a notable event
// e.g. when a high-priority ticket comes in.
const priority = String(record.priority || "").toLowerCase();
if (priority === "high") {
playSound("notification");
userNotify("High-priority ticket received", "warning", { position: "top-center" });
}
Confirm-then-call pattern (modal first, action second)
// In practice, split into two steps: this one shows a confirm modal,
// the next workflow step (gated on a button click) runs the action.
showModal(`
<p>Send invoice #${record.invoice_number} to ${record.customer_email}?</p>
<button onclick="window.dispatchEvent(new CustomEvent('confirm-send-invoice'))">Send</button>
`, { title: "Confirm", size: "sm" });
Dispatch a custom DOM event
// Other front-end code on the page can subscribe with `window.addEventListener("invoice:sent", ...)`.
triggerEvent("invoice:sent", {
id: record.id,
number: record.invoice_number,
amount: record.total
});
Build a CSV in code and download it
// Inputs: rows (array of objects with the same keys)
const rows = Array.isArray(params["rows"]) ? params["rows"] : [];
const cols = rows.length ? Object.keys(rows[0]) : [];
const escape = v => {
const s = String(v == null ? "" : v);
return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
};
const csv = [cols.join(",")]
.concat(rows.map(r => cols.map(c => escape(r[c])).join(",")))
.join("\n");
const dataUrl = `data:text/csv;charset=utf-8,${encodeURIComponent(csv)}`;
downloadFile(dataUrl, `export-${DateTime.format(DateTime.now(), "YYYY-MM-DD")}.csv`);
userNotify(`Exported ${rows.length} rows`, "success");
Reload the page after a record update
userNotify("Saved", "success", { duration: 1500 });
setTimeout(() => reloadPage(false), 1500);
We'd love to hear your feedback.