Vue 3 composition -- Dialog template error

I'm trying to adapt the demo samples into my vue 3 composition app using <script setup>.

All is fine until I try to use a custom dialog template.
I've made my dialog template into its own component:

<template>
<div>
<table>
<tbody>
<tr>
<td class="e-label">ID</td>
<td>
<div class="e-float-input e-control-wrapper">
<input
id="Id"
name="Id"
type="text"
class="e-field"
v-model="data.Id"
disabled
/>
</div>
</td>
</tr>
<tr>
<td class="e-label">Status</td>
<td>
<v-select
v-model="data.Status"
:items="statusData"
item-text="text"
item-value="text"
label="Status"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Assignee</td>
<td>
<v-select
v-model="data.Assignee"
:items="assigneeData"
label="Assignee"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Priority</td>
<td>
<v-select
v-model="data.Priority"
:items="priorityData"
label="Priority"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Summary</td>
<td>
<div class="e-float-input e-control-wrapper">
<textarea
type="text"
name="Summary"
id="Summary"
class="e-field"
v-model="data.Summary"
></textarea>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script setup>
import { ref } from "vue";

const statusData = ref([
{ text: "Open" },
{ text: "InProgress" },
{ text: "Testing" },
{ text: "Close" },
]);
const priorityData = ref([
"Low",
"Normal",
"Critical",
"Release Breaker",
"High",
]);
const assigneeData = ref([
"Nancy Davloio",
"Andrew Fuller",
"Janet Leverling",
"Steven walker",
"Robert King",
"Margaret hamilt",
"Michael Suyama",
]);
const data = ref({});
</script>

<style scoped></style>



And I've got this in my main page:

<ejs-kanban
ref="kanbanInstanceRef"
id="kanban"
keyField="status"
width="100%"
height="1100px"
:dataSource="kanbanData"
:cardSettings="cardSettings"
:actionComplete="handleAction"
:enable-tooltip="true"
:dialogSettings="dialogSettings"
>
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>


and my script section:

<script setup>
import {
KanbanComponent as EjsKanban,
ColumnsDirective as EColumns,
ColumnDirective as EColumn,
} from "@syncfusion/ej2-vue-kanban";
import { ref, onMounted, watch } from "vue";
import { extend } from "@syncfusion/ej2-base";
import TaskService from "@/services/task.service";
import DialogTemplate from "@/components/DialogTemplate.vue";

const taskService = new TaskService();
const dataManager = ref([]);
const isDataLoaded = ref(false);
const kanbanInstanceRef = ref(null);
const kanbanData = ref(null);

const status = ref("");
const title = ref("");
const summary = ref("");
const priority = ref(0);
const color = ref("#bbbbbb");
const dueDate = ref("");
const rankId = ref(0);
const showForm = ref(false);
const asignee = ref();

let cardSettings = {
contentField: "summary",
headerField: "title",
};

const kanbanDataRef = ref(extend([], kanbanData.value, null, true));
const dialogSettings = {
template: DialogTemplate,
};
const addClick = () => {
const cardIds = kanbanDataRef.value.map((obj) =>
parseInt(obj.Id.replace("Task ", ""), 10)
);
const cardCount = Math.max(...cardIds) + 1;
const cardDetails = {
Id: "Task " + cardCount,
Status: "Open",
Priority: "Normal",
Assignee: "Andrew Fuller",
Estimate: 0,
Tags: "",
Summary: "",
};
kanbanInstanceRef.value.ej2Instances.openDialog("Add", cardDetails);
// kanbanInstanceRef.value.ej2Instances.openDialog("Add", dialogSettings);
};

// Fetch data on component mount
onMounted(async () => {
try {
const data = await taskService.getAllTasks();
dataManager.value = data.data;
isDataLoaded.value = true;
} catch (error) {
console.error("Error fetching data:", error);
}
});

// Watch for changes in dataManager and update dataSource accordingly
watch(
() => dataManager.value,
() => {
kanbanData.value = dataManager.value;
}
);
</script>




