質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.93%

Vue.js ToDoリスト タスクの表示の切り替え

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 233

Junkichi89

score 6

ラジオボタンの表示ごとにタスクの表示を切り替えをしたい Vue.js

前提・実現したいこと

Vue.jsでToDoリストを作成しています。
ラジオをボタンの選択を切り替えた際に表示も変更するようにしたいです。

<ここに質問の内容を詳しく書いてください。>

Vue.jsでTodoリストを作成しているのですが、
ラジオボタンを変更したあと、うまく作動しなくなってしまいます。
ラジオボタンを切り替えても、どのタスクを選択しても、完了、作業中が切り替わり。
完了のラジオボタンを選択している場合は、完了と表示されているタスクのみ表示され、
作業中のラジオボタンを選択している際は、作業中と表示されているタスクのみ表示したいです。
また、完了中に新しいタスクを入力しても表示されずに、タスクとしては、追加されているようにしたいです。

発生している問題・エラーメッセージ

ラジオボタンを変更したあと、うまく作動しなくなってしまいます。
「すべて」のラジオを選択しているときは、作業中と完了のボタンは、うまく作動している。
しかし、作業中や完了のラジオを選択しているとインデックスの逆から押さなければ、
全ての項目を正しく切り替えることができない。

例)
○すべて ●作業中 ○完了
1.A 作業中
2.B 作業中
3.C 作業中
→A→B→Cと押すとAが完了に切り替わったあと、Bを完了に変えようとすると再び、作業中のAが表示されてしまう。
しかし、CからB、Aと押すと問題なく変更される。

エラーメッセージ

該当のソースコード

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ToDo List Vue-version</title>

  <link rel="stylesheet" href="css/styles.css">
</head>

<body>
  <h2>ToDoリスト</h2>
  <div id="app">
    <label><input type="radio" name="list" value="all" v-model="filter">すべて</label>
    <label><input type="radio" name="list" value="active" v-model="filter">作業中</label>
    <label><input type="radio" name="list" value="completed" v-model="filter">完了</label>
    <p>ID コメント 状態</p>
    <table>
      <tr v-for="(todo, index) in filteredTodos">
        <td>{{ index }}</td>
        <td>{{ todo.title }}</td>
        <td><button @click="statusChange(index)">
            <span>{{ todo.statusBtn }}</span>
          </button></td>
        <td><button @click="deleteTask(index)">{{ todo.delBtn }}</button></td>
      </tr>
    </table>
    <h2>新規タスクの追加</h2>
    <form @submit.prevent="addTask">
      <input type="text" v-model="newTask">
      <input type="submit" value="追加" class="button">
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    'use strict';

    new Vue({
      el: '#app',
      data: {
        statusBtn: '', newTask: '', todos: [], filter: 'all'
      },
      computed: {
        filteredTodos() {
          if (this.filter === 'all') {
            return this.todos
          } else if (this.filter === 'completed') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '完了'
            })
          } else if (this.filter === 'active') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '作業中'
            })
          }
        }
      },
      methods: {
        addTask: function () {
          let item = {
            title: this.newTask, statusBtn: '作業中', delBtn: '削除'
          }
          this.todos.push(item);
          this.newTask = '';
        },
        statusChange: function (data) {
          if (this.todos[data].statusBtn === '作業中') {
            this.todos[data].statusBtn = '完了'
          } else {
            this.todos[data].statusBtn = '作業中'
          }
        },
        deleteTask: function (index) {
          this.todos.splice(index, 1);
        }
      }
    });
  </script>
</body>

</html>

試したこと

ここに問題に対して試したことを記載してください。
https://cr-vue.mio3io.com/tutorials/todo.html#step2-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8-api-%E3%81%AE%E4%BD%BF%E7%94%A8

上記のサイトのコードを参考にして、ラジオボタンを含め、フィルター部分から修正しようとしたのですが、
下記のようなエラーが出てしまいました。そして、dataの中にstate:0を追加したのですが、改善されませんでした。

**********
[Vue warn]: Property or method "item" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

[Vue warn]: Error in render: "TypeError: Cannot read property 'state' of undefined"

(found in <Root>)
・・・・

**********

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ToDo List Vue-version</title>

  <link rel="stylesheet" href="css/styles.css">
</head>

