How to Add a Date Picker to Your Vue App
Tags: vue, webdev, javascript, tutorial
Published: 2020-01-25 03:51:11

**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/**

For many applications, recording dates is an important feature. Having date picker inputs is often a handy feature to have. Fortunately, many developers have made date picker components that other developers can easily add to their apps.

There are great date picker components made for Vue.js. Vue-ctk-date-time-picker is one of the better ones to use. It is located at [https://github.com/chronotruck/vue-ctk-date-time-picker](https://github.com/chronotruck/vue-ctk-date-time-picker). It is very flexible. You can customize it by nesting your components inside the date time picker component. Also, you can style it the way you want by setting the classes. You can also disable the date or time picker portion of the component.

In this article, we will make a simple calendar app where users can drag over a date range and add a calendar entry. Users can also click on an existing calendar entry and edit the entry. Existing entries can also be deleted. The form for adding and editing the calendar entry will have a date and time pickers to select the date and time.

Vue.js has many calendar widgets that we can add to our apps. One of them is Vue.js Full Calendar. It has a lot of features. It has a month, week, and day calendar. Also, you can navigate easily to today or any other days with back and next buttons. You can also drag over a date range in the calendar to select the date range. With that, you can do any manipulation you want with the dates.

We will save the data on the back end in a JSON file.

We will use Vue.js to build our app. To start, we run:

```bash
npx @vue/cli create calendar-app
```

Next, we select ‘Manually select features’ and select Babel, CSS Preprocessor, Vue Router, and Vuex.

After the app is created, we have to install some packages that we need. We need Axios for making HTTP requests to our back end, BootstrapVue for styling, jQuery and Moment are dependencies for the Vue-Full-Calendar package which we will use to display a calendar. Vee-Validate for form validation, Vue-Ctk-Date-Time-Picker to let users pick the date and time for the calendar events andVue-Full-Calendar is used for the calendar widget.

We run:

```bash
npm i axios bootstrap-ye jquery moment vee-validate vue-ctk-date-time-picker vue-full-calendar
```

to install all the packages.

With all the packages installed, we can start writing the app. First, we start with the form for entering the calendar entries.

Create a file called `CalendarForm.vue` in the `components` folder and add:

```html
<template>
  <div>
    <ValidationObserver ref="observer" v-slot="{ invalid }">
      <b-form @submit.prevent="onSubmit" novalidate>
        <b-form-group label="Title" label-for="title">
          <ValidationProvider name="title" rules="required" v-slot="{ errors }">
            <b-form-input
              :state="errors.length == 0"
              v-model="form.title"
              type="text"
              required
              placeholder="Title"
              name="title"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">Title is required</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
<b-form-group label="Start" label-for="start">
          <ValidationProvider name="start" rules="required" v-slot="{ errors }">
            <VueCtkDateTimePicker
              input-class="form-control"
              :state="errors.length == 0"
              v-model="form.start"
              name="start"
            ></VueCtkDateTimePicker>
            <b-form-invalid-feedback :state="errors.length == 0">Start is required</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
<b-form-group label="End" label-for="end">
          <ValidationProvider name="end" rules="required" v-slot="{ errors }">
            <VueCtkDateTimePicker
              input-class="form-control"
              :state="errors.length == 0"
              v-model="form.end"
              name="end"
            ></VueCtkDateTimePicker>
            <b-form-invalid-feedback :state="errors.length == 0">End is required</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
<b-button type="submit" variant="primary">Save</b-button>
        <b-button type="button" variant="primary" @click="deleteEvent(form.id)">Delete</b-button>
      </b-form>
    </ValidationObserver>
  </div>
</template>
<script>
import { requestsMixin } from "../mixins/requestsMixin";
import * as moment from "moment";
export default {
  name: "CalendarForm",
  props: {
    edit: Boolean,
    calendarEvent: Object
  },
  mixins: [requestsMixin],
  data() {
    return {
      form: {}
    };
  },
  watch: {
    calendarEvent: {
      immediate: true,
      deep: true,
      handler(val, oldVal) {
        this.form = val || {};
      }
    }
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      this.form.start = moment(this.form.start).format("YYYY-MM-DD HH:mm:ss");
      this.form.end = moment(this.form.end).format("YYYY-MM-DD HH:mm:ss");
if (this.edit) {
        await this.editCalendar(this.form);
      } else {
        await this.addCalendar(this.form);
      }
      const response = await this.getCalendar();
      this.$store.commit("setEvents", response.data);
      this.$emit("eventSaved");
    },
async deleteEvent(id) {
      await this.deleteCalendar(id);
      const response = await this.getCalendar();
      this.$store.commit("setEvents", response.data);
      this.$emit("eventSaved");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
button {
  margin-right: 10px;
}
</style>
```

In this file, we use the BootstrapVue form component to build our form. We use the `VueCtkDateTimePicker` to add the date and time picker for our form to let users pick the time and date.

We wrap each input with the `ValidationProvider` component to let us validate each field. Each field is required so we set the `rules` prop to `required` .

We set the `:state` binding to `errors.length == 0` to display errors only when the `errors` array has a length bigger than 0. This also applies to `b-form-invalid-feedback` component.

The form has a Save button to run `onSubmit` when the button is clicked. We check the form’s validity by calling `this.$refs.observer.validate()` . We have this object because we wrapped the form with the`ValidationObserver` component with `ref` set to `observer` .

In the function, we format the `start` and `end` dates so that we save the correct date and time.

If the `edit` prop is set to true, then we call the `this.editCalendar` function in `requestsMixin` . Otherwise, we call `this.addCalendar` in the same mixin.

Once that succeeds, we call `this.$store.commit(“setEvents”, response.data);` after calling `this.getCalendar` to put the latest calendar events into our Vuex store.

After that’s done, we emit the `eventSaved` event so that we can close the modals in located in the home page.

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

```javascript
const APIURL = "http://localhost:3000";
const axios = require("axios");
export const requestsMixin = {
  methods: {
    getCalendar() {
      return axios.get(`${APIURL}/calendar`);
    },
addCalendar(data) {
      return axios.post(`${APIURL}/calendar`, data);
    },
editCalendar(data) {
      return axios.put(`${APIURL}/calendar/${data.id}`, data);
    },
deleteCalendar(id) {
      return axios.delete(`${APIURL}/calendar/${id}`);
    }
  }
};
```

These are the functions for making HTTP requests to the back end.

Next, we modify `Home.vue` , by replacing the existing code with:

```javascript
<template>
  <div class="page">
    <div class="buttons">
      <b-button v-b-modal.add-modal>Add Calendar Event</b-button>
    </div>
    <full-calendar :events="events" @event-selected="openEditModal" defaultView="month" />
<b-modal id="add-modal" title="Add Calendar Event" hide-footer ref="add-modal">
      <CalendarForm :edit="false" @eventSaved="closeModal()" />
    </b-modal>
<b-modal id="edit-modal" title="Edit Calendar Event" hide-footer ref="edit-modal">
      <CalendarForm :edit="true" :calendarEvent="calendarEvent" @eventSaved="closeModal()" />
    </b-modal>
  </div>
</template>
<script>
// @ is an alias to /src
import CalendarForm from "@/components/CalendarForm.vue";
import { requestsMixin } from "../mixins/requestsMixin";
export default {
  name: "home",
  components: {
    CalendarForm
  },
  mixins: [requestsMixin],
  computed: {
    events() {
      return this.$store.state.events;
    }
  },
  data() {
    return {
      calendarEvent: {}
    };
  },
  async beforeMount() {
    await this.getEvents();
  },
  methods: {
    async getEvents() {
      const response = await this.getCalendar();
      this.$store.commit("setEvents", response.data);
    },
    closeModal() {
      this.$refs["add-modal"].hide();
      this.$refs["edit-modal"].hide();
      this.calendarEvent = {};
    },
    openEditModal(event) {
      let { id, start, end, title } = event;
      this.calendarEvent = { id, start, end, title };
      this.$refs["edit-modal"].show();
    }
  }
};
</script>
<style lang="scss" scoped>
.buttons {
  margin-bottom: 10px;
}
</style>
```

In this file, we include the `full-calendar` component from the Vue Full Calendar package, and add and edit calendar event modals. We use `CalendarForm` for both.

Notice that we handle the `eventSaved` event here, which is emitted by `CalendarForm` . We call `closeModal` when the event is emitted so that the modals will close.

We also pass in the `calendarEvent` and `edit` prop set to `true` when we open the edit modal.

The `ref` for the modal is set so we can show and hide the modal by their `ref` .

We get the latest state of the `events` in the Vuex store by watching `this.$store.state.events` .

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

```javascript
<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand to="/">Calendar App</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;
}
</style>
```

We add the BootstrapVue `b-navbar` here and watch the route as it changes so that we can set the `active` prop to the link of the page the user is currently in.

Next, we change the code in `main.js` to:

```javascript
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import FullCalendar from "vue-full-calendar";
import BootstrapVue from "bootstrap-vue";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import 'vue-ctk-date-time-picker/dist/vue-ctk-date-time-picker.css';
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import VueCtkDateTimePicker from 'vue-ctk-date-time-picker';
extend("required", required);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(FullCalendar);
Vue.use(BootstrapVue);
Vue.component('VueCtkDateTimePicker', VueCtkDateTimePicker);
Vue.config.productionTip = false;
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");
```

We import all the app-wide packages we use here, like BootstrapVue, Vee-Validate and the calendar and date-time picker widgets.

The styles are also imported here so we can see them throughout the app.

Next in `router.js` , replace the existing code with:

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

to set the routes for our app, so that when users enter the given URL or click on a link with it, they can see our page.

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

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

We added an `events` state for the calendar events, and a `setEvents` function that we dispatched with `this.$store.commit` so that we can set the `events` in the store and access it in all our components.

Finally, we replace the code in `index.html` with:

```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" />
    <title>Calendar App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-calendar-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 change the app’s title.

Now all the hard work is done. All we have to do is use JSON Server NPM package located at [https://github.com/typicode/json-server](https://github.com/typicode/json-server) for our back end.

Install it by running:

```bash
npm i -g json-server
```

Then run it by running:

```bash
json-server --watch db.json
```

In `db.json`, replace the existing content with:

```json
{
  "calendar": []
}
```

Next we run our app by running `npm run serve` in our app’s project folder to run our app.