but I get this error when I try to make the dialog pop up:
Uncaught TypeError: templateElement.call is not a function

    compile2 template.js:53
    compile2 template-engine.js:31
    getDialogContent dialog.js:69
    renderDialog dialog.js:35
    openDialog dialog.js:24
    cardDoubleClick action.js:148
    notify observer.js:104
    trigger base.js:190
    cardDoubleClick action.js:146
    doubleClickHandler action.js:98
    add event-handler.js:68
    wireEvents layout-render.js:1063
    initRender layout-render.js:65
    notify observer.js:101
    notify component.js:306
    dataManagerSuccess data.js:214
    notify observer.js:104
    trigger base.js:190
    dataManagerSuccess data.js:212
    refreshDataManager data.js:189
    promise callback*Data2.prototype.refreshDataManager data.js:188
    Data2 data.js:21
    initializeModules kanban.js:341
    render kanban.js:168
    appendTo component.js:224
    mounted component-base.js:94
    createHook runtime-core.esm-bundler.js:2855
    callWithErrorHandling runtime-core.esm-bundler.js:188
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:196
    __weh runtime-core.esm-bundler.js:2835
    flushPostFlushCbs runtime-core.esm-bundler.js:363
    flushJobs runtime-core.esm-bundler.js:401
    promise callback*queueFlush runtime-core.esm-bundler.js:305
    queueJob runtime-core.esm-bundler.js:299
    scheduler runtime-core.esm-bundler.js:1941
    resetScheduling reactivity.esm-bundler.js:257
    triggerEffects reactivity.esm-bundler.js:300
    triggerRefValue reactivity.esm-bundler.js:1053
    set value reactivity.esm-bundler.js:1098
    setup Task.vue:98
    createHook runtime-core.esm-bundler.js:2855
    callWithErrorHandling runtime-core.esm-bundler.js:188
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:196
    __weh runtime-core.esm-bundler.js:2835
    flushPostFlushCbs runtime-core.esm-bundler.js:363
    flushJobs runtime-core.esm-bundler.js:401
    promise callback*queueFlush runtime-core.esm-bundler.js:305
    queuePostFlushCb runtime-core.esm-bundler.js:325
    queueEffectWithSuspense runtime-core.esm-bundler.js:1735
    scheduler runtime-core.esm-bundler.js:1936
    resetScheduling reactivity.esm-bundler.js:257
    triggerEffects reactivity.esm-bundler.js:300
    triggerRefValue reactivity.esm-bundler.js:1053
    set value reactivity.esm-bundler.js:1098
    finalizeNavigation vue-router.mjs:3353
    pushWithRedirect vue-router.mjs:3218
    promise callback*pushWithRedirect vue-router.mjs:3185
    push vue-router.mjs:3110
    install vue-router.mjs:3551
    use runtime-core.esm-bundler.js:3884
    <anonymous> main.js:12



8 Replies 1 reply marked as answer

VJ Vinitha Jeyakumar Syncfusion Team January 3, 2024 06:46 AM UTC

Hi Bryan Cantwell,

The Syncfusion Vue 3 components do support slots, which can help reduce the number of properties that need to be defined and increase the readability of the component. This is because using slots allows defining the content or behaviour of the component in the parent component rather than in the component’s own code. 

In the Vue component, the v-slot directive is used to define a slot template in the component’s template where users can insert custom content. Refer to the following code sample.

Code snippet:
<ejs-kanban
            ref="kanbanInstance"
            id="kanban"
            keyField="Status"
            :dataSource="kanbanData"
            :cardSettings="cardSettings"
            :dialogSettings="dialogSettings"
          >
            <e-columns>
              <e-column headerText="To Do" keyField="Open"></e-column>
              <e-column
                headerText="In Progress"
                keyField="InProgress"
              ></e-column>
              <e-column headerText="Testing" keyField="Testing"></e-column>
              <e-column headerText="Done" keyField="Close"></e-column>
            </e-columns>
            <template v-slot:dialogTemplate="data }">
              <div>
                <table>
                  <tbody>
                    <tr>
                      <td class="e-label">ID</td>
                      <td>
                        <div class="e-float-input e-control-wrapper">
                          <input
                            id="Id"
                            name="Id"
                            type="text"
                            class="e-field"
                            v-model="data.Id"
                            disabled
                          />
                        </div>
                      </td>
                    </tr>
                    <tr>
                      <td class="e-label">Status</td>
                      <td>
                        <ejs-dropdownlist
                          id="Status"
                          name="Status"
                          class="e-field"
                          v-model="data.Status"
                          :dataSource="dataSource1"
                          placeholder="Status"
                        ></ejs-dropdownlist>
                      </td>
                    </tr>
                    <tr>
                      <td class="e-label">Assignee</td>
                      <td>
                        <ejs-dropdownlist
                          id="Assignee"
                          name="Assignee"
                          v-model="data.Assignee"
                          class="e-field"
                          :dataSource="dataSource2"
                          placeholder="Assignee"
                        ></ejs-dropdownlist>
                      </td>
                    </tr>
                    <tr>
                      <td class="e-label">Priority</td>
                      <td>
                        <ejs-dropdownlist
                          type="text"
                          name="Priority"
                          id="Priority"
                          v-model="data.Priority"
                          popupHeight="300px"
                          class="e-field"
                          placeholder="Priority"
                          :dataSource="dataSource3"
                        ></ejs-dropdownlist>
                      </td>
                    </tr>
                    <tr>
                      <td class="e-label">Summary</td>
                      <td>
                        <div class="e-float-input e-control-wrapper">
                          <textarea
                            type="text"
                            name="Summary"
                            id="Summary"
                            class="e-field"
                            v-model="data.Summary"
                          ></textarea>
                        </div>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </div>
            </template>
          </ejs-kanban>

