Categories
PrimeVue

Getting Started with Vue 3 Development with the PrimeVue Framework

PrimeVue is a UI framework that’s compatible with Vue 3.

In this article, we’ll look at how to get started with developing Vue 3 apps with PrimeVue.

Getting Started

We install the required packages by running:

npm install primevue@^3.1.1 --save
npm install primeicons --save

This will install the library with all the components and the icons.

Next, we add the PrimeVue plugin into our app:

main.js

import { createApp } from "vue";
import App from "./App.vue";
import PrimeVue from "primevue/config";
import InputText from "primevue/inputtext";
import 'primevue/resources/primevue.min.css'
import 'primevue/resources/themes/bootstrap4-light-blue/theme.css'
import 'primeicons/primeicons.css'

const app = createApp(App);
app.use(PrimeVue);
app.component("InputText", InputText);
app.mount("#app");

App.vue

<template>
  <div>
    <InputText type="text" v-model="text" />
    <p>{{text}}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      text: "",
    };
  },
};
</script>

In main.js m we call app.use to add the plugin.

Then we call app.component to register the InputText component.

import ‘primevue/resources/primevue.min.css’ imports the core CSS.

import ‘primevue/resources/themes/bootstrap4-light-blue/theme.css’ is the CSS for the theme.

import ‘primeicons/primeicons.css’ add the icons.

We can also include the script tag for the individual components:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>calendar demo</title>
    <link
      href="https://unpkg.com/primevue/resources/themes/saga-blue/theme.css "
      rel="stylesheet"
    />
    <link
      href="https://unpkg.com/primevue/resources/primevue.min.css "
      rel="stylesheet"
    />
    <link
      href="https://unpkg.com/primeicons/primeicons.css "
      rel="stylesheet"
    />
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/primevue@3.1.1/components/inputtext/inputtext.umd.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p-inputtext v-model="text"></p-inputtext>
      <p>{{text}}</p>
    </div>

    <script>
      Vue.createApp({
        data() {
          return {
            text: ""
          };
        },
        components: {
          "p-inputtext": inputtext
        }
      }).mount("#app");
    </script>
  </body>
</html>

We add the script tags for Vue 3 and PrimeVue’s inputtext component.

Then we can use it in our code.

PrimeFlex

We can install the primeflex package to add CSS to let us create layouts easily without writing everything from scratch.

To install it, we run:

npm install primeflex --save

Then we cal add the CSS by writing:

main.js

import { createApp } from "vue";
import App from "./App.vue";
import PrimeVue from "primevue/config";
import InputText from "primevue/inputtext";
import 'primeflex/primeflex.css';

const app = createApp(App);
app.use(PrimeVue);
app.component("InputText", InputText);
app.mount("#app");

Now we can use the classes by writing:

App.vue

<template>
  <div class="card">
    <InputText type="text" v-model="text" class="p-ml-2 p-d-inline" />
    <p>{{ text }}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      text: "",
    };
  },
};
</script>

p-ml-2 adds some margin on the left side.

p-d-inline makes the input display inline.

We can add shadows with the p-shadow-{x} class, where x can be 1 to 24.

p-d-flex lets us create a flex layout.

p-d-inline-flex lets us create an inline flex layout.

p-flex-{direction} lets us set the flex-direction. Where direction can be one of:

  • row (default)
  • row-reverse
  • column
  • column-reverse

The order of items inside a flex container can be changed with the p-order-{value} classes.

We can also add layouts that change according to breakpoints:

<template>
  <div class="p-d-flex">
    <div class="p-mr-2 p-order-3 p-order-md-2">Item 1</div>
    <div class="p-mr-2 p-order-1 p-order-md-3">Item 2</div>
    <div class="p-mr-2 p-order-2 p-order-md-1">Item 3</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

We change the order depending on whether the breakpoint is md and up or otherwise.

If it’s md or higher, then we get:

Item 3 Item 1 Item 2

Otherwise, we get:

