Remove event listeners when no longer needed to prevent memory leaks
window.addEventListener('hashchange', onHashChange)
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')
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.