Chapter 8: How to Build a Reusable Component with Vue

by Deji Atoyebi

Vue is a progressive framework that allows you to build your applications as collections of self-contained, reusable components.

Components are used to create what can be called custom HTML elements. Intrinsically, each element bears similar sets of data and functionalities. For example, say you’re building a Twitter-like social network. For the newsfeed, you’ll require a way to present a list of the latest activities to users. Each activity will have a similar set of data: the name of the creator of the activity, the date the activity was published, the actual content, and so on. Also, you’d want users to carry out actions such as liking, unliking and sharing these activities.

In this scenario, simply creating a card component would cut it. And although the card component will populate different activities, the underlying structure of the data as well as functionalities will remain the same. No need to repeat and modify code. After creating the card component, all we’d need to do is to insert it into the body of our HTML document, as we do every other HTML element:

<feed-card v-for=”activity for activities”></feed-card>

While building a card component for a news feed would be quite an interesting topic, in this tutorial we’d be touching on something different: how to build a re-usable modal component.

Live Code

For the impatient among you, here’s a demo of the code running on CodePen.

But Why a Modal?

Modals are a very important, multi-purpose feature of many websites. Their main function is to bring information to the immediate attention of users. They are particularly useful when crucial information is to be passed or an action must be carried out before users gain access to, say, a resource. A common use case of modals is to present an authentication (login or sign-up) form before a user can carry out a particular action. I assume that you’ll be required to create a modal component at least once during your journey as a web developer—hence the importance of this tutorial.

What To Consider Before Building Our Component

Before we get our hands dirty with actually building a modal component, it’s important to consider a couple of points.

Do We Use a Single-file Component or Not?

Don’t be deceived by the name: single-file components (SFCs) are just regular components. The “single-file” part is only meant to denote that, rather than your component being defined in the HTML file in which it’s used, alongside the root Vue instance, it would be defined in a file of its own. This comes with its own advantages, part of which is reusability (if you’re building a non-trivial web app that uses your component in different HTML pages).

In this tutorial, we’ll leverage Vue’s incremental adoptability. We’ll start by building everything in one file and including Vue from a CDN, then in a second step, we’ll look at how to refactor things into an SFC.

Do We Use ES6 or Not?

If you’re going to be using Vue at all, you need to decide whether or not your code will make use of ES6 features. Using ES6 features comes with cross-browser compatibility issues, often meaning you’ll need to use Babel to transpile it to something older browsers can understand.

For the code in this guide, we’ll use ES6 throughout. It’s a ratified standard, after all, and we’re in the favorable position of not having to support ancient browsers. Note that when we convert the modal to SFCs, the JavaScript will be bundled and transpiled via webpack.

Important Components and Behavior

What are the important components of a modal and how do we expect our modal to behave?

Every modal has two common components to it: the mask and the container (let’s call them that). The mask is the transparent dark (or white) background that appears with most modals. The modal overlies the rest of the content in the page. Once you click on a mask, the modal exits. The container, on the other hand, is the part of the modal that “contains” the important stuff.

With this knowledge, creating a reusable modal component becomes super easy, as we’ve been able to whittle down its design into its two major parts.

We’ll use Materialize for our design, as we don’t want to get too stuck with writing too much code in CSS.

Building the Modal Component

Now it’s time for the fun stuff. First, create a file named modal.html and add the following code. This pulls in our dependencies from a CDN and defines the HTML structure for our mask and container:

<!DOCTYPE html>
<html>
  <head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Page Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
  <style></style>
</head>
<body>
  <div class="container">
    <div class="modal-mask">
      <div class="modal-container"></div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script></script>
</body>
</html>

Next, let’s add in some basic styles to the <style> block in the <head> section:

.modal-mask{
  position: fixed;
  z-index: 100;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .3);
  overflow-y:auto;
}
.modal-container {
  width: 80%;
  height: auto;
  margin: 50px auto 0;
  position: relative;
  padding: 16px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
  box-sizing: border-box;
}

Open the file in your browser. You’ll see that the modal mask is a transparent dark background. The height and width are set to 100%, as we want it to occupy the whole page while the modal is in focus. The modal’s container is a <div> element that occupies 80% of the width of the mask. Feel free to customize yours however you wish.

Creating a Vue Component

The next step is to make a Vue component out of our modal. Add the following code to the final <script> block:

new Vue({
  el: ".container",
  data() {
    return {};
  }
});

