Vuetify 3 TypeScript Tutorial Series -Part 7

Habibi Coding | حبيبي كودنق
Nerd For Tech
Published in
6 min readFeb 11, 2024
tutorial banner

In Part 6 of this tutorial series, we covered the following steps:

  • Implemented a form component for task creation
  • Implemented a composable to create a task
  • Created the page for creating a task
  • Created a component to display task details

If you missed Part 6, you can find it here: Part 6

Display card details

I would suggest to enable the user to see the details of a task by clicking on it. So create in components the file TaskDetailsCard.vue and add these lines:

<script setup lang="ts">
import {PropType} from "vue";
import {TaskFetchResponse} from "@/dtos/taskDto";
import {formatDate} from "@/composables/formatDate";

defineProps({
task: Object as PropType<TaskFetchResponse>
});
const emit = defineEmits(['back-clicked']);
const handleBackClick = () => {
emit('back-clicked');
};
</script>

Component Properties:

  • defineProps is used to declare the component's properties. It's a Vue composition API feature that allows defining the props a component accepts.
  • The task property is defined, with its type set as PropType<TaskFetchResponse>. This means the task prop expects an object that matches the structure defined by the TaskFetchResponse type or interface.

defineEmits:

  • This is a Vue 3 Composition API function used to define custom events that the component can emit. In this case, a single event named 'back-clicked' is defined.
  • Components using this script can listen to the 'back-clicked' event, which is useful for parent-child component communication.

emit:

  • The emit function is created by defineEmits. It's used to trigger or emit the defined custom events.

handleBackClick Function:

  • This is a method defined to handle a specific action, presumably a click event. When invoked, it emits the 'back-clicked' event.
  • This function can be tied to a button or a similar interactive element in the template. When the user interacts with this element (e.g., clicks a back button), this function will be called.

In summary, this script setup block is used to define a single property, task, which is expected to be an object conforming to the TaskFetchResponse type. The component also imports a utility function formatDate for date formatting. The PropType import helps to ensure the task prop adheres to the specified type, enhancing type safety and making the component's expected data structure clearer. When the button is clicked, the handleBackClick function is executed, emitting the 'back-clicked' event. This pattern is common in Vue for child-to-parent communication, where the child component informs the parent of certain actions or changes.

Then add the HTML structure:

<template>
<v-card class="mx-auto center-card-text">
<v-list lines="two">
<v-list-subheader>Task Detail</v-list-subheader>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Task ID: </span> {{ task.id }}
</template>
</v-list-item>
<v-divider inset></v-divider>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Description: </span> {{ task.description }}
</template>
</v-list-item>
<v-divider inset></v-divider>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Reminder set: </span> {{ task.isReminderSet }}
</template>
</v-list-item>
<v-divider inset></v-divider>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Task open: </span> {{ task.isTaskOpen }}
</template>
</v-list-item>
<v-divider inset></v-divider>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Created on: </span> {{ formatDate(task.createdOn) }}
</template>
</v-list-item>
<v-divider inset></v-divider>

<v-list-item>
<template v-slot:subtitle>
<span class="font-weight-bold">Priority: </span> {{ task.priority }}
</template>
</v-list-item>
<v-divider inset></v-divider>

</v-list>
</v-card>

<v-btn block class="clear-btn" @click="handleBackClick">back</v-btn>

</template>

Lastly the styling for this component:

<style scoped>
.center-card-text {
text-align: center;
}
</style>

Page for task details

I think now it would be a good idea to rename TaskCard.vue to TaskOverviewCard.vue .

renaming files

Remember, we have added in the method handleCardClicked() in TasksOverviewPage.vue

const handleCardClicked = (id: number) => {
router.push({name: TASK_DETAIL_VIEW, params: {id: id.toString()}}).then();
};

and we added in the <template> the line:

@card-clicked="handleCardClicked"

Event Listener:

  • @card-clicked="handleCardClicked": This listens for a card-clicked event emitted by TaskOverviewCard. When this event is emitted, the handleCardClicked method from the parent component is called.

