v-bind Many Props
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe checkout form will have six fields... and now, thanks to our shiny form-input
component, we can add those with very little duplication. Copy the form-input
element and paste it five times: One, two, three, four, five.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 50 - 74 |
Now... I'll super quickly update each component to pass in the right props. Zoom! Each input will point to a different key on our form
data and will use a different key on validationErrors
.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
<form-input | |
id="customerName" | |
v-model="form.customerName" | |
label="Name:" | |
:error-message="validationErrors.customerName" | |
/> | |
<form-input | |
id="customerEmail" | |
v-model="form.customerEmail" | |
label="Email:" | |
:error-message="validationErrors.customerEmail" | |
/> | |
<form-input | |
id="customerAddress" | |
v-model="form.customerAddress" | |
label="Address:" | |
:error-message="validationErrors.customerAddress" | |
/> | |
<form-input | |
id="customerZip" | |
v-model="form.customerZip" | |
label="Zip Code:" | |
:error-message="validationErrors.customerZip" | |
/> | |
<form-input | |
id="customerCity" | |
v-model="form.customerCity" | |
label="City:" | |
:error-message="validationErrors.customerCity" | |
/> | |
<form-input | |
id="customerPhone" | |
v-model="form.customerPhone" | |
label="Phone Number:" | |
:error-message="validationErrors.customerPhone" | |
/> | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 50 - 74 |
Let's go see how it looks! Click to check out and... nice! I mean, it's doesn't look great, but we'll improve that soon.
Using v-bind with an Object
Before we do, that still felt like a lot of repetition. Like, on this field, we're repeating customerCity
3 different times.
Fortunately, we can clean this up with a clever use of v-bind
. At the bottom, add a methods
key and create a new method called getFieldProps()
: This will return a map of all the props needed for a specific input. To generate that, this needs id
and label
arguments.
// ... lines 1 - 38 | |
<script> | |
// ... lines 40 - 41 | |
export default { | |
name: 'CheckoutForm', | |
// ... lines 44 - 59 | |
methods: { | |
// ... lines 61 - 67 | |
getFieldProps(id, label) { | |
// ... lines 69 - 73 | |
}, | |
}, | |
}; | |
</script> |
Inside, return an object with the props the field needs... which as a reminder, are id
, label
and error-message
. So, set id: id
... or better, shorten to just id
, label
and error-message
. But, this needs to be errorMessage
in camel-case: Vue will handle normalizing that to error-message
. Anyways, errorMessage
set to this.validationErrors[id]
.
// ... lines 1 - 38 | |
<script> | |
// ... lines 40 - 41 | |
export default { | |
name: 'CheckoutForm', | |
// ... lines 44 - 59 | |
methods: { | |
// ... lines 61 - 67 | |
getFieldProps(id, label) { | |
return { | |
id, | |
label, | |
errorMessage: this.validationErrors[id], | |
}; | |
}, | |
}, | |
}; | |
</script> |
Up in the template, we can use this to shorten things. Remember, :error-message
is short for v-bind:error-message
which binds a single prop. But we can also use v-bind
to bind a bunch of props at once. Remove error-message
, label
and id
and instead say v-bind
- with no colon - then getFieldProps()
passing the id and label.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
<form-input | |
v-model="form.customerName" | |
v-bind="getFieldProps('customerName', 'Name:')" | |
/> | |
<form-input | |
v-model="form.customerEmail" | |
v-bind="getFieldProps('customerEmail', 'Email:')" | |
/> | |
<form-input | |
v-model="form.customerAddress" | |
v-bind="getFieldProps('customerAddress', 'Address:')" | |
/> | |
<form-input | |
v-model="form.customerZip" | |
v-bind="getFieldProps('customerZip', 'Zip Code:')" | |
/> | |
<form-input | |
v-model="form.customerCity" | |
v-bind="getFieldProps('customerCity', 'City:')" | |
/> | |
<form-input | |
v-model="form.customerPhone" | |
v-bind="getFieldProps('customerPhone', 'Phone Number:')" | |
/> | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 38 - 78 |
There is still some repetition between v-model
and v-bind
, but it's an improvement. I'll type as fast as Fabien normally types to quickly repeat this for the other 5 fields.
Phew! Unless I messed something up (very possible), that should... not break anything. Move over, hit checkout and... awesome! Everything seems to be working!
Adding Markup to make the Form Look nicer
Now let's make this look a bit nicer by organizing the fields into a few columns. This doesn't have much to do with Vue... I just don't like ugly forms.
Above the first field, add <div class="form-row">
, wrap the first two fields inside and indent them. Both elements now need an extra class. Pass class="col"
two times.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
<div class="form-row"> | |
<form-input | |
v-model="form.customerName" | |
class="col" | |
v-bind="getFieldProps('customerName', 'Name:')" | |
/> | |
<form-input | |
v-model="form.customerEmail" | |
class="col" | |
v-bind="getFieldProps('customerEmail', 'Email:')" | |
/> | |
</div> | |
// ... lines 18 - 42 | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 47 - 87 |
But I want to point something out. We do not have a class prop inside our <form-input>
custom component. And so, when we pass class
to this, Vue will automatically add this as an attribute to the top level element of form-input
. We can see this in the browser. If we inspect the element, yep! Both outer elements - which have a form-group
- now also have a col
class. That's exactly what we want.
Back in index.vue
, leave the customerAddress
on its own row, but wrap the last 3 fields inside of another <div class="form-row">
. Add the ending div, indent, and give all 3 of these class="col"
. And... I think I have some extra whitespace my editor is mad about. Much better.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
// ... lines 5 - 23 | |
<div class="form-row"> | |
<form-input | |
v-model="form.customerZip" | |
class="col" | |
v-bind="getFieldProps('customerZip', 'Zip Code:')" | |
/> | |
<form-input | |
v-model="form.customerCity" | |
class="col" | |
v-bind="getFieldProps('customerCity', 'City:')" | |
/> | |
<form-input | |
v-model="form.customerPhone" | |
class="col" | |
v-bind="getFieldProps('customerPhone', 'Phone Number:')" | |
/> | |
</div> | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 47 - 87 |
Go check it out. That looks great!
Customizing the input type Attribute
Let's make one last improvement. All of these fields are <input type="text">
. If we wanted to handle other field types like select
elements or checkboxes, we would need to do more work in <form-input>
to make it more flexible or even create some new components.
I'm not going to do that now, but I at least want to be able to render different input types, like <input type="email">
and <input type="tel">
for the phone number.
No problem!. Our <form-input>
now needs to be more flexible. So let's add a new prop. Copy the value
prop, call this one type
... and change the default to text
so that we don't have to pass this in.
// ... lines 1 - 28 | |
<script> | |
export default { | |
name: 'FormInput', | |
// ... lines 32 - 44 | |
type: { | |
type: String, | |
default: 'text', | |
}, | |
// ... lines 49 - 52 | |
}, | |
// ... lines 54 - 58 | |
}; | |
</script> |
Use this up in the template: replace type="text"
with :type="type"
.
<template> | |
<div class="form-group"> | |
// ... lines 3 - 8 | |
<input | |
// ... lines 10 - 11 | |
:type="type" | |
// ... lines 13 - 18 | |
> | |
// ... lines 20 - 25 | |
</div> | |
</template> | |
// ... lines 28 - 61 |
Thanks to the default value, we only need to pass this for two fields. Find customerEmail
. What's cool is that we can mix the v-bind
that's set to an entire object with other, specific props without any issues. What I mean is, when we pass in the type prop with type="email"
, that will merge nicely with whatever props getFieldProps()
adds.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
<div class="form-row"> | |
// ... lines 6 - 11 | |
<form-input | |
// ... lines 13 - 14 | |
type="email" | |
// ... line 16 | |
/> | |
</div> | |
// ... lines 19 - 44 | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 49 - 89 |
Repeat this on customerPhone
: type="tel"
.
<template> | |
<div class="row p-3"> | |
<div class="col-12"> | |
<form> | |
// ... lines 5 - 24 | |
<div class="form-row"> | |
// ... lines 26 - 37 | |
<form-input | |
// ... lines 39 - 40 | |
type="tel" | |
// ... line 42 | |
/> | |
</div> | |
</form> | |
</div> | |
</div> | |
</template> | |
// ... lines 49 - 89 |
Go check it! You probably won't notice any difference on a computer, but if you inspect the element... yep! It's <input type="email">
.
Okay! We are ready to set up this form to submit via Ajax. When we do that, we're going to make handling and rendering form validation a main concern.