Skip to content

Backbone Project — Build a Complete Task Management App

DodaTech Updated 2026-06-28 7 min read

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

  1. Forgetting to include the localStorage plugin. The Backbone.LocalStorage plugin is a separate library. Without it, save() tries to contact a server.
  2. Not calling collection.fetch() on app start. LocalStorage data is not loaded until fetch() is called. The app starts with an empty collection.
  3. Creating the AppView before the DOM is ready. Wrap app initialization in $(document).ready() or place scripts at the bottom of the body.
  4. Not cleaning up event listeners on sub-views. When the form view is hidden, its event listeners still respond. Call remove() on hidden views.
  5. 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

  1. How does localStorage persistence work in this project?
  2. What event triggers the task list to re-render after adding a new task?
  3. How does the search filter work in the AppView?
  4. What is the role of the TaskFormView's form:saved event?
  5. 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

How do I add categories to this task manager?

Add a category attribute to the Task model. Add a select dropdown in the form. Filter by category in a new method on the Tasks collection.

Can I deploy this task manager?

Yes. Upload the files to any static web server. It works entirely in the browser.

How do I add due date reminders?

Use setInterval to check due dates against the current time and trigger a notification event.

How do I add drag and drop reordering?

Use jQuery UI sortable on the task list container. Update the sortOrder attribute on each model after drag.

How do I sync this with a server?

Replace the localStorage adapter with a REST backend. Set urlRoot on the Model and url on the Collection.

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