Embed API

Technische Referenz

Hochgeladene interaktive HTML-Inhalte mit LearningView verbinden

Diese Anleitung richtet sich in erster Linie an Bots und KI-Assistenten.

Gib deinem Bot einfach die URL https://learningview.org/embed-api.php und bitte ihn, die hier beschriebene Schnittstelle in deinen generierten, interaktiven Lerninhalt einzubauen. So kann der Inhalt mit LearningView kommunizieren, zum Beispiel Lernstände speichern und laden oder eine Klassenübersicht anzeigen.

Die technischen Details kannst du bei Bedarf unten aufklappen.

Mit generativen Werkzeugen lassen sich heute einfach eigene interaktive Lerninhalte als HTML-Inhalte erstellen – beispielsweise Übungen, Lernspiele, Simulationen, Quizze oder kleine Lernprogramme. Solche Inhalte können direkt in LearningView eingesetzt werden. Wähle dazu bei einer Aufgabe den Typ „Anbieter“ und anschliessend „Generiert“ aus.

Generierte Inhalte vor dem Einsatz sorgfältig prüfen

Mit generativen Werkzeugen erstellte Inhalte müssen vor dem Einsatz mit Schülerinnen und Schülern fachlich und didaktisch genau überprüft werden. Sie dürfen nicht unreflektiert eingesetzt werden. Die Verantwortung für Korrektheit, Eignung und altersgerechte Gestaltung liegt bei der Lehrperson.

Ein vom Nutzer in LearningView hochgeladener HTML-Inhalt wird in einem eingebetteten Fenster geöffnet. Diese Seite erklärt, wie der erstellte Inhalt über die Browser-Schnittstelle window.parent.postMessage() mit LearningView kommunizieren kann. Dadurch kann der Inhalt Informationen zum Abschluss melden, seinen Bearbeitungsstand speichern und laden, als Lehrperson eine Klassenübersicht darstellen oder einen Dateidownload auslösen.

Geltungsbereich: Die hier beschriebene Schnittstelle steht zur Verfügung, wenn der hochgeladene HTML-Inhalt innerhalb von LearningView geöffnet wird. Wird die Datei direkt in einem Browserfenster geöffnet, existiert kein empfangendes LearningView-Elternfenster.
Technische Hinweise für die Kommunikation zwischen interaktivem Inhalt und LearningView

01 · Dateiformat

Interaktiven Inhalt möglichst als einzelne HTML-Datei erzeugen

Ein generatives Werkzeug sollte einen interaktiven Inhalt idealerweise als eine einzelne, eigenständig lauffähige .html-Datei erzeugen. HTML, CSS, JavaScript und möglichst auch alle weiteren benötigten Inhalte sollen direkt in dieser Datei integriert sein. Dadurch kann der Nutzer den Inhalt besonders einfach in LearningView hochladen.

Anweisung für generative Werkzeuge
  • Bevorzugt eine einzelne, vollständige und eigenständig lauffähige .html-Datei erzeugen.
  • Styles, Skripte und nach Möglichkeit auch Bilder oder weitere Daten direkt in die HTML-Datei integrieren.
  • Werden mehrere Dateien benötigt, ein vollständiges Projekt mit relativen Dateipfaden anlegen.
  • Das Mehrdatei-Projekt muss vor dem Hochladen in LearningView als ZIP-Datei verpackt werden.
  • Um diesen Schritt zu vereinfachen, den fertigen Inhalt nach Möglichkeit direkt als .zip-Datei bereitstellen.

Eine ZIP-Datei ist insbesondere sinnvoll, wenn der Inhalt separate Bilder, Audio- oder Videodateien, Datendateien, Stylesheets oder JavaScript-Dateien benötigt. Alle benötigten Projektdateien müssen im ZIP enthalten und über relative Pfade miteinander verknüpft sein.

02 · Grundlagen

Nachrichten an das Elternfenster senden

Nachrichten werden immer vom HTML-Inhalt an window.parent gesendet. Der folgende kleine Helfer verhindert lediglich, dass beim direkten Öffnen der Datei eine Nachricht an das eigene Fenster gesendet wird.

const LEARNINGVIEW_ORIGIN = "https://learningview.org";

function sendToLearningView(message) {
  if (window.parent === window) {
    console.warn("Dieser HTML-Inhalt läuft nicht in LearningView.");
    return;
  }

  window.parent.postMessage(message, LEARNINGVIEW_ORIGIN);
}
Senden

Kindfenster → LearningView

Der HTML-Inhalt ruft window.parent.postMessage(...) auf.

Empfangen

LearningView → Kindfenster

Antworten werden über ein message-Ereignis am eigenen Fenster empfangen.

Sicherheitshinweis: Als Ziel-Origin wird https://learningview.org verwendet. Der Browser stellt die Nachricht dadurch nur dem Elternfenster zu, wenn dessen Origin exakt übereinstimmt. Beim Empfangen sollten sowohl event.source === window.parent als auch event.origin === LEARNINGVIEW_ORIGIN geprüft werden.
Kein localStorage in eingebetteten Inhalten: HTML-Inhalte werden in LearningView in einer Sandbox ausgeführt. Deshalb steht window.localStorage dem eingebetteten Inhalt nicht zur Verfügung; ein Zugriff kann fehlschlagen oder eine Ausnahme auslösen. Generierte Anwendungen sollten localStorage nicht verwenden und dürfen für ihre Funktion nicht davon abhängig sein. Persistente Zustände sollen stattdessen über die nachfolgend beschriebene LearningView-Schnittstelle mit saveAnswerData und getAnswerData gespeichert und geladen werden.

03 · Persistente Antwortdaten

Daten speichern und laden

saveAnswerData / getAnswerData

Mit den Antwortdaten kann ein interaktiver HTML-Inhalt seinen Zustand im Aufgabenstatus der Schülerin oder des Schülers ablegen. LearningView speichert genau eine Zeichenkette. Strukturierte Zustände werden deshalb als JSON serialisiert.

Speicherung pro Schüler:in

LearningView speichert und lädt die Daten für jede Schülerin und jeden Schüler eines Kurses einzeln. Bei Partner- und Gruppenarbeiten wird der gespeicherte Datenstand für alle Gruppenmitglieder als separate Kopie abgelegt.

Einzelne Antwortdaten können mit einem Schülerkonto gelesen und gespeichert werden. Beim Anzeigen einer Schülerlösung durch eine Lehrperson ist nur Lesen möglich. Für die Übersicht über den ganzen Kurs gibt es getClassAnswerData.

Daten speichern

Das Feld data muss eine Zeichenkette sein. Sobald LearningView die Daten erfolgreich gespeichert hat, sendet es die Zeichenkette "SAVED" an den HTML-Inhalt zurück. Der Listener muss vor der Speicheranfrage registriert werden.

function saveAnswerData(state) {
  return new Promise((resolve, reject) => {
    const timeout = window.setTimeout(() => {
      window.removeEventListener("message", receiveSaveConfirmation);
      reject(new Error("Keine Speicherbestätigung von LearningView"));
    }, 15000);

    function receiveSaveConfirmation(event) {
      if (event.source !== window.parent) return;
      if (event.origin !== LEARNINGVIEW_ORIGIN) return;
      if (event.data?.type === "saveAnswerDataError") {
        window.clearTimeout(timeout);
        window.removeEventListener("message", receiveSaveConfirmation);
        reject(new Error(event.data.error));
        return;
      }
      if (event.data !== "SAVED") return;

      window.clearTimeout(timeout);
      window.removeEventListener("message", receiveSaveConfirmation);
      resolve();
    }

    window.addEventListener("message", receiveSaveConfirmation);
    sendToLearningView({
      type: "saveAnswerData",
      data: JSON.stringify(state)
    });
  });
}

const state = {
  version: 1,
  currentQuestion: 3,
  answers: ["Bern", "Zürich", "Genf"]
};

saveAnswerData(state).then(() => {
  console.log("Daten wurden gespeichert.");
});
Speicheranfragen nacheinander senden: Die Bestätigung "SAVED" enthält keine Anfrage-ID. Deshalb sollte erst nach der Bestätigung die nächste Speicheranfrage gesendet werden. Häufige Änderungen können zusätzlich gebündelt werden (Debounce).
Grössenlimit und gebündeltes Speichern
  • saveAnswerData.data darf höchstens 1 MB (1.048.576 Bytes in UTF-8) gross sein. Die Anzahl der JavaScript-Zeichen ist dafür nicht massgeblich. Beispielsweise benötigen Umlaute und Emojis mehrere Bytes.
  • Grössere Zustände werden verworfen und nicht gespeichert. LearningView zeigt einmal pro geöffnetem Inhalt einen Hinweis. Ein zuvor akzeptierter, noch gepufferter Zustand bleibt erhalten.
  • Die erste Speicheranfrage wird sofort übertragen. Danach startet höchstens alle 2 Sekunden eine weitere Übertragung. Während einer laufenden Übertragung startet keine zweite.
  • In der Zwischenzeit hält LearningView nur den neuesten akzeptierten Zustand im Puffer. Neue Zustände ersetzen den gepufferten Zustand, ohne den Timer erneut zu starten. Dies ist eine Begrenzung mit nachfolgendem Speichern, kein unbegrenzt aufschiebendes Debounce.
  • Die Bestätigung "SAVED" erfolgt erst, wenn der neueste akzeptierte Zustand erfolgreich gespeichert ist. Überschriebene Zwischenstände erhalten keine eigene Bestätigung.
  • Beim normalen Schliessen des Inhalts wird ein ausstehender Zustand nach Ablauf der Wartezeit weiterhin gespeichert. Die abschliessende Ergebnisübernahme wartet darauf. Das Schliessen oder Neuladen der gesamten App bzw. Browserseite kann eine noch ausstehende Speicherung abbrechen.

