# Daten nach Zeitraum laden

> Ereignisse beim Scrollen mit super-scheduler/ranges laden: abbrechbare Anfragen in Abschnitten, Cache, Ladeplatzhalter, Wiederholungen und der Serververtrag.

Source: https://superscheduler.org/de/docs/range-loading/
Reviewed: 2026-10-07

Erzeugen Sie einen Loader mit `createRangeLoader` aus `super-scheduler/ranges`, geben Sie ihm eine Funktion `load({ start, end, signal })`, die die Ereignisse dieses halboffenen Zeitraums zurückgibt, und binden Sie ihn mit `extensions={[loader]}` an. Der Loader fordert beim Mount und nach dem Abklingen von Scrollen oder Zoomen feste Tagesabschnitte rund um den sichtbaren Zeitraum an, bricht Anfragen ab, die das Fenster verlassen, hält die zuletzt geladenen Abschnitte im Cache und führt Ereignisse nach ID zusammen. Ihr Server muss nur Abfragen `[start, end)` mit stabilen Ereignis-IDs beantworten.

Bereichsweises Laden hält eine lange Zeitleiste schnell, ohne den gesamten Datenbestand an den Browser zu schicken. Der Loader fragt Ihr Backend nach dem Zeitraum, den der Besucher sehen kann, plus einem Rand, und vergisst weit entfernte Zeiträume wieder. Er wird mit SuperScheduler Pro als `super-scheduler/ranges` ausgeliefert; Lite zeigt die Ereignisse an, die Sie übergeben.

