Viewports
A Node can display other Nodes, usually by providing a layout for them. This is done by using a property called a viewport
, which accepts child Nodes to display.
A viewport
property connects to other Nodes' view
properties, which contain render functions. For browser-based viewports, the property should use the datatype web-view[]
, defined as follows:
viewport:
datatype: web-view[]
web-view[]
is an array of web-view
. This means the property can accept a list of web-view
datatype values from view
properties.
A viewport
property is typically used to display other Nodes' view
properties. In order to display other Nodes, we first need to define a view
property (see Views).
view:
datatype: web-view
viewport:
datatype: web-view[]
Once the view
and viewport
properties are set up, we need to define a render function for view
that uses the child Nodes connected to viewport
.
To get the data from Nodes connected to viewport
, we use context.get() just like with any other property.
// viewport will contain an array of render functions
const viewport = context.get('viewport');
Please see the Demos below for different implementations of parent render functions that loop through the viewport data and render the children.
A Viewport Property does not have to use the property key viewport
, other keys can be used. A Node can also have multiple viewports. However, if your node only has a single viewport, using viewport
as the key will follow standard naming convention.
gallery_viewport:
datatype: web-view[]
alt_gallery_viewport:
datatype: web-view[]
Demo
- Vanilla
- React
- Vue
- Svelte
- Solid
This is a very basic example to illustrate the concept of a viewport
. Consider using a templating engine like pug, hyperscript, lit, or a frontend framework.
// This render function will render all the children connected to viewport
function render(element) {
// Since ((viewport)) has the datatype \`web-view[]\`, an array of render functions will be returned
const viewport = context.get('viewport') || [];
// Loop through each item and call the function provided
viewport.forEach((viewRender) => {
// Create an element for the child to mount to
const viewElement = document.createElement('div');
element.appendChild(viewElement);
// Pass in the element to the child render function
viewRender?.(viewElement);
});
}
context.set('view', render);
import { createRoot } from 'react-dom/client';
import { useState, useRef, useEffect, createElement } from 'react';
function ViewComponent(props) {
const mountingEl = useRef(null);
useEffect(() => {
// Pass in the element for the render function to mount
props.view?.(mountingEl.current);
}, []); // Empty dependency array means this effect runs once on mount
return <div ref={mountingEl} />;
}
function MainComponent() {
const [viewport, setViewport] = useState(context.get('viewport') || []);
useEffect(() => {
// Setup subscription to sync value with useState
context.subscribe('viewport', (v) => {
setViewport([...v]);
});
}, []); // Empty dependency array means this effect runs once on mount
return (
<div>
{viewport &&
viewport.map(function (view, i) {
return <ViewComponent view={view} index={i} key={i} />;
})}
</div>
);
}
function render(el: HTMLDivElement) {
const root = createRoot(el);
root.render(createElement(MainComponent, null, null));
}
context.set('view', render);
Vue composition API with Single File Components (SFC).
import { createApp } from 'vue';
import MainComponent from './MainComponent.vue';
context.set('view', (el) => createApp(MainComponent).mount(el));
<script setup>
import { ref } from 'vue';
import ViewComponent from './ViewComponent.vue';
const viewport = ref(context.get('viewport') || []);
context.subscribe('viewport', (v) => {
viewport.value = [...v];
});
</script>
<template>
<div v-for="view in viewport">
<ViewComponent :view="view" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps(['view']);
const viewElement = ref(null);
onMounted(() => {
// Pass in the element for the render function to mount
props.view?.(viewElement.value);
});
</script>
<template>
<div ref="viewElement" />
</template>
Svelte
import MainComponent from './MainComponent.svelte';
function render(el: HTMLDivElement) {
new MainComponent({ target: el });
}
context.set('view', render);
<script>
import { onMount } from 'svelte';
import ViewComponent from './ViewComponent.svelte';
let viewport = context.get('viewport') || [];
onMount(() => {
const unsubscribeViewport = context.subscribe('viewport', (v) => {
viewport = [...v];
});
return () => {
unsubscribeViewport();
};
});
</script>
<div>
{#each viewport as view}
<ViewComponent view={view} />
{/each}
</div>
<script>
import { onMount } from 'svelte';
export let view;
let viewElement;
onMount(() => {
view?.(viewElement);
});
</script>
<div bind:this={viewElement}></div>
import { render } from 'solid-js/web';
import { createSignal, For, onMount, onCleanup, Show } from 'solid-js';
const [viewport, setViewport] = createSignal(context.get('viewport') || []);
function ViewComponent(props) {
let viewElement;
onMount(() => {
props.view?.(viewElement);
});
return <div ref={viewElement} />;
}
function MainComponent() {
return (
<div>
<For each={viewport()}>{(view, index) => <ViewComponent view={view} index={index()} />}</For>
</div>
);
}
context.subscribe('viewport', (v) => {
setViewport([...v]);
});
context.set('view', (el: HTMLElement) => render(() => <MainComponent />, el));