2

I have a todo list in vue and I'm using pop() to clear out/delete list items. See the code in question below:

// components
Vue.component('todoitem', {
  template: "<li>Test Item</li>"
})

// app code
var app = new Vue({
  el: '#app',
  data: {
    todos: [
      { text: 'Sample Item 1' },
      { text: 'Sample Item 2' },
      { text: 'Sample Item 3' }
    ],
    button: {
      text: 'Hide'
    },
    seen: true
  },
  methods: {
    addItem: function() {
      let item = document.getElementById("list-input").value;
      let error = document.getElementById("error");
      if (item == "") {
        error.style.display = "block";
      } else {
        app.todos.push({ text: item });
        error.style.display = "none";
      }
    },
    removeItem: function() {
    	this.todos.pop();
    },
    toggleSeen: function() {
      app.seen = !app.seen;
      app.button.text = app.seen ? 'Hide' : 'Show';
    }
  }
});
.todo-list {
  list-style-type: square;
}

.todo-list__delete {
  display: none;
}

li:hover .todo-list__delete {
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>

<div id="app">
  <ul class="todo-list">
    <li v-for="todo in todos">
      {{ todo.text }}
      <a v-on:click="removeItem" class="todo-list__delete" href="#">Delete</a>
    </li>
  </ul>

  <input type="text" id="list-input">
  <input type="submit" id="list-submit" v-on:click="addItem">
  <span id="error" style="color: red; display: none;">Please Enter Text</span>

  <ul>
    <todoitem></todoitem>
  </ul>

  <h2 v-if="seen">SEEN</h2>
  <button id="hide-seen" v-on:click="toggleSeen">{{ button.text }}</button>
</div>

The expected behavior is that when delete is clicked, it invokes the removeItem function and with the usage of this in that function it deletes the selected item. However what actually happens is it just deletes nodes starting from the bottom.

I thought the issue is that with this I'm actually referencing the delete link and not actually the <li> element I'm trying to remove. So I tried both:

removeItem: function() {
    this.todos.parentElement.pop();
}

And:

removeItem: function() {
    this.parentElement.todos.pop();
}

With no luck.

How does this work in Vue?

2
  • You might want to review what the pop function actually does. I think you will find the explanation for your behavior. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Commented Oct 27, 2017 at 17:00
  • Ok. I was just looking for the opposite of push() but that makes sense. Commented Oct 27, 2017 at 17:05

1 Answer 1

0

In that context, this refers to the Vue component (not the DOM element). this.todos is the todos array inside the component's data object, and pop removes the last item of an array. So that's why the last element is being removed.

If you want to remove a specific element you'll need to pass some information to the removeItem function about which element you want removed and then have removeItem() drop that specific element from the todos array instead of popping the last element. One simple way to do this would be to pass the array index to the removeItem function, and then splice that index out of the todos array:

<li v-for="(todo, index) in todos">
  ...
  <a v-on:click="removeItem(index)">Delete</a>
</li>
removeItem: function(index) {
  this.todos.splice(index, 1);
},

Your full snippet with that change applied is below:

// components
Vue.component('todoitem', {
  template: "<li>Test Item</li>"
})

// app code
var app = new Vue({
  el: '#app',
  data: {
    todos: [
      { text: 'Sample Item 1' },
      { text: 'Sample Item 2' },
      { text: 'Sample Item 3' }
    ],
    button: {
      text: 'Hide'
    },
    seen: true
  },
  methods: {
    addItem: function() {
      let item = document.getElementById("list-input").value;
      let error = document.getElementById("error");
      if (item == "") {
        error.style.display = "block";
      } else {
        app.todos.push({ text: item });
        error.style.display = "none";
      }
    },
    removeItem: function(index) {
      this.todos.splice(index, 1);
    },
    toggleSeen: function() {
      app.seen = !app.seen;
      app.button.text = app.seen ? 'Hide' : 'Show';
    }
  }
});
.todo-list {
  list-style-type: square;
}

.todo-list__delete {
  display: none;
}

li:hover .todo-list__delete {
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>

<div id="app">
  <ul class="todo-list">
    <li v-for="(todo, index) in todos">
      {{ todo.text }}
      <a v-on:click="removeItem(index)" class="todo-list__delete" href="#">Delete</a>
    </li>
  </ul>

  <input type="text" id="list-input">
  <input type="submit" id="list-submit" v-on:click="addItem">
  <span id="error" style="color: red; display: none;">Please Enter Text</span>

  <ul>
    <todoitem></todoitem>
  </ul>

  <h2 v-if="seen">SEEN</h2>
  <button id="hide-seen" v-on:click="toggleSeen">{{ button.text }}</button>
</div>

Sign up to request clarification or add additional context in comments.

1 Comment

My original method for choosing which item to delete was problematic (it'd delete all items with the same text.) I changed it to use the array index instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.