PR
スポンサーリンク

Vue.jsでTodoアプリを作る(バリデーションの追加)

前回のVue.jsでTodoアプリを作る(編集機能・ローカルストレージへの保存・細かいデザイン)で登録したTodoを編集できるようにして、リロードしてもTodoが消えてしまわないようにローカルストレージの保存を行いました。

今回は、Todoを登録するときと編集をするときに空欄だった時にエラーが表示されるようにしました。

イメージは以下の画像の通りです。

デザインもより明示的に理解できるようにしてみました。

全体のコードは以下の通りです。

C:/npm_sample/src/App.vue

<template>
  <div id="app">
    <h1>ToDo List</h1>
    <ul>
      <li v-for="todoError in todoErrors" :key="todoError" class="errorDescription">
        {{ todoError }}
      </li>
    </ul>
    <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': true, 'completed': todo.completed}">{{ 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: [],
      todoErrors: []
    }
  },
  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() {
      this.todoErrors = [];  // 新しいTodoを追加する前にエラー配列をクリア
      if (!this.newTodo.trim()) {
        this.todoErrors.push('Todoを入力してください');
      }
      if (!this.newTodoDueDate) {
        this.todoErrors.push('期限を入力してください');
      }
      if (this.todoErrors.length === 0) { // エラーがない場合のみTodoを追加
        // 新しいToDo項目を追加するとき、buttonTextプロパティを含めます
        this.todos.push({ 
          text: this.newTodo,
          completed: false,
          buttonText: '完了',
          dueDate: this.newTodoDueDate,
          editing: false
        });
        this.newTodo = ''; // フィールドをリセット
        this.newTodoDueDate = ''; // 期限日フィールドをリセット
        this.saveTodos()
      } 
    },
    toggleCompleted(index) {
      const todo = this.todos[index];
      todo.completed = !todo.completed; // 完了状態を切り替え
      todo.buttonText = todo.completed ? '未完了' : '完了'; // ボタンテキストも更新
      this.saveTodos();
    },
    deleteTodo(index) {
      if (window.confirm("本当に削除しますか?")) {
        this.todos.splice(index, 1);
        this.saveTodos()
      }
    },
    editTodo(todo) {
      todo.editing = true;
    },
    saveEdit(todo) {
      this.todoErrors = [];
      if (!todo.text) {
        this.todoErrors.push('Todoを入力してください');
      }
      if (!todo.dueDate) {
        this.todoErrors.push('期限を入力してください')
      }
      if (this.todoErrors.length === 0) {
        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; /* 角を丸く */
}

.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; /* 角を丸く */
}

/* ホバー時のスタイル */
.completeButton:hover,
.deleteButton:hover {
  opacity: 0.85; /* ボタンを少し透明にすることでホバー効果を強調 */
}

.completeButton, .deleteButton {
  margin-left: 10px;
}

.completed {
  background-color: #d4edda; /* 完了項目の背景色 */
  color: #155724; /* 完了項目のテキスト色 */
  text-decoration: line-through; /* 完了項目に取り消し線を追加 */
}

/* 完了ボタンのスタイルを条件によって変更するためのスタイルを追加 */
.completeButton.completed {
  color: white;
  background-color: #2846a7; /* 完了時のボタンの背景色 */
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  margin: 8px 0;
  background-color: #f3f3f3;
  padding: 10px;
  border-radius: 4px;
}

.pastDue {
  color: red;
}

.errorDescription {
  color: red;
}
</style>

解説:

  • this.todoErrors = [];: エラー配列をクリアしてから新しいToDoの追加処理を始めます。
  • this.todoErrors.push(...);: 新しいToDoの追加条件が満たされない場合、対応するエラーメッセージをエラー配列に追加します。
  • if (this.todoErrors.length === 0) { ... }: エラーがない場合のみ、ToDoを追加し、ローカルストレージに保存します。

コメント

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