return {
      kanbanData: extend([], kanbanDatanulltrue),
      cardSettings: {
        contentField: 'Summary',
        headerField: 'Id',
      },
      dialogSettings: {
        template: 'dialogTemplate',
      },
      dataSource1: statusData,
      dataSource2: assigneeData,
      dataSource3: priorityData,
      data: {},
    };





Regards,
Vinitha




BC Bryan Cantwell replied to Vinitha Jeyakumar January 24, 2024 01:50 AM UTC

First thing is I use composition api with <script setup> so there was some slight adjustment,
But the real problem is that { data } is undefined for the dialogTemplate.
It works fine for cardTemplate, but I need help to correctly edit a task.
Here is my code so far. I can view all my tasks, but cannot edit one because:
Uncaught TypeError: data is undefined

    dialogTemplate Task.vue:47
    renderFnWithContext runtime-core.esm-bundler.js:816
    render template.js:35
    renderComponentRoot runtime-core.esm-bundler.js:876
    componentUpdateFn runtime-core.esm-bundler.js:5997
    run reactivity.esm-bundler.js:176
    update runtime-core.esm-bundler.js:6128
    setupRenderEffect runtime-core.esm-bundler.js:6138
    mountComponent runtime-core.esm-bundler.js:5906
    processComponent runtime-core.esm-bundler.js:5860
    patch runtime-core.esm-bundler.js:5328
    render2 runtime-core.esm-bundler.js:6649
    render runtime-dom.esm-bundler.js:1473
    compile2 template.js:41
    compile2 template-engine.js:31
    getDialogContent dialog.js:69
    renderDialog dialog.js:35
    openDialog dialog.js:24
    cardDoubleClick action.js:148
    notify observer.js:104
    trigger base.js:190
    cardDoubleClick action.js:146
    doubleClickHandler action.js:98
    add event-handler.js:68
    wireEvents layout-render.js:1063
    initRender layout-render.js:65
    notify observer.js:101
    notify component.js:306
    dataManagerSuccess data.js:214
    notify observer.js:104
    trigger base.js:190
    dataManagerSuccess data.js:212
    refreshDataManager data.js:189
    promise callback*Data2.prototype.refreshDataManager data.js:188
    Data2 data.js:21
    initializeModules kanban.js:341
    render kanban.js:168
    appendTo component.js:224
    mounted component-base.js:94
    createHook runtime-core.esm-bundler.js:2874
    callWithErrorHandling runtime-core.esm-bundler.js:193
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:201
    __weh runtime-core.esm-bundler.js:2854
    flushPostFlushCbs runtime-core.esm-bundler.js:369
    flushJobs runtime-core.esm-bundler.js:407
    promise callback*queueFlush runtime-core.esm-bundler.js:310
    queueJob runtime-core.esm-bundler.js:304
    scheduler runtime-core.esm-bundler.js:1965
    resetScheduling reactivity.esm-bundler.js:263
    triggerEffects reactivity.esm-bundler.js:302
    triggerRefValue reactivity.esm-bundler.js:1067
    set value reactivity.esm-bundler.js:1112
    setup Task.vue:247
    createHook runtime-core.esm-bundler.js:2874
    callWithErrorHandling runtime-core.esm-bundler.js:193
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:201
    __weh runtime-core.esm-bundler.js:2854
    flushPostFlushCbs runtime-core.esm-bundler.js:369
    flushJobs runtime-core.esm-bundler.js:407
    promise callback*queueFlush runtime-core.esm-bundler.js:310
    queuePostFlushCb runtime-core.esm-bundler.js:330
    queueEffectWithSuspense runtime-core.esm-bundler.js:1749
    scheduler runtime-core.esm-bundler.js:1960
    resetScheduling reactivity.esm-bundler.js:263
    triggerEffects reactivity.esm-bundler.js:302
    triggerRefValue reactivity.esm-bundler.js:1067
    set value reactivity.esm-bundler.js:1112
    finalizeNavigation vue-router.mjs:3353
    pushWithRedirect vue-router.mjs:3218
    promise callback*pushWithRedirect vue-router.mjs:3185
    push vue-router.mjs:3110
    install vue-router.mjs:3551
    use runtime-core.esm-bundler.js:3852
    <anonymous> main.js:12




