前回のVue.jsでTodoアプリを作成する(期限の設定)でTodoに対して期限を設定する機能と期限が超えているTodoに関しては文字を赤くするようにしました。
今回は一度設定したTodoを編集できるようにするとともに、今までのアプリではリロードをすると設定したTodoが消えてしまうので、ローカルストレージに保存してリロードしてもTodoが消えてしまわないようにしていきたいと思います。
完成後のイメージは以下の画像の通りです。
改修後の全体コードは以下の通りです。
C:/npm_sample/src\/App.vue
<template>
<div id="app">
<h1>ToDo List</h1>
<div class="input-area">
<input type="text" v-model="newTodo" @keyup.enter="addTodo">
<input type="date" v-model="newTodoDueDate">
<button @click="addTodo">追加</button>
</div>
<ul>
<li v-for="(todo, index) in todos" :key="index"
:class="{ 'completed': todo.completed, 'pastDue': isPastDue(todo.dueDate), 'editing': todo.editing }">
<div class="todo-container">
<template v-if="todo.editing">
<input type="text" v-model="todo.text">
<input type="date" v-model="todo.dueDate">
<button @click="saveEdit(todo)">保存</button>
</template>
<template v-else>
<div class="todo-content">
<span>{{ todo.text }}</span>
<span class="due-date">- 期限: {{ todo.dueDate }}</span>
</div>
<div class="buttons">
<button @click="toggleCompleted(index)" class="completeButton">{{ todo.buttonText }}</button>
<button @click="editTodo(todo)">編集</button>
<button @click="deleteTodo(index)" class="deleteButton">削除</button>
</div>
</template>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
newTodo: '',
newTodoDueDate: '',
todos: [],
}
},
created() {
this.loadTodos();
},
methods: {
loadTodos() {
const todos = localStorage.getItem('todos')
if (todos) {
this.todos = JSON.parse(todos)
}
},
saveTodos() {
localStorage.setItem('todos', JSON.stringify(this.todos))
},
addTodo() {
if (this.newTodo.trim() && this.newTodoDueDate) {
// 新しいToDo項目を追加するとき、buttonTextプロパティを含めます
this.todos.push({
text: this.newTodo,
completed: false,
buttonText: '完了',
dueDate: this.newTodoDueDate,
editing: false
});
this.newTodo = ''; // フィールドをリセット
this.newTodoDueDate = ''; // 期限日フィールドをリセット
this.saveTodos()
}
},
toggleCompleted(index) {
// 特定のToDo項目のcompleted状態とbuttonTextを更新
this.todos[index].completed = !this.todos[index].completed;
this.todos[index].buttonText = this.todos[index].completed ? '戻す' : '完了';
this.saveTodos()
},
deleteTodo(index) {
if (window.confirm("本当に削除しますか?")) {
this.todos.splice(index, 1);
this.saveTodos()
}
},
editTodo(todo) {
todo.editing = true;
},
saveEdit(todo) {
todo.editing = false;
this.saveTodos()
},
isPastDue(dueDate) {
const today = new Date();
today.setHours(0, 0, 0, 0); // 時間をリセットして今日の日付のみにする
const due = new Date(dueDate);
return due < today; //期限が今日より前であればtrueを返す
}
}
}
</script>
<style>
#app {
max-width: 600px;
margin: 0 auto;
font-family: 'Helvetica Neue', Arial, sans-serif;
}
.input-area {
display: flex;
margin-bottom: 20px;
}
input[type="text"], input[type="date"] {
flex: 1;
padding: 10px;
border: 2px solid #ccc;
border-radius: 4px;
margin-right: 5px;
}
/* フォーカス時のスタイル */
input[type="date"]:focus {
outline: none;
border-color: #4CAF50;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
margin-left: 5px;
}
.todo-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.todo-content {
display: flex;
flex-direction: column;
}
.due-date {
color: #666;
font-size: 0.9em;
}
.buttons {
display: flex;
align-items: center;
}
button {
background-color: #4CAF50; /* 緑色のボタン */
border: none; /* 枠線なし */
color: white; /* テキスト色 */
padding: 10px 15px; /* パディング */
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
transition-duration: 0.4s; /* ホバー効果のためのトランジション */
cursor: pointer; /* マウスカーソルを指に */
border-radius: 4px; /* 角を丸く */
}
button:hover {
background-color: #45a049; /* ホバー時に少し暗く */
}
.completeButton {
background-color: rgb(24, 130, 243); /* 青色のボタン */
border: none; /* 枠線なし */
color: white; /* テキスト色 */
padding: 10px 15px; /* パディング */
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
transition-duration: 0.4s; /* ホバー効果のためのトランジション */
cursor: pointer; /* マウスカーソルを指に */
border-radius: 4px; /* 角を丸く */
}
.completeButton:hover {
background-color: #221a93;
}
.deleteButton {
background-color: rgb(243, 24, 24); /* 赤色のボタン */
border: none; /* 枠線なし */
color: white; /* テキスト色 */
padding: 10px 15px; /* パディング */
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
transition-duration: 0.4s; /* ホバー効果のためのトランジション */
cursor: pointer; /* マウスカーソルを指に */
border-radius: 4px; /* 角を丸く */
}
.deleteButton:hover {
background-color: #931a1a; /* ホバー時に少し暗く */
}
.completeButton, .deleteButton {
margin-left: 10px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 8px 0;
background-color: #f3f3f3;
padding: 10px;
border-radius: 4px;
}
.pastDue {
color: red;
}
</style>
<template> セクションの解説:
<div id="app">
: このDIVタグは、Vueインスタンスのすべてのコンテンツを包含します。ID「app」は、VueインスタンスがHTMLと接続する際のアンカーポイントです。<input>
タグ: ユーザーからの入力を受け取ります。v-model
ディレクティブを使って、Vueのデータプロパティ(newTodo
,newTodoDueDate
)と双方向データバインディングを行います。これにより、入力フィールドの値がこれらのプロパティに自動的に同期されます。<button @click="addTodo">追加</button>
: 「追加」ボタンは、クリックされるとaddTodo
メソッドをトリガーします。<ul>
と<li>
: ToDoアイテムのリストを表示します。v-for
ディレクティブを使用して、todos
配列の各要素に対してリストアイテムを繰り返し生成します。key
属性は、Vueが各要素を一意に識別するのに役立ちます。<template v-if="todo.editing">
: 条件付きレンダリングを行います。todo.editing
がtrue
の場合、編集用の入力フィールドと保存ボタンが表示されます。そうでない場合は、タスクの内容とデューデートが表示されます。
<script> セクションの解説:
data()
: コンポーネントの状態を定義するデータプロパティを返します。ここでは新しいToDoのテキストと期限、そしてToDoリストを格納する配列が含まれています。created()
: Vueインスタンスのライフサイクルフックの一つで、インスタンスが作成された直後に実行されます。ここではloadTodos
メソッドを呼び出して、ローカルストレージからToDoリストを読み込みます。methods
: コンポーネントで使用されるメソッドを定義します。これには、ToDoアイテムを追加、編集、削除、保存する機能が含まれています。loadTodos()
: ローカルストレージから保存されたToDoリストを読み込みます。saveTodos()
: 現在のToDoリストをローカルストレージに保存します。addTodo()
: 新しいToDoアイテムをリストに追加し、入力フィールドをクリアします。toggleCompleted()
: 特定のToDoアイテムの完了状態をトグルします。editTodo()
とsaveEdit()
: ToDoアイテムを編集モードに切り替え、編集を保存します。
<style> セクションの解説:
CSSスタイル定義は、アプリケーションの外観をカスタマイズします。ここでは、入力エリア、ボタン、ToDoアイテムのコンテナなどにスタイルが適用されています。特に、Flexboxを使用して要素を配置し、ボタンにはホバー効果が追加されています。
コメント