Drupal 8 Twig Field Formatter - Part One

Service
Development
Type
Technical

Twig is a powerful templating engine employed by Drupal starting with Drupal 8. It's power can be seen throughout the codebase. Many modules and components more often than not employ Twig for their visual formatting. Even Drupal's Views can use Twig to rewrite the output of its fields. Twig can be used to format fields elsewhere too, but this is where is starts to get a bit tricky.

To use Twig to format anything in a theme we just need to:

  • Create the file for it (and Drupal will automatically start using it).

To use Twig to format a field in a view we just need to:

  • Check the rewrite option.
  • Write a twig template.

To use Twig to format a field outside of a view or theme we need to:

  • Create a module.
  • Add the template to a file.
  • Expose the theme hook from our module to tell Drupal about our twig template.
  • Create a field formatter class.
  • Find the exact machine name of the field type it applies to and reference it from the annotation
  • And return a render array from within the field formatter that tells the field to use the twig template

What if we need to apply a similar method to another field? Well then we start the process all over again.

Sometimes it's desirable to just slightly change the formatting of a field without going through that entire process. For example if a subtitle is needed, or the tag used for the display label needs to be changed from h3 to h4 in one view mode but not another, or an email icon is needed before an email field, then these would be great places to customize the output freely. For this purpose we created a Twig Field Formatter.

The process to create this started the same as creating any other field formatter, but with a twist. We created the field formatter class and added the annotations needed for the plugin discovery, but we needed this formatter to apply to every field type, because every field can be formatted with twig. To achieve this the field_types portion of the field formatter annotation was left blank, and instead was dynamically populated in the Field Formatter Info Alter hook.

function omni_twig_field_formatter_info_alter(array &$info) {
  $manager = \Drupal::service('plugin.manager.field.field_type');
  foreach ($manager->getDefinitions() as $field_type => $field_definition) {
    $info['omni_twig']['field_types'][] = $field_type;
  }
}

Once the formatter was registered to every field type, we needed a way to specify a twig template through the UI, so a settings form was added to the field formatter with a text area that asked for a twig template.

public function settingsSummary() {
  return [$this->t('Render the field using a Twig Template.')];
}
 
public static function defaultSettings() {
  return [
    'template' => '{# Enter your twig code here. #}',
  ] + parent::defaultSettings();
}
 
public function settingsForm(array $form, FormStateInterface $form_state) {
  return [
    'template' => [
      '#title' => $this->t('Twig Template'),
      '#type' => 'textarea',
      '#default_value' => $this->getSetting('template'),
      '#description' => 'The template used for formatting this field.',
    ],
  ];
}

Now that the template can be given through the UI, the template needed to be applied to the field when it's rendered. This can be done through either the 'viewElements' or 'view' member of the field formatter class. The 'viewElements' member renders each value (or delta) that the field contains, and the 'view' member is responsible for setting up the initial render array and calling the 'viewElements' member. Since a twig template can loop and render each delta itself, there's no need for the 'viewElements' function, but since it's required in our field formatter due to extending the FormatterBase class, we defined it as an empty function, and decided to define the view member itself.

The 'view' member of a field formatter is responsible for building the render array that drupal will use to display the field. In this case we used a twig template that isn't contained within a file, so this render array at minimum would render a twig template from the value supplied by the settings form of the field formatter:

public function view(FieldItemListInterface $items, $langcode = NULL) {
  $elements = [
    '#type' => 'inline_template',
    '#template' => $this->getSetting('template'),
    '#context' => [
      'items' => $items
    ],
  ];
 
  return $elements;
}

This seemed to be the magic bullet we needed to render fields using twig templates from the UI, but a problem arising from this was that now the field must be rendered manually using Twig when all we needed was to modify the field slightly. While this is fine for text fields, it's not trivial when working with more complex fields like addresses. We needed a way to invoke another field formatter from within this one, so we could reuse as much of the existing formatters' functionalities as possible. This required the use of Twig extensions to provide helper functions or filters to automate this, which is described in Part Two (Coming soon...).

Let's Get Started

Contact us to start your next project!

We are always happy to talk about your projects or ideas and we love talking with our clients! Reach out and find out what we can do for you!

CALL OR VISIT

Monarch Digital is a team of expert developers and designers that can handle any of your website needs. Contact us so we can discuss what we can do for your organization!

Email Us