<body>
  <h2>ToDoリスト</h2>
  <div id="app">
    <label v-for="label in options"><input type="radio" v-bind:value="label.value" v-model="current"
        name="label">{{ label.label }}</label>
    <p>ID コメント 状態</p>
    <table>
      <tr v-for="(todo, index) in filteredTodos" v-bind:key="index">
        <td>{{ index }}</td>
        <td>{{ todo.title }}</td>
        <td><button @click="statusChange(item)">{{ labels[item.state] }}</button></td>
        <td><button @click="deleteTask(index)">削除</button></td>
      </tr>
    </table>
    <h2>新規タスクの追加</h2>
    <form @submit.prevent="addTask">
      <input type="text" v-model="newTask">
      <input type="submit" value="追加" class="button">
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    'use strict';

    new Vue({
      el: '#app',
      data: {
        options: [
          { value: -1, label: 'すべて' },
          { value: 0, label: '作業中' },
          { value: 1, label: '完了' }
        ],
        newTask: '', 
        todos: [],
        current: -1,
      },
      computed: {
        filteredTodos() {
          return this.todos.filter(function (todo) {
            return this.current < 0 ? true : this.current === todo.state
          }, this)
        },
        labels() {
          return this.options.reduce(function (a, b) {
            return Object.assign(a, { [b.value]: b.label })
          }, {})
        }
      },
      methods: {
        addTask: function () {
          let item = {
            title: this.newTask,
            state: 0
          }
          this.todos.push(item);
          this.newTask = '';
        },
        statusChange: function (item) {
          item.state = item.state ? 0 : 1
        },
        deleteTask: function (index) {
          this.todos.splice(index, 1);
        }
      }
    });
  </script>
</body>

</html>

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。
残りタスクが1つになった後、削除ボタンが作動しなってしまいました。
削除ボタンを押すとすべてのタスクを削除できるようにし、連番も再び初めからふりなおすにしたいのですが、
これは、新たに質問をした方が良いでしょうか?

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ToDo List Vue-version</title>

  <link rel="stylesheet" href="css/styles.css">
</head>

<body>
  <h2>ToDoリスト</h2>
  <div id="app">
    <label><input type="radio" name="list" value="all" v-model="filter">すべて</label>
    <label><input type="radio" name="list" value="active" v-model="filter">作業中</label>
    <label><input type="radio" name="list" value="completed" v-model="filter">完了</label>
    <p>ID コメント 状態</p>

    <table>
      <tr v-for="todo in filteredTodos">
        <td>{{ todo.id }}</td>
        <td>{{ todo.title }}</td>
        <td><button @click="statusChange(todo.id)">{{ todo.statusBtn }}</button></td>
        <td><button @click="deleteTask(todo.id)">{{ todo.delBtn }}</button></td>
      </tr>
    </table>
    <h2>新規タスクの追加</h2>
    <form @submit.prevent="addTask">
      <input type="text" v-model="newTask">
      <input type="submit" value="追加" class="button">
    </form>
  </div>


  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    'use strict';

    new Vue({
      el: '#app',
      data: {
        id: 0, statusBtn: '', newTask: '', todos: [], filter: 'all'
      },
      computed: {
        filteredTodos() {
          if (this.filter === 'all') {
            return this.todos
          } else if (this.filter === 'completed') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '完了'
            })
          } else if (this.filter === 'active') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '作業中'
            })
          }
        }
      },
      methods: {
        addTask: function () {
          let item = {
            id: this.id++, title: this.newTask, statusBtn: '作業中', delBtn: '削除'
          }
          this.todos.push(item);
          this.newTask = '';
        },
        statusChange: function (id) {
          for (let todo of this.todos) {
            if (todo.id === id) {
              if (todo.statusBtn === '作業中') {
                return todo.statusBtn = '完了'
              } else {
                return todo.statusBtn = '作業中'
              }
              break
            }
          }
        },
        deleteTask: function (id) {
          for (let todo of this.todos) {
            if (todo.id === id) {
              this.todos.splice(todo.id, 1);
              break
            }
          }
        }
      }
    });
  </script>
</body>

</html>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

v-forのindexを利用してtodosオブジェクトに対して操作している事が原因です。
indexの値はボタンを押すたびに変化するので期待する動作にはなりません。

todosオブジェクトの中に一意となるidパラメータを作成する事で解決するサンプルプログラムを以下に貼り付けます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ToDo List Vue-version</title>

  <link rel="stylesheet" href="css/styles.css">
</head>

