VueJs Directives: Form Binding and Event Handling

Created on April 6, 2022.


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 in which 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 webpages. The same couldn’t be said of media such as magazines or TV ads.

The v-model Directive

Vue 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 use of the v-model.

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

  <script src="https://unpkg.com/[email protected]"></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 above inside our Vue instance will also update the form field. Add the following code to the JavaScript above to see the changes.

Append code to previous example's JavaScript!
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 instance and vice-versa.

Binding Form Text Input

Binding form text is as straightforward as demonstrated in the above example, you need to initate 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.

Append code to previous example's HTML!
<textarea v-model="longMsg"></textarea>
  <br>
  {{ longMsg }}

 

Append code to previous example's JavaScript!
(ex 1)
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.

Append code to previous example's JavaScript!
<input type="number" v-model="someNumber">
  <br>
  {{ someNumber }}

 

Append code to previous example's JavaScript!
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.

Append code to previous example's HTML!
<select id="age-select" v-model="selectedAge">
    <option v-for="item in options" :value="item.key">{{ item.label }}</option>
  </select>
  <br>
{{ selectedAge }}

 

Append code to previous example's JavaScript!
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 beloning to the same query.

Append code to previous example's 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 }}

 

Append code to previous example's JavaScript!
return {
  isANoob: "Yes I am",
}

It’s good practice to initiate radio fields 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.

Append code to previous example's 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 }}

 

Append code to previous example's JavaScript!
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, just as with most front-end JavaScript environments, we do not favor the page refreshes as those would certainly mess up our app states.

Let’s demonstrate this in an example.

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

  <script src="https://unpkg.com/[email protected]"></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, 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 VueJs templates, we would have the regular event attributes only replacing the on in the beginning with v-on:.

Let’s see an example.

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

  <script src="https://unpkg.com/[email protected]"></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.

Append code to previous example's template!
<input type="text" v-model="text" v-on:blur="text = text + ' a'">

 

Append code to previous example's JavaScript!
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 like demonstrated in the above examples.

The v-on shorthand

Just like Vue provides the colon : as the shorthand for the v-bind directive, it also provides “at” sign - @ as the shorthand for the v-on directive. So instead of using the long v-on on the above examples, we can shorten the code by using the @, ending up with.

Append code to previous code block
<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, and next, we are going to see how we can call functions with it. Let’s first see how to initiate functions in Vue.

Methods in Vue

Methods or functions in Vue are initiated inside the “methods” option of the Vue 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 out template as follows.

Practice this code!
<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="https://unpkg.com/[email protected]"></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.

Note

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

Inside Vue 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 it’s 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.

Practice this code!
<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="https://unpkg.com/[email protected]"></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 all of 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.

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, 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.

Next

VueJs Watchers: Responding to data changes in Vue

Previous

VueJs Directives: Class and Style Binding

Subscribe to the VueNoob Newsletter

[[[ noty.message ]]]

By using this site, you agree that you have read and understand its Privacy Policy.