PR
スポンサーリンク

Vue.jsでTodoアプリを作る(編集機能・ローカルストレージへの保存・細かいデザイン)

前回の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.editingtrueの場合、編集用の入力フィールドと保存ボタンが表示されます。そうでない場合は、タスクの内容とデューデートが表示されます。

<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を使用して要素を配置し、ボタンにはホバー効果が追加されています。

コメント

タイトルとURLをコピーしました