<body>
  <h2>ToDoリスト</h2>
  <div id="app">
    <label><input type="radio" name="list" value="all" v-model="filter">すべて</label>
    <label><input type="radio" name="list" value="active" v-model="filter">作業中</label>
    <label><input type="radio" name="list" value="completed" v-model="filter">完了</label>
    <p>ID コメント 状態</p>
    <table>
      <tr v-for="todo in filteredTodos">
        <td>{{ todo.id }}</td>
        <td>{{ todo.title }}</td>
        <td><button @click="statusChange(todo.id)">
            <span>{{ todo.statusBtn }}</span>
          </button></td>
        <td><button @click="deleteTask(todo.id)">{{ todo.delBtn }}</button></td>
      </tr>
    </table>
    <h2>新規タスクの追加</h2>
    <form @submit.prevent="addTask">
      <input type="text" v-model="newTask">
      <input type="submit" value="追加" class="button">
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    'use strict';

    new Vue({
      el: '#app',
      data: {
        id: 0, statusBtn: '', newTask: '', todos: [], filter: 'all'
      },
      computed: {
        filteredTodos() {
          if (this.filter === 'all') {
            return this.todos
          } else if (this.filter === 'completed') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '完了'
            })
          } else if (this.filter === 'active') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '作業中'
            })
          }
        }
      },
      methods: {
        addTask: function () {
          let item = {
            id: this.id++, title: this.newTask, statusBtn: '作業中', delBtn: '削除'
          }
          this.todos.push(item);
          this.newTask = '';
        },
        statusChange: function (id) {
          for (let todo of this.todos) {
            if (todo.id === id) {
              if (todo.statusBtn === '作業中') {
                todo.statusBtn = '完了'
              } else {
                todo.statusBtn = '作業中'
              }
              break
            }
          }
        },
        deleteTask: function (id) {
          for (let todo of this.todos) {
            if (todo.id === id) {
              todo.splice(index, 1);
              break
            }
          }
        }
      }
    });
  </script>
</body>

</html>

diff
イメージ説明

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/24 05:58

    回答ありがとうございました。
    v-forのindexとtodosのオブジェクトの関連が問題だと指摘いただき、ありがとうございました。
    サンプルコードも参考にさせていただきました。
    しかし、削除ボタンが上手く作動させれなくなったの追記させていただきました。。

    キャンセル

check解決した方法

0

削除ボタンの実装ですが、
こちらのサイトを参考に修正できました。
STEP10 状態の変更と削除の処理
まだ、連番の動きが期待通りになっていませんが、今回の質問の内容は解決しましたので、解決済とし、
新たに質問を挙げさせていただきます。
回答ありがとうございました。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>ToDo List Vue-version</title>

  <link rel="stylesheet" href="css/styles.css">
</head>

<body>
  <h2>ToDoリスト</h2>
  <div id="app">
    <label><input type="radio" name="list" value="all" v-model="filter">すべて</label>
    <label><input type="radio" name="list" value="active" v-model="filter">作業中</label>
    <label><input type="radio" name="list" value="completed" v-model="filter">完了</label>
    <p>ID コメント 状態</p>

    <table>
      <tr v-for="(todo in filteredTodos">
        <td>{{ todo.id }}</td>
        <td>{{ todo.title }}</td>
        <td><button @click="statusChange(todo.id)">{{ todo.statusBtn }}</button></td>
        <td><button @click="deleteTask(todo.id)">{{ todo.delBtn }}</button></td>
      </tr>
    </table>
    <h2>新規タスクの追加</h2>
    <form @submit.prevent="addTask">
      <input type="text" v-model="newTask">
      <input type="submit" value="追加" class="button">
    </form>
  </div>


  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    'use strict';

    new Vue({
      el: '#app',
      data: {
        id: 0, statusBtn: '', newTask: '', todos: [], filter: 'all'
      },
      computed: {
        filteredTodos() {
          if (this.filter === 'all') {
            return this.todos
          } else if (this.filter === 'completed') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '完了'
            })
          } else if (this.filter === 'active') {
            return this.todos.filter((todo) => {
              return todo.statusBtn === '作業中'
            })
          }
        }
      },
      methods: {
        addTask: function () {
          let item = {
            id: this.id++, title: this.newTask, statusBtn: '作業中', delBtn: '削除'
          }
          this.todos.push(item);
          this.newTask = '';
        },
        statusChange: function (id) {
          for (let todo of this.todos) {
            if (todo.id === id) {
              if (todo.statusBtn === '作業中') {
                return todo.statusBtn = '完了'
              } else {
                return todo.statusBtn = '作業中'
              }
              break
            }
          }
        },
        deleteTask: function (smt) {
          let index = this.todos.indexOf(smt)
          this.todos.splice(index, 1);
        }
      }
    });
  </script>
</body>

</html>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.93%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る