Managing Imported Fieldset Handles with Antlers Runtime

April 22, 2022 —John Koster

A common design pattern when building Statamic sites is to use fieldsets to create reusable fields across different collections, forms, or pages. Additionally, it is also not uncommon to see specific partials created to display these fieldsets. This is nice since it allows for the same template code and groups of fields to be managed in a central location, and helps to keep things well organized and clean.

As an example, we could have the following fieldset to manage Frequently Asked Questions:

1title: FAQ
3 -
4 handle: faq
5 field:
6 collapse: false
7 sets:
8 questions:
9 display: Questions
10 fields:
11 -
12 handle: question
13 field:
14 input_type: text
15 antlers: false
16 display: Question
17 type: text
18 icon: text
19 listable: hidden
20 instructions_position: above
21 read_only: false
22 -
23 handle: answer
24 field:
25 antlers: false
26 display: Answer
27 type: textarea
28 icon: textarea
29 listable: hidden
30 instructions_position: above
31 read_only: false
32 display: Questions
33 type: replicator
34 icon: replicator
35 listable: hidden
36 instructions_position: above
37 read_only: false

We could create a partial named components/faq.antlers.html with the following template that would render our fieldset:

2 {{ faq }}
3 <dt>{{ question }}</dt>
4 <dd>{{ answer }}</dd>
5 {{ /faq }}

and simply include the following in other templates when we wanted to reuse our Frequently Asked Questions template:

1{{ partial:components/faq }}

#Working with Fieldset Prefixes

The previous example works great when field names do not change. However, because fieldsets can be prefixed, it becomes difficult to create truly reusable partials when targeting fieldsets (or when you may want to render the same group of fields multiple times on the same page, each with a different prefix). Let's assume that the previous fieldset was imported twice within the same blueprint. Once without a prefix, and a second time with the product_ prefix:

1title: Home
3 main:
4 display: Main
5 fields:
6 -
7 handle: title
8 field:
9 type: text
10 required: true
11 character_limit: 0
12 display: Title
13 validate:
14 - required
15 -
16 import: faq
17 -
18 import: faq
19 prefix: product_

Our page would now contain a faq and product_faq variable, each with their own list of Frequently Asked Questions. With this setup, we could either duplicate our partial or simply alias the single variable to render both:

1{{ partial:components/faq }}
3{{ partial:components/faq :faq="product_faq" }}

This technique works well when the imported fieldsets are small (such as in this example), but can quickly become tedious if the imported fieldset contains many different fields. This often leads to attempts to writing invalid Antlers code like the following:

1{{# Inside `components/faq.antlers.html`. #}}
3 {{ {prefix}faq }}
4 <dt>{{ question }}</dt>
5 <dd>{{ answer }}</dd>
6 {{ /{prefix}faq }}
9{{# In another file. #}}
10{{ partial:components/faq prefix="product_" }}

Even if the above had worked, it would make handling the non-prefixed case much more difficult. To help with this situation, Antlers Runtime supports the concept of forcing the Runtime to check if a variable exists with any given prefix, before falling back to the normal cascade rules.

#Fieldset Prefixes and Variable Names

To have the Runtime check if variables exist with a prefix, you simply need to add the handle_prefix parameter to either the partial tag or the scope tag:

1{{ partial:components/faq }}
3{{ partial:components/faq handle_prefix="product_" }}

When the Runtime encounters the handle_prefix parameter, every variable will be checked to see if it exists with a prefix (but only within the partial or scope tag on which it was applied).

When the above code is being evaluated, it would be as if we had written this Antlers instead:

2 {{ faq }}
3 <dt>{{ question }}</dt>
4 <dd>{{ answer }}</dd>
5 {{ /faq }}
9 {{ product_faq }}
10 <dt>{{ question }}</dt>
11 <dd>{{ answer }}</dd>
12 {{ /product_faq }}

The first partial call would render like normal, because no prefix was specified. However, in the second partial call we are specifying the product_ prefix. Because a product_faq variable does exist, that will be used instead of the faq variable (if no prefixed match was found, Antlers will revert back to normal variable Cascade logic).

The scope tag version might look something like this:

1{{ scope handle_prefix="product_" }}
3 {{ faq }}
4 <dt>{{ question }}</dt>
5 <dd>{{ answer }}</dd>
6 {{ /faq }}
8{{ /scope }}

Some absolutely amazing

The following amazing people help support this site and my open source projects ♥️
If you're interesting in supporting my work and want to show up on this list, check out my GitHub Sponsors Profile.