bytes & beans.

Blog

Let's build a Craft module: It's the Great Filter!

The Situation

The design calls for certain characters in the headings to be a different color. Specifically, all the punctuation such as commas and periods should be red.

We can just hard-code the titles with a <span style="color: red"> around the punctuation. That defeats the whole purpose of having a CMS, but it does create billable hours every time someone makes a blog post.

We can hardcode a colored period at the end of the title. This doesn't quite meet the design spec, and some users, will forget, and include punctuation in the title anyway.

We can add a second title field, make it a Redactor field, install the redactor plugin that allows users to color the text, write a custom configuration file for that field limiting it to a single line with no formatting except colors, limit them to only the approved shade of red, and instruct the user to color it himself.
This creates a confusing, redundant second title field, and end users shouldn't be entrusted to do anything.

So, it's time to automate something and Craft/Twig/Yii doesn't have a ready-made solution for you. It's time to make a module!

Our Solution

The plan is to create a new filter, named 'redAccents' for the .twig templates that will take a string, and add the <span> tags where we need them.

So...
{{"It's the Great Filter, Charlie Brown." | redAccents}}
Will output:
It's the Great Filter<span style="color:red">,</span> Charlie Brown<span style="color:red">.</span>

What is a module

A module is a self-contained chunk of code that expands, or modifies your project's capabilities. It's like a plugin that can't be seen by the plugin store or settings, and is specific to your project. Technically, Craft plugins are modules with some extra functionality.

Modules are great for odd custom code that the end user shouldn't be using directly and should never be allowed to uninstall. Such as adding a console command that scrapes thousands of PDFs and creates entries from the data they contain, or our filter here.

Building the module

Set up the file scaffolding

We could go deep in the weeds setting this all up by hand. That is beyond the scope of this article, and we don't want to

Instead we will be using https://pluginfactory.io. This tool will automatically set up our file structure, and generate a few pieces of code we will need to have Craft load and use our module.

At https://pluginfactory.io: select API version 'Craft CMS Module version 3.x', fill in the other details, and select only 'Code Comments' and 'Twig Extensions' from the list. Code comments are a lot of help and we are just extending twig.

Hit build and the tool will generate and download a folder for your.

Drop that new folder into your projects /modules folder.

The readme.md will guide you through installing the module.
You will be adding a couple lines to your project's '/config/app.php', and 'composer.json'.

Now run the command 'composer dump-autoload'.
Composer checks to see what needs to be loaded once and will only start loading new things from the list if you tell it to check the list.

Add the filter to our template

Let's keep it simple. Here is the code for ourProject.url/test:

<body>
<h1>{{ "Let's build a module: It's the great filter!" | redAccent }}</h1>
<p> This code will throw a syntax error for unknown filter! </p>
</body>

Excellent. We now have a broken page, because our redAccent filter doesn't exist.
This is all going according to plan, really.

Build the filter

In its simplest form, a Twig filter is just a function that accepts a string, transforms that string, and outputs another string. Technically, you can accept the string, do whatever you like ,and return nearly anything, but nine times out of ten you will be transforming strings.

For more in depth details on what you can do with filter functions, see Extending Twig #filters.

Open 'modules/yourmodule/src/twigextensions/RedaccentsModuleTwigExtension.php' and marvel at how helpful the code comments pluginFactory.io provides are.

To build a filter, the code we are interested in is:

public function getFilters()
{
return [
new TwigFilter('someFilter', [$this, 'someInternalFunction']),
];
}

Simple, just how I like it.
GetFilters just returns an array of TwigFilters.
TwigFilter(), takes two arguments: the name of the new filter, and that array; [$this, 'someInternalFunction'].
someInternalFunction is a public function that accepts a string as its sole input

To add our new filter, we just have to either replace someFilter with our own filter and function, or add it to the list. So we change the code too...

public function getFilters()
{
return [
new TwigFilter('redAccent', [$this, 'redAccentFunction'], ['is_safe' => ['html']]),
];
}

Notice that we added an options array argument to our TwigFilter function call. The 'is_safe' option tells Craft that the output of this function is safe and does not need to be escaped.

And add our 'redAccentFunction' method to the class.

public function redAccentFunction($text = null)
{
$result = array();
$asplode = str_split($text);
foreach ($asplode as $character) {
if (str_contains(".,:?!", $character)) {
$result[] = "<span style=\"color:red;\">" . $character . "</span>";
}
else {
$result[] = $character;
}
}
return implode("",$result);
}

That is it!

From this point, you can do practically anything you like in that function. Remove every instance of the letter 'T', count the words and return the word count, wrap the entire thing in a <blink> tag, go crazy.

This is barely the tip of the iceberg, in the Extending Twig documentation you will find many more tools and options available to you, and just filters and functions alone will provide a lot of functionality.