Skip to content

Lazy Loading #201

@pathakdurgesh

Description

@pathakdurgesh

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);
    }
});

});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions