Iframe (Experimental)

Iframe is used to show a html in a iframe.

Warning: This component is experimental 🧪 and may not work as expected.

The iframe is sandboxed on an opaque origin, so the html cannot reach the app's page, its DOM, its cookies or its storage. It talks to the app only through window.toolgui, and can only write to its own state.

API

func Iframe(c *tgframe.Container, html string, conf ...*IframeConf)
func IframeValue[T any](c *tgframe.Container, html string, conf ...*IframeConf) *T
  • c is the container to add the iframe to.
  • html is the html to show in the iframe.
  • conf is an optional configuration, at most one.

IframeConf:

FieldDescriptionDefault
IDThe user specific id, from the embedded tgframe.Base.hashed id
ScriptAllow the iframe to run javascript.false
WidthCSS width of the iframe.100%
HeightCSS height of the iframe, or auto.150px

IframeValue returns the latest value the guest sent through window.toolgui.update. It reads the value, it does not draw the iframe: give it the same html and conf the Iframe call gets, so that both name the same component. It returns nil until the guest has a value, which is not the same as a value that happens to be the zero T. A guest that has not sent yet and one that sent null both read as nil — null is how a guest says it has nothing. It fails the run rather than return a silent zero when what the guest sent does not fit T.

Examples

Simple

Show h1 element in the iframe.

	tgcomp.Iframe(
		p.Main,
		"<b>Hello world gen by html</b>",
		&tgcomp.IframeConf{ID: "iframe_with_simple"})

Script

Run a script inside the iframe to update its content.

	htmlWithScript := `
	<b id="test">Hello world not changed</b>
	<script>
		const element = document.getElementById('test');
		element.innerText = 'Hello world gen by script';
	</script>`
	tgcomp.Iframe(
		p.Main,
		htmlWithScript,
		&tgcomp.IframeConf{Script: true, ID: "iframe_with_script"})

Sizing

tgcomp.Iframe(p.Main, "<h1>Hello World</h1>", &tgcomp.IframeConf{
	Script: true,
	Width:  "300px",
	Height: "400px",
})

Interactive

window.toolgui is available inside every iframe that has Script enabled. It talks to the app over postMessage, which is what lets the iframe stay on an opaque origin.

  • window.toolgui.update(value) - send an arbitrary JSON value back to the server. It is stored in the state under the iframe's own id — the app fills the id in, so an iframe cannot write to another component's state.
  • window.toolgui.upload(file) - upload a File. Returns a Promise<{ok: boolean, error?: string}>.
  • window.toolgui.onRender(fn) - run fn(props, theme) on every render, and once immediately if a render already arrived.
  • window.toolgui.autoHeight() - report the document height to the app. Pair it with Height: "auto".
  • window.toolgui.props / .theme / .id - the latest values from the app. Undefined until the first render.
type clickedValue struct {
	Clicked bool `json:"clicked"`
}

	html := `<button id="btn">Click me to update</button>
		<script>
			const btn = document.getElementById('btn');
			btn.addEventListener('click', (event) => {
				window.toolgui.update({clicked: true});
			});
		</script>`
	conf := &tgcomp.IframeConf{
		Script: true,
		Height: "60px",
		ID:     "iframe_with_interactive",
	}

	tgcomp.Iframe(p.Main, html, conf)

	tgcomp.Text(p.Main, time.Now().Format("2006-01-02 15:04:05"))

	// nil until the guest clicks for the first time.
	clicked := false
	if v := tgcomp.IframeValue[clickedValue](p.Main, html, conf); v != nil {
		clicked = v.Clicked
	}

	tgcomp.Text(p.Main, fmt.Sprintf("Status: %v", clicked))

Reacting to reruns, and auto height

onRender fires on the first render and again whenever the props or the theme change, so an iframe can update itself without being reloaded.

autoHeight reports the guest's own height as it changes; with Height: "auto" the app resizes the iframe to match.

Two caveats. It measures the body, so a guest whose body is sized off the viewport (height: 100%) will feed its own height back and should set a fixed Height instead. And because the iframe is isolated it is a separate rendering context, which the browser throttles while it is scrolled out of view: an offscreen iframe stays at the default height and takes its real one as it becomes visible.

	tgcomp.Iframe(
		p.Main,
		`<div id="out">waiting for render</div>
		<script>
			const out = document.getElementById('out');
			window.toolgui.onRender((props, theme) => {
				out.innerText = 'theme=' + theme + ' id=' + props.id;
			});
			window.toolgui.autoHeight();
		</script>`,
		&tgcomp.IframeConf{
			Script: true,
			Height: "auto",
			ID:     "iframe_with_render",
		})