I want to create a content type that has a paragraph. Easy enough.
What happens when I want to extend this concept and have a paragraph within the first paragraph? Importantly, then display the second paragraph in a twig file.
How to create a content type that holds a paragraph within a paragraph and make it display in twig files?
First, I thought I would use kint() or dump() to review the output and generate the structure to the data directly in the twig file.
Given we already know the you can access media within a paragraph item using something like
paragraph.field_paragraph_image.entity.field_media_image.entity.fileuri
No great shakes here. Maybe using this method to access the data within the second paragraph? However, I needed to really break down the structure. But before doing so, I explored Google and search the following terms:
display paragraph within a paragraph in a twig file render twig file with paragraph within a paragraph
However, no great answers were coming up. Which, being up front I knew would be the case. As mentioned earlier, I needed to break down the structure first. Otherwise you are making guesses. A great way to burn up time and fuel frustration. Time to pivot.
Structure
Before I go into how I resolved this issue, first I want to outline the structure of my content type and relevant parent / child paragraphs.
Content Type
I have a content type named Groups.
Groups - description is : Groups are a collection of content items for the purpose of displaying in a menu
The Groups content type holds two fields:
- Body - self explanatory
- Reference - will point to a paragraph named Item
Paragraphs
There are two paragraphs that work in a child / parent relationship:
- Item
- Content items
1. Item paragraph structure
The parent in this structure is Item and has the following fields:
Label | Machine name | Field type | Field settings |
---|---|---|---|
Contents | field_contents | Entity reference revisions | limited (1) |
Icon | field_paragraph_icon | Entity reference | limited (1) |
Link | field_paragraph_link | Link | limited (1) |
Title | field_paragraph_title | Text (plain, long) | limited (1) |
Notes about a couple of the above types:
- Icon is a media reference.
- Contents is paragraph reference that points to a paragraph named Content Items.
2. Content Items paragraph structure
The Content items has the following fields:
Label | Machine name | Field type | Field settings |
---|---|---|---|
Contents | field_paragraph_contents | Entity reference | unlimited |
Notes about the contents type:
- Contents is an entity reference that points to another content type. While it doesn't matter what type, currently it points to the content type Knowledge. I won't break down the structure Knowledge as this is not the focus of this article.
Overview of the structure
Groups (content type)
--> Contents (paragraph :: field_paragraph_content_items)
--> Contents (paragraph :: field_paragraph_contents)
Setting up the code
There are a few files that I need to set-up for this to work.
{your_theme}.theme file
In the theme create a preprocess function for paragraph. The preprocess is called when Note I have copied across my entire function. However, I'll only focus on the key sections that will highlighted in bold. This function will:
- First look for the content type groups from the bundle request...
$paragraph->getParentEntity()->bundle() - Next get the parent paragraph target_id value.
$paragraph->field_paragraph_content_items->target_id; - Call a sub function that will prepare the data for the twig file.
fishfrdc_paragraph_link()
/** * Implements template_preprocess_paragraph(). * * @param array|mixed $variables * An associative array containing: * - elements: An array of elements to display in view mode. * - paragraph: The paragraph object. * - view_mode: View mode; e.g., 'full', 'teaser'... */ function bales_preprocess_paragraph(&$variables) { /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */ $paragraph = $variables['paragraph']; // Get the parent bundle. $parentBundle = $paragraph->getParentEntity()->bundle(); if ($parentBundle == "groups") { $fid = $paragraph->field_paragraph_icon->target_id; $media_entity_load = Media::load($fid); $mid = $media_entity_load->field_media_svg[0]->target_id; // Using the mid, load the image file. If you first don't get the mid, // then you will be loading the node fid rather than what is required // the media fid. $icon_path = bales_get_file_path($mid); $variables['icon_path'] = $icon_path; // Review the paragraph field contents. $target_id = $paragraph->field_paragraph_content_items->target_id; $variables = bales_paragraph_link($variables, $target_id); } }
First function written. Now to present the function that will extract the the node data.
/** * Generate a node link from a paragraph. * * @param int $target_id * Paragraph id. */ function bales_paragraph_link($variables, $target_id) { if (!is_null($target_id)) { $paragraph = Paragraph::load($target_id); $contents = $paragraph->field_paragraph_contents->getValue(); foreach ($contents as $key => $content) { $nid = $content['target_id']; $node = \Drupal\node\Entity\Node::load($nid); $variables['sub_category'][$key]['title'] = $node->getTitle(); $linkto = Url::fromRoute('entity.node.canonical', ['node' => $nid]); $variables['sub_category'][$key]['url'] = $linkto; } } return $variables; }
In the function above, the process is fairly self explanatory. But I'll add content where required.
target_id
The target_id load the respective paragraph. Earlier we set the field setting to unlimited, so we need to loop through and as many node.nid's as have been set.
node nid
Load the node for the relevant nid using
$node = \Drupal\node\Entity\Node::load($nid);
link
Set the link
$linkto = Url::fromRoute('entity.node.canonical', ['node' => $nid]);
Define the required data to variables array
$variables['sub_category'][$key]['title'] = $node->getTitle(); $variables['sub_category'][$key]['url'] = $linkto;
Twig file
To access the sub_category data, in the paragraphs directory
-> themes
-> custom
-> {your theme}
-> templates
-> paragraphs
Add a new file paragraph--item.html.twig
{% set classes = [ 'paragraph', 'paragraph--type--' ~ paragraph.bundle|clean_class, view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class, not paragraph.isPublished() ? 'paragraph--unpublished', 'button', 'tw-relative', 'tw-flex', 'tw-flex-col', 'tw-justify-stretch', 'tw-items-center', 'tw-p-4', 'lg:tw-p-6', 'tw-pb-10', 'lg:tw-pb-16', 'tw-bg-grey-superlight', ] %} {% block paragraph %} <div{{attributes.addClass(classes)}}> {% block content %} <a href="{{ content.field_paragraph_link|render|striptags|trim }}" title="{{ content.field_paragraph_title.value }}" class="tw-w-full tw-no-underline"> <img src="{{ icon_path }}" alt="{{ content.field_paragraph_title.value }}" class="tw-w-20 tw-h-20 tw-block tw-mx-auto tw-mb-4"/> <div class="h4 tw-text-blue-darker tw-w-full tw-text-left tw-m-0">{{ content.field_paragraph_title }}</div> </a> {% if sub_category|length > 0 %} <span class="tw-text-sm tw-text-blue-darker box-link-toggle tw-text-left tw-w-full tw-mt-2">{{ 'Sub category links'|t }} <i class="fal fa-chevron-down tw-ml-2"></i> </span> <div class="tw-overflow-hidden tw-flex tw-flex-col tw-justify-start tw-items-start tw-w-full tw-transition-all tw-h-0"> {% for item in sub_category %} <a href="{{ item.url }}" class="tw-text-sm tw-text-blue-darker tw-pt-2 lg:tw-pt-0">{{ item.title }}</a> {% endfor %} </div> {% endif %} <a href="{{ content.field_paragraph_link|render|striptags|trim }}" title="{{ content.field_paragraph_title.value|render }}" class="tw-no-underline tw-absolute tw-bottom-4 tw-right-4 lg:tw-bottom-6 lg:tw-right-6 tw-mt-4"> <img src="/{{ base_path ~ directory }}/assets/images/blue-arrow.svg" alt="{{ content.field_paragraph_title.value|render }}" width="18px" class="tw-block arrow"/> </a> {% endblock %} </div> {% endblock paragraph %}