Twig comes included in the External Module Framework as of REDCap version 14.6.4. Each module has its own Twig Environment that comes pre-loaded with several EM framework methods. The "Example Twig Page" from the Module Development Examples module bundled with REDCap for working example.
When you're ready to use Twig, you first need to call initializeTwig(). This will load the twig classes into the Autoloader. The recommended best practice twig template directory named views will be used by default:
//../modules/ExampleModule.php
// loads creates a Twig Environment for your module
// default template directory is 'views' in your module's root directory
$this->initializeTwig();
//Use Twig to render templates
$this->getTwig()->render(...);If your module requires an alternate twig templates directory, an optional templateDirectoryName argument may be used specify that directory:
$this->initializeTwig(templateDirectoryName: 'alternate/path/to/views');As of REDCap Version 17.0.1, an options array may be optionally specified containing any desired Twig Environment Options:
$this->initializeTwig(options: [
'use_yield' => true,
]);As of Framework Version 17, the strict_variables environment option will default to true when Yes, this a development/test/staging server (non-production) is selected in Control Center. While not recommended, the "always false" behavior from previous framework versions can be restored as follows:
$this->initializeTwig(options: [
'strict_variables' => false,
]);If your module extends AbstractExternalModule, simply call $this->getTwig() or $module->getTwig() depending on your context. The most common use case is calling Twig's render() which may look like this:
$this->getTwig()->render('reports/enrollment_summary.html.twig', [
'title' => $title,
'data' => $data,
'fontSize' => 10,
]);Module object methods that are generally appropriate to use in a front-end context may be accessed directly in Twig templates like so:
<a href="{{ getUrl('public-page.php') }}">My Link</a>
Any other methods defined on your module class can also be made accessible by extending Twig as described below.
Functions, filters and tags can be added to Twig by extending it. Be sure to call initializeTwig() before using any Twig Classes like TwigFilter() or TwigFunction(). Here is an example of what a module's twig extensions may look like:
private function loadTwigExtensions(): void
{
$this->initializeTwig();
$this->getTwig()->addFunction(new TwigFunction('reportRoute', function ($reportRoute) {
return $this->getUrl('report.php') . '&reportRoute=' . $reportRoute;
}));
}We've included some of the key templating methods in the EM Framework in Twig already. The list of those can be found in the EM Framework codebase, in a file named /classes/framework/FrameworkTwigExtensions.php.
All views should be stored in a /views directory, with subfolders as needed.
- Lowercase
snake_casefor template names, directories and variables (e.g.user_profile insteadofuserProfileandproduct/edit_form.html.twiginstead ofProduct/EditForm.html.twig) as outlined in the Symfony Best Practices for templating. - Prefix template fragments (also called "partial templates" or "partial") with an underscore to better differentiate them from complete templates (e.g.
_user_metadata.html.twigor_caution_message.html.twig) as outlined in the Symfony Best Practices for templating. - Include an additional extension before the
.twigextension to better indicate the rendered output (e.g.nav.html.twigorajax-script.js.twig)
-
Use a base template called
base.html.twigas the foundation of your templating. This file should include HTML, CSS and JS needed for all pages, as well as Twigblocksfor sections to be filled in by other templates as outlined in Twig's Template Inheritance documentation. This way, child templates only contain the blocks specific to their rendering. -
Use template fragments and Twig's
include()function so template code is not duplicated between templates. For example, consider this mark up for a title used across several templates:<div class="projhdr"> <i class="fas fa-clipboard-check"></i> {{ title }} </div>Instead of repeating these few lines across several templates, create a template fragment named
_title.html.twig. and include on templates using{{ include('_title.html.twig' with {title: title}). -
When including another template, explicitly define the context. Twig allows includes to reference variables in the global context, but this can be confusing, as it isn't clear what variables are required without carefully reading the template fragment. Instead, explicitly define each template fragment's context (e.g.
{{ include('_title.html.twig' with {title: title})instead of{{ include('_title.html.twig')}})