Handling user input with Vue

Handling user input using the v-model and v-on directives

In the last post we looked at rendering array data in HTML with the use of the v-for directive. We store an array of objects, called items, in the application state:

const vueApp = Vue.createApp({
  data() {
    return {
      items: [
        {
          id: 1,
          label: "Pack of eggs",
        },
        {
          id: 2,
          label: "Bread",
        },
        {
          id: 3,
          label: "Milk",
        },
        {
          id: 4,
          label: "Cereal",
        },
      ],
    };
  },
}).mount("#vueApp");

We then render an <li> for each item in items with the value of label in a paragraph tag:

<ul>
  <li v-for="item in items" :key="item.id">
    <p>{{ item.label }}</p>
  </li>
</ul>

Adding items to the list

Lets update the HTML to allow a user to add items to the list:

We'll want the user to be able to type the name in to an input field. We'll add a v-model attribute to tell Vue to create a two-way bind between the input and the app's state, with a key called newItem. On page load, Vue will automatically populate the input with any data stored in this key. Vue will also update the state in the app when the user enters text in to the input.

We can see this two-way binding in action by adding {{ newItem }} to the HTML below the input, or if you open the dev-tools and watch the state update. We saw this on the 1st post

Next we need to add a button for the user to click so the item is added to the array of items. We'll use Vue's v-on directive that adds a click event listener. This is similar to setting up a element.addEventListener() in JavaScript.

<input type="text" v-model="newItem" />

<button v-on:click="addNewItemToList">Add item</button>

<ul>
  <li v-for="item in items" :key="item.id">
    <p>{{ item.label }}</p>
  </li>
</ul>

The method we call with the v-on:click directive needs to be added to our Vue code. We have to create a methods: {} object, under our data() function, in our Vue code:

const vueApp = Vue.createApp({
  data () {
    return {
      items: [
        {
          id: 1,
          label: 'Pack of eggs'
        },
        newItem: '',
        ...
      ]
    }
  },

  methods: {
    addNewItemToList () {

    }
  }
}).mount('#vueApp')

Functions in the methods object can be called from within the HTML using directives or from within the Vue app code. To call the function from within the Vue app itself, you would need to use this.addNewItemToList().

We write regular JavaScript within these functions. We're simply going to check if there's any text in newItem and then push the value into the array of items with a random id. We'll then reset the value of newItem to an empty string, so the user can write their next item without having to delete their last entry:

...
  methods: {
    addNewItemToList () {
      if (!this.newItem.length) return

      this.items.push({
        id: Math.floor(Math.random() * (100 - 1) + 1),
        label: this.newItem
      })
      // set newItem back to an empty string, clearing the input for the next item
      this.newItem = ''
    }
  }
...

I've also added a directive to the input to call the addNewItemToList method. This listens out for a keydown event with a modifier called .enter so it only fires when a user presses the enter key:

<input v-model="newItem" type="text" aria-label="Type a new item to add to your list" class="text-black p-2" placeholder="Next item..." @keydown.enter="addNewItemToList" />

We can also use Vue to bind the state of newItem to a disabled properpty on the <button> We use the :disabled="!newItem.length" below to check if there's no length to the newItem string. This then enables the disabled attribute on the button, and then removes the attribute when newItem has a length greater than 0:

<button class="py-2 px-4 border-r border-none bg-green-500" :disabled="!newItem.length" @click="addNewItemToList">Add item</button>

We style the disabled button state with the following:

button[disabled] {
  cursor: not-allowed;
  opacity: 0.5;
}

Demo

A demo of what we've created is below:

  • Pack of eggs

  • Bread

  • Milk

  • Cereal

The next post will look at how methods: {} can be used to fetch data and populate our list with data from an API.