Advanced Usage – Nesting/Repeating Components

Yes. A component field can contain another component field. Since it’s using the same technique as repeater fields, it can be repeated as well.

Scenario

You are building a custom WordPress theme (again). This time, the client wants testimonial blocks on the site. This testimonial block is really really special. First, each testimonial block needs the followings, a) May or may not have a head-shot of the person, b) Name of the person, c) Quote of the person’s testimonial, d) The position of the person, e) The organization the person is in, f) All testimonial block will have the same fields, but different layout/design, g) Have 0 or more custom buttons, h) Lastly, this testimonial block are everywhere on the site, such as, sidebar, global footer (on every page), or a page template built with acf flexible content.

For the head-shot, the clients wants to be able to control the shape, circle or square. As well as the color of the border.

For the buttons, it can either link internally to site’s page, or link outside to their company’s page, or the person’s linked-in page.

Like so,
example-2_1

Normal Solution

It the client’s site only had 2 or 3 of these testimonial blocks, you’d be fine just manually create 2 or 3 sets of testimonial block in different field group. But if the client came back and want to add more on other template, or add some features on the testimonial block… Well, good luck, my friend.


Using the ACF Component Field Plugin

After we got the request from client, we might be thinking, yeah, sure, we can create one component, “Testimonial Component”. Well, yes, that could work. But if we step back a bit and think it through, we already have a “Button Component” in place that’s used in other part of the site. Also, the same head-shot design might be used on other part of the site, such as the team page, or author page. So, what we are actually looking here is three different components, “Button Component”, “Head-shot Component”, and finally the “Testimonial Component”. The “Testimonial Component” will then reusing the “Button Component” and “Head-shot Component” in it.

  1. We already have the button component in place, we can just reuse it. (see how handy components are?)
  2. Let’s create the “Head-shot Component” first. It’s easier to build your nested components going up, rather than going down. The “Head-shot Component” will look something like this:
    example-2_2
  3. Up next is to build our testimonial component. by reusing the “Button Component” and “Head-shot Component” we just built. The “Testimonial Component” looks something like this, notice here we are nesting components with in another component:
    example-2_3
  4. According the client’s requirement, the buttons are optional and it could have more than 1. How do we do that? Well, remember I mentioned on the homepage, component fields are just like repeater fields. Anything we can do with repeater field, we can do with component fields. So, if we want to have 0 or more than one buttons, we can set the “min” value to 0, and the “max” value to nothing.
    example-2_4
  5. Now we are ready to use this “Testimonial Component” in our normal field group.
    example-2_5
  6. If we go to where we assigned the group to, we should see our nice testimonial blocks.
    example-2_6
  7. Oh shoot… forgot to add the company name and person’s position. But guess what? Because we make testimonial a component, all we need to do, is to go back the the “Testimonial Component” group, and add our missing fields in. It’ll automatically apply to both the sidebar_testimonial and the footer_testimonial. While we are at it, let’s add some tabs, so the testimonial component doesn’t take that much of a space. (Yes, you can add tab in component, you do whatever you want)
    example-2_7
  8. Finally, a client-friendly editing panel is now finished with just a few simple steps. And it’s easy to add/remove/edit after the project is completed.
    example-2_8

Outputting in the template files

The logic is still the same, a component field is treated just like a repeater field. If you make an element into a component, you most likely are going to use it frequently on your site, so creating a output function will save your time and make you a better programmer.

function my_button_component($field_name) {
    if (is_string($field_name)) {
        $field_name = array($field_name);
    }

    $output = '';

    while (call_user_func_array('have_rows', $field_name)) { 
        the_row();

        $button_text = get_sub_field('button_text');  
        $button_class = get_sub_field('button_style');  
        $button_link = get_sub_field('button_type') == 'internal'  
            ? get_sub_field('page_link')  
            : get_sub_field('custom_link');  
        $button_external = get_sub_field('new_tab')? ' target="_blank"' : '';

        $output .= sprintf( '<a href="%s" class="%s" %s>%s</a>',
            $button_link, $button_class, $button_external, $button_text );
    }

    return $output;
}

function my_headshot_component($field_name) {
    if (is_string($field_name)) {
        $field_name = array($field_name);
    }

    $output = '';

    while (call_user_func_array('have_rows', $field_name)) { 
        $headshot_style    = get_sub_field('headshot_style');
        $border_color      = get_sub_field('border_color');
        $headshot_image_id = get_sub_field('headshot_image');
        $headshot_image    = wp_get_attachment_image($headshot_image_id, 'your-image-size');

        $output .= sprintf( '<div class="headshot %s bordered-%s">%s</div>',
            $headshot_style, $border_color, $heashot_image );
    }

    return $output;
}

function my_testimonial_component($field_name) {
    if (is_string($field_name)) {
        $field_name = array($field_name);
    }

    $output = '';

    while (call_user_func_array('have_rows', $field_name)) { 
        $headshot = my_headshot_component('head-shot');
        $buttons .= my_button_component('buttons');

        $person_name  = get_sub_field('person_name');
        $quote        = get_sub_field('quote');
        $company_name = get_sub_field('company_name');
        $position     = get_sub_field('position');

        $output .= sprintf( 
            '<div class="testimonial-holder %1$s">
                <div class="headshot-column">%2$s</div>
                <div class="quote-column">
                    <h3>%3$s</h3>
                    <div class="quote">%4$s</div>
                    <h5>%5$s <span>%6$s</span></h5>
                    %7$s
                </div>
            </div>',
            $additional_classes, 
            $headshot, 
            $person_name, 
            $quote, 
            $company_name, 
            $position, 
            $buttons
        );
    }

    return $output;
}
<aside id="site-sidebar">
    <?php echo my_testimonial_component('sidebar_testimonial'); ?>
</aside>

<main id="site-content">
    <?php echo my_testimonial_component('footer_testimonial'); ?>
</main>

Getting Components from Non-Current Post Object

Here are some example if you want to get testimonial block that’s NOT from the current post.

// from blog page
$blog_page_id = get_option('page_for_posts');
echo my_testimonial_component(array('testimonials', $blog_page_id));

// from term page
$uncategorized_term = get_term(1, 'category');
echo my_testimonial_component(array('testimonials', $uncategorized_term));

// options page
echo my_testimonial_component(array('default_testimonials', 'options'));