221 lines
6.6 KiB
Markdown
221 lines
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.
|