Sie brauchen ihn nicht immer. Ein Plan mit einigen Tausend Ereignissen lässt sich mit einer einzigen Anfrage laden, und der Planer virtualisiert ihn (siehe [Virtualisierung und Performance](https://superscheduler.org/de/docs/performance-virtualization/)). Greifen Sie zum bereichsweisen Laden, wenn die Zeitleiste Jahre umfasst, wenn der vollständige Datenbestand zu groß ist, um ihn abzurufen oder im Speicher zu halten, oder wenn Ihre API ohnehin nach Datum paginiert.

## Wie der Loader arbeitet
Der Loader teilt die Zeitachse in Abschnitte von `chunkDays` Tagen und hält ein Fenster aus Abschnitten rund um den sichtbaren Bereich:

- **Feste Abschnittsgrenzen.** Die Grenzen sind Vielfache von `chunkDays`, gezählt ab dem 1970-01-01; sie hängen also weder von `startDate` noch von der Scrollposition oder von `weekStarts` ab. Siebentägige Abschnitte beginnen an einem Donnerstag; mit `chunkDays: 14` und `startDate="2026-01-01"` ist der erste sichtbare Abschnitt `[2026-01-01, 2026-01-15)`. Derselbe Abschnitt erzeugt immer dieselbe Anfrage, sodass sich Antworten cachen lassen.
- **Anfragen an Grenzen, nie pro Frame.** Das gewünschte Fenster besteht aus jedem Abschnitt, der sich mit dem sichtbaren Zeitraum überschneidet, plus `prefetch` Abschnitten auf jeder Seite (ein vorab geladener Abschnitt darf vor `startDate` liegen). Fehlende Abschnitte werden direkt nach dem Mount angefordert und erneut, sobald eine Scroll- oder Zoomgeste abgeklungen ist. Programmatisches Scrollen mit `control.scrollTo()` zählt als Scrollen.
- **Abbruch.** Eine Anfrage, deren Abschnitt das gewünschte Fenster verlässt, bevor sie beantwortet ist, wird über ihr `AbortSignal` abgebrochen. Ignoriert Ihre Funktion das Signal, wird ihr verspätetes Ergebnis trotzdem verworfen.
- **Cache und Verdrängung.** Höchstens `cacheChunks` Abschnitte werden behalten. Darüber hinaus werden die Abschnitte verdrängt, die am weitesten von der Ansicht entfernt sind, und ihre Ereignisse verlassen den Planer, sofern kein anderer behaltener Abschnitt sie ebenfalls geliefert hat. Beim Zurückscrollen werden sie erneut angefordert.
- **Zusammenführung nach ID.** Ein Ereignis, das zwei Abschnitte liefern, weil es eine Grenze überspannt, erscheint nur einmal.
- **Rückmeldung.** Während ein Abschnitt lädt, liegt ein durchscheinendes Band über seinem Zeitraum. Es hat die Klasse `super-scheduler__range-skeleton` und verwendet das Token `--super-scheduler-skeleton-base`; `skeleton: false` entfernt es.
- **Kein Verlauf.** Ladevorgänge erzeugen nie Rückgängig-Einträge und erreichen `onEventsChange` mit `reason: 'load'`.

> **Behavior:**
> Der Loader zerlegt die Zeit, nicht die Zeilen. Jede Anfrage deckt alle Ressourcen für ihren Zeitraum ab; Ihr Endpunkt liefert also die Ereignisse jeder Zeile in diesem Zeitraum. Die Zeilenköpfe kommen wie gewohnt aus den `resources`, die Sie übergeben.

## Einen Loader anbinden
Erzeugen Sie den Loader einmal pro Planer und übergeben Sie ihn in `extensions`. Das Control bindet Erweiterungen anhand ihrer Objektidentität an und gibt sie wieder frei; ein Loader, der bei jedem Rendern neu erzeugt wird, würde seinen Cache also jedes Mal neu starten. Die Funktion `load` erhält `start` und `end` des Abschnitts als `SuperScheduler.Date`-Werte; `start.value` ist der ISO-String in bürgerlicher Zeit (`2026-01-01T00:00:00`), den Sie an Ihre API senden.

```tsx
// src/RangePlanning.tsx
import { useMemo, useState, type RefObject } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { createRangeLoader } from 'super-scheduler/ranges'
import 'super-scheduler/styles.css'
import { createSaveHandler } from './save-changes'

const ROOMS: SuperScheduler.ResourceData[] = [
  { id: 'r101', name: 'Room 101' },
  { id: 'r102', name: 'Room 102' },
  { id: 'r103', name: 'Room 103' },
]
const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
  { groupBy: 'Month' },
  { groupBy: 'Day', format: 'd' },
]

/** One server row as a scheduler event. The server sends civil ISO strings with seconds. */
export function toEvent(booking: Booking): SuperScheduler.EventData {
  return {
    id: booking.id,
    resource: booking.roomId,
    start: booking.start,
    end: booking.end,
    text: booking.guest,
  }
}

/**
 * The loader is created outside render and receives the control's ref object. Its callbacks read
 * `controlRef.current` later, when a chunk fails, never while React renders.
 */
function createBookingLoader(controlRef: RefObject<SuperScheduler.Scheduler | null>) {
  return createRangeLoader({
    // One chunk, [start, end). The signal aborts requests the visitor scrolled away from.
    load: async ({ start, end, signal }) => {
      const bookings = await fetchBookings(start.value, end.value, signal)
      return bookings.map(toEvent)
    },
    chunkDays: 14,
    prefetch: 1,
    onError: (error, range) => {
      console.error(error)
      const from = range.start.toString('d MMM')
      const to = range.end.addDays(-1).toString('d MMM')
      controlRef.current?.message(
        `Could not load ${from} to ${to}. Scroll back or refresh to retry.`,
      )
    },
  })
}

export function RangePlanning() {
  const { controlRef } = useSchedulerControl()

  // Created once per scheduler: extensions are attached and disposed by object identity.
  const [loader] = useState(() => createBookingLoader(controlRef))
  const extensions = useMemo(() => [loader], [loader])
  const onEventsChange = useMemo(() => createSaveHandler(loader), [loader])

  return (
    <SuperSchedulerComponent
      controlRef={controlRef}
      startDate="2026-01-01"
      days={365}
      scale="Day"
      cellWidth={40}
      heightSpec="Fixed"
      height={480}
      timeHeaders={TIME_HEADERS}
      resources={ROOMS}
      // No `events` prop: the loader writes into the control's own store (uncontrolled).
      extensions={extensions}
      onEventsChange={onEventsChange}
    />
  )
}
```
Dieser Planer hat keine `events`-Prop, daher schreibt der Loader direkt in den eigenen Speicher des Controls. Bearbeitungen durch den Nutzer kommen weiterhin in `onEventsChange` an; der folgende Handler speichert Verschiebungen und Dauer-Änderungen, überspringt Ladevorgänge und fragt den Server erneut an, wenn ein Speichern abgelehnt wird:

```ts
// src/save-changes.ts
import { SuperScheduler } from 'super-scheduler'
import type { SchedulerEventsChangeArgs } from 'super-scheduler'
import type { RangeLoader } from 'super-scheduler/ranges'

const ticks = (value: SuperScheduler.DateInput) => new SuperScheduler.Date(value).ticks

/** The range that covers an event before and after a change. */
function span(before: SuperScheduler.EventData, after: SuperScheduler.EventData) {
  return {
    start: ticks(before.start) < ticks(after.start) ? before.start : after.start,
    end: ticks(before.end) > ticks(after.end) ? before.end : after.end,
  }
}

/**
 * Persists moves and resizes. Range loads arrive with reason 'load' and are skipped. When the server
 * rejects a change, reloading the old and new dates puts the event back where the server has it.
 */
export function createSaveHandler(loader: RangeLoader) {
  return ({ reason, changed, removed }: SchedulerEventsChangeArgs) => {
    if (reason !== 'move' && reason !== 'resize') return
    for (const after of changed) {
      const before = removed.find((item) => item.id === after.id)
      if (before === undefined || after.resource === undefined) continue
      saveBooking({
        id: String(after.id),
        resource: String(after.resource),
        // After a drag, start and end are SuperScheduler.Date objects; String() gives civil ISO.
        start: String(after.start),
        end: String(after.end),
      }).catch(() => loader.reload(span(before, after)))
    }
  }
}
```
Sie sollten kurz ein Platzhalterband über dem sichtbaren Zeitraum sehen, dann die Buchungen. Im Netzwerk-Panel erzeugt das Scrollen um einige Wochen nach rechts eine Anfrage pro neuem 14-Tage-Abschnitt, sobald das Scrollen stoppt, und schnelles Scrollen über viele Abschnitte hinweg erzeugt nur die Anfragen für die Stelle, an der Sie anhalten.

## Optionen
| Option | Typ | Standard | Bedeutung |
|---|---|---|---|
| `load` | `({ start, end, signal }) => Promise<EventData[]>` | erforderlich | Gibt die Ereignisse zurück, die sich mit `[start, end)` überschneiden. |
| `chunkDays` | `number` | `7` | Tage pro Abschnitt. Größere Abschnitte bedeuten weniger, aber größere Anfragen. |
| `prefetch` | `number` | `1` | Abschnitte, die auf jeder Seite des sichtbaren Zeitraums geladen werden. |
| `cacheChunks` | `number` | `26` | Behaltene Abschnitte, bevor die am weitesten entfernten verdrängt werden. |
| `skeleton` | `boolean` | `true` | Zeigt das Ladeband über ausstehenden Abschnitten. |
| `onError` | `(error, { start, end }) => void` | keiner | Wird einmal pro fehlgeschlagenem Abschnitt aufgerufen. Abgebrochene Anfragen sind keine Fehler. |

Das Loader-Objekt selbst stellt `reload(range?)`, `clear()` und einen Getter `loading` bereit, der `true` ist, solange irgendein Abschnitt aussteht. `loading` ist eine einfache Eigenschaft, kein Abonnement: Lesen Sie sie, wenn Sie sie brauchen, oder steuern Sie Ihren eigenen Spinner aus `load` heraus.

## Kontrollierte Ereignisse oder der Speicher des Controls
Bereichsweises Laden funktioniert mit beiden Datenmodi, die unter [Kontrollierter Zustand](https://superscheduler.org/de/docs/controlled-state/) beschrieben sind:

- **Keine `events`-Prop, oder `defaultEvents`.** Der Loader fügt neue Ereignisse in den Speicher des Controls ein, aktualisiert sie, wenn ein Abschnitt neu geladen wird, und entfernt sie, wenn ihr Abschnitt verdrängt wird. Ereignisse, die schon im Speicher liegen, werden von späteren Ladevorgängen nicht überschrieben; eine Buchung, die der Nutzer gerade verschoben hat, bleibt also, wo sie ist, bis Sie `reload()` aufrufen. Das ist die einfachste Wahl, wenn Nutzer bereichsweise geladene Daten bearbeiten.
- **Kontrollierte `events` plus `onEventsChange`.** Der Loader schreibt nie in den Speicher. Jeder fertige Abschnitt ruft `onEventsChange` mit `reason: 'load'` auf, wobei `events` die zusammengeführte Liste enthält, und Ihr State muss sie wie jede andere Änderung übernehmen. React bündelt diese Aktualisierungen; rechnen Sie mit einem Aufruf pro Abschnitt.

```tsx
// src/ControlledRange.tsx
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerEventsChangeArgs, SuperScheduler } from 'super-scheduler'
import { createRangeLoader } from 'super-scheduler/ranges'
import { toEvent } from './RangePlanning'

interface Props {
  readonly rooms: SuperScheduler.ResourceData[]
}

export function ControlledRange({ rooms }: Props) {
  // React state owns the events; the loader proposes the merged list through onEventsChange.
  const [events, setEvents] = useState<SuperScheduler.EventData[]>([])
  const owned = useMemo(() => events.slice(), [events])

  const [loader] = useState(() =>
    createRangeLoader({
      load: async ({ start, end, signal }) =>
        (await fetchBookings(start.value, end.value, signal)).map(toEvent),
    }),
  )
  const extensions = useMemo(() => [loader], [loader])

  const onEventsChange = useCallback(
    (args: SchedulerEventsChangeArgs) => {
      // Adopt every change, loads included: `events` is the full list after this change.
      setEvents([...args.events])
      if (args.reason !== 'move' && args.reason !== 'resize') return
      for (const after of args.changed) {
        const before = args.removed.find((item) => item.id === after.id)
        if (before === undefined || after.resource === undefined) continue
        const change = {
          id: String(after.id),
          resource: String(after.resource),
          start: String(after.start),
          end: String(after.end),
        }
        saveBooking(change).then(
          () => {
            // The cached chunks still hold the version loaded before the change: refetch them.
            loader.clear()
            void loader.reload()
          },
          // Rejected: put the previous version back in state.
          () =>
            setEvents((current) => current.map((item) => (item.id === after.id ? before : item))),
        )
      }
    },
    [loader],
  )

  return (
    <SuperSchedulerComponent
      startDate="2026-01-01"
      days={365}
      scale="Day"
      cellWidth={40}
      resources={rooms}
      events={owned}
      onEventsChange={onEventsChange}
      extensions={extensions}
    />
  )
}
```
> **Limitation:**
> Bei kontrollierten Ereignissen veröffentlicht jeder fertige Abschnitt die zusammengeführte Liste erneut, einschließlich der Version jedes Ereignisses, die sein Abschnitt früher geliefert hat. Ein Ereignis, das durch Ziehen oder eine Dauer-Änderung ersetzt wurde, kann daher an seine geladene Position zurückspringen, wenn ein weiterer Abschnitt eintrifft, bis der Cache aufgefrischt wird. Rufen Sie nach einem erfolgreichen Speichern `loader.clear()` und `loader.reload()` auf, wie oben gezeigt, oder verwenden Sie den eigenen Speicher des Controls, in dem Ladevorgänge vorhandene Ereignisse nie überschreiben.

## Navigation, Aktualisierung und Filter
Zwei Methoden decken die meisten Aktionen einer Werkzeugleiste ab:

- `reload()` fordert das sichtbare Fenster erneut an, ob im Cache oder nicht, und ersetzt diese Abschnitte. Ereignisse, die in den neuen Antworten fehlen, werden entfernt. `reload({ start, end })` macht dasselbe für einen beliebigen Zeitraum, was nach einem Speichern oder einer Benachrichtigung des Servers nützlich ist.
- `clear()` bricht ausstehende Anfragen ab und leert den Cache. Die Ereignisse auf dem Bildschirm entfernt es nicht.

Eine Änderung von `startDate` oder `days` über Props oder `control.update()` scrollt nicht von selbst und löst daher keinen Ladevorgang aus. Scrollen Sie zum neuen Zeitraum und rufen Sie `reload()` auf, sobald React die Änderung angewendet hat. Wenn sich die Abfrage selbst ändert, zum Beispiel ein anderer Standort oder ein Statusfilter, leeren Sie den Cache, entfernen Sie die alten Ereignisse und laden Sie neu:

```tsx
// src/SitePlanning.tsx
import { useEffect, useMemo, useRef, useState } from 'react'
import { SuperScheduler, SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import { createRangeLoader } from 'super-scheduler/ranges'
import { toEvent } from './RangePlanning'

type SiteId = 'north' | 'south'

const ROOMS: Record<SiteId, SuperScheduler.ResourceData[]> = {
  north: [
    { id: 'n1', name: 'North 1' },
    { id: 'n2', name: 'North 2' },
  ],
  south: [
    { id: 's1', name: 'South 1' },
    { id: 's2', name: 'South 2' },
  ],
}

/** The loader is created once; each chunk request reads the site chosen at that moment. */
function createSiteLoader(initial: SiteId) {
  let site = initial
  return {
    loader: createRangeLoader({
      load: async ({ start, end, signal }) =>
        (await fetchSiteBookings(site, start.value, end.value, signal)).map(toEvent),
    }),
    /** Later requests query this site. */
    setSite: (next: SiteId) => {
      site = next
    },
  }
}

export function SitePlanning() {
  const { controlRef } = useSchedulerControl()
  const [month, setMonth] = useState(() => SuperScheduler.Date.today().firstDayOfMonth())
  const [site, setSite] = useState<SiteId>('north')

  const [{ loader, setSite: setLoaderSite }] = useState(() => createSiteLoader('north'))
  const extensions = useMemo(() => [loader], [loader])

  // A new period is not a scroll: after React applied it, show its start and load the view.
  const shown = useRef(month)
  useEffect(() => {
    if (shown.current.equals(month)) return
    shown.current = month
    controlRef.current?.scrollTo(month)
    void loader.reload()
  }, [controlRef, loader, month])

  // Another site: forget its cache, drop its events and load the visible range again.
  const changeSite = (next: SiteId) => {
    setLoaderSite(next)
    setSite(next)
    loader.clear()
    controlRef.current?.update({ events: [] })
    void loader.reload()
  }

  return (
    <>
      <div role="toolbar" aria-label="Planning">
        <button type="button" onClick={() => setMonth((m) => m.addMonths(-1))}>
          Previous month
        </button>
        <button type="button" onClick={() => setMonth((m) => m.addMonths(1))}>
          Next month
        </button>
        <button type="button" onClick={() => void loader.reload()}>
          Refresh
        </button>
        <select value={site} onChange={(event) => changeSite(event.target.value as SiteId)}>
          <option value="north">North</option>
          <option value="south">South</option>
        </select>
      </div>
      <SuperSchedulerComponent
        controlRef={controlRef}
        startDate={month}
        days={month.daysInMonth()}
        scale="Day"
        cellWidth={48}
        resources={ROOMS[site]}
        extensions={extensions}
      />
    </>
  )
}
```
Bei kontrollierten Ereignissen machen Sie dasselbe mit `setEvents([])` statt mit `control.update({ events: [] })` und rufen `reload()` aus einem Effect auf, der läuft, nachdem die leere Liste angewendet wurde. Wenn es akzeptabel ist, die Scrollposition zurückzusetzen, liefert ein Rendern des Planers mit `key={site}` ein frisches Control und einen frischen Loader.

## Fehler und Wiederholungen
Wenn das Promise von `load` abgelehnt wird, ruft der Loader `onError(error, { start, end })` für diesen Abschnitt auf, entfernt sein Platzhalterband und vergisst ihn. Der Abschnitt wird erneut angefordert, sobald ein abgeschlossenes Scrollen oder Zoomen ihn noch braucht, oder bei `reload()`. Zeigen Sie den Fehler dort, wo Nutzer hinschauen: `control.message()` blendet eine kurze Meldungsleiste im Planer ein, wie im ersten Beispiel. Anfragen, die der Loader abbricht, erreichen `onError` nie.

## Der Serververtrag
Ihr Endpunkt beantwortet eine einzige Frage: Welche Ereignisse überschneiden sich mit diesem halboffenen Zeitraum in bürgerlicher Zeit?

```txt
GET /api/bookings?start=2026-01-01T00:00:00&end=2026-01-15T00:00:00
```

```json
[
  { "id": "b-1042", "roomId": "r101", "guest": "Ana Ruiz", "start": "2025-12-30T14:00:00", "end": "2026-01-03T11:00:00" },
  { "id": "b-1043", "roomId": "r102", "guest": "Tom Berg", "start": "2026-01-14T14:00:00", "end": "2026-01-16T11:00:00" }
]
```

- **Halboffene Überschneidung.** Liefern Sie jedes Ereignis mit `event.start < end` und `event.end > start`. Ein Ereignis, das vor dem Abschnitt beginnt oder nach ihm endet, gehört in die Antwort, wie `b-1042` und `b-1043` oben. Ein Ereignis, das genau bei `start` endet, gehört nicht dazu.
- **Stabile, eindeutige IDs.** Dieselbe Buchung muss in jeder Antwort dieselbe `id` haben, und keine zwei Ereignisse dürfen sich eine teilen, über alle Ressourcen hinweg. IDs werden strikt verglichen: `1` und `'1'` sind verschiedene Ereignisse.
- **Bürgerliche Zeitangaben mit Sekunden.** `start` und `end` sind Wanduhrzeiten ohne Zeitzone, und Strings brauchen Sekunden (`2026-01-14T14:00:00`). Wenn Sie Zeitpunkte (Instants) speichern, rechnen Sie sie zuerst in die Zeitzone des Geschäfts um; siehe [Sprachen, Kalenderdaten und Zeitzonen](https://superscheduler.org/de/docs/locales-dates-timezones/#time-zones).
- **Abbruch ist willkommen.** Wenn Sie das `signal` an `fetch` weitergeben, wird die Verbindung früher freigegeben; der Loader ist darauf nicht angewiesen.
- **Cachefähig.** Feste Abschnittsgrenzen sorgen dafür, dass sich identische Anfragen wiederholen; HTTP-Caching oder ein CDN kann sie also bedienen. Halten Sie den Cache kurz, wenn andere Nutzer denselben Plan bearbeiten.

## Eine Ebene tiefer: dynamicLoading und onScroll
Das Control bietet auch den klassischen, Callback-basierten Mechanismus. Mit `dynamicLoading` und einem `onScroll`-Handler ruft das Control `onScroll` auf, sobald das Scrollen `scrollDelayDynamic` Millisekunden lang geruht hat (standardmäßig 500). `args.viewport` enthält `start`, `end` und `resources` des sichtbaren Bereichs; Sie füllen `args.events` und rufen `args.loaded()` auf.

```tsx
// src/DynamicPlanning.tsx
import { useCallback, useRef, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerScrollArgs, SuperScheduler } from 'super-scheduler'
import { toEvent } from './RangePlanning'

interface Props {
  readonly rooms: SuperScheduler.ResourceData[]
  /** Events of the first screen: onScroll is not called at mount. */
  readonly initial: SuperScheduler.EventData[]
}

export function DynamicPlanning({ rooms, initial }: Props) {
  const [firstScreen] = useState(() => initial.slice())
  const pending = useRef<AbortController | null>(null)

  // Called once scrolling has been quiet for `scrollDelayDynamic` ms.
  const onScroll = useCallback((args: SchedulerScrollArgs) => {
    pending.current?.abort()
    const request = new AbortController()
    pending.current = request
    // Load a margin around the viewport: by default the result replaces every event.
    const from = args.viewport.start.addDays(-14)
    const to = args.viewport.end.addDays(14)
    args.async = true
    fetchBookings(from.value, to.value, request.signal).then(
      (bookings) => {
        args.events = bookings.map(toEvent)
        args.loaded()
      },
      () => {
        // Failed or superseded: keep what is on screen.
        args.clearEvents = false
        args.loaded()
      },
    )
  }, [])

  return (
    <SuperSchedulerComponent
      startDate="2026-01-01"
      days={365}
      scale="Day"
      cellWidth={40}
      resources={rooms}
      defaultEvents={firstScreen}
      dynamicLoading
      scrollDelayDynamic={300}
      onScroll={onScroll}
    />
  )
}
```
Im Vergleich zum Range-Loader bietet dieser Weg volle Kontrolle und sonst nichts: keine Ausrichtung an Abschnitten, kein Cache, kein Vorabladen, kein Platzhalterband, kein Abbruch. `onScroll` wird beim Mount nicht aufgerufen; rendern Sie den ersten Bildschirm also selbst. `args.async` ist anfangs `true`, sodass das Ergebnis erst angewendet wird, wenn Sie `args.loaded()` aufrufen. Standardmäßig ist `args.clearEvents` `true`, und die zurückgegebenen Ereignisse ersetzen alle Ereignisse; setzen Sie es auf `false`, um nach ID zusammenzuführen, und führen Sie zu entfernende IDs in `args.remove` auf. Da `viewport.resources` die sichtbaren Zeilen auflistet, kann dieser Mechanismus auch zeilenweise laden.

## Was in Ihrer Anwendung bleibt
- Änderungen speichern. Der Loader liest; Ihr `onEventsChange`-Handler oder explizite Aktionen schreiben.
- Live-Aktualisierungen von anderen Nutzern. Die Optionen für automatisches Aktualisieren (`autoRefreshEnabled` und verwandte) sind reserviert und bewirken nichts. Fragen Sie regelmäßig ab (Polling), wenden Sie Server-Benachrichtigungen mit `control.events.add/update/remove` an oder rufen Sie `reload({ start, end })` für den betroffenen Zeitraum auf.
- Verknüpfungen und Zeilen. Der Loader verarbeitet nur Ereignisse; übergeben Sie `links` und `resources` selbst.
- Das in das Control eingebaute Laden per HTTP (`events.load(url)`, `rows.load(url)`, `links.load(url)`) ist typisiert, aber reserviert: Es warnt in der Entwicklung und lädt nichts.

Verwandte Anleitungen: [Kontrollierter Zustand](https://superscheduler.org/de/docs/controlled-state/), [Rückgängig und Wiederholen](https://superscheduler.org/de/docs/undo-redo/), [Ressourcen, Ereignisse und Intervalle](https://superscheduler.org/de/docs/resources-events-intervals/) und die [API-Referenz](https://superscheduler.org/de/docs/api-reference/#ranges).
