β Vue.js beginners lesson 1: instance Vue
β Vue.js for beginners, lesson 2: binding attributes
β Vue.js beginners lesson 3: conditional rendering
β Vue.js beginners lesson 4: lists rendering
β Vue .js for beginners lesson 5: event processing
β Vue.js beginners lesson 6: binding classes and styles
β Vue.js beginners lesson 7: calculated properties
β Vue.js beginners lesson 8: components
β Vue. js for beginners lesson 9: custom events
The purpose of the lesson
We're going to create a form that allows site visitors to submit product reviews. In this case, it is necessary that the review could be sent only if all the fields of the form are filled in, which must be filled in.
Initial code
Here's what's in now
index.html
:
<div id="app">
<div class="cart">
<p>Cart({{ cart.length }})</p>
</div>
<product :premium="premium" @add-to-cart="updateCart"></product>
</div>
It looks like this
main.js
:
Vue.component('product', {
props: {
premium: {
type: Boolean,
required: true
}
},
template: `
<div class="product">
<div class="product-image">
<img :src="image" />
</div>
<div class="product-info">
<h1>{{ title }}</h1>
<p v-if="inStock">In stock</p>
<p v-else>Out of Stock</p>
<p>Shipping: {{ shipping }}</p>
<ul>
<li v-for="detail in details">{{ detail }}</li>
</ul>
<div
class="color-box"
v-for="(variant, index) in variants"
:key="variant.variantId"
:style="{ backgroundColor: variant.variantColor }"
@mouseover="updateProduct(index)"
></div>
<button
v-on:click="addToCart"
:disabled="!inStock"
:class="{ disabledButton: !inStock }"
>
Add to cart
</button>
</div>
</div>
`,
data() {
return {
product: 'Socks',
brand: 'Vue Mastery',
selectedVariant: 0,
details: ['80% cotton', '20% polyester', 'Gender-neutral'],
variants: [
{
variantId: 2234,
variantColor: 'green',
variantImage: './assets/vmSocks-green.jpg',
variantQuantity: 10
},
{
variantId: 2235,
variantColor: 'blue',
variantImage: './assets/vmSocks-blue.jpg',
variantQuantity: 0
}
]
}
},
methods: {
addToCart() {
this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
},
updateProduct(index) {
this.selectedVariant = index;
console.log(index);
}
},
computed: {
title() {
return this.brand + ' ' + this.product;
},
image() {
return this.variants[this.selectedVariant].variantImage;
},
inStock() {
return this.variants[this.selectedVariant].variantQuantity;
},
shipping() {
if (this.premium) {
return "Free";
} else {
return 2.99
}
}
}
})
var app = new Vue({
el: '#app',
data: {
premium: true,
cart: []
},
methods: {
updateCart(id) {
this.cart.push(id);
}
}
})
Task
We need site visitors to be able to leave reviews on products, but our site does not yet have the means to receive data from users. Forms are such means.
The solution of the problem
To solve the task before us, we need to create a form. Let's start by creating a new component specifically for working with a form. Let's call this component
product-review
. This name was chosen because the component will support the form for collecting product reviews. The component product-review
will be nested within the component product
.
Let's register a new component, start forming its template and equip it with some data:
Vue.component('product-review', {
template: `
<input>
`,
data() {
return {
name: null
}
}
})
As you can see, there is an element in the component template
<input>
, and there is a property in the component data data
, while it is empty.
How do I bind what the user enters into a field to a property
name
?
In the previous lessons, we talked about data binding using directives
v-bind
, but then we only considered one-way binding. The data flow went from the property that stores the data to the control that renders it. And now we need that what the user enters into the field would be in the property name
stored in the component data. In other words, we want the data flow to be directed from the input field to the property.
V-model directive
The directive
v-model
allows organizing two-way data binding. With this scheme of work, if something new appears in the input field, this leads to a change in the data. And, accordingly, when the data changes, the state of the control that uses this data is updated.
Let's add a directive to the input field
v-model
and bind this field to a property name
from the component data.
<input v-model="name">
Now let's add the complete form code to the component template:
<form class="review-form" @submit.prevent="onSubmit">
<p>
<label for="name">Name:</label>
<input id="name" v-model="name" placeholder="name">
</p>
<p>
<label for="review">Review:</label>
<textarea id="review" v-model="review"></textarea>
</p>
<p>
<label for="rating">Rating:</label>
<select id="rating" v-model.number="rating">
<option>5</option>
<option>4</option>
<option>3</option>
<option>2</option>
<option>1</option>
</select>
</p>
<p>
<input type="submit" value="Submit">
</p>
</form>
As you can see, the
v-model
fields input
, textarea
and are equipped with the directive select
. Please note that when setting up the field select
, a modifier was used .number
(we will talk about it in more detail below). This allows you to convert the corresponding data to a type Number
, while it is usually represented in a string.
Let's supplement the data set of the component by adding to it the data to which the above-described controls are bound:
data() {
return {
name: null,
review: null,
rating: null
}
}
At the top of the form template, you can see that when the form is submitted, a method is called
onSubmit
. We'll be creating this method soon. But first, let's talk about the role of construction .prevent
.
This is an event modifier. It prevents the page from being reloaded after the event is raised
submit
. There are other useful event modifiers as well . True, we will not talk about them.
We are now ready to create a method
onSubmit
. Let's start with this code:
onSubmit() {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.name = null
this.review = null
this.rating = null
}
As you can see, this method creates an object based on the data entered by the user. A reference to it is written to a variable
productReview
. Here we drop in null
property values name
, review
, rating
. But the work is not over yet. We still need to send somewhere productReview
. Where to send this object?
It makes sense to store product reviews in the same place where component data is stored
product
. Given that the component is product-review
nested within a component product
, we can say that it product-review
is a child of the component product
. As we learned in the previous lesson, you can use events generated by using to send data from child components to parent components $emit
.
Let's refine the method
onSubmit
:
onSubmit() {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview)
this.name = null
this.review = null
this.rating = null
}
Now we generate an event with a name
review-submitted
and pass the newly created object in it productReview
.
Next, we need to organize listening for this event by placing the
product
following in the template :
<product-review @review-submitted="addReview"></product-review>
This line reads like this: "When an event occurs
review-submitted
, the addReview
component method needs to be run product
."
Here is the code for this method:
addReview(productReview) {
this.reviews.push(productReview)
}
This method takes the object
productReview
that came from the method onSubmit
, and then puts it into an array reviews
stored in the component data product
. But there is no such array in the data of this component yet. So let's add it there:
reviews: []
Wonderful! The form elements are now bound to the component data
product-review
. This data is used to create the object productReview
. And this object is passed, when the form is submitted, to the component product
. As a result, the object is productReview
added to the array reviews
, which is stored in the component data product
.
Displaying product reviews
Now all that remains is to display on the product page the reviews left by site visitors. We will do this in the component
product
by placing the corresponding code above the code with which the component product-review
is placed in the component product
.
<div>
<h2>Reviews</h2>
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
Here we create a list of reviews using the directive
v-for
and display the data stored in the object review
using dot notation.
In the tag,
<p>
we check if there is something in the array reviews
(by checking its length). If there is nothing in the array, we print a message There are no reviews yet
.
Feedback form page
Form validation
Forms often contain fields that must be filled in before submitting the form. For example, we don't want users to submit reviews in which the review text field is empty.
Luckily for us, HTML5 supports the
required
. Its use looks like this:
<input required >
Such a construction will lead to an automatic display of an error message if the user tries to submit a form in which the required field is empty.
Error message
Having standard form field validators in the browser is very nice, as it can free us from creating our own field validators. But the way the standard data check is carried out may, in some special case, not suit us. In this situation, it makes sense to write your own form validation code.
Custom form validation
Let's talk about how to create your own form validation system.
Let's include an
product-review
array for storing error messages into the component data . Let's call it errors
:
data() {
return {
name: null,
review: null,
rating: null,
errors: []
}
}
We would like to add to this array information about errors that occur in situations where form fields are empty. We are talking about the following code:
if(!this.name) this.errors.push("Name required.")
if(!this.review) this.errors.push("Review required.")
if(!this.rating) this.errors.push("Rating required.")
The first of these lines tells the system
name
to place errors
an error message in the array if the field is empty Name required
. Other strings that validate the review
and fields work similarly rating
. If any of them is empty, array
an error message will be sent to the array .
Where to put this code?
Since we want the error messages to be written to the array only when, when trying to submit the form, it turns out that the fields are
name
, review
or are submit
not filled, we can place this code in the method onSubmit
. In addition, we will rewrite the code that is already in it, adding some checks to it:
onSubmit() {
if(this.name && this.review && this.rating) {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview)
this.name = null
this.review = null
this.rating = null
} else {
if(!this.name) this.errors.push("Name required.")
if(!this.review) this.errors.push("Review required.")
if(!this.rating) this.errors.push("Rating required.")
}
}
Now we check the fields
name
, review
and rating
. If there is data in all these fields, we create an object based on them productReview
and send it to the parent component. Then the corresponding properties are reset.
If at least one of the fields turns out to be empty, we put
errors
an error message in the array , depending on what the user did not enter before submitting the form.
All that remains is to display these errors, which can be done with the following code:
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
Here a directive is used
v-if
with which we check the array errors
for the presence of error messages in it (in fact, we analyze the length of the array). If there is something in the array, an element is displayed <p>
, which, when applied v-for
, displays a list of errors from the array errors
.
Error Messages
We now have our own form validation system.
Using the .number modifier
The modifier
.number
used in the directive v-model
can be very useful. But when applying it, keep in mind that there is one problem associated with it. If the corresponding value is empty, it will be represented as a string, not a number. The Vue Recipe Book offers a solution to this problem. It consists in explicitly converting the corresponding value to a numeric type:
Number(this.myNumber)
Workshop
Add the following question to the form: "Would you recommend this product?" Make it so that the user can answer it using the "yes" and "no" radio buttons. Check the answer to this question and include the relevant data in the object
productReview
.
- Here is a template you can use to solve this problem.
- Here is the solution to the problem.
Outcome
Today we talked about working with forms. Here's the most important thing you've learned today:
- You can use the directive to organize two-way data binding to form elements
v-model
. - The modifier
.number
tells Vue to cast the corresponding value to a numeric type. But when working with it, there is one problem related to the fact that empty values ββremain strings. - The event modifier
.prevent
allows you to prevent page reloading after the form is submitted. - With Vue, you can implement a fairly simple mechanism for custom form validation.
Did you do your homework today?
β Vue.js beginners lesson 1: instance Vue
β Vue.js for beginners, lesson 2: binding attributes
β Vue.js beginners lesson 3: conditional rendering
β Vue.js beginners lesson 4: lists rendering
β Vue .js for beginners lesson 5: event processing
β Vue.js beginners lesson 6: binding classes and styles
β Vue.js beginners lesson 7: calculated properties
β Vue.js beginners lesson 8: components
β Vue. js for beginners lesson 9: custom events