<template>
<div class="e-header-text">
<button>TASKS</button>
<!-- Enable this later and perfect this -->
<table id="property" title="Properties">
<tr>
<td>
<button id="addNew" class="e-btn e-dialog-add" @click="addClick">
Add New Card
</button>
</td>
</tr>
</table>
</div>
<div v-if="isDataLoaded">
<ejs-kanban
ref="kanbanInstance"
id="kanban"
keyField="status"
width="100%"
height="1100px"
:dataSource="kanbanData"
:cardSettings="cardSettings"
:dialogSettings="dialogSettings"
:actionComplete="handleAction"
:enable-tooltip="true"
>
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
<template v-slot:dialogTemplate="{ data }">
<div>
<table>
<tbody>
<tr>
<td class="e-label">ID</td>
<td>
<div class="e-float-input e-control-wrapper">
<input
id="Id"
name="Id"
type="text"
class="e-field"
v-model="data.id"
disabled
/>
</div>
</td>
</tr>
<tr>
<td class="e-label">Status</td>
<td>
<v-select
id="Status"
name="Status"
class="e-field"
v-model="data.status"
:items="statusData"
item-title="title"
item-value="value"
placeholder="Status"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Assignee</td>
<td>
<v-select
id="Assignee"
name="Assignee"
v-model="data.ownerId"
class="e-field"
:items="assigneeData"
placeholder="Assignee"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Priority</td>
<td>
<v-select
type="text"
name="Priority"
id="Priority"
v-model="data.priority"
popupHeight="300px"
class="e-field"
placeholder="Priority"
:items="priorityData"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Summary</td>
<td>
<div class="e-float-input e-control-wrapper">
<textarea
type="text"
name="Summary"
id="Summary"
class="e-field"
v-model="data.summary"
></textarea>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<template v-slot:cardTemplate="{ data }">
<div
class="task-new17"
@click="onTaskNewContainerClick"
:style="taskNewStyle"
>
<div class="task-one">
<div class="task-two">
<div class="task-three">
<div class="goal-one">
<div class="fitlifechallenge2">{{ data.title }}</div>
</div>
<div class="supplements">
<div class="promote-fitness-supplements2">
{{ data.summary }}
</div>
</div>
</div>
<div class="to-do-list1">
<button class="tag834" :style="tagStyle">
<div class="tag835">tag1</div>
</button>
<button class="tag836">
<div class="tag837">Challenge</div>
</button>
<button class="tag838" :style="tag1Style">
<div class="tag839">tag2</div>
</button>
<button class="tag840">
<div class="tag841">Video</div>
</button>
</div>
</div>
<div class="time-frame-tasks">
<div class="on-hold-tasks">
<img
class="iconsclock-filled27"
loading="eager"
alt=""
/>
<div class="h-left17">{{ data.dueDate }}</div>
</div>
<img
class="photo-small-icon19"
loading="eager"
alt=""
/>
</div>
</div>
</div>
</template>
</ejs-kanban>
</div>
<div v-else>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</template>

<script setup>
import {
KanbanComponent as EjsKanban,
ColumnsDirective as EColumns,
ColumnDirective as EColumn,
} from "@syncfusion/ej2-vue-kanban";
import { ref, onMounted, watch, computed, reactive } from "vue";
import TaskService from "@/services/task.service";
import { v4 as uuidv4 } from "uuid";
import { useAuthStore } from "@/store/authStore";

const authStore = useAuthStore();
const dataManager = ref([]);
const isDataLoaded = ref(false);
const kanbanInstance = ref(null);
const kanbanData = ref(null);

const status = ref("");
const title = ref("");
const summary = ref("");
const priority = ref(0);
const color = ref("#bbbbbb");
const dueDate = ref("");
const rankId = ref(0);
const showForm = ref(false);
const dialogSettings = reactive({ template: "dialogTemplate" });
const cardSettings = reactive({ template: "cardTemplate" });

const statusData = [
{ title: "To Do", value: "Open" },
{ title: "In Progress", value: "InProgress" },
{ title: "Testing", value: "Testing" },
{ title: "Done", value: "Close" },
];
const priorityData = ["Low", "Normal", "Critical", "Release Breaker", "High"];
const assigneeData = [
"Nancy Davloio",
"Andrew Fuller",
"Janet Leverling",
"Steven walker",
"Robert King",
"Margaret hamilt",
"Michael Suyama",
];

const props = defineProps({
userPermissions: {
type: Object,
required: true,
},
handleError: {
type: Function,
required: true,
},
handleSuccess: {
type: Function,
required: true,
},
});

const toggleForm = () => {
showForm.value = !showForm.value;
};

// Fetch data on component mount
onMounted(async () => {
try {
const data = await new TaskService().getAllTasks();
dataManager.value = data.data;
isDataLoaded.value = true;
} catch (error) {
console.error("Error fetching data:", error);
}
});

// Watch for changes in dataManager and update dataSource accordingly
watch(
() => dataManager.value,
() => {
kanbanData.value = dataManager.value;
}
);

// Task handler for CRUD operations
const handleAction = async (args) => {
console.log("handleAction", args);
const { addedRecords, changedRecords, deletedRecords, requestType } = args;
if (requestType === "taskCreated") {
const task = addedRecords[0];
const addTask = {
subject: task.subject,
location: task.location,
description: task.description ? task.description : "",
startTime: task.startTime,
startTimezone: task.startTimezone,
endTime: task.endTime,
endTimezone: task.endTimezone,
isAllDay: task.isAllDay,
recurrenceRule: task.recurrenceRule ? task.recurrenceRule : "",
recurrenceID: task.recurrenceId ? task.recurrenceID : "",
recurrenceException: task.recurrenceException
? task.recurrenceException
: "",
user: authStore.user.id,
business: authStore.user.businessId,
};
await handleSaveTask(addTask);
} else if (requestType === "cardChanged") {
const task = changedRecords[0];
const changeTask = {
id: task.id,
title: task.title,
summary: task.summary,
status: task.status,
priority: task.priority,
color: task.color,
dueDate: task.dueDate,
rankId: task.rankId,
ownerId: authStore.user.id,
business: authStore.user.businessId,
};
await handleSaveTask(changeTask);
} else if (requestType === "cardRemoved") {
// Handle delete operation
const task = deletedRecords[0] ? deletedRecords[0] : changedRecords[0];
await handleDeleteTask(task.id);
}
};