The code for the modal now needs to be wrapped in a <template> block so that it can be referenced in the script defining the modal as a component:

<div class="container"></div>

<template id="modal-template">
  <div class="modal-mask">
    <div class="modal-container"></div>
  </div>
</template>

Now, we can define our modal as a Vue component:

Vue.component("modal", {
  template: "#modal-template"
});

new Vue({ ... });

Now, when you open up your modal.html, you’d find that there’s absolutely nothing shown in the page. This is because the <template> block wasn’t rendered when the page was being created.

Showing and Hiding the Modal

At this point, we should touch on how to call our modal. Let’s model it after a real-word situation: the modal will display when a button is clicked. Thus, insert a button to the container div, along with our modal:

<div class="container">
  <button> Show Modal </button>
  <modal></modal>
</div>

Since the button does absolutely nothing for now, we need to work on both the root Vue instance and the modal component so that the modal appears when the button is clicked. First, change the code in your root instance:

new Vue({
  el: ".container",
  data() {
    return {
      showModal: false
    };
  },
  methods: {
    actShowModal() {
      this.showModal = true;
    }
  }
});

In the above, we initialized showModal in our root instance to indicate when our modal is being shown to a user and when it’s not. We also added a method to manipulate this data property. As you’ll soon see, calling actShowModal will make our modal pop up.

The next task is to enable our <button> to call actShowModal when it’s clicked:

<button v-on:click="actShowModal">Show Modal</button>

Unfortunately, clicking the button still makes no difference. This is because we haven’t hooked up our modal component to the data in our root instance. What we want is for our modal to display when showModal is true and disappear when showModal is false. The way to achieve this is by using props. Our code should now be:

<modal :show_modal="showModal"></modal>

And:

Vue.component("modal", {
  template: "modal-template",
  props: ["show_modal"]
});

Now that we’ve added the show_modal as a prop, we need our modal to appear when show_modal is true. This requires a simple change in the template of our modal component:

<template id="modal-template">
  <div class="modal-mask" v-if='show_modal'>
    <div class="modal-container"></div>
  </div>
</template>

Now, open up modal.html. Notice that, when you click the button, the modal pops up. Great! But there’s still a problem: there’s no way to close the modal … yet.

We need the modal to disappear when we click on the mask. To achieve this, showModal would have to be false, and we need to make this happen from the modal component since it houses the mask.

First, create a method called closeModal in the component that emits the change:

Vue.component("modal", {
  ...
  methods: {
    closeModal() {
      this.$emit("close_modal");
    }
  }
});

And then change the code of your template so it looks like this:

<template id="modal-template">
  <div class="modal-mask" v-if="show_modal" @click="closeModal">
    <div class="modal-container"></div>
  </div>
</template>

Now when the mask is clicked, the modal component emits a close_modal event. Let’s catch that event in our root Vue instance and toggle the modal’s display state to false:

<modal :show_modal="showModal" @close_modal="actCloseModal"></modal>

Also this:

methods: {
  actShowModal(){ ... },
  actCloseModal(){
    this.showModal = false;
  }
},

Now, our modal should close when the mask is clicked—which is super awesome. But there’s yet another problem: clicking the modal’s container also closes the modal.

A simple explanation for this is that the click event propagates (bubbles up). To stop that, let’s use Vue’s way of stopping such propagation, the @click.stop event modifier:

<template id="modal-template">
  <div class="modal-mask" v-if="show_modal" @click="closeModal">
    <div class="modal-container" @click.stop></div>
  </div>
</template>

There, solved! Our modal works the way it should now. Take some time to run the code we have so far and make sure it works correctly.

Creating an Authentication Modal

Currently, our modal container is empty. Let’s change that and write some code to build a login form to be embedded in this container.

Let’s start off by adding our login form into the mix. Remember, the Materialize framework is to be used, so don’t be too bothered about the HTML class definitions:

<template id="modal-template">
  <div class="modal-mask" v-if="show_modal" @click="closeModal">
    <div class="modal-container clearfix" @click.stop>
      <h4 class="blue-text">Login</h4>
      <form class="col s12">
        <div class="input-field col s12">
          <input v-model="email" id="email_address" type="text">
          <label for="email_address">Email Address</label>
        </div>
        <div class="input-field col s12 ">
          <input v-model="password" id="password" type="text" >
          <label for="password">Password</label>
        </div>
        <a class="waves-effect waves-light btn form-btn teal">Sign in</a>
      </form>
    </div>
  </div>
