Skip to main content

Live form preview

For an optimal user experience, it is recommended to show a live preview of the form along with the builder. To do so, you could display a runner in a side-by-side view with the builder (or use a popup or overlay to display the runner). Besides showing the live preview and updating the form when a change is made in the builder, you can also enable interactions between the two. That allows to bring the element that's being edited into view in the live preview. It also makes it possible to open the properties of an element in the builder by clicking on it in the live preview. These two features can greatly improve the user experience.

Live preview using stock runners

The stock runners (runners built and maintained by the Tripetto team) already contain all the required functions to get an optimal live preview experience. The only thing you need to do is feed the builder controller to the runners. The code below shows how to set up this up.

import { Builder } from "@tripetto/builder";
import { run } from "@tripetto/runner-autoscroll";

// Create a new builder instance
Builder.open(undefined, {
// This example assumes there is a HTML element with id `Builder` for the builder
element: document.getElementById("Builder"),

// When the builder is ready, create the runner for the live preview
onReady: (builder) => run({
// This example assumes there is a HTML element with id `Runner` for the runner live preview
element: document.getElementById("Runner"),
definition: builder.definition,
view: "preview",
// Feed the builder instance to the runner to enable live preview
builder,
}),
});

Run Try on CodePen

Preview vs. test mode

The stock runners have two view modes related to the live preview functionality:

  • Preview mode: Shows all elements in the form by skipping all logic;
  • Test mode: Runs the form like a real one without being able to submit data.

The preview mode is ideal for editing since it can show each element in the form as the logic is not applied. In the test mode, the forms run like a real form. Both modes are useful when creating forms. The preview mode allows viewing each element in the form without filling in the form. The test mode is ideal for actual testing the form and all the logic in it.

If you implement a live preview in your application, we suggest adding a toggle to allow the user to switch between preview and test mode.

import { Builder } from "@tripetto/builder";
import { run } from "@tripetto/runner-autoscroll";

// Create a new builder instance
Builder.open(undefined, {
// This example assumes there is a HTML element with id `builder` for the builder
element: document.getElementById("builder"),

// When the builder is ready, create the runner for the live preview
onReady: async (builder) => {
const runner = await run({
// This example assumes there is a HTML element with id `runner` for the runner live preview
element: document.getElementById("runner"),
definition: builder.definition,
view: "preview",
builder,
});

// This example assumes there are two toggle buttons
const togglePreview = document.getElementById("toggle-preview");
const toggleTest = document.getElementById("toggle-test");

togglePreview.addEventListener("click", () => {
runner.view = "preview";

togglePreview.classList.add("selected");
toggleTest.classList.remove("selected");
});

toggleTest.addEventListener("click", () => {
runner.view = "test";

toggleTest.classList.add("selected");
togglePreview.classList.remove("selected");
});
}
});

Run Try on CodePen

Preview runner styles

The stock runners support customizing the styles of the form. Things like the font face, text size, and colors can be changed. The builder contains a special editor panel for managing the styles of a runner. To let this work, each stock runner contains a specific file that contains all the available styles for the runner, called the styles contract. The builder is able to use this styles contract and populate an editor panel with it that allows the user to configure the styles. If you combine this with a live preview, the user can change the style settings and immediately see the result in the live preview panel.

Importing the styles contract

The styles contract is a function that can be imported from the stock runner packages. It is located in the /builder/styles folder.

import stylesContract from "@tripetto/runner-autoscroll/builder/styles";

Generating the styles editor panel

To generate the styles editor panel the stylesEditor method of a builder instance is used. The styles contract is generated using the stylesContract function imported from the runner package as shown above.

import stylesContract from "@tripetto/runner-autoscroll/builder/styles";

// Open styles editor using an existing builder instance
builder.stylesEditor(stylesContract);

Run Try on CodeSandbox

Update styles in live preview

The stylesEditor has a special argument that allows specifying a callback function that is invoked when the styles change. This callback function can update the live preview. The following example adds a button to open the styles editor panel. When styles are changed, the live preview is updated accordingly.

import { Builder } from "@tripetto/builder";
import { run } from "@tripetto/runner-autoscroll";
import stylesContract from "@tripetto/runner-autoscroll/builder/styles";

// Create a new builder instance
Builder.open(undefined, {
// This example assumes there is a HTML element with id `builder` for the builder
element: document.getElementById("builder"),

// When the builder is ready, create the runner for the live preview
onReady: async (builder) => {
const runner = await run({
// This example assumes there is a HTML element with id `runner` for the runner live preview
element: document.getElementById("runner"),
definition: builder.definition,
view: "preview",
builder,
});

// This example assumes there are two toggle buttons
const togglePreview = document.getElementById("toggle-preview");
const toggleTest = document.getElementById("toggle-test");

togglePreview.addEventListener("click", () => {
runner.view = "preview";
});

toggleTest.addEventListener("click", () => {
runner.view = "test";
});

// This example assumes there is an edit button for the styles
const editButton = document.getElementById("edit-styles");

editButton.addEventListener("click", () => {
// Open the styles editor
builder.stylesEditor(
stylesContract,
() => runner,
"unlicensed"
);
});
}
});

Run Try on CodePen

Preview runner translations

The stock runners support translations for different languages. The builder has a special translation editor panel that allows users to create custom translations for the stock runners. To let this work, each stock runner contains a specific file that contains translation information, called the localization (l10n) contract. The builder is able to use this l10n contract and populate an editor panel with it that allows the user to translate the text labels. If you combine this with a live preview, the user can translate the runner and immediately see the result in the live preview panel.

info

Currently, this panel only allows translating the static text labels of the runner and not the text labels within the form definition.

