Embed Packages and Simulations

Overview

The Embed code brick can show a folder you upload: an index.html plus its assets, running inside the lesson. By default that package is a closed box. If it also ships a small manifest and talks to the lesson with a few messages, it can:

  • tell the lesson that the learner has finished (so the lesson can wait for it);
  • report its height, so the frame fits its content;
  • hand over its progress, so the learner finds it where they left it;
  • report a score and each decision, which turns it into a simulation that counts in the grade, in SCORM and in xAPI;
  • expose variables that conditional content can read.

This page is for whoever builds the package. You can follow it without reading any Creator code. For the author's side (the brick's panel, scoring and warnings) see Content Bricks.

Everything is opt-in. A package without a manifest still works: it is shown, counts as completed when seen and never holds the learner back.

How the package runs

The package runs in a sandboxed frame with scripts allowed but no access to the app: no cookies, no storage of the app and no shared origin. The lesson only listens to messages that come from the frame of that very brick, so nothing else on the page can pretend to be your package. A message that is not valid is ignored silently.

An embed that points to an external URL instead of an uploaded folder has no package: its messages are always ignored and it never declares anything.

The manifest: slxd-embed.json

Put a file named slxd-embed.json at the root of the folder, next to index.html:

{
  "protocol": "slxd-embed",
  "version": 1,
  "capabilities": ["complete", "resize", "state", "result", "interaction"],
  "variables": { "trust": "number", "reused": "boolean" }
}
FieldMeaning
protocolAlways "slxd-embed".
versionAlways 1; it is the only version understood.
capabilitiesWhat the package will send. Each behavior only works if its capability is declared. Anything unknown is ignored. Up to 32 entries.
variablesOptional. The variables of state.vars that the package sends, with their type (number or boolean). Up to 64. Conditional content can only name these.

Without the file, with invalid JSON, another version or more than 16 KB, the package simply declares nothing.

The manifest is read when you upload the folder to the brick. Upload slxd-embed.json together with the rest of the folder. If you change the manifest, upload the folder again: a package uploaded earlier keeps what it declared at that time.

CapabilityWhat it enables
completeThe package can say "I'm done". Needed for the brick's blocker to wait for the package.
resizeThe package can set the height of its frame.
stateThe lesson saves the package's progress and hands it back when the learner returns.
resultThe package can report a score.
interactionThe package can report each decision (SCORM interactions and xAPI).

Reserved variable names

grade, score, maxScore, ending and kind always read the package's last result (see below), so they cannot also be variables of your state. A package whose variables declares any of them is not accepted when you upload it: the editor shows the error and the folder is not used until you rename them. Inside state.vars, a key with a reserved name is simply ignored.

Variable names start with a letter or _, continue with letters, digits or _, and are at most 64 characters. An entry with another name or type is ignored without rejecting the rest.

Messages

Every message is a plain object sent with postMessage, with the same envelope:

const send = (type, data = {}) =>
  window.parent.postMessage({ channel: "slxd-embed", version: 1, type, ...data }, "*");

The target origin is "*" because the frame cannot know the lesson's origin; nothing sensitive travels in these messages.

From the package to the lesson

typeFieldsNeedsWhat Creator does
ready——Say it as soon as the package has loaded. If the package declares state, the lesson answers with restore.
complete—completeMarks the brick as completed and releases its blocker. In a simulation it also closes it (see below).
resizeheight: number ≥ 0, in CSS pixelsresizeThe frame takes that height, kept between 100 and 4000 px and rounded up. Until the first message, the brick's configured height applies.
statestate: a JSON object; done: booleanstateSaves state exactly as sent. done: true means it is an ending: it closes the simulation and completes the brick. Send it after every decision and when an ending is reached.
resultscore, maxScore, grade, ending, kindresultOnly updates the score. It can arrive partial and the last one wins. It does not complete the brick nor close the simulation.
interactionattempt, n, response, description, result, weightinginteractionRegisters one decision. Send it after every decision.