const handleSaveTask = async (taskDoc) => {
try {
await new TaskService().saveTask({ taskDoc });
} catch (error) {
console.error("Error saving task:", error);
}
};

const handleDeleteTask = async (taskId) => {
try {
await new TaskService().deleteTask({ taskId: taskId });
} catch (error) {
console.error("Error deleting task:", error);
}
};

const addClick = () => {
const cardIds = kanbanInstance.value.ej2Instances.kanbanData.map((obj) =>
parseInt(obj.Id.replace("Task ", ""), 10)
);
const cardCount = Math.max.apply(Math, cardIds) + 1;
const cardDetails = {
id: "Task " + cardCount,
status: "Open",
priority: "Normal",
ownerId: "Andrew Fuller",
summary: "",
};
kanbanInstance.value.ej2Instances.openDialog("Add", cardDetails);
};

// Function to add a new task
const addNewTask = async () => {
// Create a new task object with default values or user inputs
const newTask = {
// id: generateUniqueId(), // You may use a function to generate a unique ID
ownerId: authStore.user.id,
title: title.value,
summary: summary.value,
status: status.value, // Set the default status
priority: priority.value, // Set the default priority
color: color.value, // Set the default color
rankId: rankId.value, // Set the default rankId
dueDate: dueDate.value, // Set the default due date
business: authStore.user.businessId,
};

// Add the new task to the data source
dataManager.value.push(newTask);

// Update the kanbanData to trigger a re-render
kanbanData.value = [...dataManager.value];

// Save the new task to the server
await handleSaveTask(newTask);

toggleForm();
};

// Function to generate a unique ID
const generateUniqueId = () => {
return uuidv4();
};

defineExpose({
kanbanInstance,
addClick,
});

const taskNewStyle = computed(() => ({
height: props.propHeight,
flex: props.propFlex,
}));

const tagStyle = computed(() => ({
width: props.propWidth,
}));

const tag1Style = computed(() => ({
padding: props.propPadding,
width: props.propWidth1,
}));
</script>


VJ Vinitha Jeyakumar Syncfusion Team January 24, 2024 11:19 AM UTC

Hi Bryan Cantwell,

We have tried to replicate the reported issue at our end using the code you have provided. but we didn't face any issues as you reported. Please check the sample below,



Can you please share us with the simple issue replicating runnable sample to validate further on our end.

Regards,
Vinitha


BC Bryan Cantwell replied to Vinitha Jeyakumar January 24, 2024 03:54 PM UTC

You didn't replicate my error at all. You just posted your same example again. Pay attention to my code I posted which uses templates for both card and dialog. There is no issue if you only use dialogTemplate, but when you also customize the cardTemplate it somehow  loses the data in the card.

For instance, with a custom cardTemplate, it loads and looks nice, but when I drag and drop the card, all of its data disappears. This is why the data fed to the dialogTemplate is undefined... because the cardTemplate is wrong somehow.

What is wrong with how I made my cardTemplate, and why is the data being lost when I perform an action on one of them?



VJ Vinitha Jeyakumar Syncfusion Team January 25, 2024 12:37 PM UTC

Bryan Cantwell,


On further validating your shared code snippet. we found that you have missed to add headerField to the cardSettings and also in the cardTemplate "card-template", "e-card-header" and "e-card-content" classes are missing, which is necessary to define the card template, so make sure to include the classes in the cardTemplate to resolve the issue. Please check the modified sample below,

Note: The headerField property of cardSettings is mandatory to render the cards in the Kanban board. It acts as a unique field that is used to avoid the duplication of card data

Code snippet:
 
<template v-slot:cardTemplate="{ data }">
                <div class="card-template">
                  <div class="e-card-header">
                    <div class="e-card-header-caption">
                      <div class="e-card-header-title e-tooltip-text">
                        {{ data.Title }}
                      </div>
                    </div>
                  </div>
                  <div class="e-card-content e-tooltip-text">
                    <div class="e-text">{{ data.Summary }}</div>
                  </div>
                  <div class="to-do-list1">
                    <button class="tag834" :style="tagStyle">
                      <div class="tag835">tag1</div>
                    </button>
                    <button class="tag836">
                      <div class="tag837">Challenge</div>
                    </button>
                    <button class="tag838" :style="tag1Style">
                      <div class="tag839">tag2</div>
                    </button>
                    <button class="tag840">
                      <div class="tag841">Video</div>
                    </button>
                  </div>
                  <div class="e-card-custom-footer"></div>
                </div>
              </template>