Bei zu grossen Daten erhält der Inhalt {type: "saveAnswerDataError", error: "ANSWER_DATA_TOO_LARGE", maxBytes: 1048576}. Schlägt eine Übertragung fehl, lautet der Fehler SAVE_FAILED. Ein Fehler ist keine Speicherbestätigung. Der Inhalt sollte ihn anzeigen und bei einem Übertragungsfehler eine erneute Speicherung anbieten. Bereits gepufferte neuere Daten werden weiterhin nach den genannten Regeln übertragen.

Daten laden

Zuerst wird der Listener registriert, danach wird getAnswerData gesendet. LearningView antwortet asynchron mit einem Objekt vom Typ sendAnswerData. Wenn noch nichts gespeichert wurde, ist data eine leere Zeichenkette.

function loadAnswerData() {
  return new Promise((resolve, reject) => {
    const timeout = window.setTimeout(() => {
      window.removeEventListener("message", receiveAnswerData);
      reject(new Error("Keine Antwort von LearningView"));
    }, 5000);

    function receiveAnswerData(event) {
      if (event.source !== window.parent) return;
      if (event.origin !== LEARNINGVIEW_ORIGIN) return;
      if (!event.data || event.data.type !== "sendAnswerData") return;

      window.clearTimeout(timeout);
      window.removeEventListener("message", receiveAnswerData);
      resolve(event.data.data); // immer eine Zeichenkette
    }

    window.addEventListener("message", receiveAnswerData);
    sendToLearningView({ type: "getAnswerData" });
  });
}

loadAnswerData().then((savedText) => {
  if (!savedText) return;

  const state = JSON.parse(savedText);
  console.log("Gespeicherter Zustand:", state);
});

Alternativ zu type akzeptiert LearningView auch das Feld action. Die Ladeanfrage darf ausserdem direkt als Zeichenkette gesendet werden:

sendToLearningView({
  action: "saveAnswerData",
  data: "ein einfacher Textwert"
});

sendToLearningView("getAnswerData");
Sonderfall: HTML-Inhalt als Anhang

Neben der oben beschriebenen Einbindung über „Anbieter“ → „Generiert“ kann ein HTML-Inhalt auch als Anhang einer Aufgabe hinzugefügt werden. Soll er in diesem Sonderfall Antwortdaten speichern oder laden, gelten folgende Voraussetzungen:

  • Der HTML-Inhalt wurde als Anhang bei einer Aufgabe hochgeladen.
  • Die Aufgabe muss in LearningView zwingend die Abgabeform keine verwenden. Eine zusätzliche Abgabeform kann nicht gleichzeitig genutzt werden, weil saveAnswerData dasselbe Antwortfeld der Aufgabe verwendet.
  • Pro Aufgabe darf nur ein interaktiver Inhalt saveAnswerData verwenden. Mehrere solche Inhalte teilen dasselbe Antwortfeld und können ihre gespeicherten Daten deshalb gegenseitig überschreiben.
  • LearningView akzeptiert Lade- und Speichernachrichten nur vom tatsächlich eingebetteten HTML-Fenster.

04 · Klassenansicht

Gespeicherte Daten des ganzen Kurses auswerten

getClassAnswerData

Öffnet eine Lehrperson einen generierten Inhalt innerhalb eines Kurses, ohne eine einzelne Schülerin oder einen einzelnen Schüler auszuwählen, kann der Inhalt eine Klassenübersicht anzeigen. LearningView liefert die individuellen Speicherstände mit Namen. Welche Werte, Tabellen oder Diagramme daraus entstehen, entscheidet der HTML-Inhalt selbst, denn nur er kennt die Struktur seiner gespeicherten Daten. Dies gilt für HTML-Dateien und die HTML-Startdatei eines ZIP-Pakets unter „Anbieter“ → „Generiert“.

Startparameter: window.learningView.mode

LearningView setzt window.learningView vor dem ersten Skript des eingebetteten HTML-Dokuments. Der Parameter ist ein JavaScript-Objekt, kein URL-Queryparameter: Die Inhalte laufen in einem isolierten Blob-Dokument. ZIP-Ressourcen werden weiterhin relativ zur Startdatei aufgelöst.

