Skip to content

Commit ae31643

Browse files
committed
convert TodoMVC from VueJS examples
1 parent db6ba8a commit ae31643

File tree

5 files changed

+228
-47
lines changed

5 files changed

+228
-47
lines changed

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"ts-loader": "^3.1.0",
2424
"vue-class-component": "^6.0.0",
2525
"vue-loader": "^13.0.5",
26+
"vue-property-decorator": "^6.0.0",
2627
"vue-template-compiler": "^2.4.4",
2728
"vue-ts-loader": "0.0.3",
2829
"webpack": "^3.6.0",

src/App.vue

Lines changed: 179 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,197 @@
11
<template>
2-
<div id="app">
3-
<img src="./assets/logo.png">
4-
<h1>{{ msg }}</h1>
5-
<h2>Essential Links</h2>
6-
<ul>
7-
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
8-
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
9-
<li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
10-
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
11-
</ul>
12-
<h2>Ecosystem</h2>
13-
<ul>
14-
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
15-
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
16-
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
17-
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
18-
</ul>
19-
</div>
2+
<div id="app">
3+
<section class="todoapp">
4+
<header class="header">
5+
<h1>todos</h1>
6+
<input class="new-todo"
7+
autofocus autocomplete="off"
8+
placeholder="What needs to be done?"
9+
v-model="newTodo"
10+
@keyup.enter="addTodo">
11+
</header>
12+
13+
<section class="main" v-show="todos.length" v-cloak>
14+
<input class="toggle-all" type="checkbox" v-model="allDone">
15+
<ul class="todo-list">
16+
<li v-for="todo in filteredTodos"
17+
class="todo"
18+
:key="todo.id"
19+
:class="{ completed: todo.completed, editing: todo == editedTodo }">
20+
21+
<div class="view">
22+
<input class="toggle" type="checkbox" v-model="todo.completed">
23+
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
24+
<button class="destroy" @click="removeTodo(todo)"></button>
25+
</div>
26+
27+
<input class="edit" type="text"
28+
v-model="todo.title"
29+
v-todo-focus="todo == editedTodo"
30+
@blur="doneEdit(todo)"
31+
@keyup.enter="doneEdit(todo)"
32+
@keyup.esc="cancelEdit(todo)">
33+
</li>
34+
</ul>
35+
</section>
36+
37+
<footer class="footer" v-show="todos.length" v-cloak>
38+
<span class="todo-count"><strong>{{ remaining }}</strong> {{ remaining | pluralize }} left</span>
39+
<ul class="filters">
40+
<li><a href="#/all" :class="{ selected: visibility == 'all' }">All</a></li>
41+
<li><a href="#/active" :class="{ selected: visibility == 'active' }">Active</a></li>
42+
<li><a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a></li>
43+
</ul>
44+
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">Clear completed</button>
45+
</footer>
46+
</section>
47+
48+
<footer class="info">
49+
<p>Double-click to edit a todo</p>
50+
<p>Written by <a href="http://evanyou.me">Evan You</a></p>
51+
<p>Converted by <a href="http://toni.uebernickel.info">Toni Uebernickel</a></p>
52+
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
53+
</footer>
54+
</div>
2055
</template>
2156

