-
Notifications
You must be signed in to change notification settings - Fork 939
Description
I want to implement lazy loading here. How can I achieve this?
I'm using the code below — it works correctly when pageNum = 1, but the data from the next pages is not being appended to the Gantt chart.
let isLoading = false; // Prevents sending multiple requests at the same time
let noMoreData = false; // Becomes true when all server data has been fetched
let currentPage = 1; // Tracks the current page number to fetch
const tasksPerPage = 50; // The number of tasks to fetch in each request (chunk size)
let mainProjectData;
function loadGanttFromServer(taskId, callback, baseline_id, closeUnloadmodel,pageNum=1) {
if (isLoading || (pageNum > 1 && noMoreData)) {
return;
}
isLoading = true;
if(pageNum === 1) {
$("#loading-mask, #loading").css({ display: 'block' });
}
var prof = new Profiler("loadServerSide");
prof.reset();
$.getJSON("/manage/server/" + hash + "/load?showsql=true&baseline_id="+baseline_id+"&page="+pageNum+"", {}, function (response)
{
if (response.ok) {
const newTasks = response.data.tasks;
if (pageNum === 1) {
// --- INITIAL LOAD (PAGE 1) ---
mainProjectData = response.data; // Store the initial project data
ge.loadProject(mainProjectData); // Load the project with the first page of tasks
// --- ONE-TIME UI SETUP ---
orgData = response.data;
GanttMaster.taskStatusDecoder = orgData.taskStatusDecode;
if(response.data.baseLineName){
// Set up baseline UI elements
setupBaselineUI(response.data.baseLineName, baseline_id, closeUnloadmodel);
}
// Set up tag filter from the initial batch of tasks
setupTagFilter(newTasks);
ge.checkpoint(); // Empty the undo stack
} else {
// --- SUBSEQUENT LOADS (APPEND DATA) ---
if (newTasks && newTasks.length > 0) {
// Append new tasks to our main data object
mainProjectData.tasks.push(...newTasks);
// for (const task of newTasks) {
// ge.addTask(task);
// }
// IDEAL METHOD: If your Gantt library supports adding tasks without a full refresh.
// This is more performant. Example:
// FALLBACK METHOD: If you must reload, save scroll position to avoid jumping.
// This is less performant and may cause a flicker.
const currentScroll = $('.splitBox1').scrollTop();
// ge.loadProject(mainProjectData); // Reload project with all tasks
// ge.redraw();
ge.loadTasks(newTasks);
ge.redraw();
$('.splitBox1, .splitBox2').scrollTop(currentScroll); // Restore scroll position
}
}
// If the server returned fewer tasks than requested, we've reached the end
if (!newTasks || newTasks.length < tasksPerPage) {
noMoreData = true;
}
// Increment the page counter for the next request
currentPage++;
if (typeof (callback) == "function") {
callback(response);
}
} else {
var errMsg = "Errors loading project\n\n" + (response.message || "");
ge.editor.custom_alert_box(errMsg);
noMoreData = true; // Stop trying to load more on error
}
prof.stop();
isLoading = false;
$("#loading-mask, #loading").css({ display: 'none' });
});
}
function setupBaselineUI(baseLineNames, baseline_id, closeUnloadmodel) {
if (baseline_id) { $('.baselineUnload').show(); }
if (closeUnloadmodel === "closeUnloadmodel") { $('.baselineUnload').hide(); }
$('#baseline-list').html('<li style="text-align: center;background: #fff;margin: 0 0 8px 0;"><span onclick="showbaseline()" class="btn-blue" style="line-height: 18px;font-weight: 400;border-radius: 16px;">Capture Baseline</span></li>');
baseLineNames.forEach(baseline => {
const baselineLoadname = baseline.baselineName.length > 20 ? baseline.baselineName.slice(0, 20) + '...' : baseline.baselineName;
// The HTML for the baseline item is complex, so it's kept as in your original code
$('#baseline-list').append('<li style="display:flex;align-items: center;padding: 8px 12px !important;" class="baseline-li baselineHoverList" value="' + baseline.baselineID + '"><div><span style="position: relative;bottom: 0px;color: #2e2e2e;font-weight: 400;font-size: 14px;">'+baselineLoadname+'</span><div style="margin-top: 5px;font-size: 10px;color: #6b7280;">'+baseline.createDate+'</div></div><small style="margin-left: 40px;color: #444;position: relative;bottom: 0px;top: 0;transform: rotate(271deg);left:12px" class="ico"><svg class="MiniIcon ArrowDownMiniIcon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" style="height: 14px;width: 14px;margin-left: 0px;"><path d="M3.5,8.9c0-0.4,0.1-0.7,0.4-1C4.5,7.3,5.4,7.2,6,7.8l5.8,5.2l6.1-5.2C18.5,7.3,19.5,7.3,20,8c0.6,0.6,0.5,1.5-0.1,2.1 l-7.1,6.1c-0.6,0.5-1.4,0.5-2,0L4,10.1C3.6,9.8,3.5,9.4,3.5,8.9z"></path></svg></small> <ul style="background: rgb(255, 255, 255);display: none;width: 182px;position: absolute;left: 180px;top: 5px;" class=" dd baselineActions"> <li class="loadBaseline" style="display: block !important;padding: 10px 12px !important;cursor:pointer" id="'+baseline.baselineID+'"><i class="icon-new-download baselineIcons"></i>Load</li> <li style="padding: 10px 12px !important; margin: 0px;"><a onclick="updateTaskTobaseline('+baseline.baselineID+')" style="padding:2px 0px 2px 20px"><i class="icon-recurring baselineIcons" style="position: absolute;left: 0;top: 50%;transform: translateY(-50%);max-width: 15px;"></i>Restore to baseline</a></li> <li style="padding: 10px 12px !important;margin: 0px;"><a onclick="deleteBaseline('+baseline.baselineID+')" style="padding:0"><i class="icon-new-delete baselineIcons"></i>Delete</a></li> </ul></li>');
});
baseLineDropdown();
}
function setupTagFilter(tasks) {
var tags = '';
for (var id in tasks) {
if (tasks[id] && tasks[id].tags && tasks[id].tags.trim() !== '') {
tags += tasks[id].tags + ',';
}
}
var tagArr = tags.slice(0, tags.length - 1).split(',');
var uniqueTags = tagArr.filter((value, index, self) => self.indexOf(value) === index && value.trim() !== '');
if (uniqueTags.length > 0) {
generateTagsList(uniqueTags); // Function to append tags to filter view
}
}
$(document).ready(function () {
// --- Initial Data Load ---
// Make sure 'hash' and other necessary variables are available.
// Replace placeholders with your actual initial values.
const initial_baseline_id = "";
const initial_close_model_flag = "";
loadGanttFromServer('','','','',currentPage);
// --- Scroll Handler Setup ---
const firstBox = $('.splitBox1');
const secondBox = $('.splitBox2');
const fs = firstBox.add(secondBox);
let stopScroll = null;
fs.on('scroll', function () {
// Exit if we are already loading or there is no more data to fetch
if (isLoading || noMoreData) {
return;
}
const el = $(this);
const scrollTop = el.scrollTop();
const scrollHeight = el[0].scrollHeight;
const containerHeight = el.outerHeight();
// Sync scrolling between the two boxes
if (el.is(".splitBox1") && stopScroll !== "splitBox2") {
stopScroll = "splitBox1";
secondBox.scrollTop(scrollTop);
} else if (el.is(".splitBox2") && stopScroll !== "splitBox1") {
stopScroll = "splitBox2";
firstBox.scrollTop(scrollTop);
}
// Reset the scroll-stopping mechanism after a short delay
setTimeout(() => { stopScroll = null; }, 150);
// Trigger lazy load when user scrolls near the bottom (e.g., 200px from the end)
if (scrollHeight > 0 && scrollTop + containerHeight >= scrollHeight - 200) {
console.log("Lazy loading page: " + currentPage);
loadGanttFromServer('','','','',currentPage);
}
});
});