Creating the basic setup for modular content blocks in ProcessWire

Creating the basic setup for modular content blocks in ProcessWire

In the first part of this article series I explained the conceptual basics of modular content blocks. Now it’s time to show you how to put this idea into practice by taking the example of my favorite CMS ProcessWire. Read on to learn how to build a set of modular content blocks in ProcessWire from scratch.

If you are a web designer or web developer and don’t know ProcessWire yet, I highly recommend reading some of the tutorials and playing around with the system using the public demo site. OK, if you didn’t already know ProcessWire, you probably wouldn’t read this article. ;-) But I never miss a chance to promote this CMS because I really like it.

Now let’s turn to building content blocks. Allons-y!

Requirements

Since there is no ready-to-use page builder extension (yet) which you could use to create content blocks in ProcessWire, you need to do a bit of coding yourself. But that’s good: This way you have complete control over every single detail.

All you need is a default ProcessWire installation and the commercial ProcessWire module ProFields. The ProFields package includes the Repeater Matrix field type. This is the basis of the content blocks setup.

In a nutshell: A Repeater Matrix field allows you to define different types of content blocks. Each of these content block types can have its own set of fields for the content itself as well as for any kind of control information. Adding a Repeater Matrix field to a template enables website editors to add as many content blocks (of the predefined types) to a page as they like. They can hide/unhide the blocks, change their order and collapse/expand them for better overview.

Screenshot of of a Repeater Matrix field, configured with four content block types (see “Add New” at the bottom)
An example of a Repeater Matrix field, configured with four content block types (see “Add New” at the bottom)

Setting up the Repeater Matrix field

You need to create only one Repeater Matrix field for the content blocks setup. To begin with, it’s enough to have two basic content block types:

  • one for copy text (named text)
  • one for pure HTML input (named html_input)

Having an input field for pure HTML is convenient if you need to add your own HTML code to a page and don’t want to grapple with any WYSIWYG editor’s code stripping features. But make sure to allow only users with sufficient HTML know-how to add this type of content blocks to pages.

Remember, this is just the first tutorial of a series. I will introduce more (more complex and more useful) content block types in the following articles.

Before you set up the Repeater matrix field, you need to create the required (inner) fields for the two content block types mentioned above:

  • A text field named anchor which you should include in each of your content block types. You can use it to give your content block a name and include this name as an ID attribute in your code (usually for better in-page navigation).
  • A textarea field for a content block type holding simple copy text. Use whatever you like for text formatting—a WYSIWYG editor, Markdown and/or Text Formatters. You don’t necessarily have to create a new field if you rather want to use an already existing body field. This is what I did in the example below. I just used the already existing textarea field named body because I was not keen on configuring another CKEditor instance.
  • A textarea field named html_input for … you guess it … direct HTML input. Set the input field type on the Details tab of the field configuration page to “Textarea” and choose “Markup/HTML” as content type. Be careful which Text Formatters to include, since you probably don’t want your code to be altered. I don’t use any Text Formatters for this field.

Let’s turn to the Repeater Matrix field itself. Create a new Repeater Matrix field named content_blocks. On the Details tab you find the “Repeater Matrix types” section. This is where you define your different content block types. For now create the two types mentioned above. The result should look like this:

Screenshot of the configuration of the content block type “text”
Configuration of the content block type “text”
Screenshot of the configuration of the content block type “html_input”
Configuration of the content block type “html_input”

That’s enough configuration for now. Add your new content_blocks field to the template(s) of your choice, and you are good to go.

Implementing content blocks in your templates

Although ProcessWire offers a built-in way to rendering fields with template files, I’m not a big fan of it. This feature depends on template files to be namend in a specific way and to be placed in a specific folder. However, if you want to use this option, you can find information about ProcessWire’s built-in automatic template file structure on the respective field’s configuration page.

Screenshot of optional render files used for a “content_blocks” field
Example of optional render files used for the “content_blocks” field you just created

But here’s why I prefer a simple PHP include, especially for content blocks:

  • Every web designer or web developer with basic PHP skills understands an include—even without deeper knowledge of ProcessWire’s template system.
  • If you are a part-time web developer or work with a bunch of different content management systems, this method saves a little bit of your brain’s memory capacity. The include tells you exactly what’s going on. No need to memorize ProcessWire specific functionalities.
  • The most important reason: If you use the delayed output method in your templates, the file rendering templates suggested by ProcessWire won’t receive all the variables you create in your init.inc file. If you use the include function instead, you have access to all of your variables without any hassle.

The code examples in this article series are suited for the delayed output method. However, you can easily adapt them in case you prefer direct output.

Let’s begin with adapting the variable you use to output the main content of a page. If you used a big body field in the past, the output variable probably looked like $output__main_content = $page->body;. Replace this with the following code snippet:

if (count($page->content_blocks)) {
    $output__main_content = include('./components/_content_blocks.inc.php');
}

That was easy, wasn’t it? Now you need to create the _content_blocks.inc.php file. You can save it wherever you like. Just remember to use the correct path when you include it. The file _content_blocks.inc.php has only one purpuse. It returns the content of all items of your content_blocks field by including the respective sub-templates. These sub-templates in turn contain the actual code which is used to render each content block type. So it’s a kind of combined switch and collector. Sounds complicated? Have a look at the code and it will become clear.

<?php namespace ProcessWire;

