Embed WTP as an iframe#
For educational use, WebTigerPython can be embedded in any website as an iframe and controlled from the parent page with the postMessage API.
Embedding the iframe#
The iframe can be embedded in any website with the iframe html tag. A whole working example can be found in the Section Demo.
<iframe src="https://webtigerpython.ethz.ch" id="iframe" allow="usb;bluetooth;clipboard-write">
</iframe>
To enable all features of WTP, the iframe needs to be allowed to use usb (micro:bit, Calliope), bluetooth (Root Robot, xBot) and clipboard-write.
The initial state of the iframe (code, layout, language, …) can be set directly in the src URL, see URL arguments.
Messages can be sent to the iframe with JavaScript. First, we need a reference to the iframe; in this example we look it up by the id specified above.
// Create a reference to the iframe
const iframe = document.querySelector("#iframe");
// Create a JavaScript object that can be sent to the iframe. See Messages
const message = {
type: "files",
data: [{ name: "main.py", data: "print(42)" }],
};
// Use the postMessage API to send messages to the iframe
iframe.contentWindow.postMessage(message, "*");
The types of messages that can be sent to the iframe and what they do are specified under Messages/to iframe.
To receive messages from the iframe, register a listener. In this basic example, we log the data of the received messages to the console.
window.addEventListener("message", function (event) {
console.log(event.data);
});
The types of messages that are sent by the iframe are specified under Messages/from iframe.
Tip
Wait for the pythonReady message before sending the first message. Messages sent before WTP has finished loading are lost, and messages that start Python code (run_code, debug_code) need Python to be loaded.
Messages#
Every message is a JavaScript object with two attributes:
type: specifies the content of the messagedata: the data specified by the type (omitted for messages without data)
Example:
{
type: "settings", // type of the message
data: { // data defined by the type
dark_mode: true,
},
}
To iframe#
Whenever a message is sent to the iframe, it is parsed and the IDE executes the task. After the task is done, the iframe responds with a message of type ready whose data is the type of the handled message. If handling the message fails, a postMessage_error message containing the error is sent before ready.
settings#
Settings are sent to the iframe to configure WTP. All fields specified in data are updated, while fields that are not specified stay the same.
Invalid values (e.g. an unknown device) are answered with a postMessage_error message. Unknown fields are ignored and only logged as a warning in the browser console of the iframe. Files can not be set with the settings, use the files message instead.
Fields that can be specified:
Field |
Type |
Description |
Example |
|---|---|---|---|
lang |
String |
IDE language code ( |
|
layout |
Array<Array | string | object> |
The editor layout. Nested array, can only contain one canvas (see layout) |
|
hide_topbar |
Boolean |
Hides the top bar |
|
dark_mode |
Boolean |
Toggles dark mode |
|
device |
String |
Device to be programmed: |
|
error_messages |
String[] |
Error enhancement methods to use |
|
cascade_input |
Boolean |
Forward |
|
debug_mode |
Boolean |
Enables the debugger |
|
breakpoints |
Object |
Breakpoints per file |
|
tutorial_size |
Number |
Width of the tutorial panel (see layout) |
|
file_system_size |
Number |
Width of the file system sidebar in percent ( |
|
playground |
Object |
Environment of the robotics simulator, as exported by the environment editor |
|
vanilla_python |
Boolean |
Deprecated. Use WebEduPy for standard Python instead. On webtigerpython.ethz.ch, |
|
tag |
String |
Tag that is attached to logged code events |
|
Note
full_screen is still accepted as a legacy alias of hide_topbar.
Example settings:
{
type: "settings",
data: {
lang: "en",
layout: ["Editor", ["Canvas", "Console"]],
tutorial_size: 0,
device: "micro:bit",
error_messages: [],
dark_mode: true,
},
}
get_settings#
Queries the current settings. The iframe answers with a message of type settings.
Example:
// Query:
{ type: "get_settings" }
// Response:
{
type: "settings",
data: {
lang: "en",
error_messages: ["TigerPython"],
dark_mode: false,
layout: ["Editor", ["Canvas", "Console"]],
device: null,
full_screen: false,
cascade_input: false,
tutorial_size: 0,
debug_mode: false,
vanilla_python: false,
playground: "...",
},
}
layout#
The layout is part of the settings, however due to its complexity, it is described separately.
The layout is a nested array. The first level specifies the UI elements on the horizontal axis, separated by vertical separators. The array can contain strings describing the components ("Editor", "Console", "Canvas") or another (nested) array containing further components / arrays. The levels always alternate between horizontal and vertical splitting.
Instead of strings, it is also possible to use objects with the fields type and size to additionally specify the size. Look at the examples below for inspiration.
Examples:
["Editor","Console"]
{
type: "settings",
data: {
layout: ["Editor", "Console"],
},
}
https://webtigerpython.ethz.ch/#?layout=[“Editor”,“Console”]
[["Editor","Console"],"Canvas"]
{
type: "settings",
data: {
layout: [["Editor", "Console"], "Canvas"],
},
}
https://webtigerpython.ethz.ch/#?layout=[[“Editor”,“Console”],“Canvas”]
[{"type":["Editor","Console"],"size":20},"Canvas"] with hidden top bar
{
type: "settings",
data: {
layout: [{ type: ["Editor", "Console"], size: 20 }, "Canvas"],
hide_topbar: true,
},
}
files#
Replaces the whole workspace with the given files. The files are sent as an array of objects which all contain name and data. The name can contain a path with folders (separated by /). If a main.py exists, it is opened in the editor, otherwise the first file.
See Multiple files for how files work in WTP.
Example files:
{
type: "files",
data: [
{ name: "main.py", data: "import helper\nhelper.greet()" },
{ name: "helper.py", data: "def greet():\n print('Hello')" },
{ name: "lib/tools.py", data: "x = 1" },
],
}
Tip
To change the files of an iframe that is already loaded, use this message. Changing only the part after # of the iframe’s src (e.g. a new code= or fs= argument) does not reload WTP.
get_files#
Queries the current files of the workspace, including unsaved changes in the editor. The iframe answers with a message of type files, whose data is an object with the file paths as keys. name contains only the file name without folders. Text files are returned as strings, binary files as Blob.
Example:
// Query:
{ type: "get_files" }
// Response:
{
type: "files",
data: {
"main.py": { name: "main.py", data: "import helper\nhelper.greet()" },
"helper.py": { name: "helper.py", data: "def greet():\n print('Hello')" },
"lib/tools.py": { name: "tools.py", data: "x = 1" },
},
}
set_run_file#
Pins the file that is executed by the run button and by run_code. Without a pinned run file, the file currently open in the editor is run.
{
type: "set_run_file",
data: { filename: "game/main.py" },
}
run_code#
Starts the Python execution of the run file.
{ type: "run_code" }
stop_execution#
Aborts the Python execution. The aborted program reports an error message (e.g. KeyboardInterrupt), followed by full_output and program_finished.
{ type: "stop_execution" }
input#
If cascade_input is enabled, every call of input() (and inputInt(), inputFloat(), …) sends an input message (see From iframe) to the parent and waits. The parent answers with a message of type input; the data is used as the user’s answer.
{ type: "input", data: "42" }
Debugger messages#
The debugger can be controlled from the parent page as well.
Caution
Debugging inside an iframe currently does not work: debug_code (and the debug button inside the iframe) runs the program to the end without pausing and without output, so the step messages have no effect. The breakpoint messages (set_breakpoints, get_breakpoints) work.
Type |
Data |
Description |
|---|---|---|
debug_code |
- |
Enables debug mode and starts the code in the debugger |
debug_step |
- |
Executes the next line (only while debugging) |
debug_breakpoint |
- |
Runs until the next breakpoint (only while debugging) |
debug_continue |
- |
Runs the rest of the program without stopping (only while debugging) |
set_breakpoints |
|
Sets the breakpoints of a file. Currently only |
get_breakpoints |
- |
Answers with a |
Each successful debugger step is acknowledged with a debug_action message.
From iframe#
Triggered by events such as code execution, the iframe sends messages to the parent window.
Type |
Sent when |
Data |
|---|---|---|
ready |
After every handled message |
Type of the handled message |
pythonReady |
WTP and Python (Pyodide) have finished loading. From now on, messages can be sent |
- |
postMessage_error |
A message to the iframe could not be handled |
Error message |
settings |
Answer to |
Settings object |
settings_update |
A setting was changed (e.g. by the user or a |
Object containing only the changed settings, with the same keys as |
files |
Answer to |
Files object |
files_changed |
A file was saved in the editor |
Array of changed file paths |
code |
Code is run, exported or flashed to a device |
Code in the editor |
code_run |
Code execution starts |
Code that is executed |
output |
Text is printed to the console |
Printed text |
error |
An error occurred |
Error message |
full_output |
Code execution ended (also when aborted) |
Complete content of the console (no data if the console is empty) |
program_finished |
Code execution ended (after |
- |
input |
The program calls |
|
breakpoints |
Answer to |
|
debug_action |
A debugger step was executed |
|
register_device, disconnect_device, flash, run_code, stop_code, export_code |
The corresponding device action is triggered |
|
Caution
Not every line is sent as a separate output message. Sometimes multiple lines are bundled together.
Examples:
{ type: "output", data: "test\n" }
{ type: "error", data: "NameError: name 'x' is not defined\n" }
{ type: "full_output", data: "test\n" }
Demo#
Specify Config and send it to Editor above
Output From the Editor is logged below
Source Code of the Demo#
<body>
<iframe
src="https://webtigerpython.ethz.ch/"
id="iframe"
style="height: 500px; width: 100%"
allow="usb;bluetooth;clipboard-write"
>
</iframe>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/10.1.1/jsoneditor.min.js"
integrity="sha512-ZZTYi+tU/p2AEQSfuloqzXrZZiPkgPrXF1Pcj4OVAm4rsUnUPGme+gga6eAMbzKu+gPa1uciaOVIcxCGIsuLEQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<div id="app">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/10.1.1/jsoneditor.css"
integrity="sha512-iOFdnlwX6UGb55bU5DL0tjWkS/+9jxRxw2KiRzyHMZARASUSwm0nEXBcdqsYni+t3UKJSK7vrwvlL8792/UMjQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<p>Specify Config and send it to Editor above</p>
<button onclick="setCode(run)">run</button>
<button onclick="setCode(stop_exec)">stop_exec</button>
<button onclick="setCode(one_file)">one_file</button>
<button onclick="setCode(two_files)">two_files</button>
<button onclick="setCode(two_files_subfolder)">two_files_subfolder</button>
<button onclick="setCode(get_files)">get_files</button>
<button onclick="setCode(get_settings)">get_settings</button>
<div id="messageInput" style="height: 500px"></div>
<button onclick="sendMessage()">Send Config</button>
<p>Output From the Editor is logged below</p>
<div id="outputArea"></div>
</div>
<script>
const iframe = document.querySelector("#iframe");
const input = document.querySelector("#messageInput");
const options = {
mode: "code",
modes: ["code", "form", "text", "tree", "view", "preview"],
};
const editor = new JSONEditor(input, options);
// set json
const one_file = {
type: "files",
data: [{ name: "main.py", data: "print(42)" }],
};
const two_files = {
type: "files",
data: [
{
name: "main.py",
data: "import drawing\n" + "drawing.draw()",
},
{
name: "drawing.py",
data:
"from gturtle import *\n" +
"def draw():\n" +
" makeTurtle()\n" +
" forward(30)\n" +
" right(90)\n" +
" forward(30)\n",
},
],
};
const two_files_subfolder = {
type: "files",
data: [
{
name: "main.py",
data: "fp = open('test/hello.txt', 'r')\nprint(fp.read())\nfp.close()",
},
{
name: "test/hello.txt",
data: "Hello World!",
},
],
};
const run = { type: "run_code" };
const stop_exec = { type: "stop_execution" };
const get_files = { type: "get_files" };
const get_settings = { type: "get_settings" };
function setCode(text) {
editor.set(text);
}
editor.set(one_file);
function sendMessage() {
iframe.contentWindow.postMessage(JSON.parse(editor.getText()), "*");
}
const output = document.querySelector("#outputArea");
output.innerHTML = "";
window.addEventListener("message", function (event) {
console.log(event.data);
output.innerHTML = output.innerHTML + "<br>" + JSON.stringify(event.data);
});
</script>
</body>