<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jack Lowrie</title>
    <description>The latest articles on DEV Community by Jack Lowrie (@jacklowrie).</description>
    <link>https://dev.to/jacklowrie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F290231%2F138a8f29-1d82-4859-bf3a-9badf6fb302a.jpeg</url>
      <title>DEV Community: Jack Lowrie</title>
      <link>https://dev.to/jacklowrie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jacklowrie"/>
    <language>en</language>
    <item>
      <title>Building a WordPress Accordion Block, no Javascript Required</title>
      <dc:creator>Jack Lowrie</dc:creator>
      <pubDate>Thu, 11 Feb 2021 15:53:06 +0000</pubDate>
      <link>https://dev.to/jacklowrie/building-a-wordpress-accordion-block-no-javascript-required-od8</link>
      <guid>https://dev.to/jacklowrie/building-a-wordpress-accordion-block-no-javascript-required-od8</guid>
      <description>&lt;p&gt;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 &lt;a href="https://dev.to/jacklowrie/building-gutenberg-blocks-with-advanced-custom-fields-acf-as-a-stopgap-2jk9"&gt;stopgap for developers who are still learning JavaScript&lt;/a&gt;, since it doesn't require you to write any JavaScript.&lt;/p&gt;

&lt;p&gt;Just looking for the code? Check out the GitHub Repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jacklowrie"&gt;
        jacklowrie
      &lt;/a&gt; / &lt;a href="https://github.com/jacklowrie/wp-accordion-block-tutorial"&gt;
        wp-accordion-block-tutorial
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Child Theme for TwentyTwenty. Demo for making an accordion block in WordPress with Bootstrap and ACF Pro.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Quick Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Getting Started&lt;/li&gt;
&lt;li&gt;Registering your first block&lt;/li&gt;
&lt;li&gt;Setting up a Field Group&lt;/li&gt;
&lt;li&gt;Rendering content in the template&lt;/li&gt;
&lt;li&gt;
Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites (assumptions)
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://developer.wordpress.org/plugins/hooks/actions/"&gt;action hooks&lt;/a&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACF functions used in this tutorial
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.advancedcustomfields.com/resources/have_rows/"&gt;&lt;code&gt;have_rows()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.advancedcustomfields.com/resources/have_rows/"&gt;&lt;code&gt;the_row()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
This pair of functions is very similar to &lt;a href="https://developer.wordpress.org/reference/functions/have_posts/"&gt;&lt;code&gt;have_posts()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.wordpress.org/reference/functions/the_post/"&gt;&lt;code&gt;the_post()&lt;/code&gt;&lt;/a&gt; in the &lt;a href="https://developer.wordpress.org/themes/basics/the-loop/"&gt;WordPress Loop&lt;/a&gt;, so if you’ve seen one pair, you’ll understand the other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.advancedcustomfields.com/resources/get_field/"&gt;&lt;code&gt;get_field()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.advancedcustomfields.com/resources/get_sub_field/"&gt;&lt;code&gt;get_sub_field()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
The latter acts much the same as the former, only in the context of a repeater field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.advancedcustomfields.com/resources/acf_register_block_type/"&gt;&lt;code&gt;acf_register_block_type()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
We’ll cover how to use it. :)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Background knowledge of Bootstrap
&lt;/h3&gt;

&lt;p&gt;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 &lt;a href="https://getbootstrap.com/docs/4.5/components/collapse/"&gt;&lt;code&gt;collapse&lt;/code&gt;&lt;/a&gt; component as well as the Bootstrap &lt;a href="https://getbootstrap.com/docs/4.5/components/card/"&gt;&lt;code&gt;card&lt;/code&gt;&lt;/a&gt; component. You don’t need to have used these before to follow along, especially if you’ve used other Bootstrap components before.&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://wordpress.org/themes/twentytwenty/"&gt;TwentyTwenty&lt;/a&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create your child theme
&lt;/h3&gt;

&lt;p&gt;Begin by creating a new directory in the themes directory. The best practice would be to name it &lt;code&gt;twentytwenty-child&lt;/code&gt;, but you can pick whatever name you’d like. Inside this directory, create a &lt;code&gt;style.css&lt;/code&gt; file and add the following header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
 Theme Name:   Twenty Twenty Child
 Author:       [your name]
 Template:     twentytwenty
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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 &lt;a href="https://developer.wordpress.org/themes/basics/main-stylesheet-style-css/#explanations"&gt;developer handbook&lt;/a&gt;. For reference, here’s the header from mine:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/*
 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
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, create a &lt;code&gt;functions.php&lt;/code&gt; file, and add the following to it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;enqueue_parent_styles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'parent-style'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_template_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/style.css'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_enqueue_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'enqueue_parent_styles'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This enqueues TwentyTwenty’s styles before ours, that way the changes we make will &lt;em&gt;extend&lt;/em&gt; TwentyTwenty rather than &lt;em&gt;replace&lt;/em&gt; them. And that’s it for now! You have a child theme ready to hack on.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enqueue Bootstrap
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;functions.php&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;enqueue_bootstrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bootstrap-styles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bootstrap-styles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'bootstrap-scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'jquery'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_enqueue_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'enqueue_bootstrap'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code ensures that Bootstrap (and it’s dependencies) will load on your site.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install Advanced Custom Fields Pro (ACF)
&lt;/h3&gt;

&lt;p&gt;You’ll also need to have the &lt;a href="https://www.advancedcustomfields.com/"&gt;Advanced Custom Fields plugin&lt;/a&gt; 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.&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Registering your first block
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;templates&lt;/code&gt; or &lt;code&gt;template-parts&lt;/code&gt; folder if your theme has one. We’ll keep ours top-level for now. Create a new &lt;code&gt;blocks&lt;/code&gt; folder in the root of your theme, then create a new template file called &lt;code&gt;accordion.php&lt;/code&gt; inside. We don’t need to add anything to this file yet. It only needs to exist.&lt;/p&gt;

&lt;p&gt;Then, we need to register that file (in &lt;code&gt;functions.php&lt;/code&gt;, just like we enqueued Bootstrap).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register_acf_block_types&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;acf_register_block_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="s1"&gt;'name'&lt;/span&gt;              &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'accordion'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s1"&gt;'title'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Accordion'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="s1"&gt;'category'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s1"&gt;'render_template'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'blocks/accordion.php'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'acf/init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'register_acf_block_types'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  You’re done! Sort of.
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://getbootstrap.com/docs/4.5/components/collapse/#accordion-example"&gt;accordion example&lt;/a&gt; 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).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DMM43gzn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qrsbklvi7k8nx432l5de.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DMM43gzn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qrsbklvi7k8nx432l5de.gif" alt="hardcoded Bootstrap accordion block" width="640" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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! &lt;/p&gt;

&lt;p&gt;FWIW, ACF’s register block function at its core is a php wrapper for WordPress’s Javascript function &lt;code&gt;RegisterBlockType()&lt;/code&gt;, 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 &lt;em&gt;without&lt;/em&gt; Javascript.&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up a field group
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;Block - Accordion&lt;/code&gt;. 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 &lt;code&gt;block&lt;/code&gt; &lt;code&gt;is equal to&lt;/code&gt; &lt;code&gt;Accordion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gHWrtnfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h50o6qqt65i4kksknmfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gHWrtnfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h50o6qqt65i4kksknmfs.png" alt="Location Rules Screenshot" width="880" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VCM5uqvN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/melr35x8mrlboqsz71li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VCM5uqvN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/melr35x8mrlboqsz71li.png" alt="Accordion Field Group Screenshot" width="880" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Rendering field content in the block template
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-header"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"headingOne"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-link btn-block text-left"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-toggle=&lt;/span&gt;&lt;span class="s"&gt;"collapse"&lt;/span&gt; &lt;span class="na"&gt;data-target=&lt;/span&gt;&lt;span class="s"&gt;"#collapseOne"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"collapseOne"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"collapseOne"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"collapse show"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"headingOne"&lt;/span&gt; &lt;span class="na"&gt;data-parent=&lt;/span&gt;&lt;span class="s"&gt;"#accordionExample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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 &lt;code&gt;.card&lt;/code&gt;. To do this, we’ll use two ACF functions, &lt;code&gt;have_rows()&lt;/code&gt; and &lt;code&gt;the_row()&lt;/code&gt;. These are used almost identically to &lt;code&gt;have_posts()&lt;/code&gt; and &lt;code&gt;the_post()&lt;/code&gt;, 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 &lt;code&gt;get_sub_field()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// Loop through rows.
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; 
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;have_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'folds'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;the_row&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$fold_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sub_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$fold_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sub_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-header"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"headingOne"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-link btn-block text-left"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-toggle=&lt;/span&gt;&lt;span class="s"&gt;"collapse"&lt;/span&gt; &lt;span class="na"&gt;data-target=&lt;/span&gt;&lt;span class="s"&gt;"#collapseOne"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"collapseOne"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$fold_title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"collapseOne"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"collapse show"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"headingOne"&lt;/span&gt; &lt;span class="na"&gt;data-parent=&lt;/span&gt;&lt;span class="s"&gt;"#accordionExample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="n"&gt;fold_content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endwhile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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 &lt;code&gt;get_row_index()&lt;/code&gt; 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:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; 
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;have_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'folds'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;the_row&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$fold_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sub_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$fold_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sub_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-header"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"heading-&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;get_row_index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-link btn-block text-left"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;data-toggle=&lt;/span&gt;&lt;span class="s"&gt;"collapse"&lt;/span&gt; &lt;span class="na"&gt;data-target=&lt;/span&gt;&lt;span class="s"&gt;"#collapse-&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;get_row_index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"collapse-&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;get_row_index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$fold_title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"collapse-&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;get_row_index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"collapse show"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"heading-&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;get_row_index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;data-parent=&lt;/span&gt;&lt;span class="s"&gt;"#accordionExample"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="n"&gt;fold_content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;endwhile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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 &lt;code&gt;#accordionExample&lt;/code&gt; to &lt;code&gt;#accordion&lt;/code&gt;. Not technically necessary, but names matter!&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  That’s it!
&lt;/h2&gt;

&lt;p&gt;We now have a fully functional accordion block. We can use this as a template to make other Bootstrap components as well, including &lt;a href="https://getbootstrap.com/docs/4.5/components/jumbotron/"&gt;Jumbotrons&lt;/a&gt;, &lt;a href="https://getbootstrap.com/docs/4.5/components/media-object/"&gt;Media Objects&lt;/a&gt;, &lt;a href="https://getbootstrap.com/docs/4.5/components/list-group/"&gt;List Groups&lt;/a&gt;, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--asemjlYX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qwc8m9yxmdxgojv5ynjm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--asemjlYX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qwc8m9yxmdxgojv5ynjm.gif" alt="finished accordion" width="640" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jacklowrie"&gt;
        jacklowrie
      &lt;/a&gt; / &lt;a href="https://github.com/jacklowrie/wp-accordion-block-tutorial"&gt;
        wp-accordion-block-tutorial
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Child Theme for TwentyTwenty. Demo for making an accordion block in WordPress with Bootstrap and ACF Pro.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Have you tried building this or other Bootstrap components as WordPress blocks? How'd it go?&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building Gutenberg Blocks with Advanced Custom Fields (ACF) as a Stopgap</title>
      <dc:creator>Jack Lowrie</dc:creator>
      <pubDate>Tue, 24 Nov 2020 14:23:12 +0000</pubDate>
      <link>https://dev.to/jacklowrie/building-gutenberg-blocks-with-advanced-custom-fields-acf-as-a-stopgap-2jk9</link>
      <guid>https://dev.to/jacklowrie/building-gutenberg-blocks-with-advanced-custom-fields-acf-as-a-stopgap-2jk9</guid>
      <description>&lt;p&gt;WordPress is a fantastic way to start learning to code. You rarely need to learn more than one new thing at a time in order to get building, and you can always find one new skill to learn (or improve) with each new project.&lt;/p&gt;

&lt;p&gt;But all that changed when &lt;del&gt;the fire nation attacked&lt;/del&gt; the block editor was introduced. Gutenberg raised the barrier to entry for programming with WordPress, and created a sizable speedbump for faithful WordPress developers that had never needed JavaScript before.&lt;/p&gt;

&lt;p&gt;This doesn’t have to kill momentum, or stop new developers from using WordPress as their ‘way in.’ There’s a workaround for building custom blocks that requires minimal JavaScript. It isn’t necessarily a long term solution (though you can build very polished websites with it). But it is an effective stopgap to keep (or start) building websites while developing the skills necessary to start building native WordPress blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using WordPress as a ladder for improvement
&lt;/h2&gt;

&lt;p&gt;Looking at WordPress as a framework, it’s easy to see the appeal of using it to learn web development. It’s extremely well-documented, surrounded by an active and supportive community, and written in a way that allows you to ‘wade in’ slowly to the code without any prior knowledge. Before Gutenberg, a new developer’s journey with WordPress might look something like this:&lt;/p&gt;

&lt;p&gt;After exploring how WordPress works as a CMS by configuring sites without code, they can slowly start introducing their own code. They can graduate from building sites using drag &amp;amp; drop themes/plugins to adding custom CSS rules via the customizer as they start to learn CSS.&lt;/p&gt;

&lt;p&gt;From there, our new dev can learn about child themes and create their first stylesheet for more comprehensive style rules. Then, they can learn about WordPress’s template hierarchy and custom templates. As they do, they can dip into as much or as little HTML and PHP as they’re comfortable with, leaning on the framework as much as they need.&lt;/p&gt;

&lt;p&gt;As their skills grow, they can begin building their own themes, and start adding custom functionality in &lt;code&gt;functions.php&lt;/code&gt;. Once they can modify WordPress behavior in &lt;code&gt;functions.php&lt;/code&gt;, it’s easy to start developing plugins: First, by moving that custom functionality from their theme into simple custom plugins to understand how plugins are structured, then by building larger more complex plugins, some of which can modify or expand WordPress’s core features.&lt;/p&gt;

&lt;p&gt;At this point, our ‘new’ dev can drop the adjective. They have enough experience reading and writing code to make learning a language for a different framework easier. Their proficiency in HTML/CSS and PHP is also strong enough to experiment with lighter PHP frameworks like Laravel or Symfony. It also makes it possible to start learning more about routing and interacting with the database (which WordPress normally handles for us) – or – they can continue learning about routing and APIs etc by extending those features in WordPress via the WordPress REST API.&lt;/p&gt;

&lt;p&gt;Because WordPress handles so much out of the box, you can isolate one language, grammar, skill or topic to learn or improve on at a time – whether that’s language-specific, or a higher-level concept, you rarely need to learn more than one new thing at a time in order to get building. You can grow with a ‘learn one new thing per project’ strategy for your entire career without leaving the WordPress ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge with Gutenberg
&lt;/h2&gt;

&lt;p&gt;In the hypothetical roadmap above (it’s not so hypothetical, I’m on it 🙂 ), there’s one now-ubiquitous language that’s noticeably absent: JavaScript. Before WordPress 5.0, JavaScript was a nice-but-optional language for WordPress developers. You could go a long way on writing or modifying only a few lines of JavaScript per project, if you did so at all. You certainly didn’t need a build process or NPM to get by.&lt;/p&gt;

&lt;p&gt;However, when Gutenberg replaced the Classic Editor, developers had to worry about rendering content both in the template and in the editor via React components. Content is still stored the same way in the database, but instead of knowing exactly where and in what order content is rendered, developers  suddenly needed to worry about content appearing in any order. Crucially, they also needed to render them appropriately both on the frontend and in the editor.&lt;/p&gt;

&lt;p&gt;If you’ve played with the WordPress block editor, you can consider the jump from Classic to Gutenberg by looking at the Classic Editor as a single unrendered block. Instead of having one ‘block’ being rendered only on the frontend with the Classic Editor, Gutenberg allowed multiple blocks to be rendered in any order, even nested, both on the front end and in the editor. There’s even a block that mimics the Classic Editor, to make it full circle.&lt;/p&gt;

&lt;p&gt;This triggered a lot of panic, though in practice if you don’t need to make custom blocks, or only need to make simple blocks, &lt;a href="https://torquemag.io/2018/10/do-you-need-to-know-react-as-a-wordpress-developer/"&gt;it wouldn’t take long to learn enough JavaScript to get up to speed.&lt;/a&gt; Even if your block requires a little more than barebones, there are &lt;a href="https://css-tricks.com/learning-gutenberg-3-primer-with-create-guten-block/"&gt;shortcut tools that can get you by while you learn the underlying code and tools.&lt;/a&gt; The problem is, without any significant JavaScript experience, the underlying code and tools can take a while to decrypt.&lt;/p&gt;

&lt;p&gt;Furthermore, if you do need to make more involved blocks, or if you want to modify the built-in editor experience at all, then all of a sudden, you’d need HTML/CSS, some PHP, JavaScript, and to be familiar with how WordPress is structured as a framework, including how its REST API works!&lt;/p&gt;

&lt;h2&gt;
  
  
  ACF as a Stopgap
&lt;/h2&gt;

&lt;p&gt;If you weren’t sold on the Advanced Custom Fields (ACF) plugin before, it really shines in the transition period between the Classic Editor and Gutenberg. Before Gutenberg, ACF was used to augment the Classic Editor, and let WordPress content creators manage content on more robust pages. While the free version was exceptionally powerful, the pro license was generally considered to be one of the best (and even most necessary) investments for a serious WordPress site.&lt;/p&gt;

&lt;p&gt;Building blocks using the ACF plugin allows newer developers to sidestep using JavaScript, and continue building sites learning one thing at a time. This method is also particularly useful for WordPress developers used to building sites and admin interfaces ‘the old way’ with just PHP/HTML/CSS, who haven’t had the chance yet to &lt;a href="https://www.youtube.com/watch?v=KrZx4IY1IgU&amp;amp;list=LLWwmj-F7U4TgWlIZCDpN0fg&amp;amp;index=191"&gt;learn JavaScript, deeply™&lt;/a&gt;. There simply is no better way to make a Gutenberg block without JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations to using ACF
&lt;/h3&gt;

&lt;p&gt;The most obvious drawbacks are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It introduces a dependency in every project&lt;/li&gt;
&lt;li&gt;That dependency isn’t free&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To use ACF with Gutenberg blocks, you need a pro license, making it harder to get away with the free version post WordPress 5.0. If you’ve been building WordPress sites with the Classic Editor and ACF pro previously, neither of these are significant changes. Furthermore, given this is a pretty major shortcut to building custom blocks, the return on investment for the pro license is substantial – even more so than it was before.&lt;/p&gt;

&lt;p&gt;Additionally, this method also fails to take full advantage of Gutenberg, and arguably offers a worse editor experience, since the ACF UI remains largely unchanged. Though this is less attractive for brand new builds, it can be an advantage when updating a site that already uses ACF to use Gutenberg, since content creators are already familiar with that editing experience and can transition more easily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DXh4Xx2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5c4o6i90o439lpdb6x3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DXh4Xx2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5c4o6i90o439lpdb6x3m.png" alt="Repeater fields don’t fit in the block settings sidebar."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perhaps the biggest limitation of using ACF to build custom blocks is, instead of editing content directly as-styled like a native block, you make edits in an input field on the side menu and watch the change happen in real-time in the rendered preview block. For simple or small fields, this is almost as good as editing the preview directly. If you’re using more complex ACF fields like repeaters or field groups, then cramming those fields into the side menu doesn’t quite work.&lt;/p&gt;

&lt;p&gt;ACF solves this by letting you edit blocks by ‘flipping’ them over to expose the ACF editor. When doing this, you can’t see how those changes would be rendered in real time (you’d have to keep flipping the block over to make tweaks).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2j6W_Eq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/45cf0cjcb04mpdcr5d2i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2j6W_Eq6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/45cf0cjcb04mpdcr5d2i.gif" alt="Flipping ACF Blocks gives you more room to edit content."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This in no way limits the ability to create polished, high quality websites; the published page looks the same either way. However, having native blocks that can be edited directly sandwiched between ACF blocks that can only be edited in the side menu (or flipped) creates a clunky editor experience. This might seem a small concession given the benefits, however it still underscores using ACF to circumvent JavaScript as a temporary measure on the way towards full Gutenberg adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will ACF drive mass developer adoption of the Block Editor?
&lt;/h2&gt;

&lt;p&gt;JavaScript becoming a core part of the WordPress stack threw a wrench in things for a lot of established WordPress developers and made WordPress a little less accessible for new developers. Using ACF to build custom blocks can counteract that for a little while, buying time for working WordPress devs to learn JavaScript. Ultimately, embracing JavaScript is a good thing. Its rate of adoption has made it too big to fail, and it is the future of the web – at a minimum, it’s the intended future of WordPress. But until we can download languages instantly and directly into our brains, this is a very effective stopgap.&lt;/p&gt;

&lt;p&gt;In the next post, we’ll look at a practical example of how to build a custom Gutenberg block with ACF, no JavaScript required.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Supporting Time Ranges for Manually Built Nova Value Metrics in Laravel</title>
      <dc:creator>Jack Lowrie</dc:creator>
      <pubDate>Mon, 06 Jul 2020 14:07:02 +0000</pubDate>
      <link>https://dev.to/jacklowrie/supporting-time-ranges-for-manually-built-nova-value-metrics-in-laravel-3jia</link>
      <guid>https://dev.to/jacklowrie/supporting-time-ranges-for-manually-built-nova-value-metrics-in-laravel-3jia</guid>
      <description>&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Manually built metrics in Nova don't support the time-range dropdown that the Nova helper functions support, and those helper functions don't handle &lt;code&gt;many&amp;lt;-&amp;gt;many&lt;/code&gt; relationships well (at least not the way I needed).&lt;/p&gt;

&lt;p&gt;To get around this, you can use two protected functions from the parent &lt;code&gt;Value&lt;/code&gt; class: &lt;code&gt;currentRange()&lt;/code&gt; and &lt;code&gt;previousRange()&lt;/code&gt;. Just don't forget to pass in the current admin's timezone!&lt;/p&gt;

&lt;p&gt;Just looking for a code snippet? Jump to the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Background (What does the metric class look like?)

&lt;ul&gt;
&lt;li&gt;Class Structure&lt;/li&gt;
&lt;li&gt;The Calculate Method&lt;/li&gt;
&lt;li&gt;Manually Building Results Values&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Problem (Where I got stuck)&lt;/li&gt;
&lt;li&gt;
How I Solved: Looking Under the Hood (How I got unstuck)&lt;/li&gt;
&lt;li&gt;
My Workaround (my code)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nova.laravel.com/docs/3.0/metrics/defining-metrics.html"&gt;Metric cards in Nova&lt;/a&gt; can be produced rapidly, are straightforward to plan and explain, and provide high-impact 'quick wins' when using Nova to build out a dashboard for a Laravel application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Class Structure &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Value metrics generally consist of four methods: &lt;code&gt;calculate()&lt;/code&gt;, &lt;code&gt;ranges()&lt;/code&gt;, &lt;code&gt;cacheFor()&lt;/code&gt;, and &lt;code&gt;uriKey()&lt;/code&gt;. We're only concerned with the first two.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ranges&lt;/code&gt; method ultimately populates the dropdown on the frontend. It returns an array of time ranges your metric will support and is pre-populated by &lt;a href="https://laravel.com/docs/master/artisan#introduction"&gt;Artisan&lt;/a&gt;. If you don't want to support ranges, you can remove this method; it's actually optional, and you can add/remove ranges from the returned array as you see fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Calculate Method &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Metrics are centered around that &lt;code&gt;calculate&lt;/code&gt; method, which can frequently be a one-liner. The parent class for ranged metrics includes methods for the most frequent queries you'd want to make (count, sum, max, min, and average), so as long as you're creating a metric for an eloquent model -- say, &lt;code&gt;users&lt;/code&gt; -- Nova (and Artisan) will do most of the work for you. The calculate method for metric measuring how your app is growing might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NovaRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and would return the number of users your app has acquired (over a given range), along with a percent increase or decrease compared to the previous period. You can also further specify your query for users matching a particular set of rules (for example, if your &lt;code&gt;users&lt;/code&gt; had an &lt;code&gt;account_status&lt;/code&gt;, you could alter the return statement like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NovaRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'account_status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metric looks pretty good on the frontend out of the box, too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--izg1HJxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1hrdxlbsmfl4xg6a9s1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--izg1HJxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1hrdxlbsmfl4xg6a9s1p.png" alt="New User Metric Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the range dropdown in the upper right; this is where the &lt;code&gt;ranges()&lt;/code&gt; method comes in -- you can choose what options appear in that dropdown by setting them in that method (or, if you're happy with the default options that are pre-populated, don't worry about it!). The actual implementation of this feature seems to be Nova magic.&lt;/p&gt;

&lt;p&gt;This is great for simple metrics like counting how many active users there are in your application, or counting the number of posts that were published, but what if you want to report on a metric that isn't covered by Nova helper functions?&lt;/p&gt;

&lt;h3&gt;
  
  
  Manually Building Results Values &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Manually building results is equally straightforward at first glance. Nova metrics support manually building result values. It even supports including reporting previous result values, so long as you calculate them yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NovaRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;//some query&lt;/span&gt;
    &lt;span class="nv"&gt;$previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;//another query (optional)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well for reporting a value in one time range, but when you build your results manually, you lose the ability to dynamically compare the metric across different time ranges (see the dropdown in the upper right of the screenshot above). What if you need that dropdown?&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We needed to return a count of the records in the pivot table (in this case, we have a badges table and users, and need to report the total number of badges earned (by all users, over a time range).&lt;/p&gt;

&lt;p&gt;Building that result manually is easy enough: We can use Laravel's database facade to count the records in the &lt;code&gt;user_badges&lt;/code&gt; pivot table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_badges'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can even compare to a previous value if we calculate it ourselves, but it won't connect to the &lt;code&gt;ranges()&lt;/code&gt; method, so this only works if we hardcode a fixed time range. What about that dropdown? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bEGOmR9O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pgy879kz42969bckkij8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bEGOmR9O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pgy879kz42969bckkij8.png" alt="Total Badges Earned Hardcoded Time Range"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was unable to find anything in the documentation on how to handle ranges if building the result values manually, especially to support the dropdown that &lt;em&gt;seems&lt;/em&gt; to be Nova magic for straightforward metrics. Fortunately, we can look at how Nova's metric helper functions are written for ideas. There &lt;em&gt;is&lt;/em&gt; an answer in the source code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Under the Hood: How Nova implements Metrics classes &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The metrics classes we generate with Artisan extend the abstract class &lt;code&gt;Value&lt;/code&gt;. This class contains the helper methods you use for simple metrics. There isn't a whole lot happening in these helpers, however. They're all one-liners that call a protected method. It's that protected method, &lt;code&gt;aggregate()&lt;/code&gt;, that's of interest to us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$dateColumn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Builder&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$column&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getQualifiedKeyName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$timezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Nova&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;resolveUserTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$previousValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;clone&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$dateColumn&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCreatedAtColumn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;previousRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$function&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nv"&gt;$column&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;clone&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;$dateColumn&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCreatedAtColumn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;currentRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$function&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nv"&gt;$column&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$previousValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method may look like a lot, but at a bird's eye view, it's doing something that's already documented, both in this post and in Nova's official docs - it's manually building a result and a previous value, and returning them! To do this, it's using two &lt;em&gt;more&lt;/em&gt; protected helper methods: &lt;code&gt;currentRange()&lt;/code&gt; and &lt;code&gt;previousRange()&lt;/code&gt;. So when we manually build results in our metrics class, we're overriding these!&lt;/p&gt;

&lt;p&gt;It follows that we can do the same in our class to support time ranges. However, that method takes two inputs, which we must remember to pass in ourselves: the timezone of the current user (conveniently calculated in the third line of the &lt;code&gt;aggregate&lt;/code&gt; method above) and the time range (which even more conveniently is passed in as part of the request).&lt;/p&gt;

&lt;p&gt;So, the strategy is to use these two helper functions to help manually build our result. &lt;/p&gt;

&lt;h2&gt;
  
  
  My Final Calculate Function &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NovaRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$timezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Nova&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;resolveUserTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_badges'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;currentRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nv"&gt;$previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_badges'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;previousRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$timezone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach, in combination with the built-in &lt;code&gt;ranges()&lt;/code&gt; method, successfully counts the number of records in the pivot table that were created within the time range selected on the front end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lUUwj_9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d6vr78jystsis39rx26a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lUUwj_9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d6vr78jystsis39rx26a.png" alt="Total Badges Earned Complete"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>tutorial</category>
      <category>nova</category>
    </item>
  </channel>
</rss>
