2

Backstory

I have created multiple Paragraph bundles. My goal is to have absolute control over the output of these bundles (1) as well as the output of the containing field (2) in a scalable way (for ease of future bundle addition -- there may be 30+ bundles in the future).

Where I'm at

I've created a theme suggestion using HOOK_theme_suggestions_HOOK_alter to override the bundles' output. This allowed me to create custom twig templates for each bundle.

/** * Implements hook_theme() * * Register a theme for each paragraph type * */ function MYMODULE_theme($existing, $type, $theme, $path){ $theme_templates = []; $paragraph_types = MYMODULE_get_paragraph_types(); // Register custom Paragraph bundle templates foreach ($paragraph_types as $paragraph_type){ $theme_templates['paragraph__MYMODULE__' . $paragraph_type] = [ 'base hook' => 'paragraph' ]; } // Register custom Paragraph bundle template fallback $theme_templates['paragraph__MYMODULE'] = [ 'base hook' => 'paragraph' ]; // Register custom Paragraph field wrapper // $module_path = drupal_get_path('module', 'MYMODULE'); // $theme_templates['paragraph__MYMODULE__field_wrapper'] = [ // 'base hook' => 'field', // 'template' => 'paragraph--MYMODULE--field-wrapper', // 'path' => $module_path . '/templates' // ]; return $theme_templates; } /** * Implements HOOK_theme_suggestions_HOOK_alter */ function MYMODULE_theme_suggestions_field_alter(array &$suggestions, array $variables, $hook) { $field = $variables['element']['#field_name']; if( $field == 'field_content_modules' ) { $suggestions[] = 'paragraph__MYMODULE__field_wrapper'; } } /** * Implements HOOK_theme_suggestions_HOOK_alter */ function MYMODULE_theme_suggestions_paragraph_alter(&$suggestions, $variables){ $entity = $variables['elements']['#paragraph']; $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); $type = $entity->getType(); $suggestions[] = 'paragraph__MYMODULE'; $suggestions[] = 'paragraph__MYMODULE__' . $type; $suggestions[] = 'paragraph__MYMODULE__' . $type . '__' . $sanitized_view_mode; } /** * Implements hook_theme_registry_alter() */ function MYMODULE_theme_registry_alter(&$theme_registry) { $module_path = drupal_get_path('module', 'MYMODULE'); $template_objects = drupal_find_theme_templates($theme_registry, '.html.twig', $module_path); $paragraph_types = MYMODULE_get_paragraph_types(); // 1. Loop through the paragraph types // 2. Check if each paragraph exists in the `$template_objects` array // 3. If it doesn't exist, remove it from the registry so we don't get any errors // 4. If it does exist, set actual path to template foreach ($paragraph_types as $type){ $template = 'paragraph__MYMODULE__' . $type; if(!isset($template_objects[$template])){ unset($theme_registry['paragraph__MYMODULE__' . $type]); } else { $theme_registry['paragraph__MYMODULE__' . $type]['path'] = $template_objects[$template]['path']; } } } /** * Helper function to get a list of paragraph types by machine name * * @return array */ function MYMODULE_get_paragraph_types(){ $paragraph_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('paragraph'); return array_keys($paragraph_bundles); } 

So part 1 is done.

Part 2 is controlling the Paragraph field (not bundle) container HTML. This should be just a plain field template override. I've added a file:

/modules/custom/MYMODULE/templates/paragraph--MYMODULE--field-wrapper.html.twig 

...and dropped in the contents of field.html.twig. When I uncomment the hook_theme code above ("Register custom Paragraph field wrapper"), I get this error:

The website encountered an unexpected error. Please try again later. Recoverable fatal error: Argument 1 passed to Drupal\Core\Render\Element::children() must be of the type array, null given, called in /Users/phil/Sites/glades/www/modules/contrib/paragraphs/paragraphs.module on line 238 and defined in Drupal\Core\Render\Element::children() (line 71 of core/lib/Drupal/Core/Render/Element.php).

Any thoughts as to what I'm doing wrong with the field template override?

Most of this was inspired by a comment by jeremypeter here: https://www.drupal.org/node/2499827#comment-12159763

1
  • For part 2, you can't register a field template with the name paragraph--, because the naming convention for templates is, that the template name starts with the base hook. Use field--paragraph--MYMODULE--... instead. Commented Jul 24, 2017 at 7:37

1 Answer 1

5

Per 4k4's comment to the original post, it was a naming convention issue as seen here: https://www.drupal.org/docs/8/theming/twig/twig-template-naming-conventions

In the hook_theme and hook_theme_suggestions_HOOK_alter function above, I changed:

paragraph__contentmodules__field_wrapper 

to:

field__paragraph__contentmodules__field_wrapper 

Working code:

<?php /** * Implements hook_theme() * * Register a theme for each paragraph type * */ function MYMODULE_theme($existing, $type, $theme, $path){ $theme_templates = []; $paragraph_types = MYMODULE_get_paragraph_types(); // Register custom Paragraph bundle templates foreach ($paragraph_types as $paragraph_type){ $theme_templates['paragraph__MYMODULE__' . $paragraph_type] = [ 'base hook' => 'paragraph' ]; } // Register custom Paragraph bundle template fallback $theme_templates['paragraph__MYMODULE'] = [ 'base hook' => 'paragraph' ]; // Register custom Paragraph field wrapper $theme_templates['field__paragraph__MYMODULE__field_wrapper'] = [ 'base hook' => 'field', ]; return $theme_templates; } /** * Implements HOOK_theme_suggestions_HOOK_alter */ function MYMODULE_theme_suggestions_field_alter(array &$suggestions, array $variables, $hook) { $field = $variables['element']['#field_name']; if( $field == 'field_content_modules' ) { $suggestions[] = 'field__paragraph__MYMODULE__field_wrapper'; } } /** * Implements HOOK_theme_suggestions_HOOK_alter */ function MYMODULE_theme_suggestions_paragraph_alter(&$suggestions, $variables){ $entity = $variables['elements']['#paragraph']; $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); $type = $entity->getType(); $suggestions[] = 'paragraph__MYMODULE'; $suggestions[] = 'paragraph__MYMODULE__' . $type; $suggestions[] = 'paragraph__MYMODULE__' . $type . '__' . $sanitized_view_mode; } /** * Implements hook_theme_registry_alter() */ function MYMODULE_theme_registry_alter(&$theme_registry) { $module_path = drupal_get_path('module', 'MYMODULE'); $template_objects = drupal_find_theme_templates($theme_registry, '.html.twig', $module_path); $paragraph_types = MYMODULE_get_paragraph_types(); // 1. Loop through the paragraph types // 2. Check if each paragraph exists in the `$template_objects` array // 3. If it doesn't exist, remove it from the registry so we don't get any errors // 4. If it does exist, set actual path to template foreach ($paragraph_types as $type){ $template = 'paragraph__MYMODULE__' . $type; if(!isset($template_objects[$template])){ unset($theme_registry['paragraph__MYMODULE__' . $type]); } else { $theme_registry['paragraph__MYMODULE__' . $type]['path'] = $template_objects[$template]['path']; } } } /** * Helper function to get a list of paragraph types by machine name * * @return array */ function MYMODULE_get_paragraph_types(){ $paragraph_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('paragraph'); return array_keys($paragraph_bundles); } 

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.