Skip to main content

Creating a headless block

Headless blocks can perform actions that don't need a visual counterpart in the runner. A good example of a headless block is the calculator stock block. It can perform complex calculations in a form, and it does that silently in the background. Of course, the outcome of a headless block is useable by other blocks and can often be recalled somewhere else in a form. This tutorial explains how to set up such a headless block. Just like a visual block, it needs two parts to work properly:

  • A builder part that makes the block available to the visual builder, so the block becomes usable in a form;
  • A runner part that does the actual operation of the block in the runner that runs the form.

This tutorial covers both parts, beginning with the builder part. And to keep it practical, we're going to build a simple headless block that retrieves the current day of the week and exposes it to a variable (slot), so it can be recalled somewhere else in the form.

🏗️ Builder part

The builder part of a headless block allows the visual builder to consume the block. It allows the builder user (form editor) to select the new block, attach it to a node, and configure its properties and settings. For this tutorial, we'll start from scratch and prepare a new project for our weekday block (you may skip this and jump straight to the block implementation when you don't need help preparing your project).

Can't wait to see the result of the builder part tutorial? Click the buttons below to run or try a live code example.

Run Try on CodeSandbox


▶️ Prepare your project

1️⃣ Create a project

First, create a new folder for the project (for example, weekday-block). Then open a command-line terminal for that new folder and run the following command to initiate the project:

npm init
tip

You can just hit the enter key for all questions asked when running the init command. The default configuration is good for now.

2️⃣ Add dependencies

Now we can add the required packages for our block. In this example, we'll use webpack as bundler. Besides the webpack dependencies we need the TypeScript compiler and Tripetto's Builder package. We also add image-webpack-loader and url-loader to enable webpack to process images, ts-loader to process TypeScript with webpack and concurrently to allow to run Tripetto's builder and webpack simultaneous during development. Let's add these dependencies by running the following command in your project folder:

npm install @tripetto/builder typescript webpack webpack-cli image-webpack-loader url-loader ts-loader concurrently

3️⃣ Add configuration files

Next, add the following files to the root of your project folder. It's the configuration for the TypeScript compiler and webpack bundler.

{
"compileOnSave": false,
"compilerOptions": {
"target": "ES5",
"moduleResolution": "Node",
"newLine": "LF",
"strict": true,
"experimentalDecorators": true
},
"include": ["./src/**/*.ts"]
}
info

Make sure the experimentalDecorators feature is enabled. We need support for decorators.

4️⃣ Project configuration

Now open the package.json file that was generated in the first step and add the highlighted lines to it:

{
"name": "weekday-block",
"main": "./dist/builder.bundle.js",
"scripts": {
"test": "webpack --mode development && concurrently -n \"tripetto,webpack\" -c \"blue.bold,green\" -k -s \"first\" \"tripetto ./example.json --verbose\" \"webpack --mode development --watch\""
},
"dependencies": {
"@tripetto/builder": "^5.0.30",
"image-webpack-loader": "^8.1.0",
"ts-loader": "^9.2.8",
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
},
"tripetto": {
"blocks": [
"."
]
}
}

Here the main property specifies the location of the JS bundle that webpack will generate. The tripetto section contains the configuration for the builder. In this case, it tries to load the block that's in the same folder (.). Tripetto will lookup the package.json file and read the main property to find the right JS file for the block.

info

More information about configuring the Tripetto CLI using package.json can be found in the configuration section of the builder documentation.

5️⃣ Prepare source file

The last step before we will test the setup is to add an empty source file that we'll use later on to declare the block in. Create a folder with the name src and add an empty file to it with the name index.ts. This will be the entry point for the block. The structure of the project should now look something like this:

project/
├─ src/
│ └─ index.ts
├─ package.json
├─ tsconfig.json
└─ webpack.config.js

6️⃣ Test it!

Now you should be able to run this setup. Run the following command to start webpack together with the Tripetto builder:

npm test
info

Webpack will now monitor code changes and update the bundle on each change. Hit F5 in the browser to reload the builder with the updated bundle. Or follow the bonus step below to enable live-reloading of the builder.

