Skip to content

Lazy Loading #201

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

});

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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