Our powerful JS Calendar component


Post by droptableusers »

Dear Bryntum,

I really appreciate this improvement but unfortunately, this was not exactly the business needed. Currently, our customer needed to display only future events on the AgendaView. Its mean they would like to see the future events only. For this purpose, we use the Filter solution provided by AgendaView:

store: {
	filters: [
	    {
		property: 'date',
		operator: '>=',
		value: today.setHours(0, 0, 0, 0),
	    },
	],
},

Unfortunately, your current implementation does not fully satisfy the needs, as there are events in the AgendaStore, but we do not display them because of the Filter. We need this field when the list returned by the AgendaStore filter is empty. If I understand correctly, this filed is currently only displayed if the AgendaStore is empty.

Full AgendaView config:

agenda: {
    date: today,
    startDate: today,
    alwaysShowCurrentDate: true,
    store: {
        filters: [
            {
                property: 'date',
                operator: '>=',
                value: today.setHours(0, 0, 0, 0),
            },
        ],
    },
    eventRenderer: ({ eventRecord, renderData }: any) => {
        return [
            {
                className: 'b-event-name',
                html:
                    '<strong>' +
                    StringHelper.encodeHtml(eventRecord.name) +
                    '</strong> | Megjegyzés: ' +
                    StringHelper.encodeHtml(eventRecord.comment),
            },
        ];
    },
},

I really appreciate any comments:

Best Regards,
Adri


Post by Animal »

This seems like a misstep on our side. We add the current date special entry too early.

Try this as an override in AgendaView:

    populateStore() {
        this._cellMap?.clear();

        const
            me = this,
            {
                store,
                eventStore,
                rowManager
            }             = me,
            { rowHeight } = rowManager,
            rowCount      = rowManager.rows?.length,
            eventHeight   = isNaN(me.eventHeight) ? 25 : me.eventHeight;

        me._eventCount = 0;
        me.populateStoreSoon.cancel();

        if (!me.date) {
            // Avoid recursion into populateStore
            me.isConfiguring = true;
            me.date = eventStore.map(r => r.startDate).sort((lhs, rhs) => lhs.valueOf() - rhs.valueOf())[0];
            me.isConfiguring = false;
        }

        const
            {
                cellMap,
                firstVisibleDate
            }              = me,
            endDate        = DH.add(me.endDate, me.range ? 0 : 1, 'd'),
            cellMapEntries = [...cellMap.values()],
            sortedEntries  = [];

        me.dateIndex = {};

        for (let i = 0, { length } = cellMapEntries; i < length; i++) {
            const
                cellData         = cellMapEntries[i],
                { events, date } = cellData;

            // Recurring event continuation blocks which are past a midnight boundary are inserted into
            // the cellMap even if they are beyond the end date. Eliminate these from the view.
            if (date >= firstVisibleDate && date < endDate) {
                // Count unique events
                for (let j = 0, { length } = events; j < length; j++) {
                    const event = events[j];

                    if (!me.isAllDayEvent(event) || !i || DH.clearTime(event.startDate).valueOf() === date.valueOf()) {
                        me._eventCount++;
                    }
                }

                // Build Store data array in ascending YYYY-MM-DD order.
                // The ordered insertion is needed because generated occurrences are
                // added to the Map in a second pass, and it will not be in order.
                // Other views iterate their date range and pull cell info using the key.
                // This view is a grid which renders rows in the store's order.
                const
                    cellInfo = store.createRecord(cellData),
                    idx      = ArrayHelper.findInsertionIndex(cellInfo, sortedEntries, compareDate);

                sortedEntries.splice(idx, 0, cellInfo);
                me.dateIndex[cellData.id] = cellInfo;
            }
        }
        const avgEventsPerCell = me._eventCount ? sortedEntries.map(e => e.events.length).reduce((a, b) => a + b, 0) / cellMapEntries.length : 0;

        // If configured to do so, ensure there is always a cell for the start date if the range turns out to be empty,
        // so that user has something to interact with
        if (me.alwaysShowCurrentDate && !sortedEntries.filter(store.filtersFunction).length) {
            sortedEntries.push(store.createRecord(me.createCellData(me.date)));
        }

        store.suspendEvents();
        store.loadData(sortedEntries);
        store.resumeEvents();

        // Give RowManager a clue so that it can calculate an appropriate rowCount.
        // If the rows are tall, we do not need many to cover the viewport.
        rowManager._rowHeight = 20;
        // RowManager#set rowHeight does not tolerate no rows.
        if (store.count) {
            rowManager.rowHeight = Math.max(avgEventsPerCell * (eventHeight + me.eventSpacing), 70);
        }

        // Setting the rowHeight does a refresh if there are existing rows and the height actually changed.
        // Otherwise, we explicitly refresh now.
        if (!rowCount || !store.count || rowManager.rowHeight === rowHeight) {
            rowManager.calculateRowCount();
            rowManager.estimateTotalHeight(true);
        }

        me.refreshCount = (me.refreshCount || 0) + 1;

        /**
         * Fires when this AgendaView refreshes.
         * @param {Calendar.widget.AgendaView} source The triggering instance.
         * @event refresh
         */
        me.trigger('refresh');

        // The owning Calendar's UI may need to sync with the new state
        me.calendar?.syncUIWithActiveView(me);

        me.columns.forEach(c => c.constructor.exposeProperties?.());

        // Evaluate this late so that it doesn't change the order of date config evaluation
        // Ensure that the menu stays aligned if scrollbar causes button movement.
        me.settings?._menu?.realign();
    }

That adds the current date cell into sortedEntries after populating the dataset from which the store is loaded.


Post by droptableusers »

Hey Animal,

How can I override in th AgendaView? We are using the ReactJS Typescript and actually our configuration contains the AgendaViewConfig whichhas not this function, so I think I cannot override it. Do we need to setup any reference for it?

Best Regards,
Adri


Post by Animal »

I just updated the post because I made a mistake, so you'll need to grab the code again.

You should be able to configure that method directly into the mode.


Post by Animal »


Post Reply