Repository navigation
Templates
The cockpit shows a title for every user task and every business case, and a text the search box looks into. You can set those in your details provider, and then you write one string per language in Java. Templates are the other way: a file per text, rendered from the aggregate, kept out of the code.
If you set no title at all and configure no templates, the cockpit shows the name from the BPMN file. That works, and for a while it is enough.
-
Switching templates on
The one key, and the four spellings it accepts. -
The five templates
Which file renders which text. -
Where a template is looked for
The three directories, most specific first, and whattemplate-pathchanges about them. -
Languages
How one template serves several, and what happens without templates. -
What a template is handed
The template context, and the one dependency a date needs. -
Naming a template from your code
Setting a value which is read as a template name.
Two things: Freemarker on the classpath, and a directory to load from.
vanillabp:
cockpit:
template-loader-path: classpath:cockpit-templatesThe value says where the directory is. classpath:cockpit-templates reads the templates from the
workflow module itself, file:/etc/my-application/cockpit-templates from the file system.
classpath*: is accepted as well and loads the same templates as classpath:, because Freemarker
asks the class loader per template and that searches every jar anyway.
A value without one of those prefixes ends the boot. Version 1 read a path without a prefix as a directory of the file system, and the cockpit server read the same kind of value as a classpath location, so one text meant two things depending on which side of the cockpit you wrote it on. Now neither side guesses. The message repeats your value with both prefixes in front of it, so an application which upgrades copies the one it meant.
A directory which cannot be read ends the boot as well, with a message naming the key, rather than showing up as a missing title once the first task arrives.
| File | What it renders |
|---|---|
workflow-title.ftl |
The title of a business case, in the list of cases and above its status site |
task-title.ftl |
The title of one user task |
task-definition-title.ftl |
The name of the kind of task, which is what a list groups and filters by |
task-fulltext-search.ftl |
The text the search box matches a user task against |
workflow-fulltext-search.ftl |
The same for a business case |
None of them has to exist. A text with no template and no value from your code falls back to the BPMN name, and the two search texts simply stay empty.
Three directories are tried, most specific first, and the first file found wins:
<module>/<workflow>/<task>/task-title.ftl
<module>/<workflow>/task-title.ftl
<module>/task-title.ftl
Each of the three segments is the id of that level: the workflow module id, the BPMN process id, the task definition. So a module which configures nothing keeps its templates in a directory named after itself, and a template put directly there serves every process of the module.
template-path renames a segment. It may be given for the module, for one workflow and for one user
task, which is how two processes share one directory of templates:
vanillabp:
workflow-modules:
taxi-ride:
cockpit:
template-path: rides
workflows:
TaxiRide:
cockpit:
template-path: taxi
user-tasks:
approve:
cockpit:
template-path: approvalWith that, task-title.ftl for the task approve of TaxiRide is looked for in rides/taxi/approval, then in rides/taxi, then in rides.
A title is reported once per language named in i18n-languages. For each of them the same template
name is asked for with that language as the locale, so Freemarker picks task-title_de.ftl where it
exists and task-title.ftl otherwise. One file therefore serves every language until you need a
second one, and then you add it next to the first.
The two search texts are rendered once, in the first language of the list. Search matches text a user typed, not a text a user reads, so a second copy of it in another language would only make the index bigger.
Without templates the cockpit reports the names written in the BPMN files. Those names exist in one
language, and bpmn-description-language says which. That is why the key is mandatory when you
configure no templates and optional when you do.
Whatever your details provider passed to setTemplateContext:
@UserTaskDetailsProvider
public UserTaskDetails retrievePayment(
final Ride ride,
final PrefilledUserTaskDetails details) {
details.setTemplateContext(Map.of("customer", ride.getCustomerName()));
return details;
}Retrieve payment for ${customer}
A map, a plain object or a record all work. A record works because the object wrapper is configured to expose its accessors, and a record is usually the right shape for the small view object a title needs.
A java.time value is the one thing which needs help. Put
no.api.freemarker:freemarker-java8 on the classpath and a template can read a LocalDate or an
OffsetDateTime; without it a template touching one fails and says so in the log. Version 1
required that library from everybody. Here it is your choice, because most titles are text.
A template which fails to render leaves the text as it was and writes the reason to the log. It does not stop the report.
A value you set is first treated as the name of a template:
details.setTitle(Map.of("en", "payment-reminder.ftl"));If payment-reminder.ftl exists in one of the three directories, it is rendered and the result
becomes the title. If no template of that name exists, the value stays what it is and becomes the
title itself. So a fixed title for one task needs no file at all, and a special template for one
task needs no configuration.
