Using Drupal Paragraphs with a Component‑Based Approach
For any company that builds web experiences, establishing internal best practices and standardizations are key for meeting goals for both quality and speed. Drupal Paragraphs, with some heavy modifications, helped organize our components project, enhancing our ability to quickly utilize shared components with amazing results for end-users in terms of flexibility and content creation.
At Bounteous, we are always striving to improve our development processes. One of the ways that our Drupal practice has accomplished that goal is by setting out to create a module of reusable code. The intention is that this module could be used on any client project to help cut down on our development time of features that are common across most projects with little to no variation from one version to the next.
That goal led to the creation of the Bounteous D8 Components project. We started out using the D8 Blocks System and a contrib module called Paragraphs as the centerpieces to that project. In this blog post, I wanted to share some of my experiences working with Paragraphs in the context of components including some challenges I encountered and how I was able to solve them. But before we get too far along, let me explain what Paragraphs is and why it’s important to our components.
So What’s With All This Paragraphs Business Anyway?
Paragraphs is a powerful Drupal module that offers seemingly unlimited possibilities with regards to content creation. It eliminates the need for putting all your content in a what-you-see-is-what-you-get (WYSIWYG) field. It is a huge upgrade over Field Collections and provides far more features and flexibility. Best of all, it allows developers to create reusable components for end-users to have far more control over their content than ever before. Simply put: It is a game-changer. For more details on what Paragraphs can do for you and your Drupal site, check out its project page.
We chose Paragraphs for a few reasons. First, it offers us the ability to create prebuilt, reusable components such as Hero Banners, Call-To-Action (CTA) blocks, FAQs, and more that can be inserted into any Drupal entity and customized in infinite ways. Secondly, in D8, Fieldable Panel Panes (FPP) were not ported overdue to the improvements to the D8 Blocks System, and Field Collections (FC) are in the process of being deprecated and replaced by Paragraphs.
A Brief History of Everything (in the Bounteous Drupal 8 Components Project)
Over a year ago, Justin Todd and I were assigned to the D8 Components project. We were tasked with porting over an FPP based module called Quilt, which was one of our more popular, unique, and complex components that was originally built in D7. This component was meant to be a selling point for our module and also serve as a template for other components that would eventually follow.
One of the primary goals for our components module is that we wanted to make these modules flexible enough to be included in any D8 project without the need for additional coding. We wanted them to be fully functional, lightweight, and themed out of the box. But, we also made sure they could easily be customized if desired. To accomplish this, we looked to some work we’ve done on past Bounteous projects.
For one of our larger clients, Bounteous developed a set of component modules using FPPs and FCs to be shared amongst their brand sites. These modules were part of a shared codebase on each project. Justin and I had both worked together on several of these projects and were able to draw from our experiences in developing reusable components. And to take things a step further, we also borrowed from similar modules that Bounteous built that included lots of configuration options for the end-user such as text sizes, position, colors, and more. We combined concepts from both projects, only we used Drupal blocks as the parent entity/component container and Paragraphs as the entity rows.
We were successful in creating a D8 version that is now in use on several Bounteous client sites. Now, using the Quilt component as a template, two Drupal team developers and I have expanded our components module to include seven additional components with more on the way. In an effort to make our components even more usable, we decided to remove Blocks as the parent entity and build an all Paragraphs version of our components, but in doing so, that also introduced new issues that we had to solve.
Paragraphs, We Have a Problem
The one advantage to using Blocks as the parent entity in our module was that we could always rely on a few constants. First, we would always know the Drupal Form ID. Second, we would always know the Paragraph field name within that Form. These two things are important because we added conditionally required admin libraries to the content creation side of our components. This means we relied heavily upon hook_form_alter() functions in Drupal.
Most of our components just used these admin libraries to insert additional CSS and/or some JavaScript for a better UI experience. The Quilt component, though, relies heavily on an admin library and cannot function properly without it. As the module development progressed, we realized that using Blocks as the only parent entity was limiting the flexibility of the module. Namely, we had Paragraph fields that couldn’t be used outside of our Block Components. That was a problem but one with an easy solution.
I don’t think our original development plan was necessarily a mistake. As mentioned previously, we were following a more D7 FPP/FC-based architecture model. At the time that was OK, but as D8 and Paragraphs evolved over the past year, it made more sense to switch to an all Paragraphs architecture in order to make our components as flexible as possible.
When we set out on this new, all-Paragraphs architecture plan, our sub-entity, the Paragraph rows, didn’t change. What did change is what we replaced the Blocks-based parent entity with a Paragraphs entity. For the most part, this code refactor was straightforward and simple. We could even keep the Blocks-based version of our components because all we had to do with our original Blocks is change the Paragraphs reference field being used from the Paragraph rows to the Paragraph parent. The Blocks version still operated as expected, but our new Paragraph field option did not.
The problem we encountered was that we could no longer conditionally attach our admin libraries when using a Paragraph field on a unknown entity. Approaching this the Drupal 8 way, we don’t want to load libraries that are not needed. Those kinds of things makes Drupal less performant. So now, we were in need of a solution to be able to load our admin libraries regardless of entities used.
The reason we couldn’t attach our admin libraries any longer is that when using a Paragraphs field on a new entity type that is created outside of our module, we no longer know the form id or the field name. Those are things that will only be known on a per-project basis by the developers working on that project. If our module forces those developers to always update form ids or the field name, then our components will no longer work out of the box and we will have failed one of our key objectives. Luckily, the Drupal community was there to save the day.
Hey, There’s a Patch For That!
We were not the only developers in need of a way to get into the Paragraphs subform. After looking through the Paragraph Issue Queue on Drupal.org, I came across the following Feature Request, “Add paragraph bundle to widget forms to allow easier editing of paragraph forms.” This feature request initially contained a patch that provided the following new hooks:
These new hooks were a lifesaver. Suddenly, it didn’t matter if we knew the Form ID or the Field Name. These hooks allowed us to skip all of that and dive directly into the subform. We could now attach admin libraries without issue. We are only really scratching the surface with these powerful new hooks.
When I first discovered this feature request and subsequent patch, Paragraphs was on version 1.2. Shortly after refactoring all of our components to include the hook_form_paragraph_subform_alter() function to load our admin libraries as well as while preparing to write this blog, Paragraphs 1.3 was released. The release listed this feature request as one of its new items. This was excellent news. It meant we no longer needed to deal with applying a patch for each project that would want to use our components module or re-applying the patch on future versions of Paragraphs. The only problem was, those handy hooks were not included. So I reapplied the patches and did some digging as to why the hooks weren’t in version 1.3 despite the feature request claiming to have been included.
It turns out, that the scope of the feature request was elegantly simplified. Rather than add a bunch of new hooks to the Paragraphs API, the module maintainers, instead, opted to pass the Paragraph Type to the existing widget alter hooks. So now, the hook_field_widget_WIDGET_TYPE_form_alter() function can do everything that the original proposed hooks can do and more. And for us, that means that we can still attach our admin libraries conditionally with a single, universal hook.
Summary
So far, we haven’t encountered any issues with using these hooks, particularly related to being able to attach our admin libraries to the components modules. In fact, these hooks are going to open up a lot of possibilities in terms of custom Paragraphs development.
With our reusable components module being available in Blocks, Paragraphs Fields, and eventually Layout Builder, the content creation possibilities are limitless. I am personally excited to see where all this is going and what kind of amazing websites that Bounteous will develop with it in the future.