</template>

And add the following to style the form’s button:

.form-btn {
  float: right;
}
.clearfix::after {
  content: "";
  clear: both;
  display: table;
}

We aren’t going to implement a back end for the authentication component, so it’s reasonable that our button does nothing. Nonetheless, let’s define the email and password data properties on our component, as we already have our inputs in the login form pointing to them through the v-model directive. This would be useful for implementing form validation. We won’t do that in this tutorial, but you can read more about this in our tutorial A Beginner’s Guide to Working with Forms in Vue in this Vue series:

Vue.component("modal", {
  template: "#modal-template",
  props: ["show_modal"],
  data() {
    return {
      email: "",
      password: ""
    };
  },
  methods: { ... }
});

Open up your modal.html, and … boom! We’ve just created a login modal all by ourselves.

Since everything works nice and smoothly, we’re afforded the opportunity to think of sleeker ways of doing things.

Making Our Modal Reusable with Slots

Although our modal is now great and usable, there’s still a little problem: the login form is tightly knitted to the modal component. So, at the moment, if we wanted to have different modals in our application—perhaps one for user registration and another for something else—we’d have to repeat our code by creating yet another modal component from scratch.

Is there a way to keep the skeleton of the modal component separate while we plug in whatever content we wish? You bet there is.

With what we have already, it’s easy to separate our login form from the modal component so that we can use the latter for other content when we need to. It all starts with creating a separate login component that would have the data and methods that are required for a login form to operate:

Vue.component("login", {
  template: "#login-template",
  data() {
    return {
      email: "",
      password: ""
    };
  }
});

Here’s the code defining login template:

<template id="login-template">
  <div class="clearfix">
    <h4 class="blue-text">Login</h4>
    <form class="col s12">
      <div class="input-field col s12">
        <input v-model="email" id="email_address" type="text">
        <label for="email_address">Email Address</label>
      </div>
      <div class="input-field col s12 ">
        <input v-model="password" id="password" type="text" >
        <label for="password">Password</label>
      </div>
      <a class="waves-effect waves-light btn form-btn teal">Sign in</a>
    </form>
  </div>
</template>

Let’s not forget to rid the modal container of the login form, as we no longer need it. Right now, what we need is to define a slot in the modal container, since that’s where we want any and every content in the modal to live:

<template id="modal-template">
  <div class="modal-mask" v-if="show_modal" @click="closeModal">
    <div class="modal-container" @click.stop>
      <slot></slot>
    </div>
  </div>
</template>

Also, delete the email and password keys in the modal component; those are now properties of our login component, just as they ought to be. The modal component’s definition should now look something like this:

Vue.component("modal", {
  template: "#modal-template",
  props: ["show_modal"],
  methods: {
    closeModal() {
      this.$emit("close_modal", false);
    }
  }
});

Inserting our login component into the modal component as provided by our slot takes this simple step:

<modal :show_modal="showModal" @close_modal="actCloseModal">
  <login></login>
</modal>

Preview your modal and you’ll find that everything works fine: the login component is rendered into the modal component.

Adding a Registration Component

To go ahead to show how reusable this modal component is now, let’s create a registration component that will also be rendered in the modal component. This will be similar to creating our login component: first, write code for the template and then the component definition.

<template id="registration-template">
  <div class="clearfix">
    <h4 class="blue-text">Register</h4>
    <form class="col s12">
      <div class="input-field col s6">
        <input v-model="firstName" placeholder="" id="first_name" type="text">
        <label for="first_name">First Name</label>
      </div>

      <div class="input-field col s6">
        <input v-model="lastName" id="last_name" type="text">
        <label for="last_name">Last Name</label>
      </div>

      <div class="input-field col s12">
        <input v-model="username" id="username" type="text" class="">
        <label for="username">Username</label>
      </div>

      <div class="input-field col s12">
        <input v-model="email" id="email_address" type="text">
        <label for="email_address">Email address</label>
      </div>

      <div class="input-field col s12">
        <input v-model="password" id="password" type="password">
        <label for="password">Password</label>
      </div>

      <a class="waves-effect waves-light btn teal form-btn">Proceed</a>
    </form>
  </div>
</template>

The component definition:

Vue.component("register", {
  template: "#registration-template",
  data() {
    return {
      firstName: "",
      lastName: "",
      username: "",
      email: "",
      password: ""
    };
  }
});

