Vue.js directives: Form binding and event handling
Tags: vue, javascript, beginners, webdev
Published: 2022-12-22 17:54:01

---
title: Vue.js directives: Form binding and event handling
published: true
description: Learning how to handle HTML forms by binding form field data and invoking subsequent event calls using Vue.js directives and methods.
tags: vue, javascript, beginners, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/la23fl7j7tjx89zqj747.png
series: A beginner's guide to Vue.js
canonical_url: https://vuenoob.com/posts/vuejs-directives-form-binding-and-event-handling/
# published_at: 2022-12-22 10:04 +0000
---

One of the most important activities in web applications is form interaction. We use forms for user authentication, providing feedback in the form of comments and answering online questionnaires, rating things such as books or contributing to polls, filling in address information on e-commerce websites, writing e-mails, and the list goes on.

Such user interaction in web apps which to a large extent involves forms proves their superiority since they possess features beyond presentational capabilities where data flows in one direction only. Businesses would get responsive feedback from their customers or even go as far as communicating with their customers when using chat tools embedded on customer-care web pages. The same couldn't be said of media such as magazines or TV ads.

## The _v-model_ directive

Vue.js enables us to manipulate form data via the special directive _v-model_. The _v-model_ is different from the _v-bind_ directive since it supports two-way data binding, meaning, data can be updated in both the presentational part, in this case, the template form fields, and the controller part of the app instance.

Let's see an example using the _v-model_ directive.

```html
<html>
  <div id="app">
    <h2>Login Form</h2>
    <form>
      <input type="text" v-model="msg">
      <br>
      {{ msg }}
    </form>
  </div>

  <script src="/js/vue.global.js"></script>
  <script>
    let app = Vue.createApp({
      data() {
        return {
          msg: "Hello, World!"
        }
      }
    }).mount("#app");
  </script>
</html>
```

Try to edit the message inside the input field in the above example to see it getting updated on the mustache output.

Trying to update the **msg** field inside our Vue.js instance above will also update the form field. Add the following code to the JavaScript above to see the changes.

```js
mounted(){
  let addRandomText = () => {
    this.msg = this.msg + " other text!"
  }
  setTimeout(addRandomText, 3000);
}
```

So, not only can data be updated from the two parts of our app, the variable also stays in sync in both parts, i.e. changes to the variable from the template are reflected by it inside the Vue.js instance and vice-versa.

### Binding form text input

Binding form text is as straightforward as demonstrated in the above example, you need to initiate the variable inside the data property and bind it to the _v-model_ directive on the input element.

Let's see another example using the _textarea_ form field.

```html
  <textarea v-model="longMsg"></textarea>
  <br>
  {{ longMsg }}
```

```js
return {
  msg: "Hello, World!",
  longMsg: "This is a very loooooooooooooooooooooooooooooooooooooooooooooooooooooooong paragraph."
}
```

Add the above code to the respective template and JavaScript parts of the previous example.

### Binding form number input

To bind numbers in form input fields you need to set the _type_ to "number" and append _.number_  to the _v-model_ directive, making sure you initiate the number variable inside the data property with a variable of the _Number_ type.

Let's see a number input example.

```html
  <input type="number" v-model="someNumber">
  <br>
  {{ someNumber }}
{{ longMsg }}
```

```js
return {
  someNumber: 0,
}
```

### Binding form select fields

With _select_ form fields, add the _v-model_ directive with the data property variable in the _select_ field. When an option is selected from the _select_ field, the _String_ or _Number_ set on the _value_ attribute of the option element is assigned to the variable, otherwise, the label is selected.

```html
<select id="age-select" v-model="selectedAge">
  <option v-for="item in options" :value="item.key">{{ item.label }}</option>
</select>
<br>
{{ selectedAge }}
```

```js
return {
    options: [{key: 12, label: '12yrs'}, {key: 14, label: '14 yrs'}],
    selectedAge: 0
}
```

### Binding form input radio fields

With form radio inputs, you set the _values_ of the dial input fields to the exact variable you want the bound data property to be set to when a radio choice is selected. You should bind the same data property to all of the radio fields belonging to the same query.