Item 2 Item 3 Item 1

Conclusion

PrimeVue is one of the earliest UI frameworks to be compatible with Vue 3.

Categories
PrimeVue

Vue 3 Development with the PrimeVue Framework — AutoComplete, Calendar, and Datepicker

PrimeVue is a UI framework that’s compatible with Vue 3.

In this article, we’ll look at how to get started with developing Vue 3 apps with PrimeVue.

AutoComplete

We can add the AutoComplete that comes with PrimeVue to add a autocomplete dropdown.

For instance, we can write:

main.js

import { createApp } from "vue";
import App from "./App.vue";
import PrimeVue from "primevue/config";
import AutoComplete from "primevue/autocomplete";

const app = createApp(App);
app.use(PrimeVue);
app.component("AutoComplete", AutoComplete);
app.mount("#app");

App.vue

<template>
  <div>
    <AutoComplete
      v-model="fruit"
      :suggestions="filteredFruits"
      [@complete](https://medium.com/r/?url=http%3A%2F%2Ftwitter.com%2Fcomplete "Twitter profile for @complete")="search($event)"
      field="fruit"
    >
      <template #item="{ item }">
        <div>
          <div>{{ item }}</div>
        </div>
      </template>
    </AutoComplete>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      fruit: null,
      filteredFruits: ["apple", "orange", "grape"],
      fruits: ["apple", "orange", "grape"],
    };
  },
  methods: {
    search({ query }) {
      if (!query.trim()) {
        this.filteredFruits = [...this.fruits];
        return;
      }
      this.filteredFruits = this.fruits.filter((f) => f.includes(query));
    },
  },
};
</script>

In main.js , we import the Autocomplete component.

The suggestions prop has an array of suggestions

The suggestion items are rendered in the item slot.

We listen to the complete event to run the search function to get the filtered items.

The query property in the first parameter of search has the search query we typed in.

Calendar

PrimeVue comes with a calendar component that renders a date picker.

To use it, we write:

main.js

import { createApp } from "vue";
import App from "./App.vue";
import PrimeVue from "primevue/config";
import Calendar from 'primevue/calendar';

const app = createApp(App);
app.use(PrimeVue);
app.component("Calendar", Calendar);
app.mount("#app");

App.vue

<template>
  <div>
    <Calendar v-model="value" />
    <p>{{ value }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: null,
    };
  },
  methods: {},
};
</script>

We import and register the Calendar component into our app.

Then we can use the Calendar component in our components to render an input that shows a date picker popup when we click it.

We bind the value we pick to the value reactive property with v-model .

We can change the selection mode to let us select multiple dates or a date range.

For example, if we write:

<template>
  <div>
    <Calendar v-model="value" selectionMode="multiple" />
    <p>{{ value }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: null,
    };
  },
  methods: {},
};
</script>

Then we can select multiple dates.

value is an array of dates instead of a single date.

If we change selectionMode to 'range' :

<template>
  <div>
    <Calendar v-model="value" selectionMode="range" />
    <p>{{ value }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: null,
    };
  },
  methods: {},
};
</script>

then we can select 2 dates, which is the start and end date respectively.

We can change the format of the selected date with the dateFormat prop:

<template>
  <div>
    <Calendar v-model="value" dateFormat="dd.mm.yy" />
    <p>{{ value }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      value: null,
    };
  },
  methods: {},
};
</script>

The format code can have the following parts:

  • d — day of the month (no leading zero)
  • dd — day of the month (2 digits)
  • o — day of the year (no leading zeros)
  • oo — day of the year (3 digits)
  • D — day name short
  • DD — day name long
  • m — month of the year (no leading zero)
  • mm — month of the year (2 digits)
  • M — month name short
  • MM — month name long
  • y — year (2 digits)
  • yy — year (4 digits)
  • @ — Unix timestamp (ms since 01/01/1970)
  • ! — Windows ticks (100ns since 01/01/0001)
  • ‘…’ — literal text
  • ‘’ — single-quote
  • anything else — literal text

