- Virtual scrolling
- Limitations for virtual scrolling
Contact Support
Virtualization in Vue Kanban component
11 Jun 202421 minutes to read
Kanban allows you to load a large amount of data without any performance degradation. This feature can be enabled by setting the enableVirtualization
property in the Kanban to true
.
Virtual scrolling
Virtual scrolling optimizes data rendering within each column when using large datasets. Only a subset of cards that are visible and about to be loaded on the screen are rendered. The number of records displayed in the Kanban is determined implicitly by the height of the Kanban area and the card height. The cardHeight
property of Kanban can be used to set the cards’ height in pixel value. By default, the card height will be auto
.
When the Kanban column is scrolled, the virtual scrolling feature dynamically loads additional data on demand into view and unloads the data that is no longer visible.
<template>
<div id="app">
<ejs-kanban id="KanbanVirtualScrolling" :enableVirtualization="true" keyField="Status"
:dataSource="kanbanData" :enableTooltip="enableTooltip" :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="In Review" keyField="Review" ></e-column>
<e-column headerText="Done" keyField="Close" ></e-column>
</e-columns>
</ejs-kanban>
</div>
</template>
<script setup>
import { KanbanComponent as EjsKanban, ColumnsDirective as EColumns, ColumnDirective as EColumn} from '@syncfusion/ej2-vue-kanban';
import { extend } from '@syncfusion/ej2-base';
import { kanbanData } from '../card-header-cs3/datasource';
let BUG_TASKS = [
'UI component not displaying images in IE browser',
'Button not responding on hover action',
'Text overlapping in mobile view',
'Dropdown menu not functioning properly',
'Form validation error',
'Alignment issue in tables',
'Column not loading completely',
'Broken UI Designs',
'Font size inconsistency',
'UI element misaligned on scroll'
];
let FEATURE_TASKS = [
'Implement new user registration flow',
'Add pagination to search results',
'Improve accessibility for visually impaired users',
'Create custom dashboard for users',
'Develop user profile editing functionality',
'Integrate with third-party API for weather data',
'Implement social media sharing for articles',
'Add support for multiple languages',
'Create onboarding tutorial for new users',
'Implement push notifications for mobile app'
];
let EPIC_TASKS = [
'Revamp UI design for entire application',
'Develop mobile application for iOS and Android',
'Create API for integration with external systems',
'Implement machine learning algorithms for personalized recommendations',
'Upgrade database infrastructure for scalability',
'Integrate with payment gateway for subscription model',
'Develop chatbot for customer support',
'Implement real-time collaboration features for team projects',
'Create analytics dashboard for administrators',
'Introduce gamification elements to increase user engagement',
];
let assignee = ['Andrew Fuller', 'Janet Leverling', 'Steven walker', 'Robert King', 'Margaret hamilt', 'Nancy Davloio', 'Margaret Buchanan', 'Laura Bergs', 'Anton Fleet', 'Jack Kathryn', 'Martin Davolio', 'Fleet Jack'];
let status = ['Open', 'InProgress', 'Review', 'Testing', 'Close'];
let priority = ['Ultra-Critical', 'Critical', 'High', 'Normal', 'Low'];
let types = ['Epic', 'Bug', 'Story'];
let tagsField = ['Feature', 'Bug', 'Enhancement', 'Documentation', 'Automation', 'Mobile', 'Web', 'iOS', 'Safari', 'Chrome', 'Firefox', 'Manual Testing'];
let storyPoints = ['1', '2', '3', '3.5', '4', '4.5', '5', '6', '7.5', '8'];
let count = 5300;
let generateKanbanDataVirtualScrollData = () => {
let kanbanVirtualData = [];
for (let a = 5000, id = 5000; a <= count; a++) {
let typeValue = types[Math.floor(Math.random() * types.length)];
let summary = typeValue === 'Bug' ? BUG_TASKS[Math.floor(Math.random() * BUG_TASKS.length)] :
typeValue === 'Story' ? FEATURE_TASKS[Math.floor(Math.random() * FEATURE_TASKS.length)] :
EPIC_TASKS[Math.floor(Math.random() * EPIC_TASKS.length)];
kanbanVirtualData.push({
Id: id,
Type: typeValue,
Priority: priority[Math.floor(Math.random() * priority.length)],
Status: status[Math.floor(Math.random() * status.length)],
Assignee: assignee[Math.floor(Math.random() * assignee.length)],
StoryPoints: storyPoints[Math.floor(Math.random() * storyPoints.length)],
Tags: [tagsField[Math.floor(Math.random() * tagsField.length)], tagsField[Math.floor(Math.random() * tagsField.length)]],
Title: 'Task ' + id,
Summary: summary,
});
id++;
}
return kanbanVirtualData;
}
kanbanData = extend([], generateKanbanDataVirtualScrollData(), null, true);
const enableTooltip = true;
const cardSettings = {
headerField: "Id",
contentField: "Summary",
selectionType: "Multiple"
};
const dialogSettings = {
fields: [
{key: 'Id', text: 'ID', type: 'TextBox'},
{key: 'Status', text: 'Status', type: 'DropDown'},
{key: 'StoryPoints', text: 'Story Points', type: 'Numeric' },
{key: 'Summary', text: 'Summary', type: 'TextArea'}
]
};
</script>
<style>
@import '../node_modules/@syncfusion/ej2-base/styles/material.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material.css';
@import '../node_modules/@syncfusion/ej2-layouts/styles/material.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/material.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material.css';
@import '../node_modules/@syncfusion/ej2-vue-kanban/styles/material.css';
</style>
<template>
<div id="app">
<ejs-kanban id="KanbanVirtualScrolling" :enableVirtualization="true" keyField="Status"
:dataSource="kanbanData" :enableTooltip="enableTooltip" :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="In Review" keyField="Review" ></e-column>
<e-column headerText="Done" keyField="Close" ></e-column>
</e-columns>
</ejs-kanban>
</div>
</template>
<script>
import { KanbanComponent, ColumnDirective, ColumnsDirective } from '@syncfusion/ej2-vue-kanban';
import { extend } from '@syncfusion/ej2-base';
let BUG_TASKS = [
'UI component not displaying images in IE browser',
'Button not responding on hover action',
'Text overlapping in mobile view',
'Dropdown menu not functioning properly',
'Form validation error',
'Alignment issue in tables',
'Column not loading completely',
'Broken UI Designs',
'Font size inconsistency',
'UI element misaligned on scroll'
];
let FEATURE_TASKS = [
'Implement new user registration flow',
'Add pagination to search results',
'Improve accessibility for visually impaired users',
'Create custom dashboard for users',
'Develop user profile editing functionality',
'Integrate with third-party API for weather data',
'Implement social media sharing for articles',
'Add support for multiple languages',
'Create onboarding tutorial for new users',
'Implement push notifications for mobile app'
];
let EPIC_TASKS = [
'Revamp UI design for entire application',
'Develop mobile application for iOS and Android',
'Create API for integration with external systems',
'Implement machine learning algorithms for personalized recommendations',
'Upgrade database infrastructure for scalability',
'Integrate with payment gateway for subscription model',
'Develop chatbot for customer support',
'Implement real-time collaboration features for team projects',
'Create analytics dashboard for administrators',
'Introduce gamification elements to increase user engagement',
];
let assignee = ['Andrew Fuller', 'Janet Leverling', 'Steven walker', 'Robert King', 'Margaret hamilt', 'Nancy Davloio', 'Margaret Buchanan', 'Laura Bergs', 'Anton Fleet', 'Jack Kathryn', 'Martin Davolio', 'Fleet Jack'];
let status = ['Open', 'InProgress', 'Review', 'Testing', 'Close'];
let priority = ['Ultra-Critical', 'Critical', 'High', 'Normal', 'Low'];
let types = ['Epic', 'Bug', 'Story'];
let tagsField = ['Feature', 'Bug', 'Enhancement', 'Documentation', 'Automation', 'Mobile', 'Web', 'iOS', 'Safari', 'Chrome', 'Firefox', 'Manual Testing'];
let storyPoints = ['1', '2', '3', '3.5', '4', '4.5', '5', '6', '7.5', '8'];
let count = 5300;
let generateKanbanDataVirtualScrollData = () => {
let kanbanVirtualData = [];
for (let a = 5000, id = 5000; a <= count; a++) {
let typeValue = types[Math.floor(Math.random() * types.length)];
let summary = typeValue === 'Bug' ? BUG_TASKS[Math.floor(Math.random() * BUG_TASKS.length)] :
typeValue === 'Story' ? FEATURE_TASKS[Math.floor(Math.random() * FEATURE_TASKS.length)] :
EPIC_TASKS[Math.floor(Math.random() * EPIC_TASKS.length)];
kanbanVirtualData.push({
Id: id,
Type: typeValue,
Priority: priority[Math.floor(Math.random() * priority.length)],
Status: status[Math.floor(Math.random() * status.length)],
Assignee: assignee[Math.floor(Math.random() * assignee.length)],
StoryPoints: storyPoints[Math.floor(Math.random() * storyPoints.length)],
Tags: [tagsField[Math.floor(Math.random() * tagsField.length)], tagsField[Math.floor(Math.random() * tagsField.length)]],
Title: 'Task ' + id,
Summary: summary,
});
id++;
}
return kanbanVirtualData;
}
export default {
name: "App",
components: {
"ejs-kanban":KanbanComponent,
"e-columns":ColumnsDirective,
"e-column":ColumnDirective
},
data: function() {
return {
kanbanData: extend([], generateKanbanDataVirtualScrollData(), null, true),
enableTooltip: true,
cardSettings: {
headerField: "Id",
contentField: "Summary",
selectionType: "Multiple"
},
dialogSettings: {
fields: [
{key: 'Id', text: 'ID', type: 'TextBox'},
{key: 'Status', text: 'Status', type: 'DropDown'},
{key: 'StoryPoints', text: 'Story Points', type: 'Numeric' },
{key: 'Summary', text: 'Summary', type: 'TextArea'}
]
}
};
},
}
</script>
<style>
@import '../node_modules/@syncfusion/ej2-base/styles/material.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material.css';
@import '../node_modules/@syncfusion/ej2-layouts/styles/material.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/material.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material.css';
@import '../node_modules/@syncfusion/ej2-vue-kanban/styles/material.css';
</style>
Configure the remote data service
When the remote data is configured for the dataSource
, the service method will receive an additional KanbanVirtualization
parameter to handle the initial data load for Kanban Virtualization.
To handle Kanban virtual scrolling, the server-side code needs to handle the Where
and Take
queries differently using the KanbanVirtualization
parameter. The following is the example code for handling Kanban virtualization’s initial data load using the KanbanVirtualization
parameter.
public IActionResult LoadCard([FromBody] ExtendedDataManagerRequest dm)
{
kanbanData = _context.KanbanCards.ToList();
IEnumerable<KanbanCard> DataSource = kanbanData.AsEnumerable();
DataOperations operation = new DataOperations();
// For normal kanban data load `Where` query handling.
if (dm.Where != null && dm.Where.Count > 0 && dm.KanbanVirtualization != "KanbanVirtualization")
{
dm.Where[0].value = dm.Where[0].value.ToString();
DataSource = operation.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator);
}
if (dm.Skip != 0)
{
DataSource = operation.PerformSkip(DataSource, dm.Skip);
}
// For normal Kanban data load `Take` query handling.
if (dm.Take != 0 && dm.KanbanVirtualization != "KanbanVirtualization")
{
DataSource = operation.PerformTake(DataSource, dm.Take);
}
// For Kanban virtual scrolling data load `Where` and `Take` query handling.
var columnCount = new List<KeyValuePair<string, int>>();
if (dm.KanbanVirtualization == "KanbanVirtualization" && dm.Where != null && dm.Where.Count > 0 && dm.Take != 0)
{
IEnumerable<KanbanCard> currentData = new List<KanbanCard>();
List<WhereFilter> currentFilter = new List<WhereFilter>();
for (int i = 0; i < dm.Where.Count; i++)
{
dm.Where[i].value = dm.Where[i].value.ToString();
currentFilter.Add(dm.Where[i]);
var filterData = operation.PerformFiltering(DataSource, currentFilter, dm.Where[i].Operator);
columnCount.Add(new KeyValuePair<string, int>(dm.Where[i].value.ToString(), filterData.Count()));
filterData = operation.PerformTake(filterData, dm.Take);
currentData = currentData.Concat(filterData);
currentFilter.Clear();
}
DataSource = currentData;
}
// To return the data for Kanban virtual scrolling.
if (dm.KanbanVirtualization == "KanbanVirtualization") {
return Json(new { result = DataSource, count = columnCount });
}
// To return the data for Kanban virtual scrolling.
else
{
return Json(DataSource);
}
}
Limitations for virtual scrolling
- When virtualization is enabled in a Kanban board and the card height is not explicitly set, it will not default to
auto
height. Instead, a fixed height of100px
will be applied to the cards. It’s important to note that the card height should be specified in pixel values, as percentage values are not accepted. - When a card is dragged and dropped, the index position of the card will not be preserved when scrolling through the column.
- Virtualization is not supported for swimlanes in the Kanban board.