A message that does not fit these shapes is ignored whole, without any warning. Check each field:

  • state.state: a plain JSON object (not an array or a single value) of at most 1024 characters once serialized. state.done is required. Creator treats the state as opaque, except for one optional member, vars, an object of numbers and booleans that conditional content reads.
  • result.score: any finite number (it may be negative). maxScore: a finite number ≥ 0. grade: a number from 0 to 100 that your package computes; it is the only value that counts for the grade. ending: an identifier. kind: good, mixed or bad.
  • interaction.attempt and n (the decision's number inside the attempt): integers from 1 to 9999. response: the identifier of the chosen option. description: text of up to 250 characters, required (an empty string is fine). result: correct, incorrect or neutral. weighting: a number greater than 0 (send 1 if the decision was worth 0).
  • Identifiers (ending, response): lowercase letters, digits and _, starting with a letter, up to 64 characters.

From the lesson to the package

typeFieldsWhen
restorestate: the last saved state, or nullIn answer to ready, if the package declares state. It is null if nothing was saved.
reset—When the exam opens a new attempt. Start over: the saved state and score are already cleared.

A minimal package

const send = (type, data = {}) =>
  window.parent.postMessage({ channel: "slxd-embed", version: 1, type, ...data }, "*");

let scene = "s1";
let vars = { trust: 0, reused: false };

window.addEventListener("message", (event) => {
  const msg = event.data;
  if (!msg || msg.channel !== "slxd-embed") return;
  if (msg.type === "restore" && msg.state) {
    scene = msg.state.scene;
    vars = msg.state.vars;
    render();
  }
  if (msg.type === "reset") {
    scene = "s1";
    vars = { trust: 0, reused: false };
    render();
  }
});

function choose(option) {
  vars.trust += option.points;
  scene = option.next;
  send("interaction", {
    attempt: 1,
    n: 1,
    response: option.id,
    description: option.label,
    result: option.points > 0 ? "correct" : "incorrect",
    weighting: Math.abs(option.points) || 1,
  });
  send("state", { state: { scene, vars }, done: false });
  render();
}

send("ready");
send("resize", { height: document.body.scrollHeight });

Finishing: result before complete

A simulation is closed by complete, or by a state with done: true. A result never closes it. The score that counts is the one from the last result received before the closing; once closed, later results are ignored. If the package closes without having sent any result, the simulation is worth 0.

So the right order at an ending is:

send("result", { score: 8, maxScore: 10, grade: 80, ending: "agreement", kind: "good" });
send("state", { state: { scene: "end", vars }, done: true }); // or: send("complete")

A state without done after an ending is a new game: the previous score is forgotten.

A package that will be marked Scored must therefore declare complete or state, or its score could never be closed.

What Creator does with each thing

Blocking the advance. The Embed code brick does not hold the learner back by default. If the author turns the blocker on (in the brick or in the theme's embedded-code settings), it only works for a package that declares complete: the learner cannot continue until the message arrives, with no timeout and no unlock button. state and result do not release it. A package without complete, and every external URL, counts as completed when seen. The editor warns when the blocker is on but the package cannot honor it.

Height. The frame follows the last resize (between 100 and 4000 px). Until the first one, it uses the height configured in the brick.

State when coming back. The simulation's state is stored under its simulation identifier, per content. When the package loads and says ready, it receives restore with the last state. In a SCORM export the state survives between sessions (it travels in the LMS's suspend data); in the preview, share links and review it lasts for the session. If the state does not fit in the LMS's room, Creator trims what it must, in a fixed order: first the simulations that neither count nor are read by conditional content, then the state of the others, keeping their variables and scores as long as possible.

Score. For the simulation to count, the author gives the brick a simulation identifier (unique in the content) and ticks Scored with a weight; the package must declare result, plus complete or state. Inside an exam, the simulation counts next to the questions: each simulation is worth its weight times grade / 100, and the exam does not close until every counted simulation is closed. In a content without exams, the content's score is the weighted average of its scored simulations once all have finished, against the content's pass threshold (shown on the export page). Scored simulations next to graded exams do not count; the editor warns.

Notes in SCORM. In a SCORM export, every interaction becomes an entry of cmi.interactions of type choice, with the id <simulation identifier>-<attempt>-<n>, the chosen option, the result (correct, wrong/incorrect or neutral) and the weighting; SCORM 2004 also stores the description. In the preview there is no LMS, so nothing is recorded.

xAPI. Where xAPI is active, each interaction is reported as answered on a decision activity (with the description, the chosen option and success for correct and incorrect; neutral carries none), and when the simulation closes a single scored statement carries the score that counts, with the ending and the kind as extensions. A partial result emits nothing. Starting a new game allows the statement again. The author can turn xAPI off for the brick.

Limits at a glance

WhatLimit
slxd-embed.json16 KB, version 1 only
Capabilitiesup to 32 entries
Variablesup to 64, number or boolean
stateJSON object, ≤ 1024 characters
Frame height100–4000 px
Decision description≤ 250 characters
attempt, nintegers 1–9999
Identifiers[a-z][a-z0-9_]*, ≤ 64 characters
Saved progress (SCORM)shared 4096-character budget for the whole content

Next steps

Embed Packages and Simulations