Conclusion

We can add autocomplete dropdowns and date pickers easily into our Vue 3 app with PrimeVue.

Categories
PrimeVue

Vue 3 Development with the PrimeVue Framework — Offset and Text Styles

PrimeVue is a UI framework that’s compatible with Vue 3.

In this article, we’ll look at how to get started with developing Vue 3 apps with PrimeVue.

Offsets

We can add margins to columns.

To do this, we can write:

<template>
  <div class="p-grid">
    <div class="p-col-4">4</div>
    <div class="p-col-4 p-offset-4">4</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

The p-offset-4 class lets us shift the right column to 4 columns to the right.

Nested Grid

Grids can be nested with the nested-grid class:

<template>
  <div class="p-grid nested-grid">
    <div class="p-col-8">
      <div class="p-grid">
        <div class="p-col-6">6</div>
        <div class="p-col-6">6</div>
        <div class="p-col-8">8</div>
      </div>
    </div>
    <div class="p-col-4">4</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Gutter

We can remove the margins for a grid column with the p-nogutter class:

<template>
  <div class="p-grid p-nogutter">
    <div class="p-col">1</div>
    <div class="p-col p-nogutter">2</div>
    <div class="p-col">3</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Margins and Padding

The PrimeVue’s PrimeFlex library comes with classes for adding margins and padding.

The general format for the class is p-{property}{position}-{value}

property can be one of:

  • m — margin
  • p — padding

position can be one of:

  • t: top
  • b: bottom
  • l: left
  • r: right
  • x: left and right
  • y: top and bottom
  • blank: all sides

value can be one of:

  • 0: $spacer * 0
  • 1: $spacer * .25
  • 2: $spacer * .5
  • 3: $spacer * 1
  • 4: $spacer * 1.5
  • 5: $spacer * 2
  • 6: $spacer * 3
  • auto: auto margin

For example, we can write:

<template>
  <div>
    <div class="p-mb-2">Margin bottom 2</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

to add bottom margins to the grid.

We can also add padding and margin according to breakpoints by writing:

<template>
  <div>
    <div class="p-m-1 p-p-1 p-m-lg-3 p-b-lg-3">padding and margin</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

lg is the breakpoint. And classes with them are applied when the screen is wide enough to hit the lg breakpoint.

Text Alignment

PrimeFlex also comes with classes to align text.

The general format of the classes is p-text-{value} , where value can be one of:

  • left
  • center
  • right
  • justify

For instance, we can write:

<template>
  <div>
    <div class="p-text-left">Left</div>
    <div class="p-text-center">Center</div>
    <div class="p-text-right">Right</div>
    <div class="p-text-justify">Justify</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Text Wrap

We can add classes to change how text is wrapped with one of the following classes:

  • p-text-nowrap
  • p-text-wrap
  • p-text-truncate

Transform

We can change the capitalization of text with the following classes:

  • p-text-lowercase
  • p-text-uppercase
  • p-text-capitalize

Text Style

We can change the text style with the following classes:

  • p-text-bold
  • p-text-normal
  • p-text-light
  • p-text-italic

Conclusion

We can style text and change column options with the PrimeFlex library.

Categories
PrimeVue

Vue 3 Development with the PrimeVue Framework — Form and Grid Layouts

PrimeVue is a UI framework that’s compatible with Vue 3.

In this article, we’ll look at how to get started with developing Vue 3 apps with PrimeVue.

Form Layouts

The PrimeFlex library that’s part of the PrimeVue framework comes with classes for creating form layouts.

For example, we can use the p-field class by writing:

<template>
  <div class="p-fluid">
    <div class="p-field">
      <label for="firstname1">First name</label>
      <InputText id="firstname1" type="text" />
    </div>
    <div class="p-field">
      <label for="lastname1">Last name</label>
      <InputText id="lastname1" type="text" />
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

We use it to layout fields.

Also, we can write:

