Building a WordPress Accordion Block, no Javascript Required
Tags: wordpress, php, tutorial, webdev
Published: 2021-02-11 15:53:06

In this post we’ll go over how to build a Bootstrap accordion as a WordPress block using Advanced Custom Fields. This is a useful approach for rapidly building WordPress blocks. It’s also a great [stopgap for developers who are still learning JavaScript](https://dev.to/jacklowrie/building-gutenberg-blocks-with-advanced-custom-fields-acf-as-a-stopgap-2jk9), since it doesn't require you to write any JavaScript.

Just looking for the code? Check out the GitHub Repo:
{% github jacklowrie/wp-accordion-block-tutorial no-readme %}
## Quick Links
- [Prerequisites](#prerequisites)
- [Getting Started](#getting-started)
- [Registering your first block](#registering-first-block)
- [Setting up a Field Group](#setting-up-field-group)
- [Rendering content in the template](#rendering-content-in-template)
- [Conclusion](#conclusion)<a name="prerequisites"></a>

## Prerequisites (assumptions)
This tutorial assumes a basic understanding of how WordPress themes are structured. In particular, you should be familiar with what functions.php is in the context of a theme, the basics of how to use [action hooks](https://developer.wordpress.org/plugins/hooks/actions/), and how templates work in WordPress Themes. If you’ve worked with child themes before or built a WordPress theme, you’ll likely be familiar with functions used in this tutorial. If you haven’t, we’ll cover how to set up a simple child theme.

### ACF functions used in this tutorial

- **[`have_rows()`](https://www.advancedcustomfields.com/resources/have_rows/)** and **[`the_row()`](https://www.advancedcustomfields.com/resources/have_rows/)**
This pair of functions is very similar to [`have_posts()`](https://developer.wordpress.org/reference/functions/have_posts/) and [`the_post()`](https://developer.wordpress.org/reference/functions/the_post/) in the [WordPress Loop](https://developer.wordpress.org/themes/basics/the-loop/), so if you’ve seen one pair, you’ll understand the other.

- **[`get_field()`](https://www.advancedcustomfields.com/resources/get_field/)** and **[`get_sub_field()`](https://www.advancedcustomfields.com/resources/get_sub_field/)**
The latter acts much the same as the former, only in the context of a repeater field.

- **[`acf_register_block_type()`](https://www.advancedcustomfields.com/resources/acf_register_block_type/)**
We’ll cover how to use it. :)

### Background knowledge of Bootstrap
Additionally, having a basic understanding of Bootstrap (what it is, why it’s useful as a "shortcut") will help. While we won’t use the Bootstrap grid, we will be implementing a version of the Bootstrap [`collapse`](https://getbootstrap.com/docs/4.5/components/collapse/) component as well as the Bootstrap [`card`](https://getbootstrap.com/docs/4.5/components/card/) component. You don’t need to have used these before to follow along, especially if you’ve used other Bootstrap components before.<a name="getting-started"></a>


## Getting Started
To follow along, you’ll need a simple WordPress website that you can hack on. For simplicity, we’ll work inside a child theme. We’ll extend [TwentyTwenty](https://wordpress.org/themes/twentytwenty/), a default theme which comes with every WordPress installation, though any theme will do. If you’re building just for yourself, this is adequate. If you’re building blocks you want to distribute, it’s better to keep them in a plugin. That way, switching themes won’t remove content from posts and pages.

### Create your child theme
Begin by creating a new directory in the themes directory. The best practice would be to name it `twentytwenty-child`, but you can pick whatever name you’d like. Inside this directory, create a `style.css` file and add the following header:
```css
/*
 Theme Name:   Twenty Twenty Child
 Author:       [your name]
 Template:     twentytwenty
*/
```
The most important part is to specify the template, which tells WordPress which theme is this one’s parent. You can add additional information, such as the theme version or a description, to your header as documented in the [developer handbook](https://developer.wordpress.org/themes/basics/main-stylesheet-style-css/#explanations). For reference, here’s the header from mine:
```css
/*
 Theme Name:   Accordion Tutorial
 Description:  Twenty Twenty child theme for demoing a Bootstrap accordion block.
 Author:  Jack Lowrie
 Author URI: https://github.com/jacklowrie
 Template:  twentytwenty
 Version: 1.0.0
 License: GNU General Public License v2 or later
 License URI:  http://www.gnu.org/licenses/gpl-2.0.html
*/
```
Then, create a `functions.php` file, and add the following to it:
```php
<?php

function enqueue_parent_styles() {
   wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' );
}
add_action( 'wp_enqueue_scripts', 'enqueue_parent_styles' );
```
This enqueues TwentyTwenty’s styles before ours, that way the changes we make will _extend_ TwentyTwenty rather than _replace_ them. And that’s it for now! You have a child theme ready to hack on.

### Enqueue Bootstrap
Of course, we can’t build anything with Bootstrap without enqueueing it! To keep it simple, we’ll load it in from Bootstrap’s CDN. add the following to your theme’s `functions.php` file:
```php
function enqueue_bootstrap() {
    wp_enqueue_style('bootstrap-styles', 'https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css');
    wp_enqueue_style('bootstrap-styles', 'https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css');
    wp_enqueue_script( 'bootstrap-scripts','https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js', array( 'jquery' ), '', true );
}
add_action( 'wp_enqueue_scripts', 'enqueue_bootstrap' );
```
This code ensures that Bootstrap (and it’s dependencies) will load on your site.

### Install Advanced Custom Fields Pro (ACF)
You’ll also need to have the [Advanced Custom Fields plugin](https://www.advancedcustomfields.com/) installed. While the core plugin is free and available in the WordPress Plugin Directory, it doesn’t give you all the capabilities and field types available with a Pro license. This tutorial makes use of the repeater field and the block-level field group, both of which require a pro license. ACF Pro is potent, and is worth the investment. Even if you don’t need to build custom blocks, you may find yourself reaching for it on more WordPress projects than not.<a name="registering-first-block"></a>

## Registering your first block

Once you have a WordPress site (with Bootstrap styles and scripts enqueued and ACF Pro activated), you’re ready to start building blocks. The bird’s eye view for this is to create a template file for your block, then register it using ACF’s block registration function to ensure that the block is only registered if ACF is present on the site.

It’s helpful to keep all block templates in a folder to stay organized. Where you keep it is up to you, especially if you’re doing this in a theme and not a plugin — it may make sense to keep it as a top-level folder in the theme or to place it inside of a `templates` or `template-parts` folder if your theme has one. We’ll keep ours top-level for now. Create a new `blocks` folder in the root of your theme, then create a new template file called `accordion.php` inside. We don’t need to add anything to this file yet. It only needs to exist.

Then, we need to register that file (in `functions.php`, just like we enqueued Bootstrap).
```php
function register_acf_block_types() {
     acf_register_block_type(array(
         'name'              => 'accordion',
         'title'             => __('Accordion'),
         'category'          => 'custom',
         'render_template'   => 'blocks/accordion.php',
     ));
 }
 add_action( 'acf/init', 'register_acf_block_types' );
```
## You’re done! Sort of.
This is actually all there is to registering a block. If you add a block to a post or page now, the accordion will show up as an option. If you use it, any markup inside that template file will show up in the corresponding spot on the post or page. In fact, let’s do that! We can add the [accordion example](https://getbootstrap.com/docs/4.5/components/collapse/#accordion-example) from the bootstrap collapse docs to a page as proof that we have a movable, renderable block (this is also an easy way to verify that we enqueued Bootstrap correctly).

![hardcoded Bootstrap accordion block](https://dev-to-uploads.s3.amazonaws.com/i/qrsbklvi7k8nx432l5de.gif)

Now, this isn’t a terribly useful block (it’s entirely uneditable, and we’re dependent on ACF to register the block even though we haven’t made any custom fields). But let’s not downplay the accomplishment! 

FWIW, ACF’s register block function at its core is a php wrapper for WordPress’s Javascript function `RegisterBlockType()`, which is how we’d register the block if we didn’t use ACF. We are avoiding that since this post is about building blocks _without_ Javascript.<a name="setting-up-field-group"></a>

## Setting up a field group
Now that we have a block template, we need to set up fields to render in that block. We can set field groups to display only on specific blocks! From the Custom Fields menu in the WordPress Dashboard, add a new field group and call it `Block - Accordion`. It’s nice to keep all the field groups for blocks together in the list, though this is up to personal preference. Then add a location rule: “show this field group if `block` `is equal to` `Accordion`.

![Location Rules Screenshot](https://dev-to-uploads.s3.amazonaws.com/i/h50o6qqt65i4kksknmfs.png)

An accordion is a perfect candidate for a repeater field in ACF. Each new row in the repeater will correspond to a new ‘fold’ in the accordion, and all the folds will have the same subfields and layout.

So, add a repeater field and call it ‘folds’, then for simplicity of this demo, add a text field called ‘title’ and a WYSIWYG subfield called ‘content’. Now, if we add our accordion block to any page, we’ll be able to add as few or as many folds to the accordion as we’d like, and the content of each fold is customizable with a WYSIWYG editor!

![Accordion Field Group Screenshot](https://dev-to-uploads.s3.amazonaws.com/i/melr35x8mrlboqsz71li.png)

We can save this data, and it will persist on the individual instance of the block, though we still need to render it in the template.<a name="rendering-content-in-template"></a>

## Rendering field content in the block template
Now, we can start to render each fold in the template. Each .card element in the example code we pasted in earlier makes up one fold. So first, let’s remove all but one ‘fold’, by deleting all the divs after the first card, taking care not to remove the closing tag for the accordion, and also remove all the placeholder content:
```html
<div class="card">

  <div class="card-header" id="headingOne">
    <h2 class="mb-0">
      <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
      
      </button>
    </h2>
  </div>

  <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
    <div class="card-body">
      
    </div>
  </div>

</div>
```
This is our fold template, which we can fill with the content from our block’s custom fields. Now, we can iterate over all the folds from the repeater and render them as a new `.card`. To do this, we’ll use two ACF functions, `have_rows()` and `the_row()`. These are used almost identically to `have_posts()` and `the_post()`, so if you’ve ever seen a WordPress loop, this might be a familiar pattern! Wrap our fold template in a while loop, then grab the fold content using `get_sub_field()`:
```php
// Loop through rows.
<?php 
while ( have_rows( 'folds' ) ) : the_row();
    $fold_title = get_sub_field( 'title' );
    $fold_content = get_sub_field( 'content' );
?>
  <div class="card">
    <div class="card-header" id="headingOne">
      <h2 class="mb-0">
        <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
          <?php echo $fold_title; ?>
        </button>
      </h2>
    </div>

    <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
      <div class="card-body">
        <?php echo fold_content; ?>
      </div>
    </div>
  </div>
<?php endwhile; ?>
```
Our accordion is almost good to go! If we look at this on a page now, we’d see each fold we added to the block rendered, but the accordion wouldn’t behave the way we want. This is because the Bootstrap accordion counts on each fold having a unique id. In our code, every fold has an id of collapseOne. We can follow a similar convention using ACF’s `get_row_index()` function, which will tell us which iteration of the while loop we’re currently in (by telling us which row we’re currently rendering). We can also update the aria labels at the same time:
```php
<?php 
while ( have_rows( 'folds' ) ) : the_row();
    $fold_title = get_sub_field( 'title' );
    $fold_content = get_sub_field( 'content' );
?>
<div class="card">

  <div class="card-header" id="heading-<?php echo get_row_index(); ?>">
    <h2 class="mb-0">
      <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapse-<?php echo get_row_index(); ?>" aria-expanded="true" aria-controls="collapse-<?php echo get_row_index(); ?>">
      <?php echo $fold_title; ?>
      </button>
    </h2>
  </div>

  <div id="collapse-<?php echo get_row_index(); ?>" class="collapse show" aria-labelledby="heading-<?php echo get_row_index(); ?>" data-parent="#accordionExample">
    <div class="card-body">
      <?php echo fold_content; ?>
    </div>
  </div>

</div>
<?php endwhile; ?>
```
Now, our accordion works! The last thing to do, since this is now a fully-functioning accordion and not just an example, is to update the id of the accordion (and the data-parent attribute of each fold) from `#accordionExample` to `#accordion`. Not technically necessary, but names matter!<a name="conclusion"></a>

## That’s it!
We now have a fully functional accordion block. We can use this as a template to make other Bootstrap components as well, including [Jumbotrons](https://getbootstrap.com/docs/4.5/components/jumbotron/), [Media Objects](https://getbootstrap.com/docs/4.5/components/media-object/), [List Groups](https://getbootstrap.com/docs/4.5/components/list-group/), and more.

![finished accordion](https://dev-to-uploads.s3.amazonaws.com/i/qwc8m9yxmdxgojv5ynjm.gif)

You can use the code from this tutorial as a template for creating other Bootstrap blocks for practice or to fit your needs. See the full code in my github repo: 
{% github jacklowrie/wp-accordion-block-tutorial no-readme %}

Have you tried building this or other Bootstrap components as WordPress blocks? How'd it go?