Skip to main content

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:

  1. Item
  2. 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:

  1. First look for the content type groups from the bundle request... 
    $paragraph->getParentEntity()->bundle()
  2. Next get the parent paragraph target_id value.
    $paragraph->field_paragraph_content_items->target_id;
  3. 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 %}

Related articles