Importing the l10n contract

The l10n contract is a function that can be imported from the stock runner packages. It is located in the /builder/l10n folder.

import l10nContract from "@tripetto/runner-autoscroll/builder/l10n";

Generating the translations editor panel

To generate the translations editor panel the l10nEditor method of a builder instance is used. The l10n contract is generated using the l10nContract function imported from the runner package as shown above.

import l10nContract from "@tripetto/runner-autoscroll/builder/l10n";

// Open translations editor using an existing builder instance
builder.l10nEditor(L10nContract);

Run Try on CodeSandbox

Update translations in live preview

The l10nEditor has a special argument that allows specifying a callback function that is invoked when the translations change. This callback function can update the live preview. The following example adds a button to open the l10n editor panel. When translations are changed, the live preview is updated accordingly.

import { Builder } from "@tripetto/builder";
import { run } from "@tripetto/runner-autoscroll";
import l10nContract from "@tripetto/runner-autoscroll/builder/l10n";

// Create a new builder instance
Builder.open(undefined, {
// This example assumes there is a HTML element with id `builder` for the builder
element: document.getElementById("builder"),

// When the builder is ready, create the runner for the live preview
onReady: async (builder) => {
const runner = await run({
// This example assumes there is a HTML element with id `runner` for the runner live preview
element: document.getElementById("runner"),
definition: builder.definition,
view: "preview",
builder
});

// This example assumes there are two toggle buttons
const togglePreview = document.getElementById("toggle-preview");
const toggleTest = document.getElementById("toggle-test");

togglePreview.addEventListener("click", () => {
runner.view = "preview";
});

toggleTest.addEventListener("click", () => {
runner.view = "test";
});

// This example assumes there is an edit button for the translations
const editButton = document.getElementById("edit-translations");

editButton.addEventListener("click", () => {
// Open the translations editor
builder.l10nEditor(
l10nContract,
() => runner
);
});
}
});

Run Try on CodePen

Preview multiple runners

It is possible to use multiple runners so that the user can preview the form in different runners with a simple switch. To do so, it is necessary to specify different namespaces for each runner. The builder blocks of those runners need to be loaded in those namespaces. When the user switches to another runner, the appropriate namespace is selected using the useNamespace method of the builder instance.

The following example implements a dropdown control with a list of runners. The user can switch to another runner by selecting one in the dropdown. The live preview then changes to the selected runner.

info

In this example the runners and block bundles are loaded using a static import. See the Loading blocks guide if you want to load bundles dynamically (lazy load).

import { Builder, IBuilderChangeEvent, IBuilderEditEvent, mountNamespace, unmountNamespace } from "@tripetto/builder";
import { run as runAutoscroll } from "@tripetto/runner-autoscroll";
import { run as runChat } from "@tripetto/runner-chat";
import { run as runClassic } from "@tripetto/runner-classic";

// This demo implements multiple runners than run simultaneously. Each runner
// comes with a bundle with the implemented builder blocks for that runner.
// Here we load those bundles in different namespaces. By activating the
// appropriate namespace during runtime in the builder we can switch between
// builder block bundles. This allows for differences between the blocks used
// in the runners.
mountNamespace("autoscroll");
import "@tripetto/runner-autoscroll/builder";
unmountNamespace();

mountNamespace("chat");
import "@tripetto/runner-chat/builder";
unmountNamespace();

mountNamespace("classic");
import "@tripetto/runner-classic/builder";
unmountNamespace();

// Create a new builder instance
Builder.open(undefined, {
element: document.getElementById("builder"),
onReady: async (builder) => {
let activeRunner = "autoscroll";

const updateRunner = () => {
builder.useNamespace(activeRunner);

document
.getElementById("runner-autoscroll")
?.classList.toggle("visible", activeRunner === "autoscroll");
document
.getElementById("runner-chat")
?.classList.toggle("visible", activeRunner === "chat");
document
.getElementById("runner-classic")
?.classList.toggle("visible", activeRunner === "classic");
};

const autoscrollRunner = await runAutoscroll({
element: document.getElementById("runner-autoscroll"),
view: "preview",
builder,
onReady: () => updateRunner()
});

const chatRunner = await runChat({
element: document.getElementById("runner-chat"),
view: "preview",
builder,
onReady: () => updateRunner()
});

const classicRunner = await runClassic({
element: document.getElementById("runner-classic"),
view: "preview",
builder,
onReady: () => updateRunner()
});

document
.getElementById("runner-switch")
?.addEventListener("change", (e) => {
activeRunner = (e.target as HTMLSelectElement)?.value;

updateRunner();
});

const togglePreview = document.getElementById("toggle-preview");
const toggleTest = document.getElementById("toggle-test");

togglePreview?.addEventListener("click", () => {
autoscrollRunner.view = "preview";
chatRunner.view = "preview";
classicRunner.view = "preview";

togglePreview.classList.add("selected");
toggleTest?.classList.remove("selected");
});

toggleTest?.addEventListener("click", () => {
autoscrollRunner.view = "test";
chatRunner.view = "test";
classicRunner.view = "test";

toggleTest.classList.add("selected");
togglePreview?.classList.remove("selected");
});
},
disableSaveButton: true,
disableCloseButton: true,
controls: "left"
});

Run Try on CodeSandbox

Listening for builder edit events

If you want to listen for edit events sent by the builder, use the following code. It implements the hook function to attach a listener that receives an object of type IBuilderEditEvent when the event occurs.

import { Builder } from "@tripetto/builder";

const builder = new Builder();

builder.hook("OnEdit", "framed", (event) => {
// `event.data` contains information about the element being edited.
// See `IBuilderEditEvent` docs for more information.
});