Forms & Data Binding

38. Forms Intro & Setup

We’ll look have Vue stores data that is stored in forms.

Create a new vue project.

$ vue create <project-name>

Delete the HelloWorld component and the references to it in App.vue.

Add style in App.vue.

body {
  margin: 0;
  background: #eee;
}

Create new component in a file named SignupForm.vue.

Load the boiler-plate component code to the new component by typing vue in the editor.

Create a simple form in the template element.

<template>
   <form>
    <label>Email:</label>
    <input type="email" required>

    <label>Password:</label>
    <input type="password" required>
  </form>
</template>

Stylize the form by adding the following styles.

<style>
  form {
    max-width: 420px;
    margin: 30px auto;
    background: white;
    text-align: left;
    padding: 40px;
    border-radius: 10px;
  }
  label {
    color: #aaa;
    display: inline-block;
    margin: 25px 0 15px;
    font-size: 0.6em;
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: bold;
  }
  input, select {
    display: block;
    padding: 10px 6px;
    width: 100%;
    box-sizing: border-box;
    border: none;
    border-bottom: 1px solid #ddd;
    color: #555;
  }
</style>

Back in App.vue, display the SignupForm component by  importing the component in the script element and adding the component to the template element.

<template>
  <SignupForm />
</template>
<script>
import SignupForm from './components/SignupForm.vue'

export default {
  name: 'App',
  components: {
    SignupForm
  }
}
</script>

Now, start the server and test the app.

$npm run serve

39. Two-way Data Binding

In the last section we saw that we can pass data from a child component back to the parent component by having the child emit a custom event and pass data via the event.  The parent component can register a handler for the event and when the event is emitted, retrieve the data from the event.

The easiest way to pass information the user enters into a form or chooses from a dropdown menu in a child component to the parent component is to use the v-model directive.  This gives us 2-way data binding.

In the exported data() property in the SignupForm component we define properties that we want to accessible in the App component.

export default {
  data() {
    return {
      email: '',
      password: ''
    }
  }
}

We then use the v-model directive in the input element to map the data typed into the input element to the email property in the exported data() object.

<input type="email" v-model="email" required>
...
<input type="password" v-model="password" required>

To see that the information typed into the inputs is bound to the data object, we can display the email and password properties of the data object in the template.

<p>email: {{ email }} </p>
<p>password: {{ password }} </p>

40. Select Fields

We can use v-model directive for other form fields like select boxes. Let’s first add a property named role to the data object.  This will hold the value that the user selects.

data() {
  return {
    email: '',
    password: '',
    role: ''   
  }
}

Now create a select element and bind the user’s selection to the role property in the data object using the v-model directive.

<select v-model="role">
  <option value="designer">Web Designer</option>
  <option value="developer">Web Developer</option>
</select>

We can include a default choice in the select element by defining the default value in the data object.

data() { 
  return { 
    email: '', 
    password: '', 
    role: 'developer' 
  } 
}

41. Checkboxes

We can also use v-model with checkboxes. In the first example below we create a terms and condition section in our form with a checkbox to indicate that they accept the terms and conditions.

In the data object, create a boolean property (terms) for the checkbox and initialize it to false.

data() {
  return {
    email: '',
    password: '',
    role: '',
    terms: false
  }
}

Then create a terms and condition section for our form and bind the value of the checkbox (true or false) to the terms property in the data object.

<form>
  ...
  <div class="terms">
    <input type="checkbox" v-model="terms" required>
    <label>Accept terms and conditions</label>
  </div>
</form>

And add some styling.

input[type="checkbox"] {
  display: inline-block;
  width: 16px;
  margin: 0 10px 0 0;
  position: relative;
  top: 2px;
}

In this next example we show how to store an array of strings that are associated with a list of checkboxes.  To hold the strings we’ll create an array property in the data object.

data() {
  return {
    ...
    terms: false,
    names: []
  }
}

Then we can create the list of checkboxes.  If the user selects a checkbox, the string that is in the value attribute of the checkbox is stored in the names array.

<div>
  <input type="checkbox" value="one" v-model="names">
  <label>one</label>
  <input type="checkbox" value="two" v-model="names">
  <label>two</label>
  <input type="checkbox" value="three" v-model="names">
  <label>three</label>
</div>

42. Keyboard Events & Modifiers

In this section we’ll create an input field to allow the user to enter their skills.  We’ll then save the skills in an array.

We’ll first add two new data properties.

data() {
  return {
    ...
    tempSkill: "",
    skills: [],
  };
},

Now we’ll create the html which displays an input field and a save button, followed by a list of “pills” that are in the skills array. Each pill contains the skill string and a delete icon.

<label>Skills</label>
<div>
  <input id='skillInput' type="text" v-model="tempSkill" @keyup="addSkill"/><button @click="saveSkill">save</button>
</div>
<div v-for="skill in skills" :key="skill" class="pill">
  <span>
    {{ skill }}
    <i class="bi bi-x-square" @click='deleteSkill(skill)'></i>
  </span>
</div>

Next, we’ll add three methods.

methods: {
  addSkill(e) {
    if (e.key === ',' && this.tempSkill) {
      this.tempSkill = this.tempSkill.substring(0,this.tempSkill.length-1)
      this.saveSkill(e)
    }
  },
  saveSkill(e) {
    e.preventDefault()
    if (this.tempSkill && !this.skills.includes(this.tempSkill)) {
      this.skills.push(this.tempSkill)
    }
    this.tempSkill = ''
    document.querySelector("#skillInput").focus()
  },
  deleteSkill(skill) {
    this.skills = this.skills.filter((value) => {
      return value !== skill
    })
  }
}

Last, we stylize the elements.

input[type='text'] {
  display: inline-block;
  width: 80%;
}
.pill {
  border-radius: 10px;
  color: #aaa;
  padding: 1px 5px;
  margin: 2px;
  display: inline-block;
}

43. Challenge – Deleting Skills

 

44. Submitting the Form

In this section we’ll look at how to perform input validation and submit the data in the form.

We’ll first create the submit button in the form.

<form @submit.prevent="submitForm">
  ...
  <div class="submit">
    <button>Create an account</button>
  </div>
</form>

We’ll also create a new data property to hold a validation error.

data() {
  return {
    ...
    passwordError: ""
  };
},

Now we’ll create the handler method.

submitForm() {
  //validate password
  this.passwordError = this.password.length > 5 ? '' : "password must be at least 6 chars long"

  if(!this.passwordError) {
    console.log('email: ', this.email)
    console.log('password: ', this.password)
    console.log('role: ', this.role)
    console.log('skills: ', this.skills)
    console.log('terms accepted: ', this.terms)
  }
}

Last, we’ll stylize the html.

.submit > button {
  background: #0b6dff;
  border: 0;
  padding: 10px 20px;
  margin-top: 20px;
  color: white;
  border-radius: 20px;
}
.submit {
  text-align: center;
}
.error {
  color: #ff0062;
  margin-top: 10px;
  font-size: 0.8em;
  font-weight: bold;
}

 

© 2022, Eric.

Leave a Reply

Your email address will not be published. Required fields are marked *