// Beim Start verfügbar, keine postMessage-Anfrage erforderlich:
const mode = window.learningView?.mode || "student";
const isClassView = mode === "class";
  • class: Lehrperson öffnet eine Aufgabe im Kurs ohne ausgewählte Person. Klassenübersicht anzeigen und getClassAnswerData anfordern.
  • student: Schülerkonto bearbeitet den Inhalt. getAnswerData und saveAnswerData verwenden.
  • studentReview: Lehrperson betrachtet eine einzelne Schülerlösung. getAnswerData verwenden und die Darstellung schreibgeschützt halten.
  • preview: Vorschau ohne Schüler- oder passenden Kurskontext. Keine Antwortdaten anfordern oder speichern.
  • Ausserhalb von LearningView und bei älteren Versionen kann window.learningView fehlen. Bestehende Inhalte ohne Modusprüfung funktionieren weiterhin als Einzelansicht; eine Klassenübersicht entsteht erst durch deren Implementierung.

Kontext per Nachricht abfragen: getEmbedContext

Direkt eingebettete Webseiten können denselben Startmodus über postMessage abfragen. Diese Anfrage liefert keine Namen, IDs oder Aufgabenstände und benötigt keine Datenfreigabe. Sie steht auch generierten HTML-Inhalten zur Verfügung.

sendToLearningView({ type: "getEmbedContext" });

// Antwort (Beispiel):
{ type: "sendEmbedContext", mode: "studentReview" }

Listener vor der Anfrage registrieren und Quelle sowie Ursprung der Antwort prüfen. Mögliche Modi: class, student, studentReview, preview. Der Modus allein ist keine Berechtigung; Datenanfragen werden zusätzlich geprüft.

Dialoggrösse anfordern: setDialogSize

Ein eingebetteter Inhalt kann die maximal verfügbare Dialogfläche anfordern, etwa für ein breites Panorama. Wie bei den Editoren nutzt der Dialog die gesamte Bildschirmfläche; für die blauen Kursbuttons bleibt unten kein reservierter Abstand. Die sicheren Bildschirmränder werden weiterhin berücksichtigt. Dies vergrössert den Dialog innerhalb von LearningView und aktiviert keinen Browser-Vollbildmodus. Ohne Anfrage bleibt die bisherige Standardgrösse erhalten. Die Funktion ist unabhängig von Datenfreigaben sowohl für externe Webseiten als auch generierte HTML-Inhalte verfügbar.

// Möglichst viel Platz nutzen:
window.parent.postMessage({type: "setDialogSize", size: "max"}, "https://learningview.org");

// Zur Standardgrösse zurückkehren:
window.parent.postMessage({type: "setDialogSize", size: "auto"}, "https://learningview.org");

Akzeptiert werden nur max und auto, keine beliebigen CSS-Werte. LearningView prüft das konkrete iframe-Fenster und bei Webseiten dessen Ursprung. Beim Wechsel werden manuelle Verschiebungen und Grössenänderungen zurückgesetzt. Es gibt keine Bestätigungsnachricht.

Nur die Kursliste anfordern: getClassStudents

Für die Anbindung externer Anwendungen kann die Lehrpersonen-Klassenansicht ausschliesslich Namen und IDs abfragen, ohne individuelle Aufgabenstände zu lesen. Es gelten dieselben Kontextbedingungen wie für getClassAnswerData: eingebetteter Inhalt im aktuellen Kurs, keine ausgewählte Schülerlösung und Abgabeform keine.

sendToLearningView({ type: "getClassStudents" });

// Antwort:
{
  type: "sendClassStudents",
  data: [{ studentId: "123", name: "Ada", surname: "Müller" }]
}

Ein leerer Kurs liefert data: []. Bei fehlender Berechtigung oder einem zwischenzeitlichen Kurswechsel enthält die Antwort zusätzlich error: "CLASS_STUDENTS_UNAVAILABLE". Auch hier müssen Quelle und Ursprung eingehender Nachrichten geprüft werden.

Beide Klassenabfragen stehen generierten HTML-Inhalten und direkt als HTTP(S)-URL eingebetteten Webseiten zur Verfügung. Bei externen Webseiten prüft LearningView neben dem konkreten iframe auch den Ursprung der eingebetteten URL und antwortet nur an diesen Ursprung. Es gibt keine anbieterspezifische Freigabeliste; die Lehrperson entscheidet über die Datenfreigabe.

Ausgewählte Person anfordern: getSelectedStudent

Beim Anzeigen einer einzelnen Schülerlösung (studentReview) kann ein Inhalt Name und LearningView-ID genau dieser Person anfragen, etwa um sie in einer externen Anwendung auszuwählen. LearningView erlaubt dies nur einer Lehrperson im zugehörigen aktuellen Kurs, wenn die Person zum Kurs gehört und die Aufgabe die Abgabeform keine hat. Eine vom iframe gesendete Schüler-ID wird nicht verwendet. Aufgabenstände werden nicht gelesen.

sendToLearningView({ type: "getSelectedStudent" });

// Antwort nach Zustimmung:
{
  type: "sendSelectedStudent",
  data: { studentId: "123", name: "Ada", surname: "Müller" }
}