<template>
  <div class="p-fluid p-formgrid p-grid">
    <div class="p-field p-col">
      <label for="firstname">First name</label>
      <InputText id="firstname" type="text" />
    </div>
    <div class="p-field p-col">
      <label for="lastname">Last name</label>
      <InputText id="lastname" type="text" />
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

to separate the screen with columns and add one field to each.

p-col lets us the screen into columns.

We can add inline items by writing:

<template>
  <div class="p-formgroup-inline">
    <div class="p-field">
      <label for="firstname" class="p-sr-only">First name</label>
      <InputText id="firstname" type="text" placeholder="Firstname" />
    </div>
    <div class="p-field">
      <label for="lastname" class="p-sr-only">Last name</label>
      <InputText id="lastname" type="text" placeholder="Lastname" />
    </div>
    <Button type="button" label="Submit" />
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

The p-formgroup-inline lets us add inline form fields.

The p-field-checkbox class lets us layout checkboxes vertically:

<template>
  <div class="p-field-checkbox">
    <Checkbox id="city1" name="city1" value="San Francisco" />
    <label for="city1">San Francisco</label>
  </div>
  <div class="p-field-checkbox">
    <Checkbox id="city2" name="city1" value="Los Angeles" />
    <label for="city2">Los Angeles</label>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Grid System

PrimeVue comes with a grid system.

To add a grid, we can write:

<template>
  <div class="p-grid">
    <div class="p-col">1</div>
    <div class="p-col">2</div>
    <div class="p-col">3</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

The p-grid class creates a grid layout.

p-col make the div become columns of the grid.

If they overflow the width of the screen, then the overflowed ones will be moved to the next row.

We can set the width of each column:

<template>
  <div class="p-grid">
    <div class="p-col-6">6</div>
    <div class="p-col-6">6</div>
    <div class="p-col-6">6</div>
    <div class="p-col-6">6</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Also, we can set the width of the columns according to breakpoints:

<template>
  <div class="p-grid">
    <div class="p-col-12 p-md-6 p-lg-3">A</div>
    <div class="p-col-12 p-md-6 p-lg-3">B</div>
    <div class="p-col-12 p-md-6 p-lg-3">C</div>
    <div class="p-col-12 p-md-6 p-lg-3">D</div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

Available breakpoints include:

  • sm — min-width 576px
  • md — min-width 768px
  • lg — min-width 992px
  • xl — min-width 1200px

Conclusion

We can add various kinds of layouts with the PrimeVue framework to build our Vue 3 apps.

Categories
Vue

How to Add Copy to Clipboard Feature to Your Vue.js App

Copy to clipboard feature is a popular feature for web apps like password managers, where it is inconvenient for people to highlight text and then copy it. It is an easy feature to add to your own web app.

In this article, we will build a password manager that lets you enter, edit, and delete passwords and let them copy their username and password to the clipboard to use them anywhere they like. We will use Vue.js to build the app.

Getting Started

To start we create the project by running npx @vue/cli create password-manager. In the wizard, choose ‘Manually select features’ and choose to include Babel, Vue Router, and Vuex in our app.

Next, we install some libraries we need. We need Axios for making HTTP requests, Bootstrap Vue for styling, V-Clipboard for the copy to clipboard functionality, and Vee-Validate for form validation. We install them by running:

npm i axios bootstrap-vue v-clipboard vee-validate

After we install the libraries, we can start building the app. First, in the components folder, create a file called PasswordForm.vue for our password form. Then in there, we add:

