Make Items Editable in Vue.js using V-if & V-Model Directives

In this article, I will be extending my To Doodle app with the v-model and v-if directives to allow editing of tasks and more.

If you haven't read my other article on creating the basic To Doodle app functionality then you may want to read that but it is not necessary to understand this.

Using v-model to Update the Data in Real-time

If you add v-model to form elements you create a 2-way binding meaning you get real-time updating. Let's see how it works.

In our v-for li items add a form input with the v-model directive and add the property you want to bind. I this case our data property is task.todo.

<li v-for="(task, index) in tasks" v-bind:class="{completed:task.selected}">
  {{ task.todo }} ( {{ index }} {{ task.selected }})  
  <input v-model="task.todo">

Check the following example, now when entering a new task not only does a task get added to the list, there is also a form input element where you can edit the task, in real-time, awesome.

See the Pen Vue.js Todo App with V-model input by Simon (@simonramsay) on CodePen.

As cool as this is, it is not really that user-friendly so we will hide it and then make it appear when we want to edit it. To do this we can use v-if.

The Vue.js v-if conditional

Who doesn't like good conditional to display or hide content?

This directive is one of my favourites, it is so clean, useful, and easy to use. You can display and hide elements by checking the value of a data property.

Add v-if to hide and show the input

We have an item in a list and you can check it off, delete it, and also update it. All of this was effortless as illustrated in the example. You can check how this was done in the first article here.

The task from the tasks array is also easily be bound to the input now added below the task and you can edit the task and the task will update in real-time using v-model.

This was nice but not really what I had envisioned, I was thinking more similar to clicking on the task list item and being able to edit the task. So with that in mind, we will wrap our input in a div with a v-if directive to hide and show the input.

First, wrap the input in a div and add the v-if="task.edit", as we haven't created a task edit property it will be false and thus the element won't show.

<div v-if="task.edit">
   <input v-model="task.todo">

We could add the property when we create the task by modifying the addTask() but I don't think it would really matter. Any JS or Vue.js gurus please chime in if it would affect performance.

With that done the input is hidden, to make it appear we can use the li item and make it click-able and set the task.edit value to true using the below code. To get more detail on how the $set() method works check the $set method article in this mini-series for Vue.js.

 <li v-for="(task, index) in tasks" v-bind:class="{completed:task.selected}">
+   <span v-on:click="$set(task, 'edit', !task.edit)">
       {{ task.todo }} (index: {{ index }})
+   </span>
  /* completed & remove bottons*/

Note how I added the $set() method to a span that is wrapping the {{}} values. This is done so that the click only happens on the task and not the li item, we also have completed and remove buttons, and an input in the li items so you will get some weird functionality if we don't do that.

If you now set the value back to false on input enter key up the v-if div will hide again. Nice.

+        v-on:keyup.enter="task.edit = false"

A few things to note and think about are:

  1. You can you click on the li item to make editable and then enter to save and close.
  2. Is having a save button instead of on enter key-up better?
  3. A save button will allow better control and formatting of your messages as you could then use enter for a new line instead of saving/updating.

You could build a global toggle to give the user the option to use enter for save/send. I personally prefer using a save/send button and not saving on enter. A toggle in the settings is a more advanced feature which we could look at later.

So with that let's make a Save button.

Adding an Edit and Save button

Since we will have a save button let's also add an edit button. We will put the edit/save button next to our other buttons.

position of the new edit button in the Vue.js app

Add a button with the same $set() method we use on our task above and then add two spans one with v-if set to false for the Edit label and add the Save label with a v-if set to true.

<button v-on:click="$set(task, 'edit', !task.edit)">
  <span v-if ="!task.edit">Edit</span>
  <span v-if="task.edit">Save</span>

Now we can click on the li item or edit button to edit the item.

We will make one small tweak here to give a more natural user experience or UX.

Hide the task while editing

Did you know that you are better to think about that and solve it before checking below, I think you will be able to do it easily with the knowledge gained by reading thus far. If you need a hint, go to the next line.

That's right, add a negated v-if directive on the task li item span like this.

-  <span v-on:click="$set(task, 'edit', !task.edit)">
+  <span v-if="!task.edit" v-on:click="$set(task, 'edit', !task.edit)">

Changing the Form Input to a Text area

Now that we have a save button we can remove the on key up and change the Input to a text area, this will allow multi-line tasks. We will leave the onclick task li item's span for edit as this is a nice feature.

Now when we add a task we can use the enter button to start a new line which is awesome. However, when you save the task the line breaks don't render. They are saved, click edit and you will see the multi-line note still exists. To fix this we can use the CSS white-space property with the pre-wrap or wrap values.

Enforce line-break using the White-space: pre-warp CSS property: value pair

Add this to the CSS for the li element and now the line-breaks will magically be rendered.

white-space: pre-wrap;
white-space: wrap;

Only Show top line with ellipse

If on saving you only want to show a few lines with an ellipse you can use ellipse like so.

.task {
  white-space: pre-wrap;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;

For more on line-clamp property, you can check my Add an Ellipse to Truncated Text with CSS: Single-line and Multi-line Options tip.

Following is To Doodle App, check out the code and reach out if you don't understand anything!


See the Pen Vue.js Todo App Multi-line Text Areas by Simon (@simonramsay) on CodePen.


Well, that's about it for this issue of making web dev simplified. Hope you learnt something. I will continue with this basic To Doodle app, adding filtering and some nice mobile features. Then I will be using what we have learnt to build out a shopping app I prototyped a few years back while studying UX at Michigan University Online. Sign up to get first access to this, a mini CSS course, and more awesome content.