Refactor event rendering with column-based event management

Improves event rendering by integrating event filtering directly into column data sources

Key changes:
- Moves event filtering responsibility to IColumnDataSource
- Simplifies event rendering pipeline by pre-filtering events per column
- Supports both date and resource-based calendar modes
- Enhances drag and drop event update mechanism

Optimizes calendar rendering performance and flexibility
This commit is contained in:
Janus C. H. Knudsen 2025-11-22 23:38:52 +01:00
parent eeaeddeef8
commit 17909696ed
9 changed files with 179 additions and 250 deletions

View file

@ -1,6 +1,7 @@
// Event rendering strategy interface and implementations
import { ICalendarEvent } from '../types/CalendarTypes';
import { IColumnInfo } from '../types/ColumnDataSource';
import { Configuration } from '../configurations/CalendarConfig';
import { SwpEventElement } from '../elements/SwpEventElement';
import { PositionUtils } from '../utils/PositionUtils';
@ -12,9 +13,12 @@ import { EventLayoutCoordinator, IGridGroupLayout, IStackedEventLayout } from '.
/**
* Interface for event rendering strategies
*
* Note: renderEvents now receives columns with pre-filtered events,
* not a flat array of events. Each column contains its own events.
*/
export interface IEventRenderer {
renderEvents(events: ICalendarEvent[], container: HTMLElement): void;
renderEvents(columns: IColumnInfo[], container: HTMLElement): void;
clearEvents(container?: HTMLElement): void;
renderSingleColumnEvents?(column: IColumnBounds, events: ICalendarEvent[]): void;
handleDragStart?(payload: IDragStartEventPayload): void;
@ -98,28 +102,22 @@ export class DateEventRenderer implements IEventRenderer {
/**
* Handle drag move event
* Only updates visual position and time - date stays the same
*/
public handleDragMove(payload: IDragMoveEventPayload): void {
const swpEvent = payload.draggedClone as SwpEventElement;
const columnDate = this.dateService.parseISO(payload.columnBounds!!.identifier);
swpEvent.updatePosition(columnDate, payload.snappedY);
swpEvent.updatePosition(payload.snappedY);
}
/**
* Handle column change during drag
* Only moves the element visually - no data updates here
* Data updates happen on drag:end in EventRenderingService
*/
public handleColumnChange(payload: IDragColumnChangeEventPayload): void {
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
eventsLayer.appendChild(payload.draggedClone);
// Recalculate timestamps with new column date
const currentTop = parseFloat(payload.draggedClone.style.top) || 0;
const swpEvent = payload.draggedClone as SwpEventElement;
const columnDate = this.dateService.parseISO(payload.newColumn.identifier);
swpEvent.updatePosition(columnDate, currentTop);
}
}
@ -220,32 +218,36 @@ export class DateEventRenderer implements IEventRenderer {
}
renderEvents(events: ICalendarEvent[], container: HTMLElement): void {
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events.filter(event => !event.allDay);
renderEvents(columns: IColumnInfo[], container: HTMLElement): void {
// Find column DOM elements in the container
const columnElements = this.getColumns(container);
// Find columns in the specific container for regular events
const columns = this.getColumns(container);
// Render events for each column using pre-filtered events from IColumnInfo
columns.forEach((columnInfo, index) => {
const columnElement = columnElements[index];
if (!columnElement) return;
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, timedEvents);
const eventsLayer = column.querySelector('swp-events-layer') as HTMLElement;
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = columnInfo.events.filter(event => !event.allDay);
if (eventsLayer) {
this.renderColumnEvents(columnEvents, eventsLayer);
const eventsLayer = columnElement.querySelector('swp-events-layer') as HTMLElement;
if (eventsLayer && timedEvents.length > 0) {
this.renderColumnEvents(timedEvents, eventsLayer);
}
});
}
/**
* Render events for a single column
* Note: events are already filtered for this column
*/
public renderSingleColumnEvents(column: IColumnBounds, events: ICalendarEvent[]): void {
const columnEvents = this.getEventsForColumn(column.element, events);
// Filter out all-day events
const timedEvents = events.filter(event => !event.allDay);
const eventsLayer = column.element.querySelector('swp-events-layer') as HTMLElement;
if (eventsLayer) {
this.renderColumnEvents(columnEvents, eventsLayer);
if (eventsLayer && timedEvents.length > 0) {
this.renderColumnEvents(timedEvents, eventsLayer);
}
}
@ -388,24 +390,4 @@ export class DateEventRenderer implements IEventRenderer {
const columns = container.querySelectorAll('swp-day-column');
return Array.from(columns) as HTMLElement[];
}
protected getEventsForColumn(column: HTMLElement, events: ICalendarEvent[]): ICalendarEvent[] {
const columnId = column.dataset.columnId;
if (!columnId) {
return [];
}
// Create start and end of day for interval overlap check
// In date-mode, columnId is ISO date string like "2024-11-13"
const columnStart = this.dateService.parseISO(`${columnId}T00:00:00`);
const columnEnd = this.dateService.parseISO(`${columnId}T23:59:59.999`);
const columnEvents = events.filter(event => {
// Interval overlap: event overlaps with column day if event.start < columnEnd AND event.end > columnStart
const overlaps = event.start < columnEnd && event.end > columnStart;
return overlaps;
});
return columnEvents;
}
}