7️⃣ Live-reloading (bonus)

If you want, you can enable live-reloading of the builder on each code change. To enable that, we need the webpack-livereload-plugin package:

npm install webpack-livereload-plugin

Then, open webpack.config.js and add the highlighted lines:

webpack.config.js
const path = require("path");
const webpackLiveReload = require("webpack-livereload-plugin");

module.exports = {
target: ["web", "es5"],
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "builder.bundle.js",
},
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
},
{
test: /\.svg$/,
use: ["url-loader", "image-webpack-loader"],
},
],
},
resolve: {
extensions: [".ts", ".js"],
},
externals: {
// Make sure to exclude the builder from the bundle!
"@tripetto/builder": "Tripetto",
},
plugins: [
new webpackLiveReload({
appendScriptTag: true,
}),
]
};

Now the builder should automatically reload the bundle when the code is changed.


▶️ Block implementation

When everything is prepared properly, we can begin building the actual headless block.

1️⃣ Declare the block

Let's start with the basic code of a headless block and then go through that code. Update your empty index.ts file to the code shown below and add the file icon.svg to the same folder (it's the icon that Tripetto will use for this block).

import { tripetto, NodeBlock } from "@tripetto/builder";
import icon from "./icon.svg";

@tripetto({
type: "node",
kind: "headless",
identifier: "weekday",
label: "Retrieve weekday",
icon
})
class WeekdayBlock extends NodeBlock {
// Block implementation here
}

This code is all you need to declare a new class with the name WeekdayBlock. The class is derived from the base class NodeBlock that is imported from the Tripetto builder package. The base class has all the core functionality of a block, so you can easily extend it with the functionality you need. This block has no further implementation yet, so the class is still empty. We'll come to that in a moment.

First, let's talk about the @tripetto decorator. This so-called class decorator is used to register the block to the Tripetto builder. Tripetto uses dependency injection to retrieve blocks that are registered using the decorator. The identifier, label, and icon of the block are all supplied to that decorator and are used by the visual builder to allow the user to select the block and assign it to a node. For headless blocks, you also need to supply the kind property to the decorator and set it to headless.

Now we can add the functionality to the block that we need for our weekday block. We need to do three things for that on the builder part:

  1. Define the data the block will collect;
  2. Automatically update the node name;
  3. Instruct the builder how the user can manage the block properties and settings.

Let's do that in the following steps!

2️⃣ Define block slots

The next step is to specify the type of data this block will collect. In the Tripetto world, data is collected using slots. Each slot can contain a single data item. In this case, we need a single slot that will hold the current day of the week, which is a string. So, we are going to use the String slot type for that. To define the slot, we need to add a method to our block class and decorate it with the @slots decorator. This decorator instructs Tripetto to use that method when it needs the slots for the block. Update the highlighted lines in index.ts:

index.ts
import { tripetto, slots, NodeBlock, Slots } from "@tripetto/builder";
import icon from "./icon.svg";

@tripetto({
type: "node",
kind: "headless",
identifier: "weekday",
label: "Retrieve weekday",
icon
})
class WeekdayBlock extends NodeBlock {
@slots
onSlots(): void {
this.slots.static({
type: Slots.String,
reference: "weekday",
label: "Current day of the week"
});
}
}

As you can see, we've added a new method onSlots (you can choose any name you want for this method), and decorated it with @slots. The method implements the slots field of the WeekdayBlock class to create a new static slot for the day of the week. And that's all you need to do on the builder part. You will see later on in this tutorial how we will use this slot to supply a value to it in the runner part of the block.

tip

Read the Slots guide to learn more about slots and its possibilities.

3️⃣ Update node name

For most blocks the name of the node is flexible and configurable by the builder user. But for our weekday block, the name can be set to a fixed value Current weekday. So, how do we do that? We can use the @assigned decorator to decorate a method that is invoked when the block is attached to a block. That's a perfect moment to update the name of the node to our fixed value. Update the highlighted lines in index.ts:

index.ts
import { tripetto, slots, assigned, NodeBlock, Slots } from "@tripetto/builder";
import icon from "./icon.svg";