Bei fehlender Berechtigung oder ungültig gewordenem Kontext lautet die Antwort {type: "sendSelectedStudent", data: null, error: "SELECTED_STUDENT_UNAVAILABLE"}. Die Anfrage steht generiertem HTML und direkt eingebetteten HTTP(S)-Webseiten zur Verfügung. Die Klassenabfragen bleiben in dieser Einzelansicht gesperrt.

Eine externe Klasse dauerhaft zuordnen

Mit includeConnectionKey: true kann eine freigegebene Datenanfrage zusätzlich eine zufällige Kurskennung anfordern. Das gilt für getClassStudents, getSelectedStudent und getClassAnswerData innerhalb ihrer jeweiligen Berechtigungen.

sendToLearningView({ type: "getClassStudents", includeConnectionKey: true });

// Antwort nach Zustimmung:
{
  type: "sendClassStudents",
  data: [{ studentId: "123", name: "Ada", surname: "Müller" }],
  context: { key: "…64 zufällige Hex-Zeichen…" }
}

LearningView merkt die Kennung lokal je Benutzerkonto, Kurs-ID und vollständiger iframe-URL beziehungsweise HTML-Inhaltsidentität. Ganze Kursansicht und einzelne Schülerlösungen desselben Inhalts erhalten dieselbe Kennung. Andere Kurse, Konten oder Inhalte erhalten eigene Kennungen. Die Kennung enthält weder die interne Kurs-ID noch den Kursnamen oder die Benutzer-ID. Der Anbieter kann damit seine eigene Klassenzuordnung wiederfinden; sie ist kein Zugangstoken und ersetzt weder Anmeldung noch Datenfreigabe.

Ohne diese Option bleibt das Antwortformat unverändert. Bei verweigerter Freigabe wird keine Kennung erzeugt oder übermittelt. Ohne Browser-Speicher bleibt sie nur bis zum Schliessen erhalten; nach Löschen des Speichers entsteht eine neue Zuordnung. Fehlt sichere Zufallserzeugung, fehlt context in der Antwort. Anwendungen, die eine feste Zuordnung benötigen, müssen dann anhalten. Das Zurücksetzen der Datenfreigabe entfernt die Klassenzuordnung nicht; der Anbieter sollte dafür eine eigene Funktion anbieten.

Zustimmung der Lehrperson

Vor der ersten Übertragung fragt LearningView die Lehrperson, ob sie die angeforderten Daten mit diesem Inhalt teilen möchte. Der Dialog nennt die eingebettete Adresse beziehungsweise den generierten Inhalt, den Kurs und die Datenart. getClassStudents, getClassAnswerData und getSelectedStudent werden getrennt freigegeben. Bei getSelectedStudent gilt eine gemerkte Zustimmung für die jeweils ausgewählte Person im selben Kurs, sodass beim Wechsel zur nächsten Schülerlösung nicht erneut gefragt wird. Ohne Zustimmung werden keine Namen, IDs oder Aufgabenstände übermittelt.

  • Mit der nicht vorausgewählten Option Für diesen Inhalt und Kurs in diesem Browser merken kann eine Zustimmung in LearningViews eigenem localStorage gespeichert werden. Dies ist eine lokale Erlaubnis, kein Zugangstoken für den Inhalt.
  • Die Freigabe gilt getrennt nach Lehrperson, Kurs, Datenart und vollständiger Webseiten-URL. Bei generiertem HTML verwendet LearningView stattdessen einen SHA-256-Fingerabdruck aus HTML und Ressourcenbasis. Geänderter HTML-Inhalt benötigt erneut Zustimmung; die gemeinsame Sandbox-Herkunft null ist keine Identität.
  • Ohne Merken gilt die Entscheidung nur für den geöffneten Inhalt. Eine Ablehnung wird dort ebenfalls berücksichtigt, damit wiederholte Anfragen keine neuen Dialoge auslösen. Nach erneutem Öffnen kann wieder gefragt werden.
  • Das Schildsymbol Datenfreigaben zurücksetzen in LearningViews iframe-Titelleiste erscheint nur, wenn eine gespeicherte Zustimmung für diesen Inhalt, Kurs und die aktuelle Lehrperson vorliegt. Darüber können alle drei Freigaben gelöscht werden; anschliessend verschwindet der Button. Bereits übermittelte Daten werden dadurch beim Empfänger nicht gelöscht. Nicht gemerkte Entscheidungen enden beim Schliessen des Inhalts.
  • Wenn Browser-Speicher oder ein sicherer HTML-Fingerabdruck nicht verfügbar sind, bleibt eine Zustimmung auf den geöffneten Inhalt beschränkt. Die Entscheidung wird nicht zwischen Geräten oder Browsern synchronisiert.