return {
      isDataLoaded: false,
      kanbanData: extend([], kanbanData, null, true),
      cardSettings: {
        template: 'cardTemplate',
        headerField: 'Id',
      },
}




Please let us know if it doesn't resolve your issue.

Regards,
Vinitha

Marked as answer

BC Bryan Cantwell January 25, 2024 06:47 PM UTC

That's what I needed, thanks.



BC Bryan Cantwell January 26, 2024 07:17 PM UTC

Now I have a challenge with my dialog. It does pop up now and is populated with the data from the card, but the save button does nothing (console shows nothing at all is happening) cancel of course works, and delete gives an error:
Uncaught DOMException: Element.closest: '.' is not a valid selector

    closest dom.js:325
    getColumnName dialog.js:398
    onBeforeDialogClose dialog.js:235
    notify observer.js:101
    trigger base.js:190
    hide dialog.js:1555
    dialogButtonClick dialog.js:356
    add event-handler.js:68
    setButton dialog.js:708
    initRender dialog.js:458
    render dialog.js:136
    appendTo component.js:224
    Component2 component.js:90
    Dialog2 dialog.js:124
    renderDialog dialog.js:47
    openDialog dialog.js:24
    cardDoubleClick action.js:148
    notify observer.js:104
    trigger base.js:190
    cardDoubleClick action.js:146
    doubleClickHandler action.js:98
    add event-handler.js:68
    wireEvents layout-render.js:1063
    initRender layout-render.js:65
    notify observer.js:101
    notify component.js:306
    dataManagerSuccess data.js:214
    notify observer.js:104
    trigger base.js:190
    dataManagerSuccess data.js:212
    refreshDataManager data.js:189
    promise callback*Data2.prototype.refreshDataManager data.js:188
    Data2 data.js:21
    initializeModules kanban.js:341
    render kanban.js:168
    appendTo component.js:224
    mounted component-base.js:94
    createHook runtime-core.esm-bundler.js:2874
    callWithErrorHandling runtime-core.esm-bundler.js:193
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:201
    __weh runtime-core.esm-bundler.js:2854
    flushPostFlushCbs runtime-core.esm-bundler.js:369
    flushJobs runtime-core.esm-bundler.js:407
    promise callback*queueFlush runtime-core.esm-bundler.js:310
    queueJob runtime-core.esm-bundler.js:304
    scheduler runtime-core.esm-bundler.js:1965
    resetScheduling reactivity.esm-bundler.js:263
    triggerEffects reactivity.esm-bundler.js:302
    triggerRefValue reactivity.esm-bundler.js:1067
    set value reactivity.esm-bundler.js:1112
    setup Task.vue:268
    createHook runtime-core.esm-bundler.js:2874
    callWithErrorHandling runtime-core.esm-bundler.js:193
    callWithAsyncErrorHandling runtime-core.esm-bundler.js:201
    __weh runtime-core.esm-bundler.js:2854
    flushPostFlushCbs runtime-core.esm-bundler.js:369
    flushJobs runtime-core.esm-bundler.js:407
    promise callback*queueFlush runtime-core.esm-bundler.js:310
    queueJob runtime-core.esm-bundler.js:304
    reload runtime-core.esm-bundler.js:512
    tryWrap runtime-core.esm-bundler.js:542
    <anonymous> Task.vue:519
    accept client.ts:559
    fetchUpdate client.ts:476
    queueUpdate client.ts:323
    queueUpdate client.ts:323
    handleMessage client.ts:176
    handleMessage client.ts:174
    setupWebSocket client.ts:91
    setupWebSocket client.ts:90
    <anonymous> client.ts:67


This is my current version of the page, there must be some small difference because it looks (I think) just like your example...:

