bryntum / support

An issues-only repository for the Bryntum project management component suite which includes powerful Grid, Scheduler, Calendar, Kanban Task Board and Gantt chart components all built in pure JS / CSS / TypeScript
https://www.bryntum.com
54 stars 6 forks source link

forceSync: true not triggering sync call correctly #9493

Closed marciogurka closed 4 months ago

marciogurka commented 4 months ago

Forum post

According to docs, if we config forceSync: true, the sync should be triggered even with no changes. But currently that's not the behavior that we're observing.

Demo based on https://bryntum.com/products/scheduler/examples/airport/

import '../_shared/shared.js'; // not required, our example styling etc.
import Scheduler from '../../lib/Scheduler/view/Scheduler.js';
import DateHelper from '../../lib/Core/helper/DateHelper.js';
import EventModel from '../../lib/Scheduler/model/EventModel.js';

class Flight extends EventModel {
    static get fields() {
        return [
            { name : 'aircraft' },
            { name : 'airline' },
            { name : 'resourceId', dataSource : 'gate' },
            { name : 'startDate', dataSource : 'arrivalTime' },
            { name : 'endDate', dataSource : 'departureTime' },
            { name : 'durationUnit', defaultValue : 'hour' },
            { name : 'dollysRequired', type : 'number', defaultValue : 0 },
            { name : 'max', type : 'number', defaultValue : 10 },
            { name : 'starred', type : 'boolean' },
            'inbound',
            'outbound',
            { name : 'departureAirport', defaultValue : '' },
            { name : 'departureCity', defaultValue : '' },
            'arrivalCity'
        ];
    }
}

const scheduler = new Scheduler({
    appendTo     : 'container',
    eventStyle   : 'colored',
    allowOverlap : false,
    rowHeight    : 70,
    barMargin    : 5,
    columns      : [
        {
            text    : 'Gate',
            field   : 'name',
            cellCls : 'gate',
            editor  : false
        }
    ],

    features : {
        scheduleTooltip : false,
        eventDragCreate : false,
        eventTooltip    : {
            template : ({ eventRecord }) => `
            <div class="b-timing-container">
                <div class="b-departure-datetime">
                    <span class="b-departure-time">${DateHelper.format(eventRecord.startDate, 'LST')}</span>
                    <span class="b-departure-date">${DateHelper.format(eventRecord.startDate, 'ddd, DD MMM')}</span>
                </div>
                <div class="b-arrival-datetime">
                    <span class="b-arrival-time">${DateHelper.format(eventRecord.endDate, 'LST')}</span>
                    <span class="b-arrival-date">${DateHelper.format(eventRecord.endDate, 'ddd, DD MMM')}</span>
                </div>
            </div>
            <div class="b-icons-container">
                <i class="b-circle"></i>
                <div class="b-vertical-line">
                    <i class="b-fa b-fa-plane-up"></i>
                </div>
                <i class="b-fa b-fa-map-marker-alt"></i>
            </div>
            <div class="b-airports-container">
                <div class="b-departure-info">
                    <span class="b-departure-city">${eventRecord.departureCity}</span>
                    <span class="b-departure-airport">${eventRecord.departureAirport}</span>
                </div>
                <span class="b-duration">${eventRecord.fullDuration}</span>
                <div class="b-arrival-info">
                    <span class="b-arrival-city">${eventRecord.arrivalCity}</span>
                    <span class="b-arrival-airport">${eventRecord.arrivalAirport}</span>
                </div>
            </div>
`
        },
        eventEdit : {
            items : {
                airlineField : {
                    weight   : 0,
                    type     : 'textfield',
                    name     : 'airline',
                    label    : 'Airline',
                    readOnly : true
                },
                aircraftField : {
                    weight   : 1,
                    type     : 'textfield',
                    name     : 'aircraft',
                    label    : 'Aircraft',
                    readOnly : true
                },
                nameField : {
                    hidden : true
                },
                // ref for an existing field
                resourceField : {
                    // Change its label
                    label : 'Gate'
                }
            }
        }
    },

    startDate  : new Date(2023, 9, 6, 6, 30),
    endDate    : new Date(2023, 9, 8, 6),
    tickSize   : 100,
    snap       : true,
    viewPreset : {
        base    : 'hourAndDay',
        headers : [
            { unit : 'minute', increment : 30, dateFormat : 'LT' }
        ]
    },
    timeResolution : {
        increment : 5,
        unit      : 'minute'
    },
    crudManager : {
        autoLoad   : true,
        forceSync  : true,
        eventStore : {
            modelClass : Flight
        },
        transport : {
            load : {
                url : 'data/data.json'
            },
            sync : {
                url : 'test'
            }
        },
        // This config enables response validation and dumping of found errors to the browser console.
        // It's meant to be used as a development stage helper only so please set it to false for production systems.
        validateResponse : true
    },

    eventRenderer({ eventRecord, resourceRecord }) {
        return [
            {
                class    : 'header',
                children : [
                    {
                        class : 'inbound',
                        text  : eventRecord.inbound
                    },
                    {
                        class    : 'aircraft',
                        children : [
                            { tag : 'i', class : 'b-fa b-fa-plane-departure' },
                            eventRecord.aircraft
                        ]
                    },
                    {
                        class : 'outbound',
                        text  : eventRecord.outbound
                    }
                ]
            },
            {
                class    : 'footer',
                children : [
                    {
                        tag   : 'i',
                        class : {
                            'b-fa'      : 1,
                            'b-fa-star' : 1,
                            starred     : eventRecord.starred
                        }
                    },
                    ...new Array(eventRecord.max).fill().map((item, i) => ({
                        class : {
                            box    : 1,
                            filled : i < eventRecord.dollysRequired
                        },
                        dataset : {
                            btip : `${i + 1} dollys required`
                        }
                    })),
                    {
                        tag   : 'span',
                        class : 'value',
                        text  : `${eventRecord.dollysRequired} / ${eventRecord.max}`
                    }
                ]
            }
        ];
    },

    onEventClick({ eventRecord, event }) {
        const
            { target }    = event,
            { classList } = target;

        if (classList.contains('b-fa-star')) {
            eventRecord.starred = !eventRecord.starred;
        }
        else if (classList.contains('box')) {
            eventRecord.dollysRequired = Array.from(target.parentElement.children).indexOf(target);
        }
    }
});

scheduler.crudManager.transport.sync.url = 'localhost?param=val&updated_after=0';
let timestamp = new Date().getTime();
setInterval(() => {
    timestamp = new Date().getTime();
    scheduler.crudManager.transport.sync.url = scheduler.crudManager.transport.sync.url.replace(/(updated_after=)[^&]+/, '$1' + timestamp);
    scheduler.crudManager.sync();
}, 5000);

The expected behavior is that the sync API call is made even with no changes to the events.

marciogurka commented 4 months ago

Initial debugging shows that on Scheduler/crud/AbstractCrudManagerMixin.js on the sync() function we need to verify the forceSync flag.

ghulamghousdev commented 4 months ago

We are already checking for forceSync. If you check the getChangesetPackage in AbstractCrudManagerMixin, you are gonna see that when there are no changes and forceSync is set to true, we are still returning an object with request and revision id.

However in getChangesetPackage in CrudManager we are checking that if the pack is not null, which is true in our case, we check for single assignment mode which also becomes true in our testCase example, we are removing the assignments. And just below that, we are validating storeIds and as changes are already undefined in the pack, (!this.crudStores.some(storeInfo => pack[storeInfo.storeId])) becomes true and we are returning null in that case. So i have added that check to execute that piece of code only when there are some changes on crudManager. We don't want that piece of code being executed if there are no changes.