Categories
Vue

How to Make a Calendar App with Vue

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

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.

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 date and time pickers to select the date and time.

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:

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:

npm i axios bootstrap-vue 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:

<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 length bigger than 0. This also applies to b-form-invalid-feedback component.

The form has a Save button to 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 theValidationObserver 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:

const APIURL = "[http://localhost:3000](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:

<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 an 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:

<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:

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:

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:

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:

<!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 for our back end.

Install it by running:

npm i -g json-server

Then run it by running:

json-server --watch db.json

In db.json , replace the existing content with:

{
  "calendar": []
}

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

Categories
Vue 3

Add Form Validation to a Vue 3 App with Vuelidate 2 — Validating Forms and Composition API

Vuelidate 2 is a popular form validation library made for the Vue 3 apps.

In this article, we’ll look at how to add form validation to our Vue 3 app with Vuelidate 2.

Validating Forms Before Submitting

We can validate forms right before we submit them.

To do this, we write:

<template>
  <form @submit.prevent="submitForm">
    <div>
      <input v-model="v$.name.$model" />
      <template v-if="v$.name.$dirty">
        <div v-for="error of v$.name.$silentErrors" :key="error.$message">
          {{ error.$message }}
        </div>
      </template>
    </div>
    <input type="submit" />
  </form>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
  methods: {
    submitForm() {
      this.v$.$touch();
      if (this.v$.$error) return;
      alert("success");
    },
  },
};
</script>

We have a form that we wrapped around the input.

Then we add the submitForm method, which calls v$.$touch to trigger validation for each field in the form.

If there’re any errors, then this.$v.$error will be truthy and we stop running the function.

Otherwise, the form is valid, we proceed and the alert is shown.

Validation can also be done asynchronously.

For instance, we can write:

<template>
  <form [@submit](https://medium.com/r/?url=http%3A%2F%2Ftwitter.com%2Fsubmit "Twitter profile for @submit").prevent="submitForm">
    <div>
      <input v-model="v$.name.$model" />
      <template v-if="v$.name.$dirty">
        <div v-for="error of v$.name.$silentErrors" :key="error.$message">
          {{ error.$message }}
        </div>
      </template>
    </div>
    <input type="submit" />
  </form>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
  methods: {
    async submitForm() {
      const isFormValid = await this.v$.$validate();
      if (!isFormValid) return;
      alert("success");
    },
  },
};
</script>

We call this.v$.$validate() to validate the form.

It returns a promise that resolves to true if all the form values are valid.

Otherwise, the returned promise resolves to false .

Composition API

We can use Vuelidate 2 with Vue 3’s Composition API./

To do this, we write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        {{ error.$message }}
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required, minLength } from "@vuelidate/validators";
import { computed, ref } from "vue";

export default {
  name: "App",
  setup() {
    const name = ref("");
    const requiredNameLength = ref(2);
    const rules = computed(() => ({
      name: {
        required,
        minLength: minLength(requiredNameLength.value),
      },
    }));

    const v$ = useVuelidate(rules, { name });
    return { name, requiredNameLength, v$ };
  },
};
</script>

We define the name reactive property with the ref function.

requiredNameLength is another reactive property that we use in the minLength function.

minLength validates that the inputted string is at least a given length.

We pass all the rules provided by Vuelidate into the object we return in the callback we pass into the computed property.

Then we call useVuelidate to return the object we use in the template.

Now we can display the errors and bind to the model the same way the previous example in the template.

Conclusion

We can validate our forms before submission and use Vuelidate with Vue 3’s Composition API.

Categories
Vue 3

Add Form Validation to a Vue 3 App with Vuelidate 2 — Models and Dirty

Vuelidate 2 is a popular form validation library made for the Vue 3 apps.

In this article, we’ll look at how to add form validation to our Vue 3 app with Vuelidate 2.

$model

We can bind our form fields to the $model property.

This way, we can show errors only when the field is dirty.

For instance, we can write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        <div>{{ error.$message }}</div>
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required },
    };
  },
};
</script>

We have the v$ reactive property that’s used to do all the form validation checks.

Once v$.name.$model has been manipulated, $dirty will become true .

And we show the error messages by rendering the entries in v$.name.$silentErrors .

Also, we can set the $autoDirty property to true to make a field that’s been manipulated have the $dirty property automatically set to true .

To do this, we write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        <div>{{ error.$message }}</div>
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $autoDirty: true },
    };
  },
};
</script>

Lazy Validations

We can also set the $lazy property to true to make Vuelidate validate fields only when the field has been manipulated.

For instance, we can write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        <div>{{ error.$message }}</div>
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
};
</script>

to set the property in the validations method.

Display More Error Information

To display more error information, we can write:

<template>
  <div>
    <input v-model="v$.name.$model" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        <strong>{{ error.$validator }}</strong>
        <span> on property</span>
        <strong>{{ error.$property }}</strong>
        <span> says:</span>
        <strong>{{ error.$message }}</strong>
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
    };
  },
  validations() {
    return {
      name: { required, $lazy: true },
    };
  },
};
</script>

error.$validation has the name of the validation rule as a string.

error.$property has the name of the field as a string.

error.$message has the form validation error message.

Conclusion

We can set how form validation is triggered with Vuelidate 2 in our Vue 3 app.

