Word templates
Template syntax
A Moliri template is an ordinary Word file (.docx) with commands of the form {# … #} in its text.
Moliri reads the literal text of the document (body text, table cells, headers and footers) and runs each command as a JavaScript expression. Whether the command sits in a content control, as the template editor creates it, or as plain text makes no difference to this.
The commands
| Command | Form | Effect |
|---|---|---|
| INS | {# expression #} or {# INS expression #} | Insert the result as text. The bare expression is the short form |
| EXEC | {#! code #} or {# EXEC code #} | Run code, insert nothing. Variables assigned here are visible in all following commands |
| FOR / END-FOR | {# FOR x IN list #} … {# END-FOR x #} | Repeat the content for each list element |
| IF / END-IF | {# IF condition #} … {# END-IF #} | Content only when the condition is true |
| IMAGE | {# IMAGE img(file, { width: 6 }) #} | Insert an image |
| LINK | {# LINK ({ url: `…`, label: `…` }) #} | Insert a clickable link; without label the address is shown |
| HTML | {# HTML `<b>bold</b>` #} | Embed an HTML fragment; Word converts it when the file is opened |
| ALIAS | {# ALIAS name command text #}, called as {#*name#} | Shorthand for a complete command |
Inside a command, JavaScript syntax applies. Strings in backticks and template literals
(`${…}`) work.
{#$o[`RegNr`] ?? ``#} {#! FLOAT_FORMAT = { "mask": "Number", "scale": "2", "thousandsSeparator": " ", "radix": "," }#} {# IMAGE img(eingang[$i], { width: 16 }) #} {# LINK ({ url: `https://beispiel.moliri.app/p/${p.project_id}/o/${$o.object_id}`, label: `Objekt öffnen` }) #}
The rule for loop variables
The most common source of errors. Inside a FOR loop, the loop variable is written with a $ in front:
{# FOR file IN $f #} {# IMAGE img($file, { width: 6 }) #} ← right: $file {# IMAGE img(file, { width: 6 }) #} ← wrong: file is undefined {# END-FOR file #}
This applies to every loop: FOR obj IN objects gives $obj, FOR i IN _.range(0, n) gives $i.
Every loop also provides $idx, the index of the innermost loop, starting at 0. A running number is
therefore {#$idx + 1#}.
In a table, FOR and END-FOR sit in rows of their own, FOR above the data row and END-FOR below it.
The data row is then repeated for each element. This is how object lists become tables, see Recipes . If both sit in the first and last cell of the same row, the
built-in engine repeats only the cells, not the row.
Backticks instead of quotation marks
Word likes to replace straight quotation marks with typographic ones. Moliri evens this out on export, and the field detection understands typographic quotation marks too. Backticks are still the better choice. Word never changes them, and they allow template literals at the same time.
Simple output
{#$o[`RegNr`] ?? ``#}
$o is the current object, and the field name goes in square brackets. The empty fallback after ?? makes sure that a field without a value stays empty and causes no follow-on errors.
Condition
{# IF $o[`Inventarstatus`] === `Dauerleihgabe` #} Dauerleihgabe {# END-IF #}
Loop over objects
{# FOR obj IN objects #} {# $obj[`RegNr`] ?? `` #} {# $obj[`Originaltitel`] ?? `` #} {# END-FOR obj #}
Field detection and loading
For the export, Moliri loads only the fields the template uses. It detects every field name that stands
in square brackets and quotation marks: $o[`Feld`], $obj[`Feld`] in loops and also o[`Feld`] in a grouping function. For keys without $o or a loop variable in front, only names that
match a field of the project count.
A field name that gets into the command another way is not detected, for example as a computed key or as
a string in _.groupBy(objects, `Leihgeber`). You can make such a field visible in a condition that
never runs:
{# IF false #} {# INS $o[`Leihgeber`] ?? `` #} {# END-IF #}
Moliri loads files only if the template mentions $f or files. This keeps pure data templates fast.
One document, or one per object
Moliri recognises the mode from the commands:
- Loop over the objects, one document. As soon as a
FORloop runs over the set of objects, the template is rendered exactly once with all objects. AnyFORwhose source namesobjectsor a variable assigned from it is recognised:FOR obj IN objects,FOR obj IN objects.filter(…)and also{#! gruppen = _.groupBy(objects, …) #}withFOR key IN Object.keys(gruppen). File and index loops (FOR file IN $f,FOR i IN _.range(…)) do not count. - Otherwise one document per object, then merged or packed as a ZIP.
objects without a loop does not change the mode. {#objects.length#} in a per-object template outputs
the total count and leaves it a per-object template.
If the detection gets it wrong, you can set the mode in the template editor: right-hand panel Moliri · Check, Settings tab, Document split selection with Automatic, One document with all objects and One document per object. The export dialog then shows “set manually”.
Section behaviour when merging
With one document per object, the section behaviour decides how the individual documents are joined:
| Value | Meaning |
|---|---|
template | The template’s section behaviour (default). Headers and footers, margins and breaks follow the template |
none | Merge without an extra break |
pagebreak | Insert only a plain page break |
section-continuous | Section break, continuous on the same page |
section-newpage | Section break, new page |
section-evenpage | Section break, next even page |
section-oddpage | Section break, next odd page |
zip | Do not merge, pack as a ZIP. One file per object |
The values are stable and will not be renamed. The output file is named <template name>_<YYYY-MM-DD>.docx or .zip.
Settings travel in the document
In the right-hand panel (Settings tab), the template editor saves three settings as an invisible marker in the template: As plain text (resolve content controls), Document split (object mode) and Transition between objects (section behaviour). On export, Moliri reads the marker. The template therefore brings its own presets with it, whoever uses it.
If the marker is missing, for example in a template written in Word, the following applies: content
controls are resolved to plain text, the mode is detected, and the section behaviour is template. In the
export dialog you can override merging and content controls for one run under Advanced settings.
Execution and errors
| Topic | Behaviour |
|---|---|
| Execution | Commands run as synchronous expressions, without await and async. img() works asynchronously internally; Moliri waits for it itself, and the template contains only the simple call |
| Arrow functions | Work and are common: $f.filter(f => …), sort((a, b) => …) |
| Isolation | On export and during the check, commands run in an isolated background process. Available are the helpers and the data that Moliri loads beforehand. No network (fetch, WebSocket, loading outside scripts), no browser storage (login, localStorage, IndexedDB). The check reports such a call as the error Not allowed: … |
| Run time | At most 60 seconds per run, that is per document or per check. The time for loading images does not count. After that, Moliri cancels the export: The template was stopped after 60 seconds: its code ran too long (perhaps an endless loop). |
null, undefined | Render as empty text, without stopping. Accessing something on undefined, however, throws an error, for example liste[$i].text when the list is too short. Hence ?? and ?. |
| Error in a command | Stops the export. The dialog shows Report could not be created with the message |
| Unknown fields | Not an error. The check warns. Under Field mapping in the template editor or on export you can redirect them to an existing field, see Map missing fields per project . Without a mapping the field stays empty |
| Old names | Field and key names from before the switch to English names (p.projektname instead of p.name) stay empty. The check lists them under Notes, and Rewrite puts in today’s names |
| Scope | Body text, headers and footers. Commands belong directly in the text; embedded objects stay as they are |
| Large exports | With one document per object, several processes in the browser render in parallel. A template with a loop over all objects renders in one pass |
The check in the template editor finds the same errors before the export. It always reports FOR and IF blocks that are not closed and templates that cannot be read; it checks JavaScript errors with real
sample data.
Further reading
- Template variables : which data is available
- Template functions : the ten helpers
- Recipes : patterns to copy
- Word reports : the export