Skip to content

opencastsoftware/govuk-frontend-freemarker

Repository files navigation

govuk-frontend-freemarker

CI Maven Central License

This repository contains Apache FreeMarker templates for the GOV.UK Design System components.

At present this repository is tracking the last two major versions of the GOV.UK Frontend library.

Installation

govuk-frontend-freemarker is published for Java 11 and above.

A Maven classifier is used to select the GOV.UK Frontend major version.

GOV.UK Frontend 5.x

Gradle (build.gradle / build.gradle.kts):

implementation("com.opencastsoftware:govuk-frontend-freemarker:0.1.2:govuk-frontend5x")

Maven (pom.xml):

<dependency>
    <groupId>com.opencastsoftware</groupId>
    <artifactId>govuk-frontend-freemarker</artifactId>
    <version>0.1.2</version>
    <classifier>govuk-frontend5x</classifier>
</dependency>

GOV.UK Frontend 4.x

Gradle (build.gradle / build.gradle.kts):

implementation("com.opencastsoftware:govuk-frontend-freemarker:0.1.2:govuk-frontend4x")

Maven (pom.xml):

<dependency>
    <groupId>com.opencastsoftware</groupId>
    <artifactId>govuk-frontend-freemarker</artifactId>
    <version>0.1.2</version>
    <classifier>govuk-frontend4x</classifier>
</dependency>

Conformance Testing

These components are A/B tested versus the original Nunjucks components using govuk-nunjucks-renderer to ensure that the translated components produce identical output for randomly generated inputs.

The allowed parameters for each component are determined based on the .yaml files in the component folders in GOV.UK Frontend. In general the coverage of these parameters is very good with a couple of exceptions:

  • The property-based testing framework that we use makes it difficult to implement mutually exclusive properties, so in components which have *Text and *Html properties, we may test scenarios that shouldn't happen in reality, such as neither property being set when at least one is mandatory.
  • We imply that parameters of type object but with no documented params are of Java type Map<String, String>. This won't cover the full range of possibilities for a HTML attributes property, which should probably be Map<String, Object>.

Known Issues

There are no known functional issues with these translated templates. If you find some, please report them on the Issues tab!

The main issue with this translation at present is its readability:

  • These templates were translated using Nunjucks' own parser. This means that whitespace stripping that would normally be applied to the template output has been applied to the templates themselves, removing linebreaks and whitespace that were added for readability.
  • Comments are missing from the translation, because the Nunjucks parser discards comments.
  • No effort has been made as yet to print the translation with minimal parentheses. This means that parentheses are used in all cases where a compound expression could be used.

We plan to address these issues in later releases of the project.

Usage

Templates can be found as resources in the com.opencastsoftware.govuk.freemarker.components package.

Data model classes have been generated representing each design system component's parameters.

For example, to render the accordion component, we can use the com.opencastsoftware.govuk.freemarker.Accordion class.

To render a template we must follow a few steps:

Set up FreeMarker

For this example we will follow the instructions in the FreeMarker documentation.

var config = new Configuration(Configuration.VERSION_2_3_32);
var stringTemplateLoader = new StringTemplateLoader();
var govukTemplateLoader = new ClassTemplateLoader(Params.class, "");
config.setTemplateLoader(new MultiTemplateLoader(new TemplateLoader[] { stringTemplateLoader, govukTemplateLoader }));
config.setDefaultEncoding(StandardCharsets.UTF_8.name());
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
config.setLogTemplateExceptions(false);
config.setWrapUncheckedExceptions(true);
config.setFallbackOnNullLoopVariable(false);

Please note the parameters of the ClassTemplateLoader. govuk-frontend-freemarker templates will be resolved relative to the class com.opencastsoftware.govuk.freemarker.Params and the package prefix used in this call.

In our examples, the StringTemplateLoader is used to load templates making use of the govuk-frontend-freemarker components.

This indirection is needed because FreeMarker templates instantiated via the Template constructor cannot import other templates. Only templates loaded via a TemplateLoader can do so.

In practice you will probably want to use another ClassTemplateLoader to load your custom templates from the classpath.

In addition to the setup above, we will set the FreeMarker boolean_format and number_format settings to "c", the setting for computers.

config.setBooleanFormat("c");
config.setNumberFormat("c");

This is because Nunjucks does not have any special formatting behaviour for numbers or booleans.

Get a Template instance

Each component is represented by a Freemarker *.ftlh file named according to the component directory in govuk-frontend. The *.ftlh extension enables FreeMarker's HTML auto-escaping behaviour.

For example, to import the accordion component, we will import "./components/accordion.ftlh".

Each component is defined as a FreeMarker macro with the prefix govuk and suffix Macro.

A FreeMarker function is also defined for each component which allows using that component in interpolations.

stringTemplateLoader.putTemplate(
    "test",
    "<#import \"./components/accordion.ftlh\" as accordion>" +
    // Usage of the macro definition
    "<@accordion.govukAccordionMacro params=params />" +
    // Usage of the function definition
    "${accordion.govukAccordion(params)}"
);

var template = config.getTemplate("test");

Populate the data model

Each data model class has a Builder class which enables you to build up the parameters of the component incrementally.

For example, the simplest invocation of the accordion component is as follows:

var accordion = Accordion.builder()
  .withId("example-id")
  .withItems(List.of())
  .build();

Render the template

Every component macro and wrapper function accepts a top-level Params object containing the data model.

This reflects the structure of the original Nunjucks templates.

In order to render the template, we use the process method of our Template instance:

var writer = new StringWriter();
template.process(Params.of(accordion), writer);

Contributing

This project is built with Gradle 8.x and requires a Java 11+ JDK.

Prerequisites

In order to run integration tests, this project makes use of a Node app, govuk-nunjucks-renderer.

It's a web app which renders Nunjucks templates via a HTTP API, in order to enable comparative testing with a diverse range of technology stacks.

To run it:

# Docker
docker run -d -p 3000:3000 ghcr.io/opencastsoftware/govuk-nunjucks-renderer:0.1.5
# Podman
podman run -d -p 3000:3000 ghcr.io/opencastsoftware/govuk-nunjucks-renderer:0.1.5

Building

To build and run tests:

./gradlew build --info

Acknowledgements

This project wouldn't exist without the work of the GOV.UK Design System and GOV.UK Frontend contributors.

License

All code in this repository is licensed under the MIT License. See LICENSE.