Backbone Project — Build a Complete Task Management App
In this tutorial, you will learn about Backbone Project. We cover key concepts, practical examples, and best practices to help you master this topic.
Build a complete task management application using Backbone.js. This project combines Models, Collections, Views, Routers, Events, and localStorage persistence into a working application. You will create, read, update, and delete tasks with full event-driven UI updates.
What You'll Learn
You will integrate every Backbone concept into a single application. By the end, you will have a working task manager that demonstrates real-world Backbone architecture patterns.
Why It Matters
Building a complete application solidifies every concept you have learned. You will see how Models connect to Views, how events flow through the system, and how all components work together in a real application.
Real-World Use
This task manager pattern is the foundation for project management tools, issue trackers, todo lists, and workflow applications used by security teams to track remediation tasks.
flowchart LR
A[Task Model] --> B[Task View]
C[Task Collection] --> D[Task List View]
E[App Router] --> F[App View]
G[localStorage Sync] --> C
B --> G
D --> F
F --> H[DOM]
Project Structure
task-manager/
index.html
css/style.css
js/
models/task.js
collections/tasks.js
views/task-view.js
views/task-list-view.js
views/task-form-view.js
views/app-view.js
routers/app-router.js
sync/localstorage.js
app.js
Step 1: The Task Model
// js/models/task.js
var Task = Backbone.Model.extend({
defaults: {
title: '',
description: '',
priority: 'medium',
status: 'pending',
createdAt: null,
dueDate: null
},
initialize: function() {
if (!this.get('createdAt')) {
this.set('createdAt', new Date().toISOString());
}
},
validate: function(attrs) {
if (!attrs.title || attrs.title.trim() === '') {
return 'Task title is required';
}
},
toggleStatus: function() {
var newStatus = this.get('status') === 'completed' ? 'pending' : 'completed';
this.set('status', newStatus);
}
});
Step 2: The Collection with localStorage
// js/collections/tasks.js
var Tasks = Backbone.Collection.extend({
model: Task,
localStorage: new Backbone.LocalStorage('tasks-backbone'),
comparator: function(task) {
// Sort: pending first, then by createdAt descending
var order = { 'pending': 0, 'in-progress': 1, 'completed': 2 };
return order[task.get('status')] + '_' + (task.get('createdAt') || '');
},
pending: function() {
return this.where({ status: 'pending' });
},
completed: function() {
return this.where({ status: 'completed' });
},
byPriority: function(priority) {
return this.where({ priority: priority });
},
search: function(query) {
if (!query) return this.models;
var lower = query.toLowerCase();
return this.filter(function(task) {
return task.get('title').toLowerCase().indexOf(lower) >= 0 ||
task.get('description').toLowerCase().indexOf(lower) >= 0;
});
}
});
Step 3: The Task View
// js/views/task-view.js
var TaskView = Backbone.View.extend({
tagName: 'li',
className: 'task-item',
template: _.template(
'<div class="task-checkbox">' +
'<input type="checkbox" <%= status === "completed" ? "checked" : "" %>>' +
'</div>' +
'<div class="task-content">' +
'<h3 class="task-title"><%= title %></h3>' +
'<p class="task-desc"><%= description %></p>' +
'<span class="task-priority <%= priority %>"><%= priority %></span>' +
'<span class="task-status <%= status %>"><%= status %></span>' +
'</div>' +
'<div class="task-actions">' +
'<button class="edit-btn">Edit</button>' +
'<button class="delete-btn">Delete</button>' +
'</div>'
),
events: {
'change input[type="checkbox"]': 'toggleComplete',
'click .delete-btn': 'deleteTask',
'click .edit-btn': 'editTask'
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
toggleComplete: function() {
this.model.toggleStatus();
this.model.save();
},
deleteTask: function() {
if (confirm('Delete this task?')) {
this.model.destroy();
}
},
editTask: function() {
this.trigger('task:edit', this.model);
}
});
Step 4: Task Form View
// js/views/task-form-view.js
var TaskFormView = Backbone.View.extend({
tagName: 'div',
className: 'task-form',
template: _.template(
'<h2><%= editing ? "Edit Task" : "New Task" %></h2>' +
'<form>' +
'<div class="form-group">' +
'<label>Title</label>' +
'<input type="text" name="title" value="<%= title %>" required>' +
'</div>' +
'<div class="form-group">' +
'<label>Description</label>' +
'<textarea name="description"><%= description %></textarea>' +
'</div>' +
'<div class="form-group">' +
'<label>Priority</label>' +
'<select name="priority">' +
'<option value="low" <%= priority === "low" ? "selected" : "" %>>Low</option>' +
'<option value="medium" <%= priority === "medium" ? "selected" : "" %>>Medium</option>' +
'<option value="high" <%= priority === "high" ? "selected" : "" %>>High</option>' +
'</select>' +
'</div>' +
'<div class="form-group">' +
'<label>Due Date</label>' +
'<input type="date" name="dueDate" value="<%= dueDate %>">' +
'</div>' +
'<button type="submit"><%= editing ? "Update" : "Add" %> Task</button>' +
'<% if (editing) { %>' +
'<button type="button" class="cancel-btn">Cancel</button>' +
'<% } %>' +
'</form>'
),
events: {
'submit form': 'handleSubmit',
'click .cancel-btn': 'cancelEdit'
},
initialize: function() {
this.editing = false;
},
render: function() {
var data = this.model ? this.model.toJSON() : {
title: '', description: '', priority: 'medium', dueDate: ''
};
data.editing = !!this.model;
this.$el.html(this.template(data));
return this;
},
handleSubmit: function(e) {
e.preventDefault();
var attrs = {
title: this.$('input[name="title"]').val().trim(),
description: this.$('textarea[name="description"]').val().trim(),
priority: this.$('select[name="priority"]').val(),
dueDate: this.$('input[name="dueDate"]').val()
};
if (this.model) {
this.model.save(attrs, {
success: _.bind(function() {
this.trigger('form:saved');
}, this)
});
} else {
this.collection.create(attrs, {
success: _.bind(function() {
this.trigger('form:saved');
this.render(); // Reset form
}, this)
});
}
},
cancelEdit: function() {
this.model = null;
this.render();
this.trigger('form:cancelled');
}
});
Step 5: App View and Router
// js/views/app-view.js
var AppView = Backbone.View.extend({
el: '#app',
initialize: function() {
this.collection = new Tasks();
this.collection.on('sync', this.render, this);
this.formView = new TaskFormView({ collection: this.collection });
this.listView = new TaskListView({ collection: this.collection });
this.formView.on('form:saved', function() {
this.$('.task-form-container').slideUp();
}, this);
this.render();
this.collection.fetch();
},
render: function() {
this.$el.html(
'<header><h1>Task Manager</h1></header>' +
'<div class="task-form-container"></div>' +
'<div class="toolbar">' +
'<button class="add-task-btn">+ New Task</button>' +
'<input type="text" class="search-input" placeholder="Search tasks...">' +
'</div>' +
'<div class="task-list-container"></div>' +
'<div class="stats"></div>'
);
this.$('.task-form-container').append(this.formView.render().el);
this.$('.task-form-container').hide();
this.$('.task-list-container').append(this.listView.render().el);
this.$('.add-task-btn').on('click', _.bind(function() {
this.formView.model = null;
this.formView.render();
this.$('.task-form-container').slideToggle();
}, this));
this.$('.search-input').on('keyup', _.bind(function(e) {
this.listView.filter(e.target.value);
}, this));
this.updateStats();
return this;
},
updateStats: function() {
var total = this.collection.length;
var pending = this.collection.pending().length;
var completed = this.collection.completed().length;
this.$('.stats').text(
'Total: ' + total + ' | Pending: ' + pending + ' | Completed: ' + completed
);
}
});
// js/routers/app-router.js
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'task/new': 'newTask',
'task/:id': 'showTask'
},
home: function() {
console.log('Home route');
},
newTask: function() {
console.log('New task form');
},
showTask: function(id) {
console.log('Show task:', id);
}
});
Step 6: Application Entry Point
// js/sync/localstorage.js — Use Backbone.LocalStorage plugin
// or create a simple localStorage adapter (see lesson on localStorage)
// js/app.js
var app = new AppView();
var router = new AppRouter();
// Start history (hash-based)
Backbone.history.start();
console.log('Task Manager started');
Running the Project
Create index.html that loads all scripts in order:
<script src="jquery.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>
<script src="backbone.localstorage.js"></script>
<script src="js/models/task.js"></script>
<script src="js/collections/tasks.js"></script>
<script src="js/sync/localstorage.js"></script>
<script src="js/views/task-view.js"></script>
<script src="js/views/task-list-view.js"></script>
<script src="js/views/task-form-view.js"></script>
<script src="js/views/app-view.js"></script>
<script src="js/routers/app-router.js"></script>
<script src="js/app.js"></script>
Common Mistakes
- Forgetting to include the localStorage plugin. The Backbone.LocalStorage plugin is a separate library. Without it,
save()tries to contact a server. - Not calling
collection.fetch()on app start. LocalStorage data is not loaded untilfetch()is called. The app starts with an empty collection. - Creating the AppView before the DOM is ready. Wrap app initialization in
$(document).ready()or place scripts at the bottom of the body. - Not cleaning up event listeners on sub-views. When the form view is hidden, its event listeners still respond. Call
remove()on hidden views. - Using the same model instance across views. If two views share a model, changes in one affect both. Use
model.clone()when needed.
Practice Questions
- How does localStorage persistence work in this project?
- What event triggers the task list to re-render after adding a new task?
- How does the search filter work in the AppView?
- What is the role of the TaskFormView's
form:savedevent? - Challenge: Add a new feature to the task manager — categories. Each task can belong to a category. Add a filter dropdown that shows tasks by category. Persist categories in localStorage.
FAQ
Mini Project
Extend the task manager with: (1) task categories with filtering, (2) a statistics dashboard showing task completion rates, (3) export to JSON functionality, (4) keyboard shortcuts (n for new task, / for search). Use Backbone events for all communication between components.
What's Next
Congratulations on completing Backbone.js! Next, explore Ember.js for a more opinionated framework. Or compare with Knockout.js for the MVVM pattern.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro