TODOリスト

オンラインスキルアップのサンプルコード

by wintyo

HTML

<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
<script src="https://unpkg.com/vue-types"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

SCSS

* {
  box-sizing: border-box;
}

.list {
  position: relative;
  padding: 0;
}

.item {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  transition: all 0.5s;
  padding: 10px;
  margin-top: 10px;
  border: solid 1px #ddd;
  border-radius: 5px;
  box-shadow: 0 0 10px 0 rgba(#000, 0.1);
  cursor: pointer;
  
  &__text {
    flex: 1 1 0;
    padding: 0 5px;
  }
}

.flip {
  // 要素が動くときにtransitionを設定する(.itemでtransitionを設定しているため-moveで書く必要はない)
  // &-move {
  //   transition: transform 0.5s;
  // }
  
  // 要素が入るときのアニメーション
  &-enter {
    &-active {
      opacity: 0;
      transform: translate3d(0, -30px, 0);
    }
    &-to {
      opacity: 1;
      transform: translate3d(0, 0, 0);
    }
  }

  // 要素が消える時のアニメーション
  &-leave {
    &-active {
      position: absolute;
    }
    &-to {
      opacity: 0;
      transform: translate3d(0, -30px, 0);
    }
  }
}

JavaScript

/**
 * 入力フォームコンポーネント
 */
const InputForm = {
  data() {
    return {
      text: ''
    };
  },
  methods: {
    /**
     * 送信時
     * @param {event} event - DOMのイベント
     */
    onSubmit(event) {
      event.preventDefault();
      this.$emit('submit', this.$data.text);
      this.$data.text = '';
    }
  },
  template: `
    <form @submit="onSubmit">
      <input v-model="$data.text" type="text" placeholder="TODO" />
      <button type="submit" :disabled="$data.text === ''">送信</button>
    </form>
  `
};

/**
 * TODOリストコンポーネント
 */
const TodoList = {
  props: {
    todoList: VueTypes.arrayOf(VueTypes.shape({
      id: VueTypes.number.isRequired,
      isDone: VueTypes.bool.isRequired,
      text: VueTypes.string.isRequired
    })).isRequired
  },
  methods: {
    /**
     * 削除ボタンをクリックした時
     * @param {event} event - DOMのイベント
     * @param {number} todoId - TODOのID
     */
    onDeleteTodo(event, todoId) {
      event.stopPropagation();
      this.$emit('delete', todoId);
    }
  },
  template: `
    <transition-group tag="ul" class="list" name="flip">
      <template v-for="item in $props.todoList">
        <li
          :key="item.id"
          class="item"
          @click="$emit('check', item.id)"
        >
          <input type="checkbox" :checked="item.isDone" />
          <span class="item__text">{{ item.text }}</span>
          <button @click="onDeleteTodo($event, item.id)">削除</button>
        </li>
      </template>
    </transition-group>
  `
};

new Vue({
  el: '#app',
  components: {
    InputForm,
    TodoList
  },
  data() {
    // 初期のTodoリストを用意する
    const todoList = [
      {
        isDone: false,
        text: '未着手タスク'
      },
      {
        isDone: true,
        text: '終了タスク'
      }
    ];
    return {
      // idを順番につける
      todoList: todoList.map((item, index) => ({ ...item, id: index })),
      // 次のTODOに降るID番号
      nextTodoId: todoList.length
    };
  },
  methods: {
    /**
     * 送信された時
     * @param {string} text - テキスト
     */
   ...