```html
<label for="dials">Are you a Vue Noob?</label>
<label>
  <input type="radio" id="dials" v-model="isANoob" value="Yes I am"> Yes
</label>
<label>
  <input type="radio" id="dials" v-model="isANoob" value="Not I'm not"> No
</label>
<br>
{{ isANoob }}
```

```js
return {
  isANoob: "Yes I am",
}
```

It's good practice to initiate radio field data variables with a default choice so as not to receive undesired data submissions.

### Binding form checkboxes

Since checkbox checks resolve to a true when checked and false when unchecked, unlike the dial input, all check options should be assigned to separate variables.

Here's a checkbox example.

```html
<label for="chk-one">What Web technologies are you familiar with?</label>
<label>
    <input type="checkbox" v-model="chkOne"> HTML
</label>
<label>
    <input type="checkbox" v-model="chkTwo"> JavaScript
</label>
<label>
    <input type="checkbox" v-model="chkThree"> CSS
</label>
<br>
HTML: {{ chkOne }} JavaScript: {{ chkTwo }} CSS: {{ chkThree }}
```

```js
return {
  chkOne: true,
  chkTwo: false,
  chkThree: false
}
```

While dealing with HTML forms, when users have completed filling in the required information we usually expect some action to be performed to proceed with the next desired steps. Normally when HTML forms are submitted via an input field with the _type="submit"_ or a button click, the page sends the form data to the URL provided in the _action_ attribute using the HTTP method provided in the _method_ form attribute followed by a page reload or redirection to a URL, all depending on the response it receives from the back-end in question.

In Vue.js, just as with most front-end JavaScript environments, we do not favour the page refreshes as those would certainly mess up our app states.

Let's demonstrate this in an example.

```html
<html>
  <div id="app">
    <form>
      <input type="text" v-model="name">
      <br>
      <button type="submit">Submit</button>
    </form>
  </div>

  <script src="/js/vue.global.js"></script>
  <script>
    let app = Vue.createApp({
      data() {
        return {
          name: "Noob",
        }
      }
    }).mount("#app");
  </script>
</html>
```

Clicking "Submit" in the above example refreshes the page, and we lose the number that we had before after the title - "Cool Form".

In JavaScript front-end frameworks we are usually submitting form data to remote back-ends through API endpoints, then carry on with the next steps depending on the responses we receive without refreshing the page, keeping the rest of the app state intact.

## Event handling in Vue

To carry out form submissions, we need to listen to button click events and call some functions that process and submit this data to where it needs to go.

In Vue.js, event listening on HTML elements is performed through the _v-on_ directive. Just as in the case with the _v-bind_ directive, we add the _v-on_ directive as a prefix to element events separated by a colon, removing the "on" usually found on elements' event attributes, meaning, when a button `onclick` attribute fires in response to a button click event, we perform a method call - `onclick="function()"`. In Vue.js templates, we would have the regular event attributes only replacing the `on` in the beginning with `v-on:`.

Let's see an example.

```html
<html>
  <div id="app">
    <h2>Count: {{ num }}</h2>
    <button v-on:click="num++">Increment</button>
  </div>

  <script src="/js/vue.global.js"></script>
  <script>
    let app = Vue.createApp({
      data() {
        return {
          num: 0,
        }
      }
    }).mount("#app");
  </script>
</html>
```

In the above example, we increment the value of **num** by one each time the button is clicked, we do that by resolving the JavaScript expression `num++` inside `v-on:click` .

Let's see another example.

```html
<input type="text" v-model="text" v-on:blur="text = text + ' a'">
```

```js
data() {
  return {
    text: 'Hello!'
  }
}
```

Try clicking in and out of the above input field to see what happens.

In both cases, when the click and blur events are fired, the JavaScript expressions are resolved.

Unlike the mustache syntax which is used for presentational purposes, the _v-on_ directive can run JavaScript expressions and update our data properties values as demonstrated in the above examples.

### The _v-on_ shorthand

Just like Vue.js provides the colon `:` as the shorthand for the _v-bind_ directive, it also provides an "at" sign - `@` as the shorthand for the _v-on_ directive. So, instead of using the long `v-on` in the above examples, we can shorten the code by using the `@`, ending up with.

```html
<button @click="num++">Increment</button>

<input type="text" v-model="text" @blur="text = text + ' a'">
```