<template>
<div class="e-header-text property-section">
<button>TASKS</button>
<!-- Enable this later and perfect this -->
<table id="property" title="Properties">
<tr>
<td>
<button id="addNew" class="e-btn e-dialog-add" @click="addClick">
Add New Card
</button>
</td>
</tr>
</table>
</div>
<div v-if="isDataLoaded">
<ejs-kanban
ref="kanbanInstance"
id="kanban"
keyField="status"
width="100%"
height="1100px"
:dataSource="kanbanDataCopy"
:cardSettings="cardSettings"
:dialogSettings="dialogSettings"
:action-complete="handleAction"
:enable-tooltip="true"
>
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
<template v-slot:dialogTemplate="{ data }">
<div>
<table>
<tbody>
<tr>
<td class="e-label">ID</td>
<td>
<div class="e-float-input e-control-wrapper">
<input
id="id"
name="id"
type="text"
class="e-field"
v-model="data.id"
disabled
/>
</div>
</td>
</tr>
<tr>
<td class="e-label">Status</td>
<td>
<v-select
id="Status"
name="Status"
class="e-field"
v-model="data.status"
:items="statusData"
item-title="title"
item-value="value"
placeholder="Status"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Assignee</td>
<td>
<v-select
id="Assignee"
name="Assignee"
v-model="data.ownerId"
class="e-field"
:items="assigneeData"
placeholder="Assignee"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Priority</td>
<td>
<v-select
type="text"
name="Priority"
id="Priority"
v-model="data.priority"
popupHeight="300px"
class="e-field"
placeholder="Priority"
:items="priorityData"
></v-select>
</td>
</tr>
<tr>
<td class="e-label">Summary</td>
<td>
<div class="e-float-input e-control-wrapper">
<textarea
type="text"
name="Summary"
id="Summary"
class="e-field"
v-model="data.summary"
></textarea>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<template v-slot:cardTemplate="{ data }">
<div class="card-template task-new17">
<div class="task-two">
<div class="task-three">
<div
class="e-card-header e-card-header-caption e-card-header-title e-tooltip-text"
>
<div class="fitlifechallenge2">{{ data.title }}</div>
</div>
<div class="supplements">
<div
class="e-card-content e-tooltip-text promote-fitness-supplements2"
>
{{ data.summary }}
</div>
</div>
</div>
<div class="to-do-list1">
<button class="tag834" :style="tagStyle">
<div class="tag835">tag1</div>
</button>
<button class="tag836">
<div class="tag837">Challenge</div>
</button>
<button class="tag838" :style="tag1Style">
<div class="tag839">tag2</div>
</button>
<button class="tag840">
<div class="tag841">Video</div>
</button>
</div>
</div>
<div class="e-card-custom-footer time-frame-tasks">
<div class="on-hold-tasks">
<img
class="iconsclock-filled27"
loading="eager"
alt=""
/>
<div class="h-left17">{{ data.dueDate }}</div>
</div>
<v-avatar size="40px" color="grey lighten-4">
<v-img
v-if="
data.assigneeUserInfo &&
data.assigneeUserInfo !== null &&
data.assigneeUserInfo.avatar !== ''
"
:src="data.assigneeUserInfo.avatar"
alt="Profile"
/>
<v-img
v-else-if="
data.assigneeBusinessInfo &&
data.assigneeBusinessInfo !== null &&
data.assigneeBusinessInfo.picture !== ''
"
:src="data.assigneeBusinessInfo.picture"
alt="Profile"
/>
<v-img v-else src="/business_images/picture.jpg" />
</v-avatar>
</div>
</div>
</template>
</ejs-kanban>
</div>
<div v-else>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</template>

<script setup>
import {
KanbanComponent as EjsKanban,
ColumnsDirective as EColumns,
ColumnDirective as EColumn,
} from "@syncfusion/ej2-vue-kanban";
import { ref, onMounted, watch, computed, reactive } from "vue";
import TaskService from "@/services/task.service";
import { v4 as uuidv4 } from "uuid";
import { useAuthStore } from "@/store/authStore";
import { extend } from "@syncfusion/ej2-base";

const authStore = useAuthStore();
const dataManager = ref([]);
const isDataLoaded = ref(false);
const kanbanInstance = ref(null);
const kanbanData = ref([]);
const kanbanDataCopy = ref([]);

const status = ref("");
const title = ref("");
const summary = ref("");
const priority = ref(0);
const color = ref("#bbbbbb");
const dueDate = ref("");
const rankId = ref(0);
const showForm = ref(false);
const dialogSettings = reactive({ template: "dialogTemplate" });
const cardSettings = reactive({
headerField: "id",
template: "cardTemplate",
});

const statusData = [
{ title: "To Do", value: "Open" },
{ title: "In Progress", value: "InProgress" },
{ title: "Testing", value: "Testing" },
{ title: "Done", value: "Close" },
];
const priorityData = [
{ title: "Low", value: 0 },
{ title: "Normal", value: 1 },
{ title: "Critical", value: 2 },
{ title: "Release Breaker", value: 3 },
{ title: "High", value: 4 },
];
var assigneeData = [
"Nancy Davloio",
"Andrew Fuller",
"Janet Leverling",
"Steven walker",
"Robert King",
"Margaret hamilt",
"Michael Suyama",
];

const props = defineProps({
userPermissions: {
type: Object,
required: true,
},
handleError: {
type: Function,
required: true,
},
handleSuccess: {
type: Function,
required: true,
},
});

const toggleForm = () => {
showForm.value = !showForm.value;
};

// Fetch data on component mount
onMounted(async () => {
try {
const data = await new TaskService().getAllTasks();
kanbanData.value = data.data;
kanbanDataCopy.value = extend([], kanbanData.value, null, true);
isDataLoaded.value = true;
console.log("task data", kanbanData.value);
} catch (error) {
console.error("Error fetching data:", error);
}
});

// Watch for changes in kanbanData and update dataSource accordingly
watch(kanbanData, (newValue) => {
kanbanDataCopy.value = extend([], newValue, null, true);
});