/**
 * =============================================================================
 * Content blocks (include)
 * =============================================================================
 *
 * Returns the content of a “content-blocks” field by including the respective
 * sub-templates. Should be user via include to populate an output variable.
 */


$content_blocks_return = '';

foreach ($page->content_blocks as $content_block) {
    $content_blocks_return .= include(__DIR__ . '/content_blocks/_' . $content_block->type . '.inc.php');
}

return $content_blocks_return;
components/_content_blocks.inc.php

In case you wonder where the $content_block->type comes from: That’s the name of each content block type. You defined it on the Repeater Matrix configuration page, see the screenshots above.

If you need to add new content block types later on, you only need to create a new sub-template file after having configured the new content block type in the Repeater Matrix field configuration.

Sub-template for content block type “Text”

This template is very simple. It returns the content block’s body field after adding the anchor name—if the anchor field contains any content—as an id attribute to the body’s first id-less HTML element.

<?php namespace ProcessWire;

/**
 * =============================================================================
 * Content block “Text” (include)
 * =============================================================================
 *
 * Returns the content of a “text” type content block. Should be
 * used via include to populate an output variable.
 *
 * Required variables to be defined in parent template:
 * $content_block
 */


if ($content_block->anchor) {
    return(preg_replace('/<([a-zA-Z][a-zA-Z0-9]*(\h(?!.*id=)[^>]*)*)>/', '<$1 id="' . $content_block->anchor . '">', $content_block->body, 1));
} else {
    return($content_block->body);
}
components/content_blocks/_text.inc.php

Sub-template for content block type “HTML input”

This template works exactly the same way as the template for the “Text” content block.

<?php namespace ProcessWire;

/**
 * =============================================================================
 * Content block “HTML input” (include)
 * =============================================================================
 *
 * Returns the content of an “html” type content block. Should be
 * used via include to populate an output variable.
 *
 * Required variables to be defined in parent template:
 * $content_block
 */


if ($content_block->anchor) {
    return(preg_replace('/<([a-zA-Z][a-zA-Z0-9]*(\h(?!.*id=)[^>]*)*)>/', '<$1 id="' . $content_block->anchor . '">', $content_block->html_input, 1));
} else {
    return($content_block->html_input);
}
components/content_blocks/_html_input.inc.php

Summary and outlook

After following the steps of this tutorrial you can use content blocks as part of your ProcessWire powered website.

By now you have created two very basic content block types to choose from. I know this not much—and probably not enough to experience the real power of content blocks yet. But be patient and look forward to the rest of this little article series. I’m going to show you how to create content blocks for …

  • lazy loading, responsive images with different layout options, placeholder boxes, captions and credentials,
  • code examples with Prism syntax h ighlighting and convenient input fields to configure some Prism plugins,
  • embedded YouTube videos with additional structured data (for SEO),
  • embedded SlideShare presenations,
  • “Tweet this” boxes,
  • some other use cases.

So stay tuned and subscribe to the blog.

Isn’t there an alternative without additional cost?

Update: Thomas Aull brought a second free alternative to my attention. So I updated this article accordingly. New parts are highlighted.

Some of you might want to spare the expenses for the commercial ProFields module. Actually there is a are two free alternatives: ProcessWire’s built-in field types Repeater and PageTable.

Option 1: Using ProcessWire’s built-in Repeater field type

Using ProcessWire’s built-in Repeater field type to create content blocks comes with a huge drawback: It only works if you get by with a very small number of content block types and if you use not more than two or three fields per content block type. Otherwise it has a massive negative impact on frontend and backend performance. You can handle the frontend performance problem with caching, though. However, I don’t recommend using the free alternative.

But for those of you who are curious or want to try it, here’s how it works: You use ProessWire’s built-in Repeater field type instead of the Repeater Matrix field type. The Repeater field also enables editors to add one or more content blocks to a page. But in contrast to Repeater Matrix, a simple Repeater field only provides one content block type. So every content block includes the same set of fields.

And here’s the trick you need to implement different content block types using a simple Repeater field:

  1. You create a Repeater content_blocks.
  2. Within this Repeater field you then create a content_block_type field with selectable predefined values which represent the different types of content blocks.
  3. For each of the (content) fields within your Repeater field you set up display dependencies. Example: Show all fields of the “image” content block only when the “image” option is selected in the content_block_type field. This way you can have pseudo-different content blocks.

Now you probably have an idea where the above-mentioned performance problems come from: Even if you don’t see the fields you don’t use, they are still there and are processed each and every time you touch the Repeater field in any way.

Let’s say you use the free Repeater field solution for a setup of five different content block types. Let’s further assume that each of these content block types consists of five fields—which is realistic according to my experience. Add the content_block_type selection field and you have a total of 26 fields per Repeater block. Now multiply this by the number of Repeater blocks you need to create a page. With this setup it’s not uncommon that pages end up having a few hundred fields. Of course, you can collapse Repeater fields in the backend and have their contained fields loaded when clicking the expand button. But even then it takes too long to be a fluent experience. That’s why I recommend using the Repeater Matrix field type when building content blocks.

Option 2: Using ProcessWire’s built-in PageTable field type

I have not implemented this solution in one of my own projects. However, I explained the pros and cons in the first article of this little series about content blocks. It should give you enough information to decide if this option might be a proper solution for you.