Refactors event overlap handling to use a DOM-centric approach with data attributes for stack tracking. This eliminates complex state management, reduces code complexity, and improves maintainability. Removes the previous Map-based linked list implementation. The new approach offers better debugging, automatic memory management, and eliminates state synchronization bugs. The solution maintains identical functionality with a significantly simpler implementation, focusing on DOM manipulation for visual stacking and column sharing. Addresses potential performance concerns of DOM queries by scoping them to specific containers.
221 lines
No EOL
6.6 KiB
Markdown
221 lines
No EOL
6.6 KiB
Markdown
# Data-Attribute Stack Tracking Solution
|
|
|
|
## Implementation Summary
|
|
|
|
Vi har nu implementeret stack tracking via data attributes i stedet for komplekse Map-baserede linked lists.
|
|
|
|
### 🎯 **How it works:**
|
|
|
|
#### **Stack Links via Data Attributes**
|
|
```html
|
|
<!-- Base event -->
|
|
<swp-event
|
|
data-event-id="event_123"
|
|
data-stack-link='{"stackLevel":0,"next":"event_456"}'
|
|
style="margin-left: 0px;">
|
|
</swp-event>
|
|
|
|
<!-- Stacked event -->
|
|
<swp-event
|
|
data-event-id="event_456"
|
|
data-stack-link='{"prev":"event_123","stackLevel":1,"next":"event_789"}'
|
|
style="margin-left: 15px;">
|
|
</swp-event>
|
|
|
|
<!-- Top stacked event -->
|
|
<swp-event
|
|
data-event-id="event_789"
|
|
data-stack-link='{"prev":"event_456","stackLevel":2}'
|
|
style="margin-left: 30px;">
|
|
</swp-event>
|
|
```
|
|
|
|
### 🔧 **Key Methods:**
|
|
|
|
#### **createStackedEvent()**
|
|
```typescript
|
|
// Links new event to end of chain
|
|
let lastElement = underlyingElement;
|
|
while (lastLink?.next) {
|
|
lastElement = this.findElementById(lastLink.next);
|
|
lastLink = this.getStackLink(lastElement);
|
|
}
|
|
|
|
// Create bidirectional link
|
|
this.setStackLink(lastElement, { ...lastLink, next: eventId });
|
|
this.setStackLink(eventElement, { prev: lastElementId, stackLevel });
|
|
```
|
|
|
|
#### **removeStackedStyling()**
|
|
```typescript
|
|
// Re-link prev and next
|
|
if (link.prev && link.next) {
|
|
this.setStackLink(prevElement, { ...prevLink, next: link.next });
|
|
this.setStackLink(nextElement, { ...nextLink, prev: link.prev });
|
|
}
|
|
|
|
// Update subsequent stack levels
|
|
this.updateSubsequentStackLevels(link.next, -1);
|
|
```
|
|
|
|
#### **restackEventsInContainer()**
|
|
```typescript
|
|
// Group by stack chains (not all stacked events together!)
|
|
for (const element of stackedEvents) {
|
|
// Find root of chain
|
|
while (rootLink?.prev) {
|
|
rootElement = this.findElementById(rootLink.prev);
|
|
}
|
|
|
|
// Collect entire chain
|
|
// Re-stack each chain separately
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🏆 **Advantages vs Map Solution:**
|
|
|
|
### ✅ **Simplified State Management**
|
|
| Aspect | Map + Linked List | Data Attributes |
|
|
|--------|------------------|-----------------|
|
|
| **State Location** | Separate Map object | In DOM elements |
|
|
| **Synchronization** | Manual sync required | Automatic with DOM |
|
|
| **Memory Cleanup** | Manual Map cleanup | Automatic with element removal |
|
|
| **Debugging** | Console logs only | DevTools inspection |
|
|
| **State Consistency** | Possible sync bugs | Always consistent |
|
|
|
|
### ✅ **Code Complexity Reduction**
|
|
```typescript
|
|
// OLD: Complex Map management
|
|
private stackChains = new Map<string, { next?: string, prev?: string, stackLevel: number }>();
|
|
|
|
// Find last event in chain - complex iteration
|
|
let lastEventId = underlyingId;
|
|
while (this.stackChains.has(lastEventId) && this.stackChains.get(lastEventId)?.next) {
|
|
lastEventId = this.stackChains.get(lastEventId)!.next!;
|
|
}
|
|
|
|
// Link events - error prone
|
|
this.stackChains.get(lastEventId)!.next = eventId;
|
|
this.stackChains.set(eventId, { prev: lastEventId, stackLevel });
|
|
|
|
// NEW: Simple data attribute management
|
|
let lastElement = underlyingElement;
|
|
while (lastLink?.next) {
|
|
lastElement = this.findElementById(lastLink.next);
|
|
}
|
|
|
|
this.setStackLink(lastElement, { ...lastLink, next: eventId });
|
|
this.setStackLink(eventElement, { prev: lastElementId, stackLevel });
|
|
```
|
|
|
|
### ✅ **Better Error Handling**
|
|
```typescript
|
|
// DOM elements can't get out of sync with their own attributes
|
|
// When element is removed, its state automatically disappears
|
|
// No orphaned Map entries
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 **Test Scenarios:**
|
|
|
|
### **Scenario 1: Multiple Separate Stacks**
|
|
```
|
|
Column has:
|
|
Stack A: Event1 → Event2 → Event3 (times: 09:00-10:00, 09:15-10:15, 09:30-10:30)
|
|
Stack B: Event4 → Event5 (times: 14:00-15:00, 14:10-15:10)
|
|
|
|
Remove Event2 (middle of Stack A):
|
|
✅ Expected: Event1 → Event3 (Event3 moves to 15px margin)
|
|
✅ Expected: Stack B unchanged (Event4→Event5 still at 0px→15px)
|
|
❌ Old naive approach: Would group all events together
|
|
```
|
|
|
|
### **Scenario 2: Remove Base Event**
|
|
```
|
|
Stack: EventA(base) → EventB → EventC
|
|
|
|
Remove EventA:
|
|
✅ Expected: EventB becomes base (0px), EventC moves to 15px
|
|
✅ Data-attribute solution: EventB.stackLevel = 0, EventC.stackLevel = 1
|
|
```
|
|
|
|
### **Scenario 3: Drag and Drop**
|
|
```
|
|
Drag Event2 from Stack A to new position:
|
|
✅ removeStackedStyling() handles re-linking
|
|
✅ Other stack events maintain their relationships
|
|
✅ No Map synchronization issues
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 **Debugging Benefits:**
|
|
|
|
### **Browser DevTools Inspection:**
|
|
```html
|
|
<!-- Easy to see stack relationships directly in HTML -->
|
|
<swp-event data-stack-link='{"prev":"123","next":"789","stackLevel":1}'>
|
|
<!-- Event content -->
|
|
</swp-event>
|
|
```
|
|
|
|
### **Console Debugging:**
|
|
```javascript
|
|
// Easy to inspect stack chains
|
|
const element = document.querySelector('[data-event-id="456"]');
|
|
const link = JSON.parse(element.dataset.stackLink);
|
|
console.log('Stack chain:', link);
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 **Performance Comparison:**
|
|
|
|
| Operation | Map Solution | Data-Attribute Solution |
|
|
|-----------|--------------|-------------------------|
|
|
| **Create Stack** | Map.set() + element.style | JSON.stringify() + element.style |
|
|
| **Remove Stack** | Map manipulation + DOM queries | JSON.parse/stringify + DOM queries |
|
|
| **Find Chain** | Map iteration | DOM traversal |
|
|
| **Memory Usage** | Map + DOM | DOM only |
|
|
| **Sync Overhead** | High (keep Map in sync) | None (DOM is source) |
|
|
|
|
### **Performance Notes:**
|
|
- **JSON.parse/stringify**: Very fast for small objects (~10 properties max)
|
|
- **DOM traversal**: Limited by chain length (typically 2-5 events)
|
|
- **Memory**: Significant reduction (no separate Map)
|
|
- **Garbage collection**: Better (automatic cleanup)
|
|
|
|
---
|
|
|
|
## ✅ **Solution Status:**
|
|
|
|
### **Completed:**
|
|
- [x] StackLink interface definition
|
|
- [x] Helper methods (getStackLink, setStackLink, findElementById)
|
|
- [x] createStackedEvent with data-attribute linking
|
|
- [x] removeStackedStyling with proper re-linking
|
|
- [x] restackEventsInContainer respects separate chains
|
|
- [x] isStackedEvent checks both style and data-attributes
|
|
- [x] Compilation successful
|
|
|
|
### **Ready for Testing:**
|
|
- [ ] Manual UI testing of stack behavior
|
|
- [ ] Drag and drop stacked events
|
|
- [ ] Multiple stacks in same column
|
|
- [ ] Edge cases (remove first/middle/last)
|
|
|
|
---
|
|
|
|
## 🎉 **Conclusion:**
|
|
|
|
This data-attribute solution provides:
|
|
1. **Same functionality** as the Map-based approach
|
|
2. **Simpler implementation** (DOM as single source of truth)
|
|
3. **Better debugging experience** (DevTools visibility)
|
|
4. **Automatic memory management** (no manual cleanup)
|
|
5. **No synchronization bugs** (state follows element)
|
|
|
|
The solution maintains all the precision of the original complex system while dramatically simplifying the implementation and eliminating entire classes of potential bugs. |