267 lines
5.1 KiB
Markdown
267 lines
5.1 KiB
Markdown
|
|
Selvfølgelig—her er en **opdateret, selvstændig `.md`-spec**, som **understøtter variable antal resources per team**, dynamisk kolonnebredde, ingen inline layout-styles, pipeline‐rendering i grupper, og CSS-controlling via custom properties.
|
|||
|
|
|
|||
|
|
Kopier → gem som fx:
|
|||
|
|
`grid-render-pipeline-dynamic-columns.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
````md
|
|||
|
|
# Grid Render Pipeline — Dynamic Columns Spec
|
|||
|
|
|
|||
|
|
Denne specifikation beskriver en generisk render-pipeline til at bygge et
|
|||
|
|
dynamisk CSS Grid layout, hvor hver "gruppe" (teams, resources, dates) har sin
|
|||
|
|
egen renderer og pipeline-styring. Layoutet understøtter **variable antal
|
|||
|
|
resources pr. team** og beregner automatisk antal kolonner. Ingen inline-styles
|
|||
|
|
til positionering anvendes.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✨ Formål
|
|||
|
|
|
|||
|
|
- Ét globalt CSS Grid.
|
|||
|
|
- Variabelt antal resources pr. team → dynamisk antal kolonner.
|
|||
|
|
- CSS-grid auto-placerer rækker.
|
|||
|
|
- Ingen inline styling af layout (ingen `element.style.gridRow = ...`).
|
|||
|
|
- CSS custom properties bruges til at definere dynamiske spænder.
|
|||
|
|
- Renderere har ens interface og bindes i pipeline.
|
|||
|
|
- `pipeline.run(ctx)` executer alle renderers i rækkefølge.
|
|||
|
|
- Hver renderer kan hente sin egen data (API, async osv.).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧩 Data Model
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
type DateString = string;
|
|||
|
|
|
|||
|
|
interface Resource {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
dates: DateString[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Team {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
resources: Resource[];
|
|||
|
|
}
|
|||
|
|
````
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧠 Context
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
interface RenderContext {
|
|||
|
|
grid: HTMLElement; // root grid container
|
|||
|
|
teams: Team[]; // data
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`grid` er HTML-elementet med `display:grid`, og `teams` er data.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎨 CSS Layout
|
|||
|
|
|
|||
|
|
Grid kolonner bestemmes dynamisk via CSS variablen `--total-cols`.
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(var(--total-cols), minmax(0, 1fr));
|
|||
|
|
gap: 6px 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cell {
|
|||
|
|
font-size: 0.9rem;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Teams (øverste række)
|
|||
|
|
|
|||
|
|
Hver team-header spænder **antal resources for team'et**:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.team-header {
|
|||
|
|
grid-column: span var(--team-cols, 1);
|
|||
|
|
font-weight: 700;
|
|||
|
|
border-bottom: 1px solid #ccc;
|
|||
|
|
padding: 4px 2px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Resources (2. række)
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.resource-cell {
|
|||
|
|
padding: 4px 2px;
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
text-align: center;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Dates (3. række)
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.dates-cell { padding: 2px 0; }
|
|||
|
|
|
|||
|
|
.dates-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 4px;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-pill {
|
|||
|
|
padding: 3px 6px;
|
|||
|
|
background: #e3e3e3;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 0.8rem;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 Beregning af kolonner
|
|||
|
|
|
|||
|
|
**Total cols = sum(resources.length for all teams)**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const totalCols = ctx.teams.reduce((sum, t) => sum + t.resources.length, 0);
|
|||
|
|
ctx.grid.style.setProperty('--total-cols', totalCols.toString());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
For hvert team defineres hvor mange kolonner det spænder:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
cell.style.setProperty('--team-cols', team.resources.length.toString());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> Bemærk: vi bruger **kun CSS vars** til layoutparametre – ikke inline
|
|||
|
|
> grid-row/grid-column.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚙ Renderer Interface
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
interface Renderer {
|
|||
|
|
id: string;
|
|||
|
|
next: Renderer | null;
|
|||
|
|
render(ctx: RenderContext): void;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Factory
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function createRenderer(id: string, fn: (ctx: RenderContext) => void): Renderer {
|
|||
|
|
return {
|
|||
|
|
id,
|
|||
|
|
next: null,
|
|||
|
|
render(ctx) {
|
|||
|
|
fn(ctx);
|
|||
|
|
if (this.next) this.next.render(ctx);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧱 De tre render-lag (grupper)
|
|||
|
|
|
|||
|
|
### Teams
|
|||
|
|
|
|||
|
|
* Appender én `.team-header` per team.
|
|||
|
|
* Sætter `--team-cols`.
|
|||
|
|
|
|||
|
|
### Resources
|
|||
|
|
|
|||
|
|
* Appender én `.resource-cell` per resource.
|
|||
|
|
* Foregår i teams-orden → CSS auto-row sørger for næste række.
|
|||
|
|
|
|||
|
|
### Dates
|
|||
|
|
|
|||
|
|
* Appender én `.dates-cell` per resource.
|
|||
|
|
* Hver celle indeholder flere `.date-pill`.
|
|||
|
|
|
|||
|
|
Append-rækkefølge giver 3 rækker automatisk:
|
|||
|
|
|
|||
|
|
1. teams, 2) resources, 3) dates.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔗 Pipeline
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function buildPipeline(renderers: Renderer[]) {
|
|||
|
|
for (let i = 0; i < renderers.length - 1; i++) {
|
|||
|
|
renderers[i].next = renderers[i + 1];
|
|||
|
|
}
|
|||
|
|
const first = renderers[0] ?? null;
|
|||
|
|
return {
|
|||
|
|
run(ctx: RenderContext) {
|
|||
|
|
if (first) first.render(ctx);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Brug
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const pipeline = buildPipeline([
|
|||
|
|
teamsRenderer,
|
|||
|
|
resourcesRenderer,
|
|||
|
|
datesRenderer
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
pipeline.run(ctx);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 Kørsel
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// 1) beregn total kolonner
|
|||
|
|
const totalCols = ctx.teams.reduce((sum, t) => sum + t.resources.length, 0);
|
|||
|
|
ctx.grid.style.setProperty('--total-cols', totalCols);
|
|||
|
|
|
|||
|
|
// 2) pipeline
|
|||
|
|
pipeline.run(ctx);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
CSS klarer resten.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧽 Principper
|
|||
|
|
|
|||
|
|
* **Ingen inline style-positionering**.
|
|||
|
|
* **CSS Grid** owner layout.
|
|||
|
|
* **JS** owner data & rækkefølge.
|
|||
|
|
* **Renderers** er udskiftelige og genbrugelige.
|
|||
|
|
* **Append i grupper** = rækker automatisk.
|
|||
|
|
* **CSS vars** styrer spans dynamisk.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✔ TL;DR
|
|||
|
|
|
|||
|
|
* Grid-cols bestemmes ud fra data.
|
|||
|
|
* Team-header `span = resources.length`.
|
|||
|
|
* Append rækkefølge = rækker.
|
|||
|
|
* Renderere i pipeline.
|
|||
|
|
* Ingen koordinater, ingen inline layout-styles.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
```
|