<template>
  <ValidationObserver ref="observer" v-slot="{ invalid }">
    <b-form @submit.prevent="onSubmit" novalidate>
      <b-form-group label="Name">
        <ValidationProvider name="name" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.name"
            required
            placeholder="Name"
            name="name"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Name is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="URL">
        <ValidationProvider name="url" rules="required|url" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.url"
            required
            placeholder="URL"
            name="url"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="Username">
        <ValidationProvider name="username" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.username"
            required
            placeholder="Username"
            name="username"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Username is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="Password">
        <ValidationProvider name="password" rules="required" v-slot="{ errors }">
          <b-form-input
            type="password"
            :state="errors.length == 0"
            v-model="form.password"
            required
            placeholder="Password"
            name="password"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Password is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-button type="submit" variant="primary" style="margin-right: 10px">Submit</b-button>
      <b-button type="reset" variant="danger" @click="cancel()">Cancel</b-button>
    </b-form>
  </ValidationObserver>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "PasswordForm",
  mixins: [requestsMixin],
  props: {
    edit: Boolean,
    password: Object
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }

if (this.edit) {
        await this.editPassword(this.form);
      } else {
        await this.addPassword(this.form);
      }
      const response = await this.getPasswords();
      this.$store.commit("setPasswords", response.data);
      this.$emit("saved");
    },
    cancel() {
      this.$emit("cancelled");
    }
  },
  data() {
    return {
      form: {}
    };
  },
  watch: {
    password: {
      handler(p) {
        this.form = JSON.parse(JSON.stringify(p || {}));
      },
      deep: true,
      immediate: true
    }
  }
};
</script>

We have the password form in this component. The form includes name, URL, username, and password fields. All of them are required. We use Vee-Validate to validate the form fields. The ValidationObserver component is for validating the whole form, while the ValidationProvider component is for validating the form fields that it wraps around.

The validation rule is specified by the rule prop of each field. We have a special url rule for the URL field. We show the validation error messages when the errors object from the scope slot has a non-zero length. The state prop is for setting the validation state which shows the green when errors has length 0 and red otherwise. The error messages are shown in the b-form-invalid-feedback component.

When the user clicks to Save button, the onSubmit function is called. We get the validation state of the form by using this.$refs.observer.validate(); . The ref refers to the ref of the ValidationObserver . If it resolves to true , then we call addPassword or editPassword to save the entry depending on the edit prop. Then we get the passwords by calling getPasswords and then put it in our Vuex store by dispatching the setPasswords mutation. Then we emit the saved event to close the modal on the home page.

We have a watch block mainly used when an existing entry is being edited, we get the password prop and set it to this.form by making a copy of the prop so that we only update the form object and nothing when data is binding.

Next, we create a mixins folder and add requestsMixin.js inside it. In the file, add:

const APIURL = "http://localhost:3000";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    getPasswords() {
      return axios.get(`${APIURL}/passwords`);
    },

    addPassword(data) {
      return axios.post(`${APIURL}/passwords`, data);
    },

    editPassword(data) {
      return axios.put(`${APIURL}/passwords/${data.id}`, data);
    },

    deletePassword(id) {
      return axios.delete(`${APIURL}/passwords/${id}`);
    }
  }
};

This contains the code to make the HTTP requests in the back end. We include this mixin in our components so that we can make requests to back end from them.

Copy to clipboard functionality

To copy the username and password buttons, we use the v-clipboard directive to let us copy the username and password respectively to the clipboard when the button is clicked.

In Home.vue, we replace the existing code with:

<template>
  <div class="page">
    <h1 class="text-center">Password Manager</h1>
    <b-button-toolbar>
      <b-button @click="openAddModal()">Add Password</b-button>
    </b-button-toolbar>
    <br />
    <b-table-simple responsive>
      <b-thead>
        <b-tr>
          <b-th>Name</b-th>
          <b-th>URL</b-th>
          <b-th>Username</b-th>
          <b-th>Password</b-th>
          <b-th></b-th>
          <b-th></b-th>
          <b-th></b-th>
          <b-th></b-th>
        </b-tr>
      </b-thead>
      <b-tbody>
        <b-tr v-for="p in passwords" :key="p.id">
          <b-td>{{p.name}}</b-td>
          <b-td>{{p.url}}</b-td>
          <b-td>{{p.username}}</b-td>
          <b-td>******</b-td>
          <b-td>
            <b-button v-clipboard="() => p.username">Copy Username</b-button>
          </b-td>
          <b-td>
            <b-button v-clipboard="() => p.password">Copy Password</b-button>
          </b-td>
          <b-td>
            <b-button @click="openEditModal(p)">Edit</b-button>
          </b-td>
          <b-td>
            <b-button @click="deleteOnePassword(p.id)">Delete</b-button>
          </b-td>
        </b-tr>
      </b-tbody>
    </b-table-simple>

    <b-modal id="add-modal" title="Add Password" hide-footer>
      <PasswordForm @saved="closeModal()" @cancelled="closeModal()" :edit="false"></PasswordForm>
    </b-modal>

    <b-modal id="edit-modal" title="Edit Password" hide-footer>
      <PasswordForm
        @saved="closeModal()"
        @cancelled="closeModal()"
        :edit="true"
        :password="selectedPassword"
      ></PasswordForm>
    </b-modal>
  </div>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";
