Vue.js Directives
When writing Vue applications, we have access to a variety of powerful directives that allow us to shape the way our content appears on the screen. This allows us to craft highly interactive user experiences with additions to our HTML templates. This chapter will be looking at each one of these directives in detail, as well as any shortcuts and patterns that allow us to improve our workflow.
By the end of this chapter you will have:
- Used attribute binding to conditionally change element behavior
- Investigated two-way binding with v-model
- Conditionally displayed information with v-if , v-else , and v-if-else
- Iterated over items in a collection with v-for
- Listened to events (such as keyboard/input) with v-on
- Used event modifiers to change the binding of a directive
- Used filters to change the view data of a binding
- Looked at how we can use shorthand syntax to save time and be more declarative
Model
One of the most common requirements for any business application is text input. Vue assists us with this need with thev-modeldirective. It allows us to create reactive two-way data bindings on form input events, making working with forms easily. It's a welcome abstraction over what would otherwise be a tedious way to get form values and input events. To explore this, we can create a new Vue project:
# Create a new Vue project
$ vue init webpack-simple vue-model
# Navigate to directory
$ cd vue-model
# Install dependencies
$ npm install
# Run application
$ npm run dev
We can head over to our rootApp.vuefile and remove everything from the template and instead add a newdivthat encompasses alabelandforminput:
<
template
>
<
div id="app"
>
<
label
>
Name:
<
/label
>
<
input type="text"
>
<
/div
>
<
/template
>
This gives us the ability to add text to our input element, that is, prompting the user to input their name. I'd like to capture this value and display it below the name element for demonstration sake. In order to do this, we'd need to add thev-modeldirective to the input element; this will allow us to capture the user input events and place the value into a variable. We'll call this variablenameand subsequently add it to ourdataobject within our Vue instance. As the value is now captured as a variable, we can display this in our template with interpolation binding:
<
template
>
<
div id="app"
>
<
label
>
Name:
<
/label
>
<
input type="text" v-model="name"
>
<
p
>
{{name}}
<
/p
>
<
/div
>
<
/template
>
<
script
>
export default {
data () {
return {
name: ''
}
}
}
<
/script
>
The result can be seen in the following screenshot:
We're not limited to working with text inputs when usingv-model, we can also capture radio buttons or checkboxes when selected. The following example shows this:
<
input type="checkbox" v-model="checked"
>
<
span
>
Am I checked? {{checked ? 'Yes' : 'No' }}
<
/span
>
This will then display in our browser like so:
The great thing aboutv-modelis that it's highly adaptable to a variety of form controls, giving us declarative power over our HTML templates.
Iteration with v-for
If we have content that we'd like to repeat over a certain number, we can usev-for. This is often used to populate a template with a dataset. For example, let's say we have a list of groceries and we wanted to display this list on the screen; we could do this withv-for. We can create a new project to see this in action:
# Create a new Vue project
$ vue init webpack-simple vue-for
# Navigate to directory
$ cd vue-for
# Install dependencies
$ npm install
# Run application
$ npm run dev
To start with, let's create an array with a list of groceries that we can display on screen. Each item has anid,name, andquantity:
<
script
>
export default {
name: 'app',
data () {
return {
groceries: [
{
id: 1,
name: 'Pizza',
quantity: 1
},
{
id: 2,
name: 'Hot Sauce',
quantity: 5
},
{
id: 3,
name: 'Salad',
quantity: 1
},
{
id: 4,
name: 'Water',
quantity: 1
},
{
id: 4,
name: 'Yoghurt',
quantity: 1
}
]
}
}
}
<
/script
>
<
style
>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: block;
}
<
/style
>
We can then iterate over each item inside of our groceries list and modify the DOM to display them on screen:
<
template
>
<
div id="app"
>
<
h1
>
Shopping List
<
/h1
>
<
ul
>
<
li v-for="item in groceries" v-bind:key="item.id"
>
{{item.name}}
<
/li
>
<
/ul
>
<
/div
>
<
/template
>
Notice how we have av-bind:key="item.id"on thelielement. This makes Vue work better with iterations that change over time and a key should be added where possible:
Bindings
In this section, we're going to look at how we can dynamically toggle CSS classes within our Vue applications. We'll start off by investigating thev-binddirective and we'll see how this can be applied to bothclassandstyleattributes. This is great for conditionally applying styles based on a particular business logic. Let's create a new Vue project for this example:
# Create a new Vue project
$ vue init webpack-simple vue-bind
# Navigate to directory
$ cd vue-bind
# Install dependencies
$ npm install
# Run application
$ npm run dev
Inside of our project, we can make checkboxes that represent the different states of our application. We'll start off with one namedred. As you may be able to infer, by checking this we can turn a particular piece of textredin color and subsequently turn it black by unchecking it.
Create adataobject namedredwith the value offalseinsideApp.vue:
<
script
>
export default {
data () {
return {
red: false
}
}
}
<
/script
>
This represent the value of our checkbox, which we'll be able to set with thev-modeldirective:
<
template
>
<
div id="app"
>
<
h1
>
Vue Bindings
<
/h1
>
<
input type="checkbox" v-model="red"
>
<
span
>
Red
<
/span
>
<
/div
>
<
/template
>
At this point, we can create a new CSS class for our color:
<
style
>
.red {
color: red;
}
<
/style
>
As you can see in the browser if we open the dev tools, we can see that the color of the text is currently set toblue:
Finally, to add the addition/removal of the class based on the context of theredvariable, we'll need to add thev-bind:classdirective to ourh1like so:
<
h1 v-bind:class="{ 'red': red }"
>
Vue Bindings
<
/h1
>
Now in our browser, we can see that we have the ability to check the box to set the text toredlike so:
Adding secondary properties
If we also wanted to add another property to our class binding, we'd need to add another property (such asstrikeThrough) to ourdataobject like so:
data () {
return {
red: false,
strikeThrough: false
}
}
We can then add anothercheckbox:
<
input type="checkbox" v-model="strikeThrough"
>
<
span
>
Strike Through
<
/span
>
With an appropriatestyle:
<
style
>
.red {
color: red;
}
.strike-through {
text-decoration: line-through;
}
<
/style
>
Finally, we then need to adjust our binding to add the extra class like so:
<
h1 v-bind:class="{ 'red': red, 'strike-through': strikeThrough }"
>
Vue Bindings
<
/h1
>
Here's the results of checking both boxes:
Style bindings
We may want to add various styles to our heading, so instead, we could usev-bind:style. We can see this in action by creating a new object namedheadingStylesinside of ourdataobject:
data () {
return {
headingStyles: {
color: 'blue',
fontSize: '20px',
textAlign: 'center'
}
}
}
Anytime we're adding CSS classes that would otherwise be kebab-case (for example,text-align) they now become camel-case (textAlign) inside of our JavaScript.
Let's add the style to our heading inside of the template:
<
h1 v-bind:style="headingStyles"
>
Vue Bindings
<
/h1
>
Every time the compiler sees av-bindor:the content inside of"is treated as JavaScript with an implicitthis.
We could also split this to addlayoutStylesas a separate object, for example:
data () {
return {
headingStyles: {
color: 'blue',
fontSize: '20px',
},
layoutStyles: {
textAlign: 'center',
padding: '10px'
}
}
}
So all that we have to do at this point is add thestylesin an array within thetemplatelike so within the<h1>tag, usingv-bind:
<
template
>
<
h1 v-bind:style="[headingStyles, layoutStyles]"
>
Vue Bindings
<
/h1
>
<
/template
>
You can now see the results of our styling on screen. Note that any items further in the array will take styling preference over items declared first.
DOM events and v-on
We can handle DOM events within Vue by usingv-on.By listening to DOM events, we're able to react to user input with everything from key-down events (such as clicking the_Enter_button) to button click events and more.
Let's make a playground project to try this in our own project:
# Create a new Vue project
$ vue init webpack-simple vue-on
# Navigate to directory
$ cd vue-on
# Install dependencies
$ npm install
# Run application
$ npm run dev
Let's imagine aninputbox such that, when we either hit theAddbutton or hit the_Enter_key, the input gets added to an array:
<
template
>
<
div id="app"
>
<
ul
>
<
li v-for="(p, index) in person" :key="index"
>
{{p}}
<
/li
>
<
/ul
>
<
input type="text" v-model="person" v-on:keyup.enter="addPerson" /
>
<
button v-on:click="addPerson"
>
Add {{ person}}
<
/button
>
<
/div
>
<
/template
>
<
script
>
export default {
name: 'app',
data () {
return {
person: '',
people: []
}
},
methods: {
addPerson() {
this.people = this.people.concat(
{id: this.people.length, name: this.person}
);
this.person = '';
}
}
}
<
/script
>
You will have to copy the object before pushing it in.
What exactly is happening here? We're capturing the value of the user input inside of the person string using thev-modeldirective; after that we're listening for bothkeyup.enterandv-on:clickevents, both of which call theaddPersonfunction, and subsequently adding thepersonto an array. After that, using thev-fordirective, we're able to output this list of people to the page:
Key modifiers
We're not limited to simply using theentermodifier, we also have access to a variety of shorthand modifiers, for example the use of the @ symbol and shorteningv-on:event.namev-on:by replacing it with the@symbol. Other shortening methods include:
- @ is the same as v-on:
- @keyup.13 is the same as @keyup.enter
- @key* can be queued, such as @keyup.ctrl.alt.delete
Other modifiers can be seen in the following table:
Name | Description | Code Example |
---|---|---|
.enter | Whenever the_Enter_key is tapped. | <input v-on:keyup.enter="myFunction" /> |
.tab | Whenever the_Tab_key is tapped. | <input v-on:keyup.tab="myFunction" /> |
.delete | Whenever the_Delete_or_Backspace_key is tapped. | <input v-on:keyup.delete="myFunction" /> |
.esc | Whenever the_Esc_key is tapped. | <input v-on:keyup.esc="myFunction" /> |
.up | Whenever the up arrow key is tapped. | <input v-on:keyup.up="myFunction" /> |
.down | Whenever the down arrow key is tapped. | <input v-on:keyup.down="myFunction" /> |
.left | Whenever the left arrow key is tapped. | <input v-on:keyup.left="myFunction" /> |
.right | Whenever the right arrow key is tapped. | <input v-on:keyup.right="myFunction" /> |
Event modifiers
Often when we're working with events inside of JavaScript, we'll modify the functionality of the event itself. This means that we need to addevent.preventDefault()orevent.stopPropagation()within our handler. Vue helps us abstract these calls by handling this inside of the template using event modifiers.
This is best shown with aformexample. Let's take our previous people example and modify this to contain aformelement:
<
template
>
<
div id="app"
>
<
ul
>
<
li v-for="p in people" v-bind:key="p.id"
>
{{p}}
<
/li
>
<
/ul
>
<
form v-on:submit="addPerson"
>
<
input type="text" v-model="person" /
>
<
button
>
Add {{ person}}
<
/button
>
<
/form
>
<
/div
>
<
/template
>
If you try and run this example, you'll notice that when we click theAddbutton, the page refreshes. This is because it's the default behavior of theformsubmitted event. As we're not POSTing data to a server at this point, we instead need to add the.preventmodifier to oursubmitevent:
<
form v-on:submit.prevent="addPerson"
>
<
input type="text" v-model="person" /
>
<
button
>
Add {{ person}}
<
/button
>
<
/form
>
Now when we select our button, theaddPersonfunction is called without refreshing the page.
Conditionally showing DOM elements
When creating business applications, there'll be many times when you only want to display a particular element if a certain condition istrueorfalse. This could include a user's age, whether the user is logged in, whether it is an administrator or any other piece of business logic you can think of.
For this, we have a variety of conditional directives such asv-show,v-if,v-else, andv-else-if, all of which act in similar yet different ways. Let's take a look at this in more detail by creating a new playground project:
# Create a new Vue project
$ vue init webpack-simple vue-conditionals
# Navigate to directory
$ cd vue-conditionals
# Install dependencies
$ npm install
# Run application
$ npm run dev
v-show
If we want to hide elements from view yet still have them in the DOM (effectivelydisplay:none), we can usev-show:
<
template
>
<
div id="app"
>
<
article v-show="admin"
>
<
header
>
Protected Content
<
/header
>
<
section class="main"
>
<
h1
>
If you can see this, you're an admin!
<
/h1
>
<
/section
>
<
/article
>
<
button @click="admin = !admin"
>
Flip permissions
<
/button
>
<
/div
>
<
/template
>
<
script
>
export default{
name: 'app',
data (){
return{
admin: true
}
}
}
<
/script
>
For example, if we had a data variable that allowed us to determine whether someone was an administrator, we'd be able to usev-showto only show protected content to appropriate users:
Notice how, in the preceding figure whenadminis set tofalse, thedisplay: nonestyle is added to the element. At first glance it may seem like this is exactly what we want, our item has disappeared! In certain circumstances, this is the case, but in other scenarios, the use ofv-ifmay be better.
v-showdoesn't remove the element from the DOM, meaning that everything is initially loaded and simply hidden if it isn't being used. Our page will have to render this content and this could cause performance issues when used in the wrong way; therefore prior to usingv-showask this question:
Do I need to show this component again? If so, will be showing it often?
If the answer to this question isyes, thenv-showmay be better in this circumstance. Otherwise, if the answer to this isno,thenv-ifmay be better in this use case.
v-if
If we'd like to conditionally remove the element from the DOM, we can usev-if. Let's replace our previousv-showdirective withv-if:
<
article v-if="admin"
>
<
header
>
Protected Content
<
/header
>
<
section class="main"
>
<
h1
>
If you can see this, you're an admin!
<
/h1
>
<
/section
>
<
/article
>
Notice that now when we look at the DOM, the element is entirely removed: