Live Demo
Key Features
Reactive State
Automatic UI updates when todos change. No manual DOM manipulation required.
Computed Properties
Filtered todo lists that automatically update based on state changes.
Data Persistence
LocalStorage integration keeps your todos saved between sessions.
Advanced Events
Keyboard shortcuts, method parameters, and declarative event binding.
Source Code
// Todo Application Component
Now.getManager('component').define('todo', {
// Enable reactive state management
reactive: true,
// Component state
state: {
title: 'Todo Application',
todos: [],
remaining: 0,
newTodo: '',
filter: 'all'
},
// Computed properties - automatically update when dependencies change
computed: {
filteredTodos() {
const {todos, filter} = this.state;
if (filter === 'all') return todos;
return todos.filter(todo =>
filter === 'completed' ? todo.completed : !todo.completed
);
}
},
// Component methods
methods: {
addTodo() {
if (!this.state.newTodo.trim()) return;
this.state.todos.push({
id: Utils.generateUUID(),
text: this.state.newTodo,
completed: false
});
this.state.newTodo = '';
this.methods.saveTodos();
},
toggleTodo(id) {
const todo = this.state.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.methods.saveTodos();
}
},
removeTodo(id) {
this.state.todos = this.state.todos.filter(t => t.id !== id);
this.methods.saveTodos();
},
clearCompleted() {
this.state.todos = this.state.todos.filter(t => !t.completed);
this.methods.saveTodos();
},
setFilter(filter) {
this.state.filter = filter;
},
loadTodos() {
const stored = localStorage.getItem('todos');
if (stored) {
this.state.todos = JSON.parse(stored);
this.methods.updateRemaining();
}
},
saveTodos() {
localStorage.setItem('todos', JSON.stringify(this.state.todos));
this.methods.updateRemaining();
},
updateRemaining() {
this.state.remaining = this.state.todos.filter(
todo => !todo.completed
).length;
}
},
// Lifecycle hooks
mounted() {
this.methods.loadTodos();
console.log('Todo component mounted');
},
// Global event handlers
events: {
'app:cleanup:end': function() {
this.methods.saveTodos();
console.log('Todos saved on cleanup');
}
}
});
<div class="todo-app" data-component="todo">
<!-- Input section -->
<div class="todo-header">
<div class="todo-input">
<input type="text"
data-model="newTodo"
placeholder="What needs to be done?"
data-on="keyup.enter:addTodo">
<button data-on="click:addTodo">Add Todo</button>
</div>
</div>
<!-- Filter buttons -->
<div class="todo-filters">
<button data-class="active:filter === 'all'"
data-on="click:setFilter('all')">All</button>
<button data-class="active:filter === 'active'"
data-on="click:setFilter('active')">Active</button>
<button data-class="active:filter === 'completed'"
data-on="click:setFilter('completed')">Completed</button>
</div>
<!-- Todo list -->
<div class="todo-list" data-for="todo of filteredTodos">
<template>
<label class="todo-item">
<input type="checkbox"
data-checked="todo.completed"
data-on="change:toggleTodo(todo.id)">
<span data-text="todo.text"></span>
<button data-on="click:removeTodo(todo.id)">Delete</button>
</label>
</template>
</div>
<!-- Footer -->
<div class="todo-footer">
<span data-text="remaining + ' items left'"></span>
<button data-on="click:clearCompleted">Clear Completed</button>
</div>
</div>
.todo-app {
max-width: 600px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.todo-input {
display: flex;
gap: var(--space-2);
}
.todo-input input {
flex: 1;
}
.todo-filters {
display: flex;
gap: var(--space-2);
}
.todo-filters button {
flex: 1;
padding: var(--space-2) var(--space-4);
background: var(--color-surface-light);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
color: var(--color-text-light);
}
.todo-filters button.active {
background: var(--color-primary);
color: white;
border-color: var(--color-primary);
}
.todo-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
border-bottom: 1px solid var(--color-border);
}
.todo-item:last-child {
border-bottom: none;
}
.todo-item input[type="checkbox"]:checked + span {
text-decoration: line-through;
color: var(--color-text-light);
}
.todo-item span {
flex: 1;
}
.todo-footer {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--color-text-light);
font-size: var(--font-size-sm);
}
How It Works
1. State Management
The todo app uses Now.js reactive state to track todos, filters, and input. When state changes, the UI automatically updates without manual DOM manipulation.
2. Computed Properties
The filteredTodos computed property automatically recalculates when todos or filter changes, providing efficient reactive filtering.
3. Event Handling
Click and keyboard events are handled using data-on attributes with method parameters, providing a clean, declarative way to bind events.
4. Data Persistence
LocalStorage integration automatically saves and loads todos. The component uses lifecycle hooks to ensure data is persisted correctly.
5. List Rendering
The data-for directive efficiently renders todo items, automatically updating the list when items are added, removed, or filtered.
6. Two-Way Binding
The data-model directive creates two-way binding for the input field, keeping the state and UI in perfect sync.