resonantjs
v1.1.8
Published
A lightweight JavaScript framework that enables reactive data-binding for building dynamic and responsive web applications. It simplifies creating interactive UIs by automatically updating the DOM when your data changes.
Readme
ResonantJs
ResonantJs is a lightweight, powerful JavaScript framework that brings reactive data-binding to vanilla JavaScript applications. Build dynamic, responsive UIs with minimal code and zero dependencies.
Zero dependencies • ~17KB minified • Lightning fast • Easy to learn
Why Choose ResonantJs?
- True Reactivity: Data changes automatically update the DOM with no manual manipulation required
- Attribute-Based: Simple HTML attributes create powerful data bindings
- Deep Object Support: Full reactivity for nested objects and arrays
- Built-in Persistence: Automatic localStorage integration for data persistence
- Dynamic Styling: Conditional CSS classes and styles based on your data
- Performance: Efficient updates with minimal overhead
- Tiny Footprint: Under 17KB minified, perfect for any project size
Quick Start
Installation
NPM
npm install resonantjsCDN
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>Hello World Example
<!DOCTYPE html>
<html>
<head>
<title>ResonantJs Demo</title>
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
</head>
<body>
<h1>Counter: <span res="counter"></span></h1>
<button onclick="counter++">Increment</button>
<button onclick="counter--">Decrement</button>
<script>
const resonant = new Resonant();
resonant.add("counter", 0, true); // value, localStorage persistence
</script>
</body>
</html>That's it. Your counter automatically updates the DOM and persists to localStorage.
Build a Todo App in 5 Minutes
Copy and paste the snippet below into an .html file and open it in your browser.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>ResonantJs • 5‑min Todo</title>
<style>
.completed { text-decoration: line-through; color: #888; }
</style>
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
</head>
<body>
<h1>Todos (<span res="tasks.length"></span>)</h1>
<input placeholder="Add a task..." res="newTask" />
<button onclick="addTask()">Add</button>
<ul>
<li res="tasks" res-display="newTask === '' || name.toLowerCase().includes(newTask.toLowerCase())">
<input type="checkbox" res-prop="completed" />
<span res-prop="name" res-style="completed ? 'completed' : ''"></span>
<button res-onclick="removeTask">Remove</button>
</li>
</ul>
<script>
const resonant = new Resonant();
resonant.addAll({
newTask: '',
tasks: [
{ name: 'Learn ResonantJs', completed: false },
{ name: 'Ship a feature', completed: true }
]
});
function addTask() {
const title = newTask.trim();
if (!title) return;
tasks.unshift({ name: title, completed: false });
newTask = '';
}
function removeTask(item) {
const idx = tasks.indexOf(item);
if (idx !== -1) tasks.delete(idx);
}
// Optional: observe changes
resonant.addCallback('tasks', (list, item, action) => {
console.log('[tasks]', action, item);
});
</script>
</body>
</html>Key Takeaways
- Use
res="tasks"on a template element inside a list container to auto-render each item - Use
res-propinside that template to bind fields of the current item - Use
res-displayfor inline filtering/conditional rendering; inside lists, bare props likecompletedrefer to the current item res-stylereturns a space-separated class string- Event handlers referenced by
res-onclickare global functions and receiveitemwhen declared with a parameter
Core Concepts
Data Binding (res)
Bind HTML elements directly to your JavaScript variables:
<span res="username"></span> <!-- Simple variable -->
<div res="user.profile.name"></div> <!-- Nested object property -->Object Properties (res-prop)
Bind to specific properties within objects:
<div res="user">
<span res-prop="name"></span>
<span res-prop="email"></span>
</div>Array Rendering
Automatically render arrays with template-based elements:
<ul>
<li res="todoItems">
<span res-prop="title"></span>
<button res-onclick="removeItem(item)">Delete</button>
</li>
</ul>Conditional Display (res-display)
Show or hide elements based on conditions:
<div res-display="user.isActive">Welcome back!</div>
<div res-display="tasks.length > 0">You have tasks</div>
<div res-display="user.role === 'admin'">Admin Panel</div>Dynamic Styling (res-style)
Apply conditional CSS classes and styles:
<div res-style="task.completed ? 'completed' : 'pending'">Task</div>
<span res-style="'priority-' + task.priority">High Priority</span>Event Handling (res-onclick)
Bind click events with context:
<button res-onclick="editTask(item)">Edit</button>
<button res-onclick-remove="true">Delete Item</button>Computed Properties
Create reactive derived values that automatically update:
const resonant = new Resonant();
resonant.add('firstName', 'John');
resonant.add('lastName', 'Doe');
// Computed property automatically updates when dependencies change
resonant.computed('fullName', () => {
return firstName + ' ' + lastName;
});<span res="fullName"></span> <!-- Automatically shows "John Doe" -->Input Binding
Two-way data binding for form elements:
<input type="text" res="user.name" />
<input type="checkbox" res="settings.darkMode" />
<select res="user.country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</select>Best Practices and Tips
- Name your state clearly: Variables you
addbecome globals onwindow(e.g.,tasks,user). Avoid collisions with existing globals. - List templates: In
res="items"templates, you can reference current item fields directly (completed,name) or asitem.completed— both work. - Array updates: Prefer
items.set(i, value)over direct index assignment for clarity; both are reactive. - Batch updates: When replacing a whole list, use
items.update(newArray)to emit a single coherent update. - Object binding: Use
res-prop=""to bind an entire object to a single element when you just want to print it. - Event handlers:
res-onclickhandlers are looked up onwindow. If your handler accepts an argument, Resonant passes the currentitem. - Quick removal: For quick removal buttons, use
res-onclick-remove="idProp"to delete by a unique key on each item. - Computed properties: Track dependencies automatically. Use plain variable names inside the function (e.g.,
firstName,lastName). They are read-only. - Conditional expressions: Keep display and style expressions simple and fast.
Performance Notes
- Resonant selectively re-renders only changed array items by tracking indices and stable object keys
- Deeply nested objects and arrays are proxied; nested edits still update only affected DOM segments
API & Attribute Reference
HTML Attributes
res— bind a variable or array/template rootres-prop— bind an object property within arescontext; empty value binds the whole itemres-display— boolean expression to show/hide elementres-style— expression returning a space-separated class stringres-onclick— call a global function; if it declares a parameter, it receives the current itemres-onclick-remove— remove from the parent array by matching the given property (e.g.,id)
JavaScript API
const resonant = new Resonant()resonant.add(name, value, persist?)resonant.addAll(objectMap)resonant.addCallback(name, (newValue, item, action) => void)resonant.computed(name, () => value)
Array Helpers
Reactive arrays include these methods:
.push,.pop,.shift,.unshift,.splice,.sort,.reverse.set(index, value),.delete(index),.update(array),.filter(fn),.filterInPlace(fn),.forceUpdate()
Callback Actions
- Scalars:
modified - Arrays:
added,removed,modified,updated,filtered
Key Features
Reactive Data Management
const resonant = new Resonant();
// Add single variables
resonant.add('counter', 0);
resonant.add('user', { name: 'John', age: 30 });
// Batch initialization
resonant.addAll({
tasks: [],
settings: { theme: 'light' },
currentUser: { name: 'Alice', role: 'admin' }
});
// Changes automatically update the UI
user.name = 'Jane'; // DOM updates instantly
tasks.push({ title: 'New task' }); // Array renders new itemCallback System
React to data changes with custom logic:
resonant.addCallback('tasks', (newValue, item, action) => {
console.log(`Tasks ${action}:`, item);
updateTaskCounter();
saveToAPI();
});
resonant.addCallback('user', (newValue, item, action) => {
if (action === 'modified') {
showNotification('Profile updated');
}
});LocalStorage Persistence
Automatic localStorage integration:
// Data persists across browser sessions
resonant.add('userPreferences', { theme: 'dark' }, true);
resonant.add('appState', { currentView: 'dashboard' }, true);
// Changes are automatically saved
userPreferences.theme = 'light'; // Saved to localStorageComputed Properties
Reactive derived values that automatically recalculate:
resonant.add('firstName', 'John');
resonant.add('lastName', 'Doe');
// Automatically updates when firstName or lastName changes
resonant.computed('fullName', () => {
return firstName + ' ' + lastName;
});
// Cannot be set directly - read-only
// fullName = 'Something'; // Will log warning and be ignored
// Chain computed properties
resonant.computed('greeting', () => {
return 'Hello, ' + fullName + '!';
});Array Operations
Full array reactivity with custom methods:
// All operations trigger UI updates
items.push(newItem); // Add item
items.splice(index, 1); // Remove item
items.update([...newItems]); // Replace entire array
items.set(index, newValue); // Update specific index
items.delete(index); // Delete by index
items.filter(v => v > 0); // Non-mutating; still triggers a 'filtered' callback
items.filterInPlace(fn); // Mutating filter + rerender
items.forceUpdate(); // Force a rerender without changing contentsReal-World Examples
Todo List with Filtering
<div>
<input res="newTask" placeholder="Add task..." />
<button onclick="addTask()">Add</button>
<select res="filter">
<option value="all">All Tasks</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<ul>
<li res="filteredTasks"
res-display="filter === 'all' || (filter === 'active' && !completed) || (filter === 'completed' && completed)">
<input type="checkbox" res-prop="completed" />
<span res-prop="title" res-style="completed ? 'completed-task' : ''"></span>
<button res-onclick="deleteTask(item)">Delete</button>
</li>
</ul>
</div>
<script>
const resonant = new Resonant();
resonant.addAll({
tasks: [
{ title: 'Learn ResonantJs', completed: false },
{ title: 'Build awesome app', completed: false }
],
newTask: '',
filter: 'all',
filteredTasks: []
});
function addTask() {
if (newTask.trim()) {
tasks.push({ title: newTask, completed: false });
newTask = '';
updateFilter();
}
}
function deleteTask(task) {
const index = tasks.indexOf(task);
tasks.splice(index, 1);
updateFilter();
}
function updateFilter() {
filteredTasks.splice(0);
tasks.forEach(task => filteredTasks.push(task));
}
resonant.addCallback('filter', updateFilter);
resonant.addCallback('tasks', updateFilter);
updateFilter();
</script>Dashboard with Statistics
<div class="dashboard">
<div class="stats">
<div class="stat-card">
<h3 res="stats.totalTasks"></h3>
<p>Total Tasks</p>
</div>
<div class="stat-card">
<h3 res="stats.completedTasks"></h3>
<p>Completed</p>
</div>
<div class="stat-card">
<h3 res="stats.completionRate"></h3>
<p>% Complete</p>
</div>
</div>
<div class="projects">
<div res="projects">
<div res-prop="" class="project-card">
<h3 res-prop="name"></h3>
<div class="progress-bar">
<div class="progress" res-style="'width: ' + progress + '%'"></div>
</div>
<div res-prop="tasks">
<div res-prop="" res-style="'task priority-' + priority">
<span res-prop="title"></span>
<span res-prop="assignee"></span>
</div>
</div>
</div>
</div>
</div>
</div>Advanced Patterns
Nested Data Structures
Handle complex, deeply nested data:
resonant.add('company', {
departments: [
{
name: 'Engineering',
teams: [
{
name: 'Frontend',
members: [
{ name: 'Alice', role: 'Senior Dev', skills: ['React', 'Vue'] },
{ name: 'Bob', role: 'Junior Dev', skills: ['HTML', 'CSS'] }
]
}
]
}
]
});
// All levels are reactive
company.departments[0].teams[0].members[0].name = 'Alice Johnson';
company.departments[0].teams[0].members.push(newMember);Computed Properties
Create reactive calculated values that automatically update when dependencies change:
const resonant = new Resonant();
resonant.add('tasks', [
{ title: 'Task 1', completed: true },
{ title: 'Task 2', completed: false },
{ title: 'Task 3', completed: true }
]);
// Computed properties automatically recalculate when 'tasks' changes
resonant.computed('totalTasks', () => {
return tasks.length;
});
resonant.computed('completedTasks', () => {
return tasks.filter(t => t.completed).length;
});
resonant.computed('completionRate', () => {
return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
});
// Shopping cart example
resonant.add('items', [
{ name: 'Widget', price: 10, quantity: 2 },
{ name: 'Gadget', price: 15, quantity: 1 }
]);
resonant.add('taxRate', 0.08);
resonant.computed('subtotal', () => {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
});
resonant.computed('tax', () => {
return subtotal * taxRate;
});
resonant.computed('total', () => {
return subtotal + tax;
});<!-- These automatically update when items change -->
<div>Subtotal: $<span res="subtotal"></span></div>
<div>Tax: $<span res="tax"></span></div>
<div>Total: $<span res="total"></span></div>Component-Like Patterns
Organize code into reusable patterns:
function createTaskManager(containerId) {
const resonant = new Resonant();
resonant.addAll({
tasks: [],
filter: 'all',
newTask: ''
});
resonant.addCallback('tasks', updateStats);
return {
addTask: () => { /* implementation */ },
removeTask: (task) => { /* implementation */ },
setFilter: (filter) => { /* implementation */ }
};
}Examples & Demos
Explore our comprehensive examples:
- Basic Counter - Simple reactive counter
- Task Manager - Complete task management app
- Houses Demo - Complex nested data structures
- Tests Showcase - Interactive testbed used in CI
Each example demonstrates different aspects of ResonantJs and can serve as starting points for your projects.
Performance & Browser Support
Performance
- Minimal overhead: Only updates affected DOM elements
- Efficient diffing: Smart change detection for nested objects
- Lazy evaluation: Conditional expressions only run when dependencies change
- Memory efficient: Automatic cleanup of unused observers
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
- Mobile browsers (iOS Safari, Chrome Mobile)
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Setup
git clone https://github.com/amurgola/ResonantJs.git
cd ResonantJs
npm install
npm testRunning Tests
npm test # Run all tests
npm test -- test/specific.test.js # Run specific test fileLicense
ResonantJs is released under the MIT License. See LICENSE file for details.
Support & Community
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Full API Documentation
Star us on GitHub • Try the Demo • Read the Docs
Built with care by Andrew Paul Murgola
