Calendar/docs/stack-binding-system.md
Janus Knudsen 72019a3d9a wip
2025-09-09 14:35:21 +02:00

5.8 KiB

Stack Binding System - Calendar Plantempus

Oversigt

Dette dokument beskriver hvordan overlappende events er bundet sammen i Calendar Plantempus systemet, specifikt hvordan 2 eller flere events der ligger oven i hinanden (stacked) er forbundet.

Stack Binding Mekanisme

SimpleEventOverlapManager

Systemet bruger SimpleEventOverlapManager til at håndtere event overlap og stacking. Denne implementation bruger data-attributes på DOM elementerne til at holde styr på stack chains.

Hvordan Stacked Events er Bundet Sammen

Når 2 eller flere events ligger oven i hinanden, oprettes en linked list struktur via data-stack-link attributter:

Eksempel med 2 Events:

// Event A (base event):
<swp-event data-stack-link='{"stackLevel":0,"next":"event-B"}'>

// Event B (stacked event):
<swp-event data-stack-link='{"prev":"event-A","stackLevel":1}'>

Eksempel med 3 Events:

// Event A (base event):
<swp-event data-stack-link='{"stackLevel":0,"next":"event-B"}'>

// Event B (middle event): 
<swp-event data-stack-link='{"prev":"event-A","next":"event-C","stackLevel":1}'>

// Event C (top event):
<swp-event data-stack-link='{"prev":"event-B","stackLevel":2}'>
interface StackLink {
  prev?: string;        // Event ID af forrige event i stacken
  next?: string;        // Event ID af næste event i stacken  
  stackLevel: number;   // 0 = base event, 1 = første stacked, etc
}

Visual Styling

Hvert stacked event får automatisk styling baseret på deres stackLevel:

  • Event A (base): margin-left: 0px, z-index: 100
  • Event B (middle): margin-left: 15px, z-index: 101
  • Event C (top): margin-left: 30px, z-index: 102

Formel:

  • margin-left = stackLevel * 15px
  • z-index = 100 + stackLevel

Stack Chain Navigation

Systemet kan traversere stack chains i begge retninger:

// Find næste event i stacken
const link = getStackLink(currentElement);
if (link?.next) {
  const nextElement = document.querySelector(`swp-event[data-event-id="${link.next}"]`);
}

// Find forrige event i stacken  
if (link?.prev) {
  const prevElement = document.querySelector(`swp-event[data-event-id="${link.prev}"]`);
}

Automatisk Chain Opdatering

Når events fjernes fra en stack, opdateres chain automatisk:

  1. Middle element fjernet: Prev og next events linkes direkte sammen
  2. Chain breaking: Hvis events ikke længere overlapper, brydes chain
  3. Stack level opdatering: Alle efterfølgende events får opdateret stackLevel

Overlap Detection

Events klassificeres som stacking hvis:

  • De overlapper i tid OG
  • Start tid forskel > 30 minutter
const timeDiffMinutes = Math.abs(
  new Date(event1.start).getTime() - new Date(event2.start).getTime()
) / (1000 * 60);

return timeDiffMinutes > 30 ? OverlapType.STACKING : OverlapType.COLUMN_SHARING;

Eksempler

2 Events Stack

Event A: 09:00-11:00 (base)     → margin-left: 0px,  z-index: 100
Event B: 09:45-10:30 (stacked)  → margin-left: 15px, z-index: 101

Stack chain:

A ←→ B

Data attributes:

  • A: {"stackLevel":0,"next":"B"}
  • B: {"prev":"A","stackLevel":1}

3 Events Stack

Event A: 09:00-11:00 (base)     → margin-left: 0px,  z-index: 100
Event B: 09:45-10:30 (middle)   → margin-left: 15px, z-index: 101  
Event C: 10:15-11:15 (top)      → margin-left: 30px, z-index: 102

Stack chain:

A ←→ B ←→ C

Data attributes:

  • A: {"stackLevel":0,"next":"B"}
  • B: {"prev":"A","next":"C","stackLevel":1}
  • C: {"prev":"B","stackLevel":2}

Visual Stack Chain Diagram

graph TD
    A[Event A - Base Event] --> A1[data-stack-link]
    B[Event B - Middle Event] --> B1[data-stack-link]
    C[Event C - Top Event] --> C1[data-stack-link]
    
    A1 --> A2[stackLevel: 0<br/>next: event-B]
    B1 --> B2[prev: event-A<br/>next: event-C<br/>stackLevel: 1]
    C1 --> C2[prev: event-B<br/>stackLevel: 2]
    
    A2 --> A3[margin-left: 0px<br/>z-index: 100]
    B2 --> B3[margin-left: 15px<br/>z-index: 101]
    C2 --> C3[margin-left: 30px<br/>z-index: 102]
    
    subgraph Stack Chain Navigation
        A4[Event A] -.->|next| B4[Event B]
        B4 -.->|next| C4[Event C]
        C4 -.->|prev| B4
        B4 -.->|prev| A4
    end
    
    subgraph Visual Result
        V1[Event A - Full Width]
        V2[Event B - 15px Offset]
        V3[Event C - 30px Offset]
        V1 -.-> V2
        V2 -.-> V3
    end

Stack Chain Operations

sequenceDiagram
    participant DOM as DOM Element
    participant SM as SimpleEventOverlapManager
    participant Chain as Stack Chain
    
    Note over DOM,Chain: Creating Stack Chain
    DOM->>SM: createStackedEvent(eventB, eventA, 1)
    SM->>Chain: Set eventA: {stackLevel:0, next:"eventB"}
    SM->>Chain: Set eventB: {prev:"eventA", stackLevel:1}
    SM->>DOM: Apply margin-left: 15px, z-index: 101
    
    Note over DOM,Chain: Removing from Chain
    DOM->>SM: removeStackedStyling(eventB)
    SM->>Chain: Get eventB links
    SM->>Chain: Link eventA -> eventC directly
    SM->>Chain: Update eventC stackLevel: 1
    SM->>DOM: Update eventC margin-left: 15px
    SM->>Chain: Delete eventB entry

Fordele ved Data-Attribute Approach

  1. Ingen global state - alt information er på DOM elementerne
  2. Persistent - overlever DOM manipulationer
  3. Debuggable - kan inspiceres i browser dev tools
  4. Performant - ingen in-memory maps at vedligeholde
  5. Robust - automatisk cleanup når elements fjernes

Se Også