@tripetto({
type: "node",
kind: "headless",
identifier: "weekday",
label: "Retrieve weekday",
icon
})
class WeekdayBlock extends NodeBlock {
@slots
onSlots(): void {
this.slots.static({
type: Slots.String,
reference: "weekday",
label: "Current day of the week"
});
}

@assigned
onAssign(): void {
this.node.name = "Current weekday";
}
}

4️⃣ Define block editor

The final step is to instruct the builder how to manage the properties and settings for the block. That's simple in this case, as there are no properties or settings that needs to be managed for the weekday block. Instead, we can show a message when the editor panel is opened for this block. Let's implement that.

In this case, we need to add a method to the WeekdayBlock class and then decorate it with the @editor decorator. This method will be invoked by the builder when the editor panel for the block is requested. We can use the editor field of the WeekdayBlock class to define UI controls to manage the block. Update the highlighted lines in index.ts:

index.ts
import { tripetto, slots, assigned, editor, NodeBlock, Slots, Forms } from "@tripetto/builder";
import icon from "./icon.svg";

@tripetto({
type: "node",
kind: "headless",
identifier: "weekday",
label: "Retrieve weekday",
icon
})
class WeekdayBlock extends NodeBlock {
@slots
onSlots(): void {
this.slots.static({
type: Slots.String,
reference: "weekday",
label: "Current day of the week"
});
}

@assigned
onAssign(): void {
this.node.name = "Current weekday";
}

@editor
onEdit(): void {
this.editor.form({
controls: [
new Forms.Notification(
"This block will retrieve the current day of the week.",
"success"
)
]
});
}
}

Run Try on CodeSandbox

This code uses the form method to define a custom form with a Notification control in it.

tip

Read the Block editor guide to learn more about building block editor panels.

💯 Builder part done!

And there, we have completed the builder part of the block! Next, we're going to define the runner part of it.

🏃 Runner part

The runner part of a headless block performs the actual operation. Since headless blocks don't require rendering to a runner UI, they can be reused across runners. So, you don't need a specific implementation for each runner. Let's build the runner part of the weekday example block. The runner part should retrieve the current day of the week and store that value in the weekday slot.

info

This tutorial assumes you have a runner project up and running. If not, follow the instructions to prepare a project in the visual block tutorial.

Can't wait to see the end result of the runner part tutorial? Click the buttons below to run or try a live code example.

Run Try on CodeSandbox

▶️ Block implementation

1️⃣ Declare the block

Let's start with the basic code of the runner part of a headless block and then go through that code. Create a file weekday.ts in your runner project with the following code:

weekday.ts
import { tripetto, HeadlessBlock } from "@tripetto/runner";

@tripetto({
type: "headless",
identifier: "weekday",
})
class WeekdayBlock extends HeadlessBlock {
do(): void {
// Perform block operation here
}
}

This code declares a new class with the name WeekdayBlock. The class is derived from the base class HeadlessBlock that is imported from the Tripetto Runner library. The @tripetto decorator is used to register the block to the Tripetto runner.

caution

If you want to use a headless block in one of the stock runners, make sure to make the block available in the blocks namespace of the stock runner. You can do that using the namespace property of the @tripetto decorator (when you only target one runner) or by using mountNamespace.

2️⃣ Define block operation

The next step is to implement the operation for the block. To do so, we need to implement the do method of the HeadlessBlock. Update the highlighted lines in the WeekdayBlock class:

weekday.ts
import { tripetto, HeadlessBlock } from "@tripetto/runner";

@tripetto({
type: "headless",
identifier: "weekday",
})
class WeekdayBlock extends HeadlessBlock {
do(): void {
this.valueOf("weekday")?.set(
new Date().toLocaleString("default", { weekday: "long" })
);
}
}

This code retrieves the weekday slot (that was defined in the builder part of the block) using the valueOf method and sets the value to the current day of the week.

Run Try on CodeSandbox

💯 Runner part done!

That's all we need to do for the runner part of our example weekday block.

⏭️ Up next

Dive deeper into the following topics to master the art of building blocks for Tripetto: