How To Use Google Maps With Vue.js Apps
Tags: vue, webdev, tutorial, javascript
Published: 2020-01-22 03:06:51

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

**Many more articles at https://medium.com/@hohanga**

**Even more articles at http://thewebdev.info/**

[Vue.js](https://vuejs.org/) is a great framework for building front-end web apps. It uses a component-based architecture which makes organizing code easy. It allows you to use the latest features JavaScript has to offer, which means writing code to build your apps is easier than ever.

Component-based architecture means that our front-end app consists of parts that are nested within each other. One component that is often used in applications is Google Maps. Developers have created a high-quality Google Maps component for Vue.js apps.

In this piece, we will build an address book app with Vue.js that has maps to display a contact’s location with Google Maps. A contact form allows us to add and edit our contacts and we can get and delete contacts.

To add Google Maps functionality to our app, we use the `[vue2-google-maps](https://www.npmjs.com/package/vue2-google-maps)` package.

To build our app, first, we need to set up a back end. To do this, we use a Node.js package, called [_JSON Server_](https://github.com/typicode/json-server)_,_ to run our back end. Once this is running, it provides us with routes to save our contact entries from the front end.

To install the package, run:

```
npm install -g json-server
```

We will run this later so we can save our contacts.

Also, we have to get an API key to use the Google Maps API. To do this, go to [https://developers.google.com/maps/documentation/](https://developers.google.com/maps/documentation/) and sign in to your Google account by clicking the _Sign In_ link at the top right.

Then, go to [https://cloud.google.com/maps-platform/maps/?refresh=1](https://cloud.google.com/maps-platform/maps/?refresh=1) and click _Get Started_. After that, follow the instructions to get the API key.

Now, we can start building our app. To do this, install the Vue CLI by running:

```bash
npm install -g @vue/cli
```

Then, create the app by running:

```bash
vue create vee-validate-address-book-app
```

`vee-validate-address-book-app` is our app name. When running the wizard, be sure you choose to include [Vuex](https://vuex.vuejs.org/) and [Vue Router](https://router.vuejs.org/) as we will use it later.

Next, we have to install some libraries.

We need an HTTP client, a [Material Design](https://material.io/design/) library for making our app look good, and the [VeeValidate](https://baianat.github.io/vee-validate/) library. To do this, run `npm i axios vee-validate vue-material vue2-google-maps`.

[Axios](https://github.com/axios/axios) is our HTTP client for communicating with the back end. [Vue Material](https://vuematerial.io/) is our Material Design library. vue2-google-maps is the Google Maps library that we’re going to use.

Next, we create our components that we nest in our page components. To do this, create a `components` folder in the project folder and, within it, create a file called `ContactForm.vue`.

In this file, we put:

```javascript
<template>
  <div class="contact-form">
    <div class="center">
      <h1>{{editing ? 'Edit': 'Add'}} Contact</h1>
    </div>
    <form novalidate class="md-layout" @submit="save">
      <md-field :class="{ 'md-invalid': errors.has('firstName') }">
        <label for="firstName">First Name</label>
        <md-input
          name="firstName"
          v-model="contact.firstName"
          v-validate="'required'"
          :disabled="sending"
        />
        <span class="md-error" v-if="errors.has('firstName')">First Name is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('lastName') }">
        <label for="lastName">Last Name</label>
        <md-input
          name="lastName"
          v-model="contact.lastName"
          :disabled="sending"
          v-validate="'required'"
        />
        <span class="md-error" v-if="errors.has('lastName')">Last Name is required.</span>
      </md-field>
<br />
<md-field :class="{ 'md-invalid': errors.has('addressLineOne') }">
        <label for="addressLineOne">Address Line 1</label>
        <md-input
          name="addressLineOne"
          v-model="contact.addressLineOne"
          :disabled="sending"
          v-validate="'required'"
        />
        <span class="md-error" v-if="errors.has('addressLineOne')">Address line 1 is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('addressLineTwo') }">
        <label for="addressLineTwo">Address Line 2</label>
        <md-input name="addressLineTwo" v-model="contact.addressLineTwo" :disabled="sending" />
        <span class="md-error" v-if="errors.has('addressLineTwo')">Address line 2 is required</span>
      </md-field>
<br />
<md-field :class="{ 'md-invalid': errors.has('city') }">
        <label for="city">City</label>
        <md-input name="city" v-model="contact.city" :disabled="sending" v-validate="'required'" />
        <span class="md-error" v-if="errors.has('city')">City is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('country') }">
        <label for="country">Country</label>
        <md-select
          name="country"
          v-model="contact.country"
          md-dense
          :disabled="sending"
          v-validate.continues="'required'"
        >
          <md-option :value="c" :key="c" v-for="c in countries">{{c}}</md-option>
        </md-select>
        <span class="md-error" v-if="errors.firstByRule('country', 'required')">Country is required.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('postalCode') }">
        <label for="postalCode">Postal Code</label>
        <md-input
          name="postalCode"
          v-model="contact.postalCode"
          :disabled="sending"
          v-validate="{ required: true, regex: getPostalCodeRegex() }"
        />
        <span
          class="md-error"
          v-if="errors.firstByRule('postalCode', 'required')"
        >Postal Code is required.</span>
        <span
          class="md-error"
          v-if="errors.firstByRule('postalCode', 'regex')"
        >Postal Code is invalid.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('phone') }">
        <label for="phone">Phone</label>
        <md-input
          name="phone"
          v-model="contact.phone"
          :disabled="sending"
          v-validate="{ required: true, regex: getPhoneRegex() }"
        />
        <span class="md-error" v-if="errors.firstByRule('phone', 'required')">Phone is required.</span>
        <span class="md-error" v-if="errors.firstByRule('phone', 'regex')">Phone is invalid.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('gender') }">
        <label for="gender">Gender</label>
        <md-select
          name="gender"
          v-model="contact.gender"
          md-dense
          :disabled="sending"
          v-validate.continues="'required'"
        >
          <md-option value="male">Male</md-option>
          <md-option value="female">Female</md-option>
        </md-select>
        <span class="md-error" v-if="errors.firstByRule('gender', 'required')">Gender is required.</span>
      </md-field>
<br />
<md-field :class="{ 'md-invalid': errors.has('age') }">
        <label for="age">Age</label>
        <md-input
          type="number"
          id="age"
          name="age"
          autocomplete="age"
          v-model="contact.age"
          :disabled="sending"
          v-validate="'required|between:0,200'"
        />
        <span class="md-error" v-if="errors.firstByRule('age', 'required')">Age is required.</span>
        <span class="md-error" v-if="errors.firstByRule('age', 'between')">Age must be 0 and 200.</span>
      </md-field>
      <br />
      <md-field :class="{ 'md-invalid': errors.has('email') }">
        <label for="email">Email</label>
        <md-input
          type="email"
          name="email"
          autocomplete="email"
          v-model="contact.email"
          :disabled="sending"
          v-validate="'required|email'"
        />
        <span class="md-error" v-if="errors.firstByRule('email', 'required')">Email is required.</span>
        <span class="md-error" v-if="errors.firstByRule('email', 'email')">Email is invalid.</span>
      </md-field>
<md-progress-bar md-mode="indeterminate" v-if="sending" />
<md-button type="submit" class="md-raised">{{editing ? 'Edit':'Create'}} Contact</md-button>
    </form>
  </div>
</template>
<script>
import { COUNTRIES } from "@/helpers/exports";
import { contactMixin } from "@/mixins/contactMixin";
export default {
  name: "ContactForm",
  mixins: [contactMixin],
  props: {
    editing: Boolean,
    contactId: Number
  },
  computed: {
    isFormDirty() {
      return Object.keys(this.fields).some(key => this.fields[key].dirty);
    },
    contacts() {
      return this.$store.state.contacts;
    }
  },
  data() {
    return {
      sending: false,
      contact: {},
      countries: COUNTRIES.map(c => c.name)
    };
  },
  beforeMount() {
    this.contact = this.contacts.find(c => c.id == this.contactId) || {};
  },
  methods: {
    async save(evt) {
      evt.preventDefault();
      try {
        const result = await this.$validator.validateAll();
        if (!result) {
          return;
        }
        if (this.editing) {
          await this.updateContact(this.contact, this.contactId);
          await this.getAllContacts();
          this.$emit("contactSaved");
        } else {
          await this.addContact(this.contact);
          await this.getAllContacts();
          this.$router.push("/");
        }
      } catch (ex) {
        console.log(ex);
      }
    },
    async getAllContacts() {
      try {
        const response = await this.getContacts();
        this.$store.commit("setContacts", response.data);
      } catch (ex) {
        console.log(ex);
      }
    },
    getPostalCodeRegex() {
      if (this.contact.country == "United States") {
        return /^[0-9]{5}(?:-[0-9]{4})?$/;
      } else if (this.contact.country == "Canada") {
        return /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/;
      }
      return /./;
    },
    getPhoneRegex() {
      if (["United States", "Canada"].includes(this.contact.country)) {
        return /^[2-9]\d{2}[2-9]\d{2}\d{4}$/;
      }
      return /./;
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.contact-form {
  margin: 0 auto;
  width: 90%;
}
</style>
```

In the file above, we have the contact form for adding and updating contacts in our address book. It is where VeeValidate is used the most.

Notice that, in most input controls within the `form` tag, we have the `v-validate` prop. This is where we specify what kind of input the control accepts.

`required` means that the form field is required. `regex` means we validate against a specified regular expression. This allows for custom form validation where there are no available built-in rules for VeeValidate, or when you need to validate the field differently, depending on the value of another field.

For example, for phone number, we have this function:

```javascript
getPhoneRegex() {
  if (["United States", "Canada"].includes(this.contact.country)){
    return /^[2-9]\d{2}[2-9]\d{2}\d{4}$/;
  }
  return /./;
}
```

It allows us to validate the number to see if it matches the North American telephone format when we enter United States or Canada. Otherwise, we let people enter whatever they want.

Similarly, for postal code, we have:

```javascript
getPostalCodeRegex() {
  if (this.contact.country == "United States") {
    return /^[0-9]{5}(?:-[0-9]{4})?$/;
  } else if (this.contact.country == "Canada") {
    return /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/;
  }
  return /./;
}
```

This allows us to check for US zip codes and Canadian postal codes.

To display errors, we can check if errors exist for a form field, then display errors.

For example, for first name, we have:

```html
<span class="md-error" v-if="errors.has('firstName')">First Name is required.</span>
```

`errors.has(‘firstName’)` checks if the first name field meets the validation criteria that we specified.

As we’re checking whether it’s filled in, there is only one possible error, so we can just display the only error when `errors.has(‘firstName’)` returns `true` . For something more complex, like phone, we have:

```html
<span class="md-error" v-if="errors.firstByRule('phone', 'required')">Phone is required.</span>
<span class="md-error" v-if="errors.firstByRule('phone', 'regex')">Phone is invalid.</span>
```

This allows us to check for each validation rule separately. For the phone number field, we have to check if it’s filled in and if what’s filled in has a valid format. The `errors.firstByRule` function allows us to do that.

`errors.firstByRule(‘phone’, ‘required’)` returns `true` if the field is not filled in and `false` otherwise. `errors.firstByRule(‘phone’, ‘regex’)` returns `true` is the phone number is filled in in an incorrect format and `false` otherwise.

VeeValidate provides a `this.field` object to your component, so we can check if fields are dirty, meaning whether they have been manipulated or not, by adding:

```javascript
Object.keys(this.fields).some(key => this.fields[key].dirty)

```

Each property is a form field and each property of the `this.fields` object has a `dirty` property, so we can check if the fields are manipulated or not.

In the `save` function of the `methods` object, we have:

```javascript
async save(evt) {
  evt.preventDefault();
  try {
    const result = await this.$validator.validateAll();
    if (!result) {
      return;
    }
    if (this.editing) {
      await this.updateContact(this.contact, this.contactId);
      await this.getAllContacts();
      this.$emit("contactSaved");
    } else {
      await this.addContact(this.contact);
      await this.getAllContacts();
      this.$router.push("/");
    }
  } catch (ex) {
    console.log(ex);
  }
},
```

We need `evt.preventDefault()` to stop the form from submitting the normal way, i.e. without calling the Ajax code below.

`this.$validator.validateAll()` validates the form. `this.$validator` is an object provided by VeeValidate. It returns a promise, so we need the function to be `async` and we need `await` before the function call.

If `result` is falsy, the form validation failed, so we run `return` to stop the rest of the function from executing. Finally, if the form fields are all valid, we can submit.

As this form is used for both adding and editing contacts, we have to check which action we’re doing. If we edit, we call `await this.updateContact(this.contact, this.contactId);` to update our contact.

Otherwise, we add a contact, so we call `await this.addContact(this.contact);` In either case, we call `await this.getAllContacts();` to refresh our contacts and put them in the store.

If we are adding, we will redirect to the home page at the end by calling `this.$router.push(“/”);`.

`this.updateContact`, `this.addContact`, and `this.getAllContacts` are all from our `contactMixin` which we’ll write shortly.

In `Map.vue`, we put:

```javascript
<template>
  <GmapMap :center="{lat, lng}" :zoom="15" style="width: 70vw; height: 300px">
    <GmapMarker
      :position="google && new google.maps.LatLng(lat, lng)"
      :clickable="true"
      :draggable="true"
    />
  </GmapMap>
</template>
<script>
import { gmapApi } from "vue2-google-maps";
export default {
  name: "Map",
  props: {
    lat: Number,
    lng: Number
  },
  computed: {
    google: gmapApi
  }
};
</script>
<style lang="scss" scoped>
</style>
```

Next, we write some helper code. Create a folder, called `helpers`, and within it, make a file called `exports.js` and put in the following:

```json
export const COUNTRIES = [
    { "name": "Afghanistan", "code": "AF" },
    { "name": "Aland Islands", "code": "AX" },
    { "name": "Albania", "code": "AL" },
    { "name": "Algeria", "code": "DZ" },
    { "name": "American Samoa", "code": "AS" },
    { "name": "AndorrA", "code": "AD" },
    { "name": "Angola", "code": "AO" },
    { "name": "Anguilla", "code": "AI" },
    { "name": "Antarctica", "code": "AQ" },
    { "name": "Antigua and Barbuda", "code": "AG" },
    { "name": "Argentina", "code": "AR" },
    { "name": "Armenia", "code": "AM" },
    { "name": "Aruba", "code": "AW" },
    { "name": "Australia", "code": "AU" },
    { "name": "Austria", "code": "AT" },
    { "name": "Azerbaijan", "code": "AZ" },
    { "name": "Bahamas", "code": "BS" },
    { "name": "Bahrain", "code": "BH" },
    { "name": "Bangladesh", "code": "BD" },
    { "name": "Barbados", "code": "BB" },
    { "name": "Belarus", "code": "BY" },
    { "name": "Belgium", "code": "BE" },
    { "name": "Belize", "code": "BZ" },
    { "name": "Benin", "code": "BJ" },
    { "name": "Bermuda", "code": "BM" },
    { "name": "Bhutan", "code": "BT" },
    { "name": "Bolivia", "code": "BO" },
    { "name": "Bosnia and Herzegovina", "code": "BA" },
    { "name": "Botswana", "code": "BW" },
    { "name": "Bouvet Island", "code": "BV" },
    { "name": "Brazil", "code": "BR" },
    { "name": "British Indian Ocean Territory", "code": "IO" },
    { "name": "Brunei Darussalam", "code": "BN" },
    { "name": "Bulgaria", "code": "BG" },
    { "name": "Burkina Faso", "code": "BF" },
    { "name": "Burundi", "code": "BI" },
    { "name": "Cambodia", "code": "KH" },
    { "name": "Cameroon", "code": "CM" },
    { "name": "Canada", "code": "CA" },
    { "name": "Cape Verde", "code": "CV" },
    { "name": "Cayman Islands", "code": "KY" },
    { "name": "Central African Republic", "code": "CF" },
    { "name": "Chad", "code": "TD" },
    { "name": "Chile", "code": "CL" },
    { "name": "China", "code": "CN" },
    { "name": "Christmas Island", "code": "CX" },
    { "name": "Cocos (Keeling) Islands", "code": "CC" },
    { "name": "Colombia", "code": "CO" },
    { "name": "Comoros", "code": "KM" },
    { "name": "Congo", "code": "CG" },
    { "name": "Congo, The Democratic Republic of the", "code": "CD" },
    { "name": "Cook Islands", "code": "CK" },
    { "name": "Costa Rica", "code": "CR" },
    {
        "name": "Cote D\"Ivoire", "code": "CI"
    },
    { "name": "Croatia", "code": "HR" },
    { "name": "Cuba", "code": "CU" },
    { "name": "Cyprus", "code": "CY" },
    { "name": "Czech Republic", "code": "CZ" },
    { "name": "Denmark", "code": "DK" },
    { "name": "Djibouti", "code": "DJ" },
    { "name": "Dominica", "code": "DM" },
    { "name": "Dominican Republic", "code": "DO" },
    { "name": "Ecuador", "code": "EC" },
    { "name": "Egypt", "code": "EG" },
    { "name": "El Salvador", "code": "SV" },
    { "name": "Equatorial Guinea", "code": "GQ" },
    { "name": "Eritrea", "code": "ER" },
    { "name": "Estonia", "code": "EE" },
    { "name": "Ethiopia", "code": "ET" },
    { "name": "Falkland Islands (Malvinas)", "code": "FK" },
    { "name": "Faroe Islands", "code": "FO" },
    { "name": "Fiji", "code": "FJ" },
    { "name": "Finland", "code": "FI" },
    { "name": "France", "code": "FR" },
    { "name": "French Guiana", "code": "GF" },
    { "name": "French Polynesia", "code": "PF" },
    { "name": "French Southern Territories", "code": "TF" },
    { "name": "Gabon", "code": "GA" },
    { "name": "Gambia", "code": "GM" },
    { "name": "Georgia", "code": "GE" },
    { "name": "Germany", "code": "DE" },
    { "name": "Ghana", "code": "GH" },
    { "name": "Gibraltar", "code": "GI" },
    { "name": "Greece", "code": "GR" },
    { "name": "Greenland", "code": "GL" },
    { "name": "Grenada", "code": "GD" },
    { "name": "Guadeloupe", "code": "GP" },
    { "name": "Guam", "code": "GU" },
    { "name": "Guatemala", "code": "GT" },
    { "name": "Guernsey", "code": "GG" },
    { "name": "Guinea", "code": "GN" },
    { "name": "Guinea-Bissau", "code": "GW" },
    { "name": "Guyana", "code": "GY" },
    { "name": "Haiti", "code": "HT" },
    { "name": "Heard Island and Mcdonald Islands", "code": "HM" },
    { "name": "Holy See (Vatican City State)", "code": "VA" },
    { "name": "Honduras", "code": "HN" },
    { "name": "Hong Kong", "code": "HK" },
    { "name": "Hungary", "code": "HU" },
    { "name": "Iceland", "code": "IS" },
    { "name": "India", "code": "IN" },
    { "name": "Indonesia", "code": "ID" },
    { "name": "Iran, Islamic Republic Of", "code": "IR" },
    { "name": "Iraq", "code": "IQ" },
    { "name": "Ireland", "code": "IE" },
    { "name": "Isle of Man", "code": "IM" },
    { "name": "Israel", "code": "IL" },
    { "name": "Italy", "code": "IT" },
    { "name": "Jamaica", "code": "JM" },
    { "name": "Japan", "code": "JP" },
    { "name": "Jersey", "code": "JE" },
    { "name": "Jordan", "code": "JO" },
    { "name": "Kazakhstan", "code": "KZ" },
    { "name": "Kenya", "code": "KE" },
    { "name": "Kiribati", "code": "KI" },
    {
        "name": "Korea, Democratic People\"S Republic of", "code": "KP"
    },
    { "name": "Korea, Republic of", "code": "KR" },
    { "name": "Kuwait", "code": "KW" },
    { "name": "Kyrgyzstan", "code": "KG" },
    {
        "name": "Lao People\"S Democratic Republic", "code": "LA"
    },
    { "name": "Latvia", "code": "LV" },
    { "name": "Lebanon", "code": "LB" },
    { "name": "Lesotho", "code": "LS" },
    { "name": "Liberia", "code": "LR" },
    { "name": "Libyan Arab Jamahiriya", "code": "LY" },
    { "name": "Liechtenstein", "code": "LI" },
    { "name": "Lithuania", "code": "LT" },
    { "name": "Luxembourg", "code": "LU" },
    { "name": "Macao", "code": "MO" },
    { "name": "Macedonia, The Former Yugoslav Republic of", "code": "MK" },
    { "name": "Madagascar", "code": "MG" },
    { "name": "Malawi", "code": "MW" },
    { "name": "Malaysia", "code": "MY" },
    { "name": "Maldives", "code": "MV" },
    { "name": "Mali", "code": "ML" },
    { "name": "Malta", "code": "MT" },
    { "name": "Marshall Islands", "code": "MH" },
    { "name": "Martinique", "code": "MQ" },
    { "name": "Mauritania", "code": "MR" },
    { "name": "Mauritius", "code": "MU" },
    { "name": "Mayotte", "code": "YT" },
    { "name": "Mexico", "code": "MX" },
    { "name": "Micronesia, Federated States of", "code": "FM" },
    { "name": "Moldova, Republic of", "code": "MD" },
    { "name": "Monaco", "code": "MC" },
    { "name": "Mongolia", "code": "MN" },
    { "name": "Montenegro", "code": "ME" },
    { "name": "Montserrat", "code": "MS" },
    { "name": "Morocco", "code": "MA" },
    { "name": "Mozambique", "code": "MZ" },
    { "name": "Myanmar", "code": "MM" },
    { "name": "Namibia", "code": "NA" },
    { "name": "Nauru", "code": "NR" },
    { "name": "Nepal", "code": "NP" },
    { "name": "Netherlands", "code": "NL" },
    { "name": "Netherlands Antilles", "code": "AN" },
    { "name": "New Caledonia", "code": "NC" },
    { "name": "New Zealand", "code": "NZ" },
    { "name": "Nicaragua", "code": "NI" },
    { "name": "Niger", "code": "NE" },
    { "name": "Nigeria", "code": "NG" },
    { "name": "Niue", "code": "NU" },
    { "name": "Norfolk Island", "code": "NF" },
    { "name": "Northern Mariana Islands", "code": "MP" },
    { "name": "Norway", "code": "NO" },
    { "name": "Oman", "code": "OM" },
    { "name": "Pakistan", "code": "PK" },
    { "name": "Palau", "code": "PW" },
    { "name": "Palestinian Territory, Occupied", "code": "PS" },
    { "name": "Panama", "code": "PA" },
    { "name": "Papua New Guinea", "code": "PG" },
    { "name": "Paraguay", "code": "PY" },
    { "name": "Peru", "code": "PE" },
    { "name": "Philippines", "code": "PH" },
    { "name": "Pitcairn", "code": "PN" },
    { "name": "Poland", "code": "PL" },
    { "name": "Portugal", "code": "PT" },
    { "name": "Puerto Rico", "code": "PR" },
    { "name": "Qatar", "code": "QA" },
    { "name": "Reunion", "code": "RE" },
    { "name": "Romania", "code": "RO" },
    { "name": "Russian Federation", "code": "RU" },
    { "name": "RWANDA", "code": "RW" },
    { "name": "Saint Helena", "code": "SH" },
    { "name": "Saint Kitts and Nevis", "code": "KN" },
    { "name": "Saint Lucia", "code": "LC" },
    { "name": "Saint Pierre and Miquelon", "code": "PM" },
    { "name": "Saint Vincent and the Grenadines", "code": "VC" },
    { "name": "Samoa", "code": "WS" },
    { "name": "San Marino", "code": "SM" },
    { "name": "Sao Tome and Principe", "code": "ST" },
    { "name": "Saudi Arabia", "code": "SA" },
    { "name": "Senegal", "code": "SN" },
    { "name": "Serbia", "code": "RS" },
    { "name": "Seychelles", "code": "SC" },
    { "name": "Sierra Leone", "code": "SL" },
    { "name": "Singapore", "code": "SG" },
    { "name": "Slovakia", "code": "SK" },
    { "name": "Slovenia", "code": "SI" },
    { "name": "Solomon Islands", "code": "SB" },
    { "name": "Somalia", "code": "SO" },
    { "name": "South Africa", "code": "ZA" },
    { "name": "South Georgia and the South Sandwich Islands", "code": "GS" },
    { "name": "Spain", "code": "ES" },
    { "name": "Sri Lanka", "code": "LK" },
    { "name": "Sudan", "code": "SD" },
    { "name": "Suriname", "code": "SR" },
    { "name": "Svalbard and Jan Mayen", "code": "SJ" },
    { "name": "Swaziland", "code": "SZ" },
    { "name": "Sweden", "code": "SE" },
    { "name": "Switzerland", "code": "CH" },
    { "name": "Syrian Arab Republic", "code": "SY" },
    { "name": "Taiwan, Province of China", "code": "TW" },
    { "name": "Tajikistan", "code": "TJ" },
    { "name": "Tanzania, United Republic of", "code": "TZ" },
    { "name": "Thailand", "code": "TH" },
    { "name": "Timor-Leste", "code": "TL" },
    { "name": "Togo", "code": "TG" },
    { "name": "Tokelau", "code": "TK" },
    { "name": "Tonga", "code": "TO" },
    { "name": "Trinidad and Tobago", "code": "TT" },
    { "name": "Tunisia", "code": "TN" },
    { "name": "Turkey", "code": "TR" },
    { "name": "Turkmenistan", "code": "TM" },
    { "name": "Turks and Caicos Islands", "code": "TC" },
    { "name": "Tuvalu", "code": "TV" },
    { "name": "Uganda", "code": "UG" },
    { "name": "Ukraine", "code": "UA" },
    { "name": "United Arab Emirates", "code": "AE" },
    { "name": "United Kingdom", "code": "GB" },
    { "name": "United States", "code": "US" },
    { "name": "United States Minor Outlying Islands", "code": "UM" },
    { "name": "Uruguay", "code": "UY" },
    { "name": "Uzbekistan", "code": "UZ" },
    { "name": "Vanuatu", "code": "VU" },
    { "name": "Venezuela", "code": "VE" },
    { "name": "Viet Nam", "code": "VN" },
    { "name": "Virgin Islands, British", "code": "VG" },
    { "name": "Virgin Islands, U.S.", "code": "VI" },
    { "name": "Wallis and Futuna", "code": "WF" },
    { "name": "Western Sahara", "code": "EH" },
    { "name": "Yemen", "code": "YE" },
    { "name": "Zambia", "code": "ZM" },
    { "name": "Zimbabwe", "code": "ZW" }
]

export const GOOGLE_API_KEY = 'your api key';
```

This provides the countries that we reference in `ContactForm.vue` and the API key from Google that we will use later.

Next, we add our mixin to manipulate the contacts by communicating with the back end. We make a folder called `mixins` and create a file called `contactMixin.js` within it.

In the file, we put:

```javascript
const axios = require('axios');
const apiUrl = 'http://localhost:3000';
export const contactMixin = {
    methods: {
        getContacts() {
            return axios.get(`${apiUrl}/contacts`);
        },
        addContact(data) {
            return axios.post(`${apiUrl}/contacts`, data);
        },
        updateContact(data, id) {
            return axios.put(`${apiUrl}/contacts/${id}`, data);
        },
        deleteContact(id) {
            return axios.delete(`${apiUrl}/contacts/${id}`);
        }
    }
}
```

This will let us include the functions in the `methods` object of the component object we include, or mixin with, by putting it in the `mixins` array of our component object.

Similarly, we need another mixin for the API call to the Google Maps API.

We create a file called `mapMixin.js` within the same folder as `contactMixin.js` and put:

```javascript
import { GOOGLE_API_KEY } from '@/helpers/exports'
const axios = require('axios');
const mapUrl = 'https://maps.googleapis.com/maps/api/geocode/json?address=';
export const mapMixin = {
    methods: {
        getLatLng(address) {
            return axios.get(`${mapUrl}${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`);
        }
    }
}
```

The function make a `GET` request to get the latitude and longitude from the address as described in the [Google documentation](https://developers.google.com/maps/documentation/geocoding/start).

Next, we add our pages.

To do this, create a `views` folder, if it doesn’t already exist, and add `ContactFormPage.vue`. In there, put:

```javascript
<template>
  <div class="about">
    <ContactForm :edit="false" />
  </div>
</template>
<script>
// @ is an alias to /src
import ContactForm from "@/components/ContactForm.vue";
export default {
  name: "ContactFormPage",
  components: {
    ContactForm
  }
};
</script>
```

This displays the `ContactForm` component we created. We set the `:edit` prop to `false` so that it’ll add our contact instead of editing.

Next, we add the home page to display a list of contacts. In the `views` folder, we add a file called `HomePage.vue` if it doesn’t already exist. Then, in there, we put:

```javascript
<template>
  <div class="home">
    <div class="center">
      <h1>Address Book Home</h1>
    </div>
    <md-table>
      <md-table-row>
        <md-table-head md-numeric>ID</md-table-head>
        <md-table-head>First Name</md-table-head>
        <md-table-head>Last Name</md-table-head>
        <md-table-head>Address Line 1</md-table-head>
        <md-table-head>Address Line 2</md-table-head>
        <md-table-head>City</md-table-head>
        <md-table-head>Country</md-table-head>
        <md-table-head>Postal Code</md-table-head>
        <md-table-head>Gender</md-table-head>
        <md-table-head>Age</md-table-head>
        <md-table-head>Email</md-table-head>
        <md-table-head></md-table-head>
        <md-table-head></md-table-head>
        <md-table-head></md-table-head>
      </md-table-row>
      <md-table-row v-for="c in contacts" :key="c.id">
        <md-table-cell md-numeric>{{c.id}}</md-table-cell>
        <md-table-cell>{{c.firstName}}</md-table-cell>
        <md-table-cell>{{c.lastName}}</md-table-cell>
        <md-table-cell>{{c.addressLineOne}}</md-table-cell>
        <md-table-cell>{{c.addressLineTwo}}</md-table-cell>
        <md-table-cell>{{c.city}}</md-table-cell>
        <md-table-cell>{{c.country}}</md-table-cell>
        <md-table-cell>{{c.postalCode}}</md-table-cell>
        <md-table-cell>{{c.gender}}</md-table-cell>
        <md-table-cell md-numeric>{{c.age}}</md-table-cell>
        <md-table-cell>{{c.email}}</md-table-cell>
        <md-table-cell>
          <md-button class="md-primary" @click="openMap(c)">Open Map</md-button>
        </md-table-cell>
        <md-table-cell>
          <md-button class="md-primary" @click="selectedContactId = c.id; showDialog = true">Edit</md-button>
        </md-table-cell>
        <md-table-cell>
          <md-button class="md-accent" @click="removeContact(c.id)">Delete</md-button>
        </md-table-cell>
      </md-table-row>
    </md-table>
<md-dialog :md-active.sync="showDialog">
      <md-dialog-content>
        <ContactForm
          :editing="true"
          :contactId="selectedContactId"
          @contactSaved="selectedContactId = undefined; showDialog = false"
        />
      </md-dialog-content>
    </md-dialog>
    <md-dialog :md-active.sync="showMapDialog">
      <md-dialog-title>Map</md-dialog-title>
      <Map :lat='lat' :lng='lng' />
      <md-dialog-actions>
        <md-button class="md-primary" @click="showMapDialog = false">Close</md-button>
      </md-dialog-actions>
    </md-dialog>
  </div>
</template>
<script>
import { contactMixin } from "@/mixins/contactMixin";
import { mapMixin } from "@/mixins/mapMixin";
import ContactForm from "@/components/ContactForm.vue";
import Map from "@/components/Map.vue";
export default {
  name: "HomePage",
  mixins: [contactMixin, mapMixin],
  components: {
    ContactForm,
    Map
  },
  props: {
    editing: Boolean,
    id: Number
  },
  computed: {
    contacts() {
      return this.$store.state.contacts;
    }
  },
  data() {
    return {
      showDialog: false,
      selectedContactId: undefined,
      showMapDialog: false,
      lat: 0,
      lng: 0
    };
  },
  beforeMount() {
    this.getAllContacts();
  },
  methods: {
    async getAllContacts() {
      try {
        const response = await this.getContacts();
        this.$store.commit("setContacts", response.data);
      } catch (ex) {
        console.log(ex);
      }
    },
    async removeContact(id) {
      try {
        await this.deleteContact(id);
        await this.getAllContacts();
      } catch (ex) {
        console.log(ex);
      }
    },
    async openMap(contact) {
      try {
        const address = `${contact.addressLineOne}, ${contact.addressLineTwo}, ${contact.city}, ${contact.country}`;
        const response = await this.getLatLng(address);
        const loc = response.data.results[0].geometry.location
        this.lat = loc.lat;
        this.lng = loc.lng;
        this.showMapDialog = true;
      } catch (ex) {
        console.log(ex);
      }
    }
  }
};
</script>
<style scoped>
.md-dialog-container {
  padding: 20px;
}
.md-content.md-table.md-theme-default {
  width: 95%;
  margin: 0 auto;
}
</style>
```

We get our contacts during page load by calling the `this.getAllContacts` function in the `beforeMount` function.

Notice that we have `this.getContacts` function from our mixin. Mixins allow us to reuse code.

Code in our mixins cannot have the same name as the functions in our `methods` objects in our components because mixin functions hook straight into our `methods` as we exported an object with the `methods` field in our mixin code.

In the table, we have a button to let us open the dialog box to display the map of the location of the contact. We used the `Map` component that we added earlier by getting the address of the contact and then getting the latitude and longitude from it.

Then, pass the latitude and longitude into the `Map` component, which can be used by the Google Maps component to display the map.

Notice that we have the following in the code above:

```javascript
props: {  
  editing: Boolean,  
  id: Number  
}
```

This is where we check if the passed in props have the right type.

We only accept boolean for the `editing` prop and number for `id`. If we pass in anything else, we will get an error.

In `App.vue`, we add our menu and top bar by putting the following:

```javascript
<template>
  <div id="app">
    <md-toolbar class="md-accent">
      <md-button class="md-icon-button" @click="showNavigation = true">
        <md-icon>menu</md-icon>
      </md-button>
      <h3 class="md-title">Vee Validate Address Book App</h3>
    </md-toolbar>
    <md-drawer :md-active.sync="showNavigation" md-swipeable>
      <md-toolbar class="md-transparent" md-elevation="0">
        <span class="md-title">Vee Validate Address Book App</span>
      </md-toolbar>
<md-list>
        <md-list-item>
          <router-link to="/">
            <span class="md-list-item-text">Home</span>
          </router-link>
        </md-list-item>
<md-list-item>
          <router-link to="/contact">
            <span class="md-list-item-text">Add Contact</span>
          </router-link>
        </md-list-item>
      </md-list>
    </md-drawer>
<router-view />
  </div>
</template>
<script>
export default {
  name: "app",
  data: () => {
    return {
      showNavigation: false
    };
  }
};
</script>
<style lang="scss">
.center {
  text-align: center;
}
</style>
```

In `main.js`, we add our boilerplate code to include Vue Material and VeeValidate in our app:

```javascript
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.min.css'
import VeeValidate from 'vee-validate';
import * as VueGoogleMaps from 'vue2-google-maps'
import { GOOGLE_API_KEY } from '@/helpers/exports'
Vue.use(VeeValidate);
Vue.use(VueMaterial);
Vue.use(VueGoogleMaps, {
  load: {
    key: GOOGLE_API_KEY,
    v: '3.26'
  }
})
Vue.config.productionTip = false
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
```

In `router.js`, we add our routes so we can see our pages:

```javascript
import Vue from 'vue'
import Router from 'vue-router'
import HomePage from './views/HomePage.vue'
import ContactFormPage from './views/ContactFormPage.vue'
Vue.use(Router)
export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomePage
    },
    {
      path: '/contact',
      name: 'contact',
      component: ContactFormPage
    }
  ]
})
```

In `store.js`, we put:

```javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    contacts: []
  },
  mutations: {
    setContacts(state, payload) {
      state.contacts = payload;
    }
  },
  actions: {}
})
```

To store our contact in a place where all components can access it.

The store uses the Vuex library so that we have a `this.$store` object to call our mutation with the `this.$store.commit` function and get the latest data from the store via the `computed` property of our component object, like so:

```javascript
contacts() {  
  return this.$store.state.contacts;  
}
```

Finally, in `index.html`, we put:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons">
  <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/vue-material.min.css">
  <link rel="stylesheet" href="https://unpkg.com/vue-material/dist/theme/default.css">
  <title>Vue Google Maps Address Book App</title>
</head>
<body>
  <noscript>
    <strong>We're sorry but vee-validate-tutorial-app doesn't work properly without JavaScript enabled. Please enable it
      to continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>
</html>
```

To add the Roboto font and Material icons to our app.

Now we are ready to start our JSON server. Go to the project folder and run `json-server — watch db.json` to start the server. It will allow us to call these routes without any configuration:

```
GET    /contacts  
POST   /contacts  
PUT    /contacts/1  
DELETE /contacts/1
```

These are all the routes we need. Data will be saved to `db.json` of the folder that we’re in, which should be our app’s project folder.