What we’re hoping to do is to render the login or register component into the modal component separately. Say there are two buttons in a web page and while one shows the login modal when clicked, the other shows the registration modal. We should have this as our code:

<modal :show_modal="showModal" @close_modal="actCloseModal" choice="choice">
  <login v-if="choice === 'login'"></login>
  <register v-else-if="choice === 'register'"></register>
</modal>

As the code suggests, there’s a data property on our root instance named choice that could either be “login” or “register”. This is then used in our modal component as a prop, as can be seen in the following code:

Vue.component("modal", {
  template: "#modal-template",
  props: ["show_modal", "choice"],
  methods: { ... }
});

Vue.component("login", { ... });
Vue.component("register", { ... });

new Vue({
  el: ".container",
  data() {
    return {
      showModal: false,
      choice: ""
    };
  },
  methods: { ... }
});

Next, let’s create the aforementioned two buttons—one to show the login modal, and the other to show the registration modal. So now we have these:

<div class="container">
  <button v-on:click="actShowModal('login')"> Login </button>
  <button v-on:click="actShowModal('register')"> Register </button>

  <modal :show_modal="showModal" @close_modal="actCloseModal" choice="choice">
    <login v-if="choice === 'login'"> </login>
    <register v-else-if="choice === 'register'"></register>
  </modal>
</div>

Our actShowModal method is defined on the root instance will then change to accept a choice as argument:

new Vue({
  el: ".container",
  data() { ... },
  methods: {
    actShowModal(choice) {
      this.choice = choice;
      this.showModal = true;
    },
    actCloseModal() {
      this.showModal = false;
    }
  }
});

Some Accessibility Improvements

One thing that we shouldn’t overlook is that people may be using a keyboard to navigate our modal. Let’s make life better for them:

<div class="container" @keyup.escape.stop="actCloseModal()">
  ...
</div>

This will let the modal be dismissed by pressing the Esc key.

When the modal opens, you might also want to focus the first element in the form. This would be done by creating a custom directive like so:

Vue.directive("focus", {
  inserted: el => {
    el.focus();
  }
});

You can apply this to any element that should receive focus. In our case, this will be the first input element in either form:

<input v-model="email" id="email_address" type="text" v-focus>
<input v-model="firstName" placeholder="" id="first_name" type="text" v-focus>

And now, we’re all done! Our modal component nicely renders both the register and login components based on the button we click. Open your modal.html to see the outcome.

What we’ve done is quite valuable, in that it prevents unnecessary code duplication. We’ve ended up creating a somewhat one-size-fits-all modal component. However, to make it perfectly reusable, we need to turn it and other components to SFCs and use webpack to bundle our code as well as transpile it so that it works on all major browsers. We’ll do this in the next part of this tutorial.

Single-file Components

Single-file components are a clean way to achieve modularity while working with Vue.js. They allow us to create .vue files which house our components, and they also come in handy when we’re working on a complex application and would like to avoid the clutter that writing Vue code in an HTML file presents. In our case, turning our Modal, Registration and Login components into SFCs helps prevent the extremely clunky act of copying and pasting code for reuse.

Since your browser cannot read .vue files, we’ll be using Vue CLI to scaffold a project. Under the hood, this uses webpack as well as a couple of other dependencies to bundle our code.

Let’s get started.

Basic Setup

To use the CLI, you’ll need a recent version of Node.js installed on your system. You can do this by downloading the binaries from the official website, or by using a version manager. This is probably the easiest way, as it allows you to manage multiple versions of Node on the same machine.

Next, proceed to installing Vue CLI globally on your machine with the following command:

npm install -g @vue/cli

Create a scaffold for our project with the following command:

vue create my-modal

Select the default preset, then when the CLI has finished installing all the dependencies, change into this directory:

cd my-modal

Next, let’s install the Materialize dependency:

npm i materialize-css

Open up src/main.js so that we can include it and make it global to our application:

import Vue from "vue";
import App from "./App.vue";
import "materialize-css/dist/css/materialize.min.css";
import "materialize-css/dist/js/materialize.min.js";

Vue.config.productionTip = false;
Vue.directive("focus", {
  inserted: el => {
    el.focus();
  }
});

new Vue({
  render: h => h(App)
}).$mount("#app");

While we’re at it, we can add our custom directive, too.

Creating Our Single-file Components

Open up src/App.vue and replace the code with the following:

