Premium support for our pure JavaScript UI components


Post by jarmen »

When working with a lazy loaded tree store, adding the first child and expanding the node causes the crud manager to load resources and wipe out any pending resource changes. This does not occur if the parent already had children, even if those children have not yet been loaded.

We would expect the following behavior:

  • If a parent is marked with children: false, and remoteChildCount: 0, then adding the first child locally and expanding should not trigger a resource load request for the parent thus wiping out any pending crud changes.

Example base on https://bryntum.com/products/schedulerpro/examples/infinite-scroll-crudmanager/

import { SchedulerPro } from '../../build/schedulerpro.module.js?485313';
import shared from '../_shared/shared.module.js?485313';

const scheduler = new SchedulerPro({
    appendTo   : 'container',
    viewPreset : 'dayAndMonth',

// To center the view on a certain date
visibleDate : new Date(2024, 1, 6),

// Enables endless timeline scrolling
infiniteScroll  : true,
// The infiniteScroll gets a better UX if a larger bufferCoef is used. When using lazyLoading, this only means that
// the timeline "shifts" more seldom. Only events inside or close to the visible date range is requested from the
// backend
bufferCoef      : 20,
// Affects when the timespan shifts upon horizontal scroll
bufferThreshold : 0.01,

// Current backend easily runs out of memory, this prevents the requested date ranges from being too long
minZoomLevel : 11,

tickSize : 30,

project : {
    autoLoad        : true,
    autoSync        : false,
    lazyLoad        : true,
    loadUrl         : 'php/read.php',
    syncUrl         : 'php/sync.php',
    phantomIdField  : 'phantomId',
    assignmentStore : {
        syncDataOnLoad : false
    },
    resourceStore : {
        syncDataOnLoad : false,
        tree: true,
        fields         : [
            { name : 'calendar', persist : false },
            { name : 'maxUnit', persist : false },
            { name : 'parentId', persist : false }
        ]
    },
    eventStore : {
        syncDataOnLoad : false,
        fields         : [
            { name : 'duration', persist : false },
            { name : 'effort', persist : false },
            { name : 'constraintDate', persist : false }
        ]
    },
    listeners: {
        beforeLoadApply: ({ response, options }) => {

        if (response['resources']) {
            if (options.params.parentId === 1) {
                response['resources'].rows = []
                response['resources'].total = 0
            }
            else {
                for(const resource of response['resources'].rows) {
                    resource.children = []
                    resource.remoteChildCount = 0
                }

                response['resources'].rows[1].children = [{ id: `${response['resources'].rows[1].id}.child`, name: `${response['resources'].rows[1].name} Child`}]
                response['resources'].rows[1].remoteChildCount = 1;

                response['resources'].total = response['resources'].total + 1;
            }
        }
    }
}
},

features : {
    // Grouping is not supported when using a lazyLoad store
    group  : false,
    filter : false,
    tree: true,
},

columns : [
    {
        type: 'tree',
        tree: true,
        text       : 'Resource',
        field      : 'name',
        width      : 200,
        sortable   : false,
        filterable : false
    }
],

tbar : [
    '->',
    {
        type : 'button',
        text : 'Reset data',
        icon : 'b-fa b-fa-refresh',
        ref  : 'resetButton',
        onClick({ source }) {
            // In addition to clearing all records, this will also remove any lazy loading cache on these stores
            scheduler.eventStore.data = [];
            scheduler.assignmentStore.data = [];
            scheduler.resourceStore.data = [];
            fetch('php/reset.php').then(() => {
                scheduler.project.load();
            });
        }
    },
    {
        type: 'button',
        text: 'Create Phantom',
        onClick() {
            const resource1 = scheduler.resourceStore.getById(1);

        resource1.appendChild({ name: `${resource1.name} First Child`})

        const resource2 = scheduler.resourceStore.getById(2);
        resource2.appendChild({ name: `${resource2.name} Second Child`})

        setTimeout(() => console.log('see both resource added, as expected', scheduler.crudManager.changes), 3000);
    }
},
{
    type: 'button',
    text: 'Expand',
    onClick: async () => {
        const resource2 = scheduler.resourceStore.getById(2);
        await scheduler.expand(resource2);

        setTimeout(() => {
            console.log('expanding resource 2 (had remote children keeps changes)', scheduler.crudManager.changes);

            const resource1 = scheduler.resourceStore.getById(1);
            scheduler.expand(resource1);

            setTimeout(() => console.log('expanding resource 1 (does not have remote children) drops all resource adds', scheduler.crudManager.changes), 3000);
        }, 3000);
    }
}
]
});

Post by marcio »

Hey jarmen,

Thanks for reaching out and for the detailed example.

I adapted the expand button function to this

{
    type: 'button',
    text: 'Expand',
    onClick: async () => {
        console.log('changes before resource 2 is expanded', scheduler.crudManager.changes);
        const resource2 = scheduler.resourceStore.getById(2);
        await scheduler.expand(resource2);

    setTimeout(() => {
        console.log('expanding resource 2 (had remote children keeps changes)', scheduler.crudManager.changes);

        const resource1 = scheduler.resourceStore.getById(1);
        scheduler.expand(resource1);

        setTimeout(() => console.log('expanding resource 1 (does not have remote children) drops all resource adds', scheduler.crudManager.changes), 3000);
    }, 3000);
}
}

And I see that the scheduler.crudManager.changes is null before expanding. Did I get that correct? How can I reproduce the issue you're mentioning?

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post by jarmen »

If you just click expand, that is correct for what you see.

For my test case however, I am expecting you to first click "Create Phantom" first to add the new records. Then, with your modified code, you will see that crud manager has changes and is not null before expanding when clicking "Expand"


Post by jarmen »

Here's a handler that chains everything together with one click to describe the scenario

{
    type: 'button',
    text: 'Scenario',
    onClick: () => {
        const resource1 = scheduler.resourceStore.getById(1);
        const resource2 = scheduler.resourceStore.getById(2);
        
resource1.appendChild({ name: `${resource1.name} First Child`}) resource2.appendChild({ name: `${resource2.name} Second Child`}) setTimeout(() => { console.log('see both resource added, as expected', scheduler.crudManager.changes) scheduler.expand(resource2).then(() => { setTimeout(() => { console.log('expanding resource 2 (had remote children keeps changes)', scheduler.crudManager.changes); scheduler.expand(resource1); setTimeout(() => console.log('expanding resource 1 (does not have remote children) drops all resource adds', scheduler.crudManager.changes), 3000); }, 3000); }); }, 3000); } }

Post by marcio »

Hey jarmen,

Thanks for clarifying. That looks like a bug.

Here's a ticket to track the fix progress https://github.com/bryntum/support/issues/11381.

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post Reply