Skip to main content

Collections

Collections are used to define lists of items within blocks. For example, options in a dropdown block. This guide shows how to create a collection.

🔖 Declaring an item

The first step is to define the item type for the collection. This is done using the Collection.Item base class. Let's start with a simple example where we define an option for a dropdown list:

import { tripetto, definition, name, Collection, NodeBlock } from "@tripetto/builder";

class DropdownOption extends Collection.Item<Dropdown> {
@definition
@name
name = "";

@definition
alias?: string;
}

@tripetto({
type: "node",
identifier: "dropdown",
label: "Dropdown",
icon: "data:image/svg+xml;base64,PHN2ZyAvPg=="
})
class Dropdown extends NodeBlock {}

Here we define a new class DropdownOption that is derived from Collection.Item. This base class needs to know the class type that holds the collection. So in the example, we also defined an empty block Dropdown that we will use to add the collection to. The DropdownOption class has two fields for the name and alias of the dropdown option. Those fields are decorated with the @definition decorator, so they become part of the form definition. The name field is also decorated with the @name decorator. This specifies the field that contains the name of the item that is shown in the list in the builder.

tip

There are more decorators you can use for collection items.

📇 Declaring the collection

The next step is declaring the actual collection in the dropdown block. To do that, we use a special helper function Collection.of. It does two things:

  • Creates a collection provider for managing the collection;
  • Decorates the collection field with the @definition decorator so it becomes part of the form definition (so you don't have to apply the @definition decorator to a collection field).

Let's extend the example and declare the collection:

import { tripetto, definition, name, Collection, NodeBlock } from "@tripetto/builder";

class DropdownOption extends Collection.Item<Dropdown> {
@definition
@name
name = "";

@definition
alias?: string;
}

@tripetto({
type: "node",
identifier: "dropdown",
label: "Dropdown",
icon: "data:image/svg+xml;base64,PHN2ZyAvPg=="
})
class Dropdown extends NodeBlock {
options = Collection.of<DropdownOption, Dropdown>(DropdownOption, this);
}

This creates a new field options that is a collection of DropdownOption instances. The collection is now automatically part of the form definition. All the items in the collection are stored in and retrieved from the form definition.

📒 Adding a collection feature

Now we want to allow the builder user to manage the collection and to create new dropdown options. To do that, we need to define a block editor and add a collection feature for managing the collection. Let's create an editor panel for the block by adding a method, decorating it with the @editor decorator, and then implementing the collection feature:

import { tripetto, definition, name, editor, Collection, NodeBlock } from "@tripetto/builder";

class DropdownOption extends Collection.Item<Dropdown> {
@definition
@name
name = "";

@definition
alias?: string;
}

@tripetto({
type: "node",
identifier: "dropdown",
label: "Dropdown",
icon: "data:image/svg+xml;base64,PHN2ZyAvPg=="
})
class Dropdown extends NodeBlock {
options = Collection.of<DropdownOption, Dropdown>(DropdownOption, this);

@editor
onEdit(): void {
this.editor.collection({
collection: this.options,
title: "Dropdown options",
placeholder: "Unnamed option",
sorting: "manual",
editable: true,
emptyMessage: "Click the + button to add an option..."
});
}
}

The builder user can now add new options to the list of dropdown options. An option that doesn't have a name yet is listed as Unnamed option in the list (as specified by the placeholder). By setting sorting to manual we indicate that the builder user can sort this list by dragging and dropping items to the desired position. editable is set to true to allow the builder user to edit the name of an item by double-tapping on it.

📝 Adding an item editor

The last step is to define an editor panel for the dropdown options itself. Since we defined two fields in the DropdownOption class, we want to be able to manage these fields. The procedure for adding an editor panel to a collection item is identical to that for the blocks. Just add a method and decorate it with the @editor decorator, as shown in the following example:

import { tripetto, definition, name, editor, isString, Collection, NodeBlock } from "@tripetto/builder";

class DropdownOption extends Collection.Item<Dropdown> {
@definition
@name
name = "";

@definition
alias?: string;

@editor
onEdit(): void {
this.editor.option({
name: "Name",
form: {
title: "Option name",
controls: [
new Forms.Text(
"singleline",
Forms.Text.bind(this, "name", "")
)
]
},
locked: true,
});

this.editor.option({
name: "Alias",
form: {
title: "Alias",
controls: [
new Forms.Text(
"singleline",
Forms.Text.bind(this, "alias", undefined)
),
],
},
activated: isString(this.value),
});
}
}

@tripetto({
type: "node",
identifier: "dropdown",
label: "Dropdown",
icon: "data:image/svg+xml;base64,PHN2ZyAvPg=="
})
class Dropdown extends NodeBlock {
options = Collection.of<DropdownOption, Dropdown>(DropdownOption, this);

@editor
onEdit(): void {
this.editor.collection({
collection: this.options,
title: "Dropdown options",
placeholder: "Unnamed option",
sorting: "manual",
editable: true,
emptyMessage: "Click the + button to add an option..."
});
}
}

And now we have a full working example for a dropdown block. The builder user can add new dropdown options and then open the editor panel for each option to manage the name and alias.

tip

If you want, you can add type guards. See the Block properties guide for more information.

🗃️ Using item slots

Sometimes you want to add a (dynamic) slot for each item. This is possible using the available collection item decorators. In most situations, you want to create a slot when an item is created. The slot probably has the name of the item as its label. So you want to update the slot label when the item is renamed. When an item is deleted from the collection, the slot of the item should be removed as well. Therefore we are going to use the @created, @renamed and @deleted decorators as shown in the following example:

import { tripetto, definition, name, editor, created, renamed, deleted, isString, Collection, NodeBlock } from "@tripetto/builder";

class Checkbox extends Collection.Item<Checkboxes> {
@definition
@name
name = "";

@created
@renamed
addSlot(): void {
this.ref.slots.dynamic({
type: Slots.Boolean,
reference: this.id,
label: "Checkbox",
name: this.name
});
}

@deleted
deleteSlot(): void {
this.ref.slots.delete(this.id, "dynamic");
}
}

@tripetto({
type: "node",
identifier: "checkboxes",
label: "Checkboxes",
icon: "data:image/svg+xml;base64,PHN2ZyAvPg=="
})
class Checkboxes extends NodeBlock {
checkboxes = Collection.of<Checkbox, Checkboxes>(Checkbox, this);

@editor
onEdit(): void {
this.editor.collection({
collection: this.checkboxes,
title: "Checkboxes",
placeholder: "Unnamed checkbox",
sorting: "manual",
editable: true,
emptyMessage: "Click the + button to add a checkbox..."
});
}
}

In this example, the addSlot method is invoked whenever an item is created or renamed. The id of the item is used as slot reference. When a slot already exists, it updates/overwrites the supplied properties. So we can safely call addSlot when the item is renamed. When the item is deleted from a collection, the method deleteSlot is called, which removes the slot from the slot collection. The ref field of the item is used to retrieve a reference to the parent NodeBlock instance.

🖼️ Adding an item icon

If you want, you can set an icon for an item. The icon is shown in the collection list in the block editor. To define an icon, simply add a field for it and decorate it with the @icon decorator:

import { tripetto, definition, name, icon, Collection } from "@tripetto/builder";

class DropdownOption extends Collection.Item<Dropdown> {
@icon
readonly icon = "data:image/svg+xml;base64,PHN2ZyAvPg==";

@definition
@name
name = "";
}