This is the reason why we don’t need to create another composable function for fetching a specific task. We could but, of course it is better to reduce network calls to the backend.

In pages create the file TaskDetailsPage.vue add these lines:

<script setup lang="ts">
import {onMounted, reactive} from "vue";
import {useTaskNavigation} from '@/composables/useTaskNavigation';
import {useTaskStore} from "@/store/taskStore";
import {TaskFetchResponse} from "@/dtos/taskDto";
import Navbar from "@/components/Navbar.vue";
import MainBackground from "@/components/MainBackground.vue";
import TaskDetailsCard from "@/components/TaskDetailsCard.vue";
import router from "@/router";


defineProps({
id: String
});

const task = reactive<TaskFetchResponse>({
id: 0,
description: '',
isReminderSet: null,
isTaskOpen: null,
createdOn: null,
priority: null
})

const {handleTaskTypeSelected, logoClicked} = useTaskNavigation();
const taskStore = useTaskStore();

onMounted(showTaskDetails);

function showTaskDetails() {
Object.assign(task, taskStore.taskToEdit);
}

const clickedBackButton = () => {
router.back();
};

</script>

defineProps:

  • It defines the props that this component accepts. Here, a prop id of type String is expected. This prop is likely used to identify a specific task.

Reactive State (task):

  • task is a reactive object of type TaskFetchResponse. It holds the details of a task, with initial values set to defaults. reactive makes this object reactive, meaning Vue will track its changes and update the DOM when any of its properties change.

Composables:

  • useTaskNavigation and useTaskStore are composables used here. They provide functionality and state related to task navigation and task data.

onMounted Lifecycle Hook:

  • Calls showTaskDetails function when the component is mounted. This is a Vue lifecycle hook that runs after the component is added to the DOM.

Function showTaskDetails:

  • This function assigns the taskToEdit from taskStore to the local task reactive object. Object.assign is used to copy all properties from taskToEdit to task.

Function clickedBackButton:

  • When called, triggers Vue Router to navigate the user back to the previous page in their browsing history.

In summary, this script sets up a Vue component to display and manage task details. It uses a reactive object to track task details, composables for shared logic, and lifecycle hooks for initialization.

Then add the <template> HTML section:

<template>
<Navbar @task-type-selected="handleTaskTypeSelected" @logo-clicked="logoClicked"/>
<MainBackground>
<TaskDetailsCard :task="task" @back-clicked="clickedBackButton"/>
</MainBackground>
</template>

Break down of the above line:


<TaskDetailsCard :task="task" @back-clicked="clickedBackButton"/>

Passing Props:

  • :task="task" is a Vue.js directive for passing props to a child component.
  • The task on the right side of the equals sign is a reference to a data property, computed property, or method in the parent component.
  • The task on the left (without the colon) is the name of the prop that the TaskDetailsCard component expects to receive.
  • In essence, this code passes the task data from the parent component to the TaskDetailsCard component.

Event Handling:

  • @back-clicked="clickedBackButton" listens for a custom event named back-clicked emitted by the TaskDetailsCard component.
  • When the back-clicked event is emitted by the TaskDetailsCard component, the clickedBackButton method of the parent component is called.
  • The clickedBackButton method is expected to be defined in the parent component's script section. It's responsible for what should happen when the event is triggered (as described in your previous question, it navigates the user back to the previous page).

In summary, this line of code is doing two things: it renders the TaskDetailsCard component, passing a task object to it, and it sets up an event listener so that when the TaskDetailsCard component emits a back-clicked event, the clickedBackButton method in the parent component gets called, handling the event appropriately.

Next, hop again to router package and modify index.ts , add in children[] the object:

{
path: 'tasks/:id',
name: TASK_DETAIL_VIEW,
component: TaskDetailsPage,
props: true,
}

Then start again the project:

pnpm dev

Open http://localhost:3000/ and click on one item card and you should see:

task details

With that, we conclude the first part of this tutorial series. If you found it useful and informative, give it a clap. Here is Part 8

Don’t forget to check out the video playlist on YouTube.

Here is the source code on GitHub, check out the branch: part-seven

--

--