examples/classic/todomvc/app.js JAVASCRIPT 158 lines View on github.com → Search inside
1// Full spec-compliant TodoMVC with localStorage persistence2// and hash-based routing in ~150 lines.34// localStorage persistence5var STORAGE_KEY = 'todos-vuejs-2.0'6var todoStorage = {7  fetch: function () {8    var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')9    todos.forEach(function (todo, index) {10      todo.id = index11    })12    todoStorage.uid = todos.length13    return todos14  },15  save: function (todos) {16    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))17  }18}1920// visibility filters21var filters = {22  all: function (todos) {23    return todos24  },25  active: function (todos) {26    return todos.filter(function (todo) {27      return !todo.completed28    })29  },30  completed: function (todos) {31    return todos.filter(function (todo) {32      return todo.completed33    })34  }35}3637// app Vue instance38var app = new Vue({39  // app initial state40  data: {41    todos: todoStorage.fetch(),42    newTodo: '',43    editedTodo: null,44    visibility: 'all'45  },4647  // watch todos change for localStorage persistence48  watch: {49    todos: {50      handler: function (todos) {51        todoStorage.save(todos)52      },53      deep: true54    }55  },5657  // computed properties58  // https://v2.vuejs.org/v2/guide/computed.html59  computed: {60    filteredTodos: function () {61      return filters[this.visibility](this.todos)62    },63    remaining: function () {64      return filters.active(this.todos).length65    },66    allDone: {67      get: function () {68        return this.remaining === 069      },70      set: function (value) {71        this.todos.forEach(function (todo) {72          todo.completed = value73        })74      }75    }76  },7778  filters: {79    pluralize: function (n) {80      return n === 1 ? 'item' : 'items'81    }82  },8384  // methods that implement data logic.85  // note there's no DOM manipulation here at all.86  methods: {87    addTodo: function () {88      var value = this.newTodo && this.newTodo.trim()89      if (!value) {90        return91      }92      this.todos.push({93        id: todoStorage.uid++,94        title: value,95        completed: false96      })97      this.newTodo = ''98    },99100    removeTodo: function (todo) {101      this.todos.splice(this.todos.indexOf(todo), 1)102    },103104    editTodo: function (todo) {105      this.beforeEditCache = todo.title106      this.editedTodo = todo107    },108109    doneEdit: function (todo) {110      if (!this.editedTodo) {111        return112      }113      this.editedTodo = null114      todo.title = todo.title.trim()115      if (!todo.title) {116        this.removeTodo(todo)117      }118    },119120    cancelEdit: function (todo) {121      this.editedTodo = null122      todo.title = this.beforeEditCache123    },124125    removeCompleted: function () {126      this.todos = filters.active(this.todos)127    }128  },129130  // a custom directive to wait for the DOM to be updated131  // before focusing on the input field.132  // https://v2.vuejs.org/v2/guide/custom-directive.html133  directives: {134    'todo-focus': function (el, binding) {135      if (binding.value) {136        el.focus()137      }138    }139  }140})141142// handle routing143function onHashChange () {144  var visibility = window.location.hash.replace(/#\/?/, '')145  if (filters[visibility]) {146    app.visibility = visibility147  } else {148    window.location.hash = ''149    app.visibility = 'all'150  }151}152153window.addEventListener('hashchange', onHashChange)154onHashChange()155156// mount157app.$mount('.todoapp')

Code quality findings 10

Remove event listeners when no longer needed to prevent memory leaks
warning performance event-listener-leak
window.addEventListener('hashchange', onHashChange)
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var STORAGE_KEY = 'todos-vuejs-2.0'
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var todoStorage = {
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var filters = {
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var app = new Vue({
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.remaining === 0
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return n === 1 ? 'item' : 'items'
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var value = this.newTodo && this.newTodo.trim()
Use let or const to avoid scope issues and hoisting
info correctness var-declaration
var visibility = window.location.hash.replace(/#\/?/, '')

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.