Evan You
8 years ago
6 changed files with 161 additions and 188 deletions
@ -0,0 +1,157 @@ |
|||
// Full spec-compliant TodoMVC with localStorage persistence
|
|||
// and hash-based routing in ~150 lines.
|
|||
|
|||
// localStorage persistence
|
|||
var STORAGE_KEY = 'todos-vuejs-2.0' |
|||
var todoStorage = { |
|||
fetch: function () { |
|||
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') |
|||
todos.forEach(function (todo, index) { |
|||
todo.id = index |
|||
}) |
|||
todoStorage.uid = todos.length |
|||
return todos |
|||
}, |
|||
save: function (todos) { |
|||
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) |
|||
} |
|||
} |
|||
|
|||
// visibility filters
|
|||
var filters = { |
|||
all: function (todos) { |
|||
return todos |
|||
}, |
|||
active: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return !todo.completed |
|||
}) |
|||
}, |
|||
completed: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return todo.completed |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// app Vue instance
|
|||
var app = new Vue({ |
|||
// app initial state
|
|||
data: { |
|||
todos: todoStorage.fetch(), |
|||
newTodo: '', |
|||
editedTodo: null, |
|||
visibility: 'all' |
|||
}, |
|||
|
|||
// watch todos change for localStorage persistence
|
|||
watch: { |
|||
todos: { |
|||
handler: function (todos) { |
|||
todoStorage.save(todos) |
|||
}, |
|||
deep: true |
|||
} |
|||
}, |
|||
|
|||
// computed properties
|
|||
// http://vuejs.org/guide/computed.html
|
|||
computed: { |
|||
filteredTodos: function () { |
|||
return filters[this.visibility](this.todos) |
|||
}, |
|||
remaining: function () { |
|||
return filters.active(this.todos).length |
|||
}, |
|||
allDone: { |
|||
get: function () { |
|||
return this.remaining === 0 |
|||
}, |
|||
set: function (value) { |
|||
this.todos.forEach(function (todo) { |
|||
todo.completed = value |
|||
}) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
filters: { |
|||
pluralize: function (n) { |
|||
return n === 1 ? 'item' : 'items' |
|||
} |
|||
}, |
|||
|
|||
// methods that implement data logic.
|
|||
// note there's no DOM manipulation here at all.
|
|||
methods: { |
|||
addTodo: function () { |
|||
var value = this.newTodo && this.newTodo.trim() |
|||
if (!value) { |
|||
return |
|||
} |
|||
this.todos.push({ |
|||
id: todoStorage.uid++, |
|||
title: value, |
|||
completed: false |
|||
}) |
|||
this.newTodo = '' |
|||
}, |
|||
|
|||
removeTodo: function (todo) { |
|||
this.todos.splice(this.todos.indexOf(todo), 1) |
|||
}, |
|||
|
|||
editTodo: function (todo) { |
|||
this.beforeEditCache = todo.title |
|||
this.editedTodo = todo |
|||
}, |
|||
|
|||
doneEdit: function (todo) { |
|||
if (!this.editedTodo) { |
|||
return |
|||
} |
|||
this.editedTodo = null |
|||
todo.title = todo.title.trim() |
|||
if (!todo.title) { |
|||
this.removeTodo(todo) |
|||
} |
|||
}, |
|||
|
|||
cancelEdit: function (todo) { |
|||
this.editedTodo = null |
|||
todo.title = this.beforeEditCache |
|||
}, |
|||
|
|||
removeCompleted: function () { |
|||
this.todos = filters.active(this.todos) |
|||
} |
|||
}, |
|||
|
|||
// a custom directive to wait for the DOM to be updated
|
|||
// before focusing on the input field.
|
|||
// http://vuejs.org/guide/custom-directive.html
|
|||
directives: { |
|||
'todo-focus': function (el, value) { |
|||
if (value) { |
|||
el.focus() |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
|
|||
// handle routing
|
|||
function onHashChange () { |
|||
var visibility = window.location.hash.replace(/#\/?/, '') |
|||
if (filters[visibility]) { |
|||
app.visibility = visibility |
|||
} else { |
|||
window.location.hash = '' |
|||
app.visibility = 'all' |
|||
} |
|||
} |
|||
|
|||
window.addEventListener('hashchange', onHashChange) |
|||
onHashChange() |
|||
|
|||
// mount
|
|||
app.$mount('.todoapp') |
@ -1,127 +0,0 @@ |
|||
/*global Vue, todoStorage */ |
|||
|
|||
(function (exports) { |
|||
|
|||
'use strict'; |
|||
|
|||
var filters = { |
|||
all: function (todos) { |
|||
return todos; |
|||
}, |
|||
active: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return !todo.completed; |
|||
}); |
|||
}, |
|||
completed: function (todos) { |
|||
return todos.filter(function (todo) { |
|||
return todo.completed; |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
exports.app = new Vue({ |
|||
// app initial state
|
|||
data: { |
|||
todos: todoStorage.fetch(), |
|||
newTodo: '', |
|||
editedTodo: null, |
|||
visibility: 'all' |
|||
}, |
|||
|
|||
// watch todos change for localStorage persistence
|
|||
watch: { |
|||
todos: { |
|||
handler: function (todos) { |
|||
todoStorage.save(todos); |
|||
}, |
|||
deep: true |
|||
} |
|||
}, |
|||
|
|||
// computed properties
|
|||
// http://vuejs.org/guide/computed.html
|
|||
computed: { |
|||
filteredTodos: function () { |
|||
return filters[this.visibility](this.todos); |
|||
}, |
|||
remaining: function () { |
|||
return filters.active(this.todos).length; |
|||
}, |
|||
allDone: { |
|||
get: function () { |
|||
return this.remaining === 0; |
|||
}, |
|||
set: function (value) { |
|||
this.todos.forEach(function (todo) { |
|||
todo.completed = value; |
|||
}); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
filters: { |
|||
pluralize: function (n) { |
|||
return n === 1 ? 'item' : 'items' |
|||
} |
|||
}, |
|||
|
|||
// methods that implement data logic.
|
|||
// note there's no DOM manipulation here at all.
|
|||
methods: { |
|||
addTodo: function () { |
|||
var value = this.newTodo && this.newTodo.trim(); |
|||
if (!value) { |
|||
return; |
|||
} |
|||
this.todos.push({ |
|||
id: todoStorage.uid++, |
|||
title: value, |
|||
completed: false |
|||
}); |
|||
this.newTodo = ''; |
|||
}, |
|||
|
|||
removeTodo: function (todo) { |
|||
this.todos.splice(this.todos.indexOf(todo), 1); |
|||
}, |
|||
|
|||
editTodo: function (todo) { |
|||
this.beforeEditCache = todo.title; |
|||
this.editedTodo = todo; |
|||
}, |
|||
|
|||
doneEdit: function (todo) { |
|||
if (!this.editedTodo) { |
|||
return; |
|||
} |
|||
this.editedTodo = null; |
|||
todo.title = todo.title.trim(); |
|||
if (!todo.title) { |
|||
this.removeTodo(todo); |
|||
} |
|||
}, |
|||
|
|||
cancelEdit: function (todo) { |
|||
this.editedTodo = null; |
|||
todo.title = this.beforeEditCache; |
|||
}, |
|||
|
|||
removeCompleted: function () { |
|||
this.todos = filters.active(this.todos); |
|||
} |
|||
}, |
|||
|
|||
// a custom directive to wait for the DOM to be updated
|
|||
// before focusing on the input field.
|
|||
// http://vuejs.org/guide/custom-directive.html
|
|||
directives: { |
|||
'todo-focus': function (el, value) { |
|||
if (value) { |
|||
el.focus(); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
})(window); |
@ -1,24 +0,0 @@ |
|||
/*global app, Router */ |
|||
|
|||
(function (app, Router) { |
|||
|
|||
'use strict'; |
|||
|
|||
var router = new Router(); |
|||
|
|||
['all', 'active', 'completed'].forEach(function (visibility) { |
|||
router.on(visibility, function () { |
|||
app.visibility = visibility; |
|||
}); |
|||
}); |
|||
|
|||
router.configure({ |
|||
notfound: function () { |
|||
window.location.hash = ''; |
|||
app.visibility = 'all'; |
|||
} |
|||
}); |
|||
|
|||
router.init(); |
|||
|
|||
})(app, Router); |
@ -1,23 +0,0 @@ |
|||
/*jshint unused:false */ |
|||
|
|||
(function (exports) { |
|||
|
|||
'use strict'; |
|||
|
|||
var STORAGE_KEY = 'todos-vuejs-2.0'; |
|||
|
|||
exports.todoStorage = { |
|||
fetch: function () { |
|||
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); |
|||
todos.forEach(function (todo, index) { |
|||
todo.id = index |
|||
}); |
|||
exports.todoStorage.uid = todos.length; |
|||
return todos; |
|||
}, |
|||
save: function (todos) { |
|||
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); |
|||
} |
|||
}; |
|||
|
|||
})(window); |
@ -1,9 +0,0 @@ |
|||
{ |
|||
"private": true, |
|||
"dependencies": { |
|||
"director": "^1.2.0", |
|||
"vue": "^1.0.0", |
|||
"todomvc-common": "^1.0.1", |
|||
"todomvc-app-css": "^2.0.0" |
|||
} |
|||
} |
Loading…
Reference in new issue