I am trying to learn Vue 3 by creating a month planner app.
The entire has the following structure:
The calendar body component is as follows:
<template>
<div class="grid-container">
<div class="grid-item" v-for="(month, index) in months" :key="month">
{{ month }}
<hr>
<TaskContainer @toggle-side-panel='handleTogglePanel'
@close-side-panel='handleClosePanel'
@add-to-all-tasks='handleAddToAllTasks'
@del-from-all-tasks='handleDelFromAllTasks'
:allTasks=allTasks
:activeMonth=(index+1) />
</div>
<div v-show="showPanel">
<CalendarSidePanel :task='task'/>
</div>
</div>
</template>
<script>
import TaskContainer from '../Task/TaskContainer.vue'
import CalendarSidePanel from './CalendarSidePanel.vue'
import { ref, reactive, toRefs } from '@vue/runtime-core'
export default {
name: 'CalendarBody',
components: {
TaskContainer,
CalendarSidePanel
},
setup(props) {
console.log('CalendarBody task ', props.task);
const allTasks = ref([]);
const showPanel = ref(false);
const lastTask = ref(null);
const task = ref(null);
const state = reactive({
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December']
});
function handleTogglePanel(tsk) {
console.group('CalendarBody_handleTogglePanel');
console.log('tsk ', tsk);
if(tsk != null) {
task.value = tsk;
console.log('showPanel ', showPanel.value);
console.log('task ', task.value);
console.log('lastTask.value ', lastTask.value);
if(lastTask.value == null || lastTask.value != task.value.tName) {
console.log('lastTask is null or different ', lastTask);
showPanel.value = true;
lastTask.value = task.value.tName;
console.log('lastTask is assigned or changed', lastTask);
} else if(lastTask.value == task.value.tName) {
console.log('lastTask is the same ', lastTask);
showPanel.value = false;
lastTask.value = null;
}
}
console.groupEnd('CalendarBody_handleTogglePanel');
}
function handleAddToAllTasks(singleTask) {
console.group('CalendarBody_handleAddToAllTasks');
allTasks.value.push(singleTask);
console.groupEnd('CalendarBody_handleAddToAllTasks');
}
function handleDelFromAllTasks(taskName) {
console.group('CalendarBody_handleDelFromAllTasks');
console.log('BEFORE: deleting from allTasks ', allTasks.value);
allTasks.value = allTasks.value.filter(t => t.tName != taskName);
console.log('AFTER: deleting from allTasks ', allTasks.value);
console.groupEnd('CalendarBody_handleDelFromAllTasks');
}
function handleClosePanel(taskName) {
console.group('CalendarBody_handleClosePanel');
if(showPanel.value && taskName == lastTask.value) {
showPanel.value = false;
lastTask.value = null;
}
console.groupEnd('CalendarBody_handleClosePanel');
}
return {
...toRefs(state),
allTasks,
task,
showPanel,
handleClosePanel,
handleTogglePanel,
handleAddToAllTasks,
handleDelFromAllTasks
}
}
}
</script>
<style scoped>
.grid-container {
display: grid;
grid-template-columns: auto auto auto auto auto auto auto auto auto auto auto auto auto;
background-color: #2196F3;
padding: 5px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.5);
}
.grid-item {
background-color: #2196F3;
margin: 2px;
font-size: 30px;
text-align: center;
border: 0.5px solid rgba(0, 0, 0, 0.8);
}
</style>
Task container:
<template>
<div>
<TaskHeader @add-tasks="handleAddTasks" />
<div>
<TaskBody :tasks=tasks
@add-dragged-task='handleAddDropTask'
@toggle-side-panel='handleTogglePanel'
@delete-task='handleDeleteTask' />
</div>
</div>
</template>
<script>
import TaskHeader from './TaskHeader.vue';
import TaskBody from './TaskBody.vue';
import { reactive, ref } from '@vue/reactivity';
export default {
name: 'TaskContainer',
components: {
TaskHeader,
TaskBody
},
setup(props, context) {
const monthTasks = reactive({});
const tasks = ref([]);
function handleRemoveDropTask(droppedTaskName) {
console.group('TaskBody_handleRemoveDropTask');
console.log('droppedTaskName ', droppedTaskName);
const dropTask = props.allTasks.find(t => t.tName == droppedTaskName);
console.log('dropTask ', dropTask);
monthTasks[dropTask.tStartMonth] = monthTasks[dropTask.tStartMonth]
.filter(t => t.tName != droppedTaskName);
console.log('monthTasks ', monthTasks);
console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
tasks.value = tasks.value.filter(t => t.tName != droppedTaskName);
console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
console.group('TaskBody_handleRemoveDropTask');
}
function handleAddTasks(singleTask) {
console.group('TaskContainer_handleAddTasks');
tasks.value.push(singleTask);
monthTasks[props.activeMonth] = tasks.value;
console.log('adding to month\'s tasks ', monthTasks);
context.emit('add-to-all-tasks', singleTask);
console.groupEnd('TaskContainer_handleAddTasks');
}
function handleDeleteTask(taskName) {
console.group('TaskContainer_handleDeleteTask');
console.log('taskName ', taskName);
console.log('monthTasks ', monthTasks[props.activeMonth].filter(t => t.tName != taskName));
monthTasks[props.activeMonth] = monthTasks[props.activeMonth].filter(t => t.tName != taskName);
console.log('monthTasks ', monthTasks);
console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
tasks.value = tasks.value.filter(t => t.tName != taskName);
console.log('BEFORE: tasks ', tasks.value, 'lenght ', tasks.value.length);
context.emit('close-side-panel', taskName);
context.emit('del-from-all-tasks', taskName);
console.groupEnd('TaskContainer_handleDeleteTask');
}
function handleAddDropTask(dropTaskName) {
console.group('TaskContainer_handleAddDropTask');
const dropTask = props.allTasks.find(t => t.tName == dropTaskName);
dropTask.tStartMonth = props.activeMonth;
console.log('dropTask ', dropTask);
console.log('props.allTasks ', props.allTasks);
console.log('monthTasks ', monthTasks);
console.log('monthTasks ', monthTasks[props.activeMonth]);
if(monthTasks[props.activeMonth] == undefined) {
tasks.value = []
monthTasks[props.activeMonth] = tasks.value;
}
console.log('monthTasks ', monthTasks[props.activeMonth].filter(t => t.tName != dropTaskName));
monthTasks[props.activeMonth] = monthTasks[props.activeMonth].filter(t => t.Name != dropTaskName);
monthTasks[props.activeMonth].push(dropTask);
console.log('monthTasks ', monthTasks);
console.log('BEFORE: tasks ', tasks, 'length ', tasks.value.length);
tasks.value = tasks.value.filter(t => t.Name != dropTaskName);
tasks.value.push(dropTask);
console.log('AFTER: tasks ', tasks, 'length ', tasks.value.length);
console.groupEnd('TaskContainer_handleAddDropTask');
}
function handleTogglePanel(tsk) {
console.group('TaskContainer_handleTogglePanel');
context.emit('toggle-side-panel', tsk);
console.groupEnd('TaskContainer_handleTogglePanel');
}
return {
tasks,
handleAddTasks,
handleTogglePanel,
handleDeleteTask,
handleAddDropTask,
handleRemoveDropTask
}
},
props: ['allTasks', 'activeMonth'],
}
</script>
<style scoped>
</style>
One of the features is the ability to drag a task from one month to another month. As can be seen from the picture I am able to drag and drop from Jan month to Feb month, there by creating the task in the Feb month, but in order to remove the task from the Jan month, I need access to the Jan month's TaskContainer's reactive tasks or monthTasks variable in Feb month as each TaskContainer is an instance created inside the v-for of the CalendarBody's template.
My questions are:
- How can I access
Janmonths instance (specificallytasksormonthTasksvariables) insideFebmonth, so that I can remove the task1from the variables, so that the task doesn't remain inJan. - Is this the correct way to create
TaskContainercomponent? That is, declaringTaskContainerinside thev-forthereby creating a instance for each month or should I have hard codedTaskContainer12 times? - I read that a prop passed down from the parent should not be modified in child but rather only in the parent. I am finding this bit hard to implement as it is much easier to simply change a value where I want instead of emitting event to the parent for every small change. As mine is bit nested I have to emit from child to grandparent or even great grandparent. Any suggestions on how to emit directly to the component I need, something similar to provide/inject for props but for emitting?
PS:
- I am using just the base VUE without any other framework/library.
- Please ignore the CSS also as it is only a makeshift.