Also, we can display more error information than the message.

Categories
Vue 3

Add Form Validation to a Vue 3 App with Vuelidate 2

Vuelidate 2 is a popular form validation library made for the Vue 3 apps.

In this article, we’ll look at how to add form validation to our Vue 3 app with Vuelidate 2.

Installation

To install Vuelidate 2, we run:

npm install @vuelidate/core @vuelidate/validators

with NPM.

Or we can run:

yarn add @vuelidate/core @vuelidate/validators

with Yarn.

Add Form Validation

To add form validation with Vuelidate 2 into our Vue 3 app, we write:

<template>
  <div>
    <input v-model="name" />
    <div v-for="error of v$.name.$silentErrors" :key="error.$message">
      <div>{{ error.$message }}</div>
    </div>
  </div>
  <div>
    <input v-model="contact.email" />
    <div v-for="error of v$.contact.email.$silentErrors" :key="error.$message">
      <div>{{ error.$message }}</div>
    </div>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required, email } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
      contact: {
        email: "",
      },
    };
  },
  validations() {
    return {
      name: { required },
      contact: {
        email: { required, email },
      },
    };
  },
};
</script>

We import the useVuelidate hook, call it, and assign the returned value to thew v$ reactive property.

Then we add the name reactive property and bind it to the input with v-model .

And then in the validations method, we return an object with the name of the field and the validation rules.

We set each property in the object we return in the validations method to properties an object with the validation rules we want to set.

Then we get the errors with the $silentErrors property in each object.

error.$message has the error message of each field.

Dirty State

The $dirty state is provided by Vuelidate for each field to track if any manipulation has been done to it or not.

For instance, we can write:

<template>
  <div>
    <input v-model="name" @blur="v$.name.$touch" />
    <template v-if="v$.name.$dirty">
      <div v-for="error of v$.name.$silentErrors" :key="error.$message">
        <div>{{ error.$message }}</div>
      </div>
    </template>
  </div>
  <div>
    <input v-model="contact.email" @blur="v$.contact.email.$touch" />
    <template v-if="v$.contact.email.$dirty">
      <div
        v-for="error of v$.contact.email.$silentErrors"
        :key="error.$message"
      >
        <div>{{ error.$message }}</div>
      </div>
    </template>
  </div>
</template>

<script>
import useVuelidate from "@vuelidate/core";
import { required, email } from "@vuelidate/validators";

export default {
  name: "App",
  setup() {
    return { v$: useVuelidate() };
  },
  data() {
    return {
      name: "",
      contact: {
        email: "",
      },
    };
  },
  validations() {
    return {
      name: { required },
      contact: {
        email: { required, email },
      },
    };
  },
};
</script>

In each input, we call the $touch method when we move focus away from the input.

This will make the $dirty property become true .

Therefore, we can use this to show validation errors when we focused on the input and then move focus away from it.

Conclusion

We can add basic form validation into our Vue 3 app with Vuelidate 2.

We can show validation errors and control when they’re shown.

Categories
Vue

How to Pass Arguments in Computed Properties in Vue.Js?

In Vue.js, computed properties are properties that are derived from other reactive properties.

If we just return an expression in the computed property, then we can access it like any other reactive property.

However, we can’t call it like a method to pass in values to computed properties this way.

In this article, we’ll look at how we can pass arguments into computed properties.

Return a Function

We can return a function in our computed property if we want to create a computed property that accepts arguments.

For instance, we can write:

<template>
  <div id="app">
    <p>{{ fullName("hello") }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      firstName: "jane",
      lastName: "smith",
    };
  },
  computed: {
    fullName() {
      return (greeting) => `${greeting} ${this.firstName} ${this.lastName}`;
    },
  },
};
</script>

We create the fullName computed property by returning a function.

It has to be an arrow function since we want the this value to be the component options object.

This way, we can access this.firstName and this.lastName properly by making sure they’re the values of the firstName and lastName reactive properties that we return in the data method.

Filters

We can also add a filter to our component so that we can format our values the way we want in our templates.

For instance, we can write:

<template>
  <div id="app">
    <p>{{ `${firstName} ${lastName}` | greet("hello") }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      firstName: "jane",
      lastName: "smith"
    };
  },
  filters: {
    greet: (name, greeting) => {
      return `${greeting} ${name}`;
    }
  }
};
</script>

We have `${firstName} ${lastName}`, which ate the values of the name parameter.

And then we call the greet filter with the 'hello' argument, which is the value of the greeting parameter.

In the filter, return the string with the parameters combined our way.

Methods

We can just create a method that is run every time we can it.

For instance, we can write:

<template>
  <div id="app">
    <p>{{ greet("hello", firstName, lastName) }}</p>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      firstName: "jane",
      lastName: "smith"
    };
  },
  methods: {
    greet(greeting, firstName, lastName) {
      return `${greeting} ${firstName} ${lastName}`;
    }
  }
};
</script>

We just add the greet method that returns the greeting , firstName and lastName parameters combined.

Then we call the greet method in our template with the arguments we want.

The returned value will be rendered.

Conclusion

We can return a function in our computed property method to let us take arguments.

Also, we can use filters and methods as an alternative to computed properties if we want to pass in arguments.