<template>
  <div class="container" @keyup.escape.stop="actCloseModal()">
    <button v-on:click="actShowModal('login')"> Login </button>
    <button v-on:click="actShowModal('register')"> Register </button>

    <modal :show_modal="showModal" @close_modal="actCloseModal" choice="choice">
      <login v-if="choice === 'login'"></login>
      <register v-else-if="choice === 'register'"></register>
    </modal>
  </div>
</template>

<script>
import modal from "./components/modal.vue";
import login from "./components/login.vue";
import register from "./components/register.vue";

export default {
  name: "app",
  components: {
    modal,
    login,
    register
  },
  data() {
    return {
      showModal: false,
      choice: ""
    };
  },
  methods: {
    actShowModal(choice) {
      this.choice = choice;
      this.showModal = true;
    },
    actCloseModal() {
      this.showModal = false;
    }
  }
};
</script>

<style>
body {
  padding: 15px;
}
</style>

There’s nothing new going on here in terms of functionality, but notice how much more organized the code is.

Next, create the components we’re going to need in the src/components folder:

touch src/components/{modal.vue,register.vue,login.vue}

When you’re finished, the src folder should look like this:

.
├── App.vue
├── assets
│   └── logo.png
├── components
│   ├── login.vue
│   ├── modal.vue
│   └── register.vue
└── main.js

The final thing that remains to do is to add the code to these components.

In modal.vue:

<template id="modal-template">
  <div class="modal-mask" v-if="show_modal" @click="closeModal">
    <div class="modal-container" @click.stop>
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "modal",
  props: ["show_modal", "choice"],
  methods: {
    closeModal() {
      this.$emit("close_modal", false);
    }
  }
};
</script>

<style>
.modal-mask{
  position: fixed;
  z-index: 100;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .3);
  overflow-y:auto;
}
.modal-container {
  width: 80%;
  height: auto;
  margin: 50px auto 0;
  position: relative;
  padding: 16px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
  box-sizing: border-box;
}
.form-btn {
  float: right;
}
.clearfix::after {
  content: "";
  clear: both;
  display: table;
}
</style>

Here we’re declaring our CSS, as it pertains more to the modal than the other components. Were we to add a scoped attribute, the definitions would only be available to the template in this file.

In login.vue:

<template id="login-template">
  <div class="clearfix">
    <h4 class="blue-text">Login</h4>
    <form class="col s12">
      <div class="input-field col s12">
        <input v-model="email" id="email_address" type="text" v-focus>
        <label for="email_address">Email Address</label>
      </div>
      <div class="input-field col s12 ">
        <input v-model="password" id="password" type="text" >
        <label for="password">Password</label>
      </div>
      <a class="waves-effect waves-light btn form-btn teal">Sign in</a>
    </form>
  </div>
</template>

<script>
export default {
  name: "login",
  data() {
    return {
      email: "",
      password: ""
    };
  }
};
</script>

And in register.vue:

<template id="registration-template">
  <div class="clearfix">
    <h4 class="blue-text">Register</h4>
    <form class="col s12">
      <div class="input-field col s6">
        <input v-model="firstName" placeholder="" id="first_name" type="text" v-focus>
        <label for="first_name">First Name</label>
      </div>

      <div class="input-field col s6">
        <input v-model="lastName" id="last_name" type="text">
        <label for="last_name">Last Name</label>
      </div>

      <div class="input-field col s12">
        <input v-model="username" id="username" type="text" class="">
        <label for="username">Username</label>
      </div>

      <div class="input-field col s12">
        <input v-model="email" id="email_address" type="text">
        <label for="email_address">Email address</label>
      </div>

      <div class="input-field col s12">
        <input v-model="password" id="password" type="password">
        <label for="password">Password</label>
      </div>

      <a class="waves-effect waves-light btn teal form-btn">Proceed</a>
    </form>
  </div>
</template>

<script>
export default {
  name: "register",
  data() {
    return {
      firstName: "",
      lastName: "",
      username: "",
      email: "",
      password: ""
    };
  }
};
</script>

And that’s all there is to it. If you now spin up the dev server using the command npm run serve, you’ll be able to see your app running at http://localhost:8080.

Conclusion

In this tutorial, I’ve demonstrated how to build a simple modal component. We looked at basic accessibility features, how to design the modal so as to be reusable, and finally how to use Vue CLI to structure our app as single-file components.

I hope I’ve shown how easy it can be to get started building components of your own, as well as how Vue’s component-based approach makes it a joy to build an interface in such a declarative way.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.81.58