Während LearningView die Freigabe prüft, sendet es {type: "classDataConsentPending", request: "getClassStudents"} beziehungsweise request: "getClassAnswerData" oder request: "getSelectedStudent". Inhalte müssen ein laufendes Antwort-Zeitlimit dann aussetzen: Die Lehrperson darf für die Entscheidung länger brauchen. Anschliessend folgt die normale Antwort. Bei Ablehnung enthält diese data: [] (bei getSelectedStudent: data: null) und error: "CLASS_DATA_CONSENT_DENIED". Eine ungültige Kursberechtigung liefert weiterhin den jeweiligen …_UNAVAILABLE-Fehler.

Anfrage und Antwort für Aufgabenstände

sendToLearningView({ type: "getClassAnswerData" });

// Antwort von LearningView (Beispiel):
{
  type: "sendClassAnswerData",
  data: [
    { studentId: "123", name: "Ada", surname: "Müller", data: '{"version":1,"count":4}' },
    { studentId: "456", name: "Lin", surname: "Meier", data: "" }
  ]
}
  • data der Antwort ist ein Array mit einem Eintrag pro Lernendem im geöffneten Kurs, unabhängig von einem Personenfilter der LearningView-Oberfläche. Ein leerer Kurs liefert [].
  • studentId ist die ID als Zeichenkette; name ist der Vorname und surname der Nachname. Namen können leer sein. Zum Zuordnen die ID verwenden, nicht den Namen.
  • data eines Eintrags ist dieselbe Zeichenkette wie bei getAnswerData. Ein interner Score-Präfix wird nicht mitgeliefert. Ohne gespeicherten Zustand ist sie "", auch wenn bereits ein Score vorliegt.
  • JSON wird nicht automatisch ausgewertet. Jeden Eintrag einzeln mit Fehlerbehandlung parsen; ein ungültiger oder älterer Zustand darf nicht die ganze Übersicht verhindern.
  • Die Antwort verwendet die aktuell in LearningView geladenen Aufgabenstände. Es gibt keine Push-Updates; zum Aktualisieren erneut anfragen. Bei Gruppenarbeiten bleibt es bei einem Eintrag pro Person, auch bei identischen Daten.
  • Keine Kurs-, Aufgaben- oder Schüler-ID mitsenden: Der geöffnete Dialog bestimmt den Kontext. Die Abfrage ist nur in der Lehrpersonen-Klassenansicht für diese Aufgabe mit Abgabeform keine verfügbar. Sie verändert keine Daten.
  • Alternativ werden {action: "getClassAnswerData"} und die Zeichenkette "getClassAnswerData" akzeptiert.

Bei einer nicht verfügbaren Klassenansicht antwortet LearningView mit {type: "sendClassAnswerData", data: [], error: "CLASS_ANSWER_DATA_UNAVAILABLE"}. Das Fehlerfeld unterscheidet eine abgelehnte Anfrage von einem leeren Kurs. Nachrichten fremder Fenster werden ignoriert. Das Ändern des Startparameters gibt keine zusätzlichen Berechtigungen.

function loadClassAnswerData() {
  return new Promise((resolve, reject) => {
    const timeout = window.setTimeout(() => {
      window.removeEventListener("message", receive);
      reject(new Error("Keine Klassenantwort von LearningView"));
    }, 5000);

    function receive(event) {
      if (event.source !== window.parent) return;
      if (event.origin !== LEARNINGVIEW_ORIGIN) return;
      if (event.data?.type === "classDataConsentPending" && event.data.request === "getClassAnswerData") {
        window.clearTimeout(timeout); // Auf die Entscheidung warten, nicht auf Netzwerkzeit begrenzen.
        return;
      }
      if (!event.data || event.data.type !== "sendClassAnswerData") return;
      window.clearTimeout(timeout);
      window.removeEventListener("message", receive);
      if (event.data.error) reject(new Error(event.data.error));
      else resolve(event.data.data);
    }

    window.addEventListener("message", receive);
    sendToLearningView({ type: "getClassAnswerData" });
  });
}

if (window.learningView?.mode === "class") {
  loadClassAnswerData().then((students) => {
    // Hier die zum eigenen Inhalt passende Übersicht erstellen.
    console.log(students);
  }).catch(console.error);
}
Hinweis für generative Werkzeuge: Zuerst den Modus prüfen. Im Klassenmodus keine individuelle Übung starten und weder getAnswerData, saveAnswerData noch Scores senden. Stattdessen getClassAnswerData laden und eine zum eigenen Datenformat passende Übersicht mit Namen, leeren Zuständen und verständlicher Fehleranzeige rendern. Namen und gespeicherte Texte mit textContent ausgeben, nicht als HTML interpretieren.

05 · Auswertung

Ergebnis als xAPI-Score melden

result.score

Ein HTML-Inhalt kann einen Prozentwert melden. LearningView übernimmt den Wert bei Schülerkonten, wenn der eingebettete Inhalt geschlossen wird. Der gemeldete Prozentwert wird anschliessend auf der Aufgabenkachel angezeigt.

