Wie kann ich die Unterstützung für die Wiederaufnahmefunktion in einem H5P-Inhaltstyp implementieren?

Die Implementierung der H5P-Funktion zum Wiederaufnehmen (oder „Speichern des Inhaltsstatus“) ist für Entwickler*innen recht einfach. Wann immer der H5P-Kern den aktuellen Zustand einer Inhaltstypinstanz speichern möchte, versucht er, eine Funktion namens getCurrentState aufzurufen und erwartet, dass die Funktion alles zurückgibt, was du möglicherweise speichern möchtest.

Wenn Benutzer*innen einen H5P-Inhalt öffnen und den Vorgang fortsetzen sollen, übergibt der H5P-Kern den vorherigen Zustand als eine Eigenschaft des dritten Arguments des Konstruktors. Das Argument wird in existierenden Inhaltstypen typischerweise extras oder contentData genannt. Du bekommst das, was du gespeichert hast. Und kannst diese Informationen verwenden, um den Zustand wiederherzustellen, wie du ihn in deinem Inhaltstyp benötigst.

Um also sicherzustellen, dass deine Zustandsdaten gespeichert werden können, musst du nur dafür sorgen, dass es eine exponierte Funktion mit entsprechendem Namen gibt, die genau das tut. Zum Beispiel:

/**
 * Answer H5P core call to return the current state.
 *
 * @returns {object} Current state.
 */
getCurrentState() {
  return {
    answersChecked: [1, 2, 3, 5, 8, 13, 21],
    somethingNested: {
      foo: 'FOO',
      bar: 'BAR'
    },
    somethingElse: true
  };
}

In der Praxis kannst du zwar jeden Datentyp zurückgeben, aber du sollten immer ein Objekt verwenden, auch wenn du gerade nur eine einzelne Zahl oder vielleicht ein Array benötigst. Möglicherweise musst du jetzt nicht viel speichern. Aber was, wenn sich später der Bedarf ergibt, wenn sich dein Inhaltstyp weiterentwickelt? Dann musst du zusätzlichen Code hinzufügen, um zu unterscheiden, ob der vorherige Zustand, den du vom H5P-Kern bekommst, noch ein boolean / number / "array" / string oder bereits ein object ist.

/**
 * @class
 * @param {object} params Parameters passed by the editor.
 * @param {number} contentId Content's id.
 * @param {object} [extras={}] Saved state, metadata, etc.
 */
constructor(params, contentId, extras = {}) {
  /*
   * `extras.previousState` should either be nullish if there is
   * no previous state that needs to be re-created or it
   * will be the return value of your `getCurrentState` function.
   */
}

Es ist wichtig anzumerken, dass einige H5P-Integrationen (wie H5P.com) zusätzliche Funktionen anbieten können, die zusätzliches Augenmerk erfordern. H5P.com kann beispielsweise einen Button einblenden, mit dem man die Übung zurücksetzen kann. Dieser Button sollte nur angezeigt werden, wenn es auch tatsächlich etwas zum Zurücksetzen gibt, und das wird anhand des vorherigen Zustands überprüft. In Konsequenz bedeutet das, dass die getCurrentState-Funktion gar nichts zurückgeben sollte, wenn die Nutzer*innen keine relevant Aktion durchgeführt haben, die zurückgesetzt werden müsste. Präziser: Übergibt man den Rückgabewert der Funktion H5P.isEmpty, dann sollte true ausgegeben werden. Es fühlt sich allerdings sauberer an, wenn man getCurrentState einfach gar nichts zurückgeben lässt, wenn es nicht erforderlich ist.

Wenn du beispielsweise eine Funktion shouldUserBeAbleToReset implementierst, die false zurückgibt, falls die Nutzer*innen noch keine Aktion durchgeführt haben, nach der es sinnvoll wäre, einen Reset-Button einzublenden – und true andernfalls – dann könnte deine getCurrentState-Funktion ähnlich wie hier aussehen:

/**
 * Answer H5P core call to return the current state.
 *
 * @returns {object} Current state.
 */
getCurrentState() {
  if (!shouldUserBeAbleToReset()) {
    return;
  }

  return {
    answersChecked: [1, 2, 3, 5, 8, 13, 21],
    somethingNested: {
      foo: 'FOO',
      bar: 'BAR'
    },
    somethingElse: true
  };
}

Es gibt noch einen Fall, in dem ein besonderes Verhalten erwartet wird, das aber in der offiziellen Dokumentation (zum Zeitpunkt, da diese Zeilen verfasst werden) nicht beschrieben wird. Falls dein Inhalstyp die resetTask-Funktion unterstützt, sollte deren Aufruf deinne Inhalt in seinen ursprünglichen Zustand zurückversetzen. Die Nutzer*innen haben also noch nichts geantwortet, und getCurrentState könnte undefined ausgeben. Allerdings wird dann noch der vorherige Zustand vor dem Aufruf von resetTask wiederhergestellt, weil ein Rückgabewert von undefined ihn unangetastet lässt. Also sollte die getCurrentState-Funktion nach dem Aufruf von resetTask den Wert {} zurückgeben, solange die Nutzer*innen nichts weiter getan haben. Das sorgt dafür, dass der existierende Zustand in der Datenbank mit einem leeren Objekt überschrieben wird.

Wie kann ich Unterstützung für Inhaltstypen  mit Unterinhalten hinzufügen?

Wenn du einen Inhaltstyp mit H5P-Unterinhalten erstellst, kann dein Inhaltstyp alle Zustände der Unterinhalte sammeln, indem er deren getCurrentState-Funktion aufruft (sofern sie eine haben) und die zurückgegebenen Werte (oder undefined) als Teil des eigenen Zustands weitergibt. Der H5P-Kern kommuniziert nicht direkt mit den Unterinhalten. Diese Lücke musst du füllen.

Umgekehrt musst du, wenn du bei der Instanziierung deines Inhaltstyps den vorherigen Zustand erhältst, die vorherigen Zustände der Unterinhalte an die richtigen Empfänger senden. Normalerweise wird die Funktion newRunnable des H5P-Kerns verwendet, um einen Unterinhalt zu instanziieren. Diese Funktion erwartet eine previousState-Eigenschaft im 5. Argument. Dabei ist deren Wert die Zustandsinformation, die du von getCurrentState erhalten hast, etwa so:

const subcontentInstance = H5P.newRunnable(
  library,
  contentId,
  H5P.jQuery(element),
  skipResize,
  { previousState: { ... } }
);