import PasswordForm from "@/components/PasswordForm";

export default {
  name: "home",
  components: {
    PasswordForm
  },
  mixins: [requestsMixin],
  computed: {
    passwords() {
      return this.$store.state.passwords;
    }
  },
  beforeMount() {
    this.getAllPasswords();
  },
  data() {
    return {
      selectedPassword: {}
    };
  },
  methods: {
    openAddModal() {
      this.$bvModal.show("add-modal");
    },
    openEditModal(password) {
      this.$bvModal.show("edit-modal");
      this.selectedPassword = password;
    },
    closeModal() {
      this.$bvModal.hide("add-modal");
      this.$bvModal.hide("edit-modal");
      this.selectedPassword = {};
    },
    async deleteOnePassword(id) {
      await this.deletePassword(id);
      this.getAllPasswords();
    },
    async getAllPasswords() {
      const response = await this.getPasswords();
      this.$store.commit("setPasswords", response.data);
    }
  }
};
</script>

In this file, we have a table to display a list of password entries and let users open and close the add/edit modals. We have buttons in each row to copy the username and passwords, and also to let users edit or delete each entry.

In the scripts section, we have the beforeMount hook to get all the password entries during page load with the getPasswords function we wrote in our mixin. When the Edit button is clicked, the selectedPassword variable is set, and we pass it to the PasswordForm for editing.

To delete a password, we call deletePassword in our mixin to make the request to the back end.

Finishing the app

Next in App.vue, we replace the existing code with:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Password Manager</b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}

button {
  margin-right: 10px;
}
</style>

This adds a Bootstrap navigation bar to the top of our pages, and a router-view to display the routes we define.

Next in main.js, replace the code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import Clipboard from "v-clipboard";
import { required } from "vee-validate/dist/rules";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

extend("required", required);
extend("url", {
  validate: value => {
    return /^(http://www.|https://www.|http://|https://)?[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$/.test(
      value
    );
  },
  message: "URL is invalid."
});
Vue.use(BootstrapVue);
Vue.use(Clipboard);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);

Vue.config.productionTip = false;

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

To add the libraries we installed to our app so we can use it in our components. We add the V-Clipboard library here so we can use it in our home page. We call extend from Vee-Validate to add the form validation rules that we want to use. Also, we imported the Bootstrap CSS in this file to get the styles.

In router.js, we replace the existing code with:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    }
  ]
})

to only include our home page.

Then in store.js , we replace the existing code with:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    passwords: []
  },
  mutations: {
    setPasswords(state, payload) {
      state.passwords = payload;
    }
  },
  actions: {}
});

This adds our passwords state to the store so we can observe it in the computed block of PasswordForm and HomePage components. We have the setPasswords function to update the passwords state and we use it in the components by call this.$store.commit(“setPasswords”, response.data); like we did in PasswordForm .

After all the hard work, we can start our app by running npm run serve.

Demo backend

To start the back end, we first install the json-server package by running npm i json-server. Then, go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{
  "passwords": [
  ]
}

So we have the passwords endpoints defined in the requests.js available.