Empfohlen: Wert zwischen 0 und 1

// Meldet 80 %
sendToLearningView({
  result: {
    score: {
      scaled: 0.8
    }
  }
});

Alternative: Rohwert und Maximalwert

// Meldet ebenfalls 80 %
sendToLearningView({
  result: {
    score: {
      raw: 8,
      max: 10
    }
  }
});
  • scaled wird mit 100 multipliziert und auf eine ganze Prozentzahl gerundet.
  • Ohne scaled wird raw / max × 100 berechnet.
  • Weitere xAPI-Felder sind erlaubt, werden für diese Auswertung aber nicht benötigt.
  • Es gibt keine Antwort- oder Bestätigungsnachricht.
Wichtig bei 0 %: Auch scaled: 0 beziehungsweise raw: 0 mit einem gültigen max wird als 0 % übernommen.

06 · Datei

Download auslösen

DOWNLOADFILE|

Der Downloadbefehl ist eine Zeichenkette. Direkt nach dem Trennzeichen | folgt die herunterzuladende Adresse. LearningView verwendet den letzten Teil des URL-Pfads als Dateinamen.

const fileUrl = "https://example.org/material/arbeitsblatt.pdf";
sendToLearningView("DOWNLOADFILE|" + fileUrl);
  • Die Datei muss für LearningView erreichbar sein.
  • Eine URL mit einem eindeutigen Dateinamen und einer passenden Endung wird empfohlen.
  • Der Befehl liefert keine Bestätigungsnachricht an den HTML-Inhalt zurück.

07 · Kopiervorlage

Minimaler vollständiger HTML-Inhalt

Dieses Beispiel lädt einen Zähler, speichert ihn nach jeder Änderung und meldet bei fünf Klicks 100 %. Lehrpersonen sehen im Klassenmodus die Zählerstände aller Lernenden; einzelne Schülerlösungen bleiben schreibgeschützt.

<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Interaktive Aufgabe</title>
</head>
<body>
  <button id="counter" type="button">Lade …</button>
  <p id="save-status" aria-live="polite"></p>
  <ul id="class-results"></ul>

  <script>
    const button = document.querySelector("#counter");
    const saveStatus = document.querySelector("#save-status");
    const LEARNINGVIEW_ORIGIN = "https://learningview.org";
    const mode = window.learningView?.mode || "student";
    const classResults = document.querySelector("#class-results");
    button.hidden = mode === "class";
    button.disabled = true;
    let count = 0;

    function sendToLearningView(message) {
      if (window.parent !== window) {
        window.parent.postMessage(message, LEARNINGVIEW_ORIGIN);
      }
    }

    function render() {
      button.textContent = `Klicks: ${count} von 5`;
    }

    window.addEventListener("message", (event) => {
      if (event.source !== window.parent) return;
      if (event.origin !== LEARNINGVIEW_ORIGIN) return;

      if (event.data?.type === "sendClassAnswerData" && mode === "class") {
        classResults.replaceChildren();
        if (event.data.error) {
          saveStatus.textContent = event.data.error === "CLASS_DATA_CONSENT_DENIED"
            ? "Die Klassendaten wurden nicht freigegeben."
            : "Klassendaten sind nicht verfügbar.";
          return;
        }
        saveStatus.textContent = event.data.data.length ? "Klassenübersicht" : "Keine Lernenden im Kurs.";
        for (const student of event.data.data) {
          const row = document.createElement("li");
          let value = "Noch kein Speicherstand";
          if (student.data) {
            try {
              const saved = JSON.parse(student.data);
              value = saved && Number.isFinite(saved.count)
                ? `${saved.count} von 5 Klicks` : "Unbekanntes Datenformat";
            } catch {
              value = "Ungültiger Speicherstand";
            }
          }
          row.textContent = `${student.name} ${student.surname}: ${value}`;
          classResults.append(row);
        }
        return;
      }

      if (event.data?.type === "saveAnswerDataError" && mode === "student") {
        saveStatus.textContent = event.data.error === "ANSWER_DATA_TOO_LARGE"
          ? "Nicht gespeichert: Der Zustand ist grösser als 1 MB."
          : "Speichern fehlgeschlagen. Bitte erneut versuchen.";
        button.disabled = false;
        return;
      }

      if (event.data === "SAVED" && mode === "student") {
        saveStatus.textContent = "Gespeichert";
        button.disabled = false;
        return;
      }

      if (!event.data || event.data.type !== "sendAnswerData") return;
      if (mode !== "student" && mode !== "studentReview") return;

      if (event.data.data) {
        try {
          const saved = JSON.parse(event.data.data);
          count = Number(saved.count) || 0;
        } catch (error) {
          console.warn("Gespeicherte Daten sind ungültig", error);
        }
      }

      render();
      button.disabled = mode !== "student";
    });

    button.addEventListener("click", () => {
      if (mode !== "student") return;
      count = Math.min(count + 1, 5);
      render();
      button.disabled = true;
      saveStatus.textContent = "Speichert …";

      sendToLearningView({
        type: "saveAnswerData",
        data: JSON.stringify({ version: 1, count })
      });

      sendToLearningView({
        result: { score: { scaled: count / 5 } }
      });
    });

    render();
    if (mode === "class") {
      saveStatus.textContent = "Lade Klassenübersicht …";
      sendToLearningView({ type: "getClassAnswerData" });
    } else if (mode === "preview") {
      saveStatus.textContent = "Vorschau ohne gespeicherte Daten";
    } else {
      sendToLearningView({ type: "getAnswerData" });
    }
  </script>
