Vuetify 3 TypeScript Tutorial Series -Part 7
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 asPropType<TaskFetchResponse>
. This means thetask
prop expects an object that matches the structure defined by theTaskFetchResponse
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 bydefineEmits
. 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
.
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 acard-clicked
event emitted byTaskOverviewCard
. When this event is emitted, thehandleCardClicked
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 typeString
is expected. This prop is likely used to identify a specific task.
Reactive State (task
):
task
is a reactive object of typeTaskFetchResponse
. 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
anduseTaskStore
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
fromtaskStore
to the localtask
reactive object.Object.assign
is used to copy all properties fromtaskToEdit
totask
.
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 theTaskDetailsCard
component expects to receive. - In essence, this code passes the
task
data from the parent component to theTaskDetailsCard
component.
Event Handling:
@back-clicked="clickedBackButton"
listens for a custom event namedback-clicked
emitted by theTaskDetailsCard
component.- When the
back-clicked
event is emitted by theTaskDetailsCard
component, theclickedBackButton
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:
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