We will be using the shorthand version of the _v-on_ directive here-onwards.

We have already seen how we can execute JavaScript expressions inside the _v-on_ directive, next, we are going to see how we can call functions with it. Let's first see how to initiate functions in Vue.js.

## Methods in Vue

Methods or functions in Vue.js are initiated inside the "methods" option of the Vue.js instance. Just as we've seen with the data properties, we add "methods" directly as a property of the _Object_ passed inside _createApp_() with all our functions as the direct properties of this "method" _Object_.

Moving the in-line expressions in the previous example into individual methods, we can then proceed to make function calls reducing the logic within our template as follows.

```html
<html>
  <div id="app">
    <h2>Count: {{ num }}</h2>
    <button @click="increment()">Increment</button>
    <hr>
    <input type="text" v-model="text" @blur="appendText()">
    <hr>
    num + length of text: {{ numPlusText }}
  </div>

  <script src="/js/vue.global.js"></script>
  <script>
    let app = Vue.createApp({
      data() {
        return {
          num: 0,
          text: 'Hello!'
        }
      },
      computed: {
        numPlusText(){
          return this.num + this.text.length;
          console.log(this.numPlusText);
        }
      },
      methods: {
        increment(val){
          this.num++
        },
        appendText(){
          this.text = this.text + ' a';
          console.log(this.numPlusText);
        }
      }
    }).mount("#app");
  </script>
</html>
```

In contrast to computed properties, methods do not cache their results, updates can be made to variables within them, and even DOM mutating operations can be performed within them.

**N.B.,** Each time a method is invoked, all of the operations inside it are performed. Due to this, it is a good practice to perform expensive operations that don't involve non-reactive dependencies such as `Date.now()` and `Math.random()` inside computed properties.

Inside Vue.js methods, we can access all the _data_ and _computed_ properties inside `this` as demonstrated above.

Open your browser's console log to see the computed property value **numPlusText** being logged out whenever the two functions above are called.

## Completing form operations

Bringing together all of what we have learned in this post let's set up a login form, and submit its data when a button click attribute event is fired, calling a specific login method that validates our form data before sending it to the external API endpoint.

```html
<html>
  <div id="app">
    <div v-if="!loggedIn">
      <h2>User Login</h2>
      <br>
      <form action="">
        <p v-show="validationError">Error: {{ validationError }}</p>
        <label for="email">Email: </label>
        <input type="email" id="email" v-model="email" required>
        <br>
        <br>
        <label for="password">Password: </label>
        <input type="text" id="password" v-model="password" required>
        <br>
        <br>
        <button @click.prevent="logIn()">{{ loading ? 'loading..' : 'Log In' }}</button>
      </form>
    </div>

    <div v-else>
      <h2>Use Dashboard</h2>
      <p>Successfully Logged In!</p>
    </div>
  </div>

  <script src="/js/vue.global.js"></script>
  <script>
    let app = Vue.createApp({
      data() {
        return {
          email: "",
          password: "",
          loggedIn: false,
          validationError: "",
          loading: false
        }
      },
      methods: {
        logIn(){
          this.validationError = "";
          let errorsCount = 0;
          if(this.password.length < 4){
            this.validationError = "Password is not valid";
            errorsCount++;
          }
          const simulateAuthentication = () => {
            this.loading = false;
            this.loggedIn = true;
          }
          if(errorsCount === 0){
            this.loading = true;
            setTimeout(simulateAuthentication, 3500);
          }
        }
      }
    }).mount("#app");
  </script>
</html>
```

When you submit the above form after filling in the required fields correctly, you will experience a page refresh and lose the app state that helps our app run correctly. This is the default HTML form behavior as explained above, it is not "a bug" in Vue.js.

Usually, when intending to make JavaScript HTTP requests instead of using the form element default behavior to perform form data submissions, we prevent it by calling _**preventDefault()**_ of the Event interface inside our control functions. To prevent the default form behavior in the case of Vue.js, we append the suffix `.prevent` at the end of our event listener `@click`, ending up with `@click.prevent="logIn()"`.

Perform this change to the above code to fix the reload issue.

In the example above, we are simulating the login process with a timer function, in real life applications we use HTTP clients to make these calls and process all of the possible responses that we might receive from the remote back-ends.