</body>
</html>
Praxisempfehlung: Beim häufigen Ändern von Eingaben sollte das Speichern kurz verzögert beziehungsweise gebündelt werden (Debounce). Zusätzlich empfiehlt sich in JSON-Daten eine version, damit spätere Versionen des Inhalts alte Daten migrieren können.

08 · Maschinenlesbare Zusammenfassung

Kurzreferenz für Entwickler:innen und KI-Assistenten

Alle Nachrichten werden aus dem eingebetteten HTML-Dokument mit window.parent.postMessage(payload, "https://learningview.org") gesendet.

Funktion Payload an LearningView Antwort / Wirkung
Antwortdaten speichern {type: "saveAnswerData", data: string} "SAVED" nach erfolgreichem Speichern
Antwortdaten laden {type: "getAnswerData"} {type: "sendAnswerData", data: string}
Klassendaten laden {type: "getClassAnswerData"} {type: "sendClassAnswerData", data: [{studentId, name, surname, data}]}; bei Ablehnung zusätzlich error
Nur Kursliste laden {type: "getClassStudents"} {type: "sendClassStudents", data: [{studentId, name, surname}]}; bei Ablehnung zusätzlich error: "CLASS_STUDENTS_UNAVAILABLE"
Ausgewählte Person laden {type: "getSelectedStudent"} {type: "sendSelectedStudent", data: {studentId, name, surname}}; bei Ablehnung data: null und error
Kontext abfragen {type: "getEmbedContext"} {type: "sendEmbedContext", mode: "studentReview"} (Beispiel)
Startmodus lesen window.learningView?.mode (keine Nachricht) class, student, studentReview oder preview
Dialoggrösse wählen {type: "setDialogSize", size: "max"} beziehungsweise size: "auto" Verfügbare Dialogfläche ausfüllen beziehungsweise Standardgrösse wiederherstellen; keine Bestätigung
Ergebnis melden {result: {score: {scaled: number}}} Der Prozentwert wird beim Schliessen übernommen und auf der Aufgabenkachel angezeigt
Datei herunterladen "DOWNLOADFILE|" + url LearningView startet den Download
  • Bevorzugt eine einzelne, eigenständig lauffähige .html-Datei mit integrierten Inhalten erzeugen; Mehrdatei-Projekte vollständig als ZIP-Datei bereitstellen.
  • In eingebetteten Inhalten kein localStorage verwenden oder voraussetzen; der Zugriff ist aufgrund der Sandbox nicht verfügbar und kann fehlschlagen.
  • Listener vor saveAnswerData registrieren und auf "SAVED" warten.
  • Listener vor getAnswerData registrieren.
  • Bei eingehenden Nachrichten event.source === window.parent und event.origin === "https://learningview.org" prüfen.
  • Für saveAnswerData.data ausschliesslich eine Zeichenkette mit höchstens 1.048.576 UTF-8-Bytes senden. LearningView überträgt höchstens alle 2 Sekunden den neuesten akzeptierten Zustand. saveAnswerDataError behandeln.
  • Objekte mit JSON.stringify() speichern und mit JSON.parse() laden.
  • Antwortdaten werden für jede Schülerin und jeden Schüler eines Kurses einzeln gespeichert und geladen.
  • Bei Partner- und Gruppenarbeiten werden die Daten als Kopie für alle Gruppenmitglieder gespeichert.
  • Individuelle Daten: Schülerkonten dürfen laden und speichern, Lehrpersonen eine Schülerlösung nur lesen.
  • Für die Klassenübersicht zuerst window.learningView?.mode === "class" prüfen, dann Listener registrieren und getClassAnswerData senden. Im Klassenmodus keine individuellen Daten oder Scores speichern.
  • Beim Sonderfall Anhang die Aufgabe auf die Abgabeform keine stellen.
  • Beim Sonderfall Anhang pro Aufgabe nur einen interaktiven Inhalt mit saveAnswerData einsetzen.
  • Für Score und Download wird keine Bestätigung gesendet.