2257
<script lang="ts">
23-
import Vue from "vue";
24-
import Component from 'vue-class-component';
58+
import { Component, Vue, Watch } from 'vue-property-decorator'
59+
60+
import Todo from './Todo'
61+
import TodoStorage from './TodoStorage'
62+
63+
Vue.filter('pluralize', (n: number): string => {
64+
return n === 1 ? 'item' : 'items'
65+
})
66+
67+
Vue.directive('todo-focus', (el, binding) => {
68+
if (binding.value) {
69+
el.focus()
70+
}
71+
})
72+
73+
const filters = {
74+
all(todos: Todo[]): Todo[] {
75+
return todos
76+
},
77+
78+
active(todos: Todo[]): Todo[] {
79+
return todos.filter((todo: Todo) => {
80+
return !todo.completed
81+
})
82+
},
83+
84+
completed(todos: Todo[]): Todo[] {
85+
return todos.filter((todo: Todo) => {
86+
return todo.completed
87+
})
88+
}
89+
}
2590
2691
@Component({
2792
name: 'app'
2893
})
2994
export default class App extends Vue {
30-
data () {
31-
return {
32-
msg: 'Welcome to Your Vue.js App, supporting TypeScript'
95+
private storage: TodoStorage = new TodoStorage()
96+
97+
private todos: Todo[] = this.storage.fetch()
98+
private visibility: string = 'all'
99+
100+
private newTodo: string = ''
101+
private editedTodo: Todo|null = null
102+
private beforeEditCache: string|null = null
103+
104+
constructor() {
105+
super()
106+
107+
window.addEventListener('hashchange', this.onHashChange)
108+
}
109+
110+
get filteredTodos(): Todo[] {
111+
return filters[this.visibility](this.todos)
112+
}
113+
114+
get remaining(): number {
115+
return filters.active(this.todos).length
116+
}
117+
118+
get allDone(): boolean {
119+
return this.remaining === 0
120+
}
121+
122+
set allDone(value: boolean) {
123+
this.todos.forEach((todo: Todo) => {
124+
todo.completed = value
125+
})
126+
}
127+
128+
addTodo() {
129+
const title = this.newTodo && this.newTodo.trim()
130+
if (!title) {
131+
return
33132
}
133+
134+
this.todos.push({
135+
id: this.storage.nextUid(),
136+
title: title,
137+
completed: false
138+
})
139+
140+
this.newTodo = ''
34141
}
35-
}
36-
</script>
37142
38-
<style lang="scss">
39-
#app {
40-
font-family: 'Avenir', Helvetica, Arial, sans-serif;
41-
-webkit-font-smoothing: antialiased;
42-
-moz-osx-font-smoothing: grayscale;
43-
text-align: center;
44-
color: #2c3e50;
45-
margin-top: 60px;
46-
}
143+
removeTodo(todo) {
144+
this.todos.splice(this.todos.indexOf(todo), 1)
145+
}
47146
48-
h1, h2 {
49-
font-weight: normal;
50-
}
147+
editTodo(todo: Todo) {
148+
this.beforeEditCache = todo.title
149+
this.editedTodo = todo
150+
}
51151
52-
ul {
53-
list-style-type: none;
54-
padding: 0;
55-
}
152+
doneEdit(todo) {
153+
if (!this.editedTodo) {
154+
return
155+
}
56156
57-
li {
58-
display: inline-block;
59-
margin: 0 10px;
60-
}
157+
this.editedTodo = null
158+
159+
todo.title = todo.title.trim()
160+
if (!todo.title) {
161+
this.removeTodo(todo)
162+
}
163+
}
61164
62-
a {
63-
color: #42b983;
165+
cancelEdit(todo) {
166+
this.editedTodo = null
167+
todo.title = this.beforeEditCache
168+
}
169+
170+
removeCompleted() {
171+
this.todos = filters.active(this.todos)
172+
}
173+
174+
@Watch('todos', { deep: true})
175+
onTodosChange() {
176+
this.storage.save(this.todos)
177+
}
178+
179+
onHashChange() {
180+
const visibility = window.location.hash.replace(/#\/?/, '')
181+
182+
if (filters[visibility]) {
183+
this.visibility = visibility
184+
} else {
185+
window.location.hash = ''
186+
187+
this.visibility = 'all'
188+
}
189+
}
64190
}
191+
</script>
192+
193+
<style lang="scss">
194+
@import url('https://unpkg.com/todomvc-app-css@2.0.6/index.css');
195+
196+
[v-cloak] { display: none; }
65197
</style>

src/Todo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default interface Todo {
2+
id: number
3+
title: string
4+
completed: boolean
5+
}

src/TodoStorage.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Todo from './Todo'
2+
3+
const STORAGE_KEY = 'todos-vuejs-2.0'
4+
5+
export default class TodoStorage {
6+
private uid: number = 1
7+
8+
nextUid(): number {
9+
return this.uid++;
10+
}
11+
12+
fetch(): Todo[] {
13+
const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
14+
15+
todos.forEach((todo, index) => {
16+
todo.id = index
17+
})
18+
19+
this.uid = todos.length
20+
21+
return todos
22+
}
23+
24+
save(todos: Todo[]) {
25+
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
26+
}
27+
}

0 commit comments

Comments
 (0)