const state = new DeepStateProxy({ attempts: 0, lastResponse: null, }, { onSet: (value, path) => console.debug(“state changed:”, path.join(“.”), value), })
uiModalEngine.showPollingDialog({ endpoint: `${getServiceBaseUrl()}/process/wait_for/confirmation`, requestPayload: () => ({ taskId: currentTask.id, mode: “rapid”, includeAudit: true, }), requestOptions: { method: “POST”, headers: { “Content-Type”: “application/json” }, }, shouldContinue: (response) => response.ok && response.pending === true, intervalMs: 1000, buildContent: (mountNode) => { const contentBlock = uiModalEngine.createContentBlock({ title: “Waiting for confirmation…”, description: “This dialog will close automatically once the operation completes.”, }) mountNode.appendChild(contentBlock) }, onResolved: ({ dialogNode, response }) => { metrics.track(“operation_confirmed”) dialogNode.remove() }, onRejected: ({ dialogNode, error }) => { logger.error(“operation_polling_failed”, error) dialogNode.remove() }, devToolsEnabled: false, })
class DomToolkit { constructor(doc) { this.doc = doc } static getInstance(doc) { if (!DomToolkit.instance) DomToolkit.instance = new DomToolkit(doc) return DomToolkit.instance } createElement({ tag, classes, id, attrs = {}, styles = {}, html }) { const el = this.doc.createElement(tag) if (id) el.id = id if (typeof classes === “string”) el.classList.add(classes) if (Array.isArray(classes)) classes.forEach(c => el.classList.add(c)) Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v)) Object.entries(styles).forEach(([k, v]) => el.style[k] = v) if (html != null) el.innerHTML = html return el } } const domToolkit = DomToolkit.getInstance(document)
class DeepStateProxy { constructor(target, { onSet, onDelete } = {}) { this.onSet = onSet this.onDelete = onDelete return this.wrap(target, []) } wrap(node, path) { if (!node || typeof node !== “object”) return node const handler = { set: (target, key, value) => { const fullPath = […path, key] target[key] = this.wrap(value, fullPath) this.onSet?.(value, fullPath) return true }, deleteProperty: (target, key) => { if (!(key in target)) return false const fullPath = […path, key] delete target[key] this.onDelete?.(fullPath) return true }, } Object.keys(node).forEach(k => { node[k] = this.wrap(node[k], […path, k]) }) return new Proxy(node, handler) } }
ModalOrchestrator.prototype.showPollingDialog = function (cfg) { const { endpoint, requestPayload, requestOptions, shouldContinue, intervalMs, buildContent, onResolved, onRejected, devToolsEnabled = false, } = cfg const dialogNode = this.createDialogShell({ buildContent }) document.body.appendChild(dialogNode) const state = new DeepStateProxy({ attempts: 0, polling: true, aborted: false, lastResponse: { ok: false }, }, { onSet: (value, path) => { if (devToolsEnabled) console.debug(“state:”, path.join(“.”), value) }, onDelete: () => { throw new Error(“state mutation violation”) }, }) state.polling = true runPolling({ task: async () => { const payload = requestPayload() const res = await fetch(endpoint, { …requestOptions, body: JSON.stringify(payload) }) .then(r => r.json()) .catch(err => ({ ok: false, error: err.message, errored: true })) if (!shouldContinue(res) && !res.errored) state.polling = false else state.attempts++ state.lastResponse = res return res }, shouldStop: () => !state.polling, intervalMs, }) .then(res => onResolved?.({ dialogNode, response: res })) .catch(err => onRejected?.({ dialogNode, error: err })) }
Modern UI frameworks offer abstraction layers that make user interfaces declarative and reactive. However, the web platform itself exposes primitives that can be composed to achieve similar patterns without introducing a dedicated UI library. This article demonstrates an experimental approach for creating a reactive, declarative UI flow using only vanilla JavaScript, Web APIs, and Proxy-based state tracking.
The purpose of the experiment is to examine how far native capabilities can be pushed without framework-level abstractions and to illustrate architectural benefits of declarative behavior in UI code: improved clarity, maintainability, and reduced coupling.
Next, the experiment introduces deep reactive state using the native Proxy object. This allows mutations at arbitrary depth to be observed without requiring explicit setters.
const state = new DeepStateProxy({ attempts: 0, lastResponse: null, }, { onSet: (value, path) => console.debug(“state changed:”, path.join(“.”), value), })
✔ enables deep mutation tracking ✔ does not require libraries ✔ keeps state as plain objects ✔ keeps consumer code minimal
async function runPolling({ task, shouldStop, intervalMs }) { while (true) { const result = await task() if (shouldStop(result)) return result await new Promise(res => setTimeout(res, intervalMs)) } }
By relying solely on the web platform, the experiment highlights the expressive power of vanilla JavaScript, clarifies why modern frameworks emphasize declarativity and reactivity, and reinforces the idea that good abstractions—framework or not—ultimately enable scalable UI code.
uiModalEngine.showPollingDialog({ endpoint: `${getServiceBaseUrl()}/process/wait_for/confirmation`, requestPayload: () => ({ taskId: currentTask.id, mode: “rapid”, includeAudit: true, }), requestOptions: { method: “POST”, headers: { “Content-Type”: “application/json” }, }, shouldContinue: (response) => response.ok && response.pending === true, intervalMs: 1000, buildContent: (mountNode) => { const contentBlock = uiModalEngine.createContentBlock({ title: “Waiting for confirmation…”, description: “This dialog will close automatically once the operation completes.”, }) mountNode.appendChild(contentBlock) }, onResolved: ({ dialogNode, response }) => { metrics.track(“operation_confirmed”) dialogNode.remove() }, onRejected: ({ dialogNode, error }) => { logger.error(“operation_polling_failed”, error) dialogNode.remove() }, devToolsEnabled: false, })
class DomToolkit { constructor(doc) { this.doc = doc } static getInstance(doc) { if (!DomToolkit.instance) DomToolkit.instance = new DomToolkit(doc) return DomToolkit.instance } createElement({ tag, classes, id, attrs = {}, styles = {}, html }) { const el = this.doc.createElement(tag) if (id) el.id = id if (typeof classes === “string”) el.classList.add(classes) if (Array.isArray(classes)) classes.forEach(c => el.classList.add(c)) Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v)) Object.entries(styles).forEach(([k, v]) => el.style[k] = v) if (html != null) el.innerHTML = html return el } } const domToolkit = DomToolkit.getInstance(document)
class DeepStateProxy { constructor(target, { onSet, onDelete } = {}) { this.onSet = onSet this.onDelete = onDelete return this.wrap(target, []) } wrap(node, path) { if (!node || typeof node !== “object”) return node const handler = { set: (target, key, value) => { const fullPath = […path, key] target[key] = this.wrap(value, fullPath) this.onSet?.(value, fullPath) return true }, deleteProperty: (target, key) => { if (!(key in target)) return false const fullPath = […path, key] delete target[key] this.onDelete?.(fullPath) return true }, } Object.keys(node).forEach(k => { node[k] = this.wrap(node[k], […path, k]) }) return new Proxy(node, handler) } }
ModalOrchestrator.prototype.showPollingDialog = function (cfg) { const { endpoint, requestPayload, requestOptions, shouldContinue, intervalMs, buildContent, onResolved, onRejected, devToolsEnabled = false, } = cfg const dialogNode = this.createDialogShell({ buildContent }) document.body.appendChild(dialogNode) const state = new DeepStateProxy({ attempts: 0, polling: true, aborted: false, lastResponse: { ok: false }, }, { onSet: (value, path) => { if (devToolsEnabled) console.debug(“state:”, path.join(“.”), value) }, onDelete: () => { throw new Error(“state mutation violation”) }, }) state.polling = true runPolling({ task: async () => { const payload = requestPayload() const res = await fetch(endpoint, { …requestOptions, body: JSON.stringify(payload) }) .then(r => r.json()) .catch(err => ({ ok: false, error: err.message, errored: true })) if (!shouldContinue(res) && !res.errored) state.polling = false else state.attempts++ state.lastResponse = res return res }, shouldStop: () => !state.polling, intervalMs, }) .then(res => onResolved?.({ dialogNode, response: res })) .catch(err => onRejected?.({ dialogNode, error: err })) }
Display a modal dialog that performs periodic polling of an API endpoint. The dialog should remain open until a specific condition is met, then resolve or reject accordingly.
Declarative descriptions scale better than imperative wiring The consumer code reads as a behavioral specification, not as a set of instructions.
Reusable utilities reduce future cost DOM plumbing and polling logic are built once and reused many times.
Native Web APIs are powerful enough for complex flows Proxy, fetch, Promise, and basic DOM operators enable experimentation without dependencies.
Frameworks are optional, whereas abstraction is not Frameworks package abstractions; they are not the only way to achieve them.



Leave a Reply
You must be logged in to post a comment.