// Task handler for CRUD operations
const handleAction = async (args) => {
console.log("handleAction", args);
const { addedRecords, changedRecords, deletedRecords, requestType } = args;
if (requestType === "taskCreated") {
console.log("taskCreated", args);
const task = addedRecords[0];
const addTask = {
id: task.id,
title: task.title,
summary: task.summary,
status: task.status,
priority: task.priority,
color: task.color,
dueDate: task.dueDate,
rankId: task.rankId,
ownerId: authStore.user.id,
ownerType: task.ownerType,
business: authStore.user.businessId,
};
await handleSaveTask(addTask);
} else if (requestType === "cardChanged") {
console.log("cardChanged", changedRecords);
const task = changedRecords[0];
const changeTask = {
id: task.id,
title: task.title,
summary: task.summary,
status: task.status,
priority: task.priority,
color: task.color,
dueDate: task.dueDate,
rankId: task.rankId,
ownerId: authStore.user.id,
ownerType: task.ownerType,
business: authStore.user.businessId,
};
await handleSaveTask(changeTask);
} else if (requestType === "cardRemoved") {
console.log("cardRemoved", args);
// Handle delete operation
const task = deletedRecords[0] ? deletedRecords[0] : changedRecords[0];
await handleDeleteTask(task.id);
}
};

const handleSaveTask = async (taskDoc) => {
console.log("handleSaveTask", taskDoc);
try {
await new TaskService().saveTask({ taskDoc });
} catch (error) {
console.error("Error saving task:", error);
}
};

const handleDeleteTask = async (taskId) => {
console.log("handleSaveTask", taskDoc);
try {
await new TaskService().deleteTask({ taskId: taskId });
} catch (error) {
console.error("Error deleting task:", error);
}
};

const addClick = () => {
const cardDetails = {
id: "Task " + generateUniqueId(),
status: "Open",
priority: "Normal",
ownerId: "Andrew Fuller",
summary: "",
};
kanbanInstance.value.ej2Instances.openDialog("Add", cardDetails);
};

// Function to add a new task
const addNewTask = async () => {
const newTask = {
// id: generateUniqueId(),
ownerId: authStore.user.id,
title: title.value,
summary: summary.value,
status: status.value, // Set the default status
priority: priority.value, // Set the default priority
color: color.value, // Set the default color
rankId: rankId.value, // Set the default rankId
dueDate: dueDate.value, // Set the default due date
business: authStore.user.businessId,
};

// Add the new task to the data source
dataManager.value.push(newTask);

// Update the kanbanData to trigger a re-render
kanbanData.value = [...dataManager.value];

// Save the new task to the server
await handleSaveTask(newTask);

toggleForm();
};

// Function to generate a unique ID
const generateUniqueId = () => {
return uuidv4();
};

defineExpose({
kanbanInstance,
addClick,
});

const taskNewStyle = computed(() => ({
height: props.propHeight,
flex: props.propFlex,
}));

const tagStyle = computed(() => ({
width: props.propWidth,
}));

const tag1Style = computed(() => ({
padding: props.propPadding,
width: props.propWidth1,
}));
</script>


VJ Vinitha Jeyakumar Syncfusion Team January 30, 2024 09:43 AM UTC

Hi Bryan Cantwell,


Your reported issues can be resolved by using the below solutions. 

While using the v-select control, add the class "e-field e-dropdownlist" like below,

Code snippet:
<v-select
                              id="Status"
                              name="Status"
                              class="e-field e-dropdownlist"
                              v-model="data.Status"
                              :options="[
                                { label: 'Canada', code: 'ca' },
                                { label: 'United States', code: 'us' },
                              ]"
                              label="Status"
                            ></v-select>


And when using dialogTemplate, add the table content inside the form element like below,

Code snippet:
 <template v-slot:dialogTemplate="{ data }">
                <div>
                  <form id="KanbanEdit">
                    <table>
                      <tbody>
                        <tr>
                          <td class="e-label">ID</td>
                          <td>
                            <div class="e-float-input e-control-wrapper">
                              <input
                                id="id"
                                name="id"
                                type="text"
                                class="e-field"
                                v-model="data.Id"
                                disabled
                              />
                            </div>
                          </td>
                        </tr>
                        <tr>
                          <td class="e-label">Status</td>
                          <td>
                            <v-select
                              id="Status"
                              name="Status"
                              class="e-field e-dropdownlist"
                              v-model="data.Status"
                              :options="[
                                { label: 'Canada', code: 'ca' },
                                { label: 'United States', code: 'us' },
                              ]"
                              label="Status"
                            ></v-select>
                          </td>
                        </tr>

                        <tr>
                          <td class="e-label">Summary</td>
                          <td>
                            <div class="e-float-input e-control-wrapper">
                              <textarea
                                type="text"
                                name="Summary"
                                id="Summary"
                                class="e-field"
                                v-model="data.Summary"
                              ></textarea>
                            </div>
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </form>
                </div>
              </template>


Regards,
Vinitha

Loader.
Up arrow icon