LemonadeJS v5 introduced setPath — a bidirectional form-to-JSON binding that most developers haven't discovered yet. No event handlers, no controlled components. Just declare the path and it syncs.
The Problem
You have a JSON object. You have a form. Keeping them in sync means writing event listeners for every field, handling different input types, managing nested objects... it adds up fast.
The Solution
let [data, setData, getData] = setPath(initialObject);
<input lm-path="user.name" />
<input lm-path="user.settings.theme" />
That's it. Form changes update data. Call setData(obj) to populate the form. Call getData() to extract current values.
Complete Example
One component showing everything: defaults, nested binding, load, save, validation callback, and reset.
const defaults = {
name: '',
email: '',
settings: {
notifications: true,
theme: 'light'
}
};
export default function UserForm(children, { setPath, onload }) {
const self = this;
// Initialize with optional change callback
let [data, setData, getData] = setPath(defaults, (value, path, el) => {
// Validation example
if (path === 'email' && value && !value.includes('@')) {
el.classList.add('invalid');
} else {
el.classList.remove('invalid');
}
});
// Load existing data
onload(async () => {
const id = getRouteId();
if (id) {
const user = await fetch(`/api/users/${id}`).then(r => r.json());
setData(user); // Populates entire form
}
});
const save = async () => {
const payload = getData(); // Gets entire form as object
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
};
const reset = () => setData(defaults);
return render => render`
<form>
<input type="text" lm-path="name" placeholder="Name" />
<input type="email" lm-path="email" placeholder="Email" />
<label>
<input type="checkbox" lm-path="settings.notifications" />
Email notifications
</label>
<select lm-path="settings.theme">
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<button type="button" onclick="${reset}">Reset</button>
<button type="button" onclick="${save}">Save</button>
</form>
`;
}
API Reference
let [data, setData, getData] = setPath(initialObject, onChange);
| Return | Description |
|---|---|
data |
Reactive object synced with form |
setData(obj) |
JSON → Form |
getData() |
Form → JSON |
Callback signature:
(value, path, element) => { }
// path = "settings.theme", etc.
Template binding:
<input lm-path="fieldName" />
<input lm-path="nested.property.path" />
Works with: <input>, <textarea>, <select>, checkboxes, radio buttons.
Why It Works Well
- Close to vanilla JS — Components are functions, templates are template literals. No transpilation needed.
- Small footprint — Less than 8KB for the entire framework, form binding included.
- No magic — You can read the code and understand what's happening.
Related Tools
LemonadeJS is part of a suite of lightweight JS tools:
- LemonadeJS - Reactive micro-framework
- Jspreadsheet - Excel-like spreadsheets
- CalendarJS - A complete collection of calendar and schedule components
- Jsuites - UI components (calendar, dropdown, color picker)
Top comments (0)