Skip to content

Repeating Content

A quote rarely has a fixed number of lines. Loops repeat a table row, paragraph or list item once for every item in a list, so your product table automatically grows or shrinks with the configuration.

How a loop works

A loop is defined by two markers:

  • [i] marks the content that is repeated, using the first item.
  • [i+1] marks where the repeated part ends. This row or paragraph is removed from the output.

You don’t need any “start” or “end” statements. Mercura compares the [i] part with the [i+1] part and repeats everything in between for every item in the list.

For example, to list every request field in the Commercial Terms tab, create a table with two rows:

FieldValue
{d.extra_fields[0].fields[i].name}{d.extra_fields[0].fields[i].value}
{d.extra_fields[0].fields[i+1]}
Result
Warranty
Payment
Delivery
Delivery Conditions

(The values are empty in the example quote, because no commercial terms were filled in.)

The second row only needs the list path followed by [i+1]. Anything after [i+1] is ignored, so {d.extra_fields[0].fields[i+1]} and {d.extra_fields[0].fields[i+1].name} do the same.

Request fields

Print all request fields of a tab, with a fallback for empty fields:

Terms
{d.extra_fields[name='Commercial Terms'].fields[i].name}{d.extra_fields[name='Commercial Terms'].fields[i].value:ifEM():show('To be agreed')}
{d.extra_fields[name='Commercial Terms'].fields[i+1]}

Configuration lines

When a quote can contain several configurations, loop over form_submissions:

ConfigurationQtyPrice
{d.form_submissions[i].form.name}{d.form_submissions[i].quantity}{d.form_submissions[i].price:formatN(2)}
{d.form_submissions[i+1]}

BoM price table

The BoM is the recommended source for price tables. It has several nested levels (tables → groups → rows → option rows), so it uses a nested loop. Each level gets its own [i] marker and its own [i+1] end row. End rows are placed innermost first.

The example below prints the first BoM table with a heading row per group:

DescriptionArticle No.Sales price
{d.form_submissions[0].form.bom.tables[0].groups[i].label}
{d.form_submissions[0].form.bom.tables[0].groups[i].rows[i].optionRows[i].optionColumns[field='name'].value}{d.form_submissions[0].form.bom.tables[0].groups[i].rows[i].optionRows[i].optionColumns[field='sku'].value}{d.form_submissions[0].form.bom.tables[0].groups[i].rows[i].optionRows[i].optionColumns[field='sales_price'].value}
{d.form_submissions[0].form.bom.tables[0].groups[i].rows[i].optionRows[i+1]}
{d.form_submissions[0].form.bom.tables[0].groups[i].rows[i+1]}
{d.form_submissions[0].form.bom.tables[0].groups[i+1]}
Result
Essentials
UR12e 36940412 398,870.00 kr
Select 400 SV Synergic DC 1.0V 79542563 78,050.00 kr
AB ROB W500 22gr, 36072505 28,440.00 kr
Software
Pulse Function 10682002 7,650.00 kr
PowerArc 10682003 2,450.00 kr
PowerArc Pulse 10682005 6,040.00 kr
Sequence Repeat 10683002 1,300.00 kr
Weaving 10950000 25,840.00 kr
Offset 10950010 13,300.00 kr
Accessories
Wear Parts Box - W500 - 1.2mm 81100260 2,870.00 kr
Torch bundle - ABIROB® W5F 36072508 13,760.00 kr
UR12E/8L KIT FOR 3PE 45050547 2,830.00 kr
Services
Basic Training Package 99906111 9,680.00 kr
Freight
Delivery incl. packaging - Basic - DK … Basic_DK_1 3,761.00 kr

How this works:

  1. Row 1 repeats once per group and prints the group label.
  2. Row 2 repeats once per option row, inside every BoM row, inside every group.
  3. Rows 3, 4 and 5 close the three loops, from the innermost (optionRows) to the outermost (groups). They are removed from the output.

Reading cells by field or by position

Each option row has one cell per BoM column. There are two ways to read a cell:

  • By field (recommended): optionColumns[field='sales_price'].value. Keeps working when you reorder columns in the BoM Builder.
  • By position: optionColumns[2].value. Shorter, but breaks when columns are reordered.

Dynamic column headings

To print the column headings from the BoM instead of typing them, repeat a column instead of a row. Place the [i] marker in one cell and the [i+1] marker in the cell directly to its right:

{d.form_submissions[0].form.bom.tables[0].headerColumns[i].label}{d.form_submissions[0].form.bom.tables[0].headerColumns[i+1].label}
Result
Description | Article No. | Sales price

Subtotal rows

Subtotal rows (type = subtotal) have subtotals instead of optionRows. The Discounts table of the example quote has two discount rows followed by a subtotal row. You can print both kinds of rows in one loop and remove the rows that don’t apply with drop:

DescriptionDiscount
{d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].label} {d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].type:ifEQ('subtotal'):drop(row)}{d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].optionRows[0].optionColumns[field='sales_price'].value}
{d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].label} {d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].type:ifNE('subtotal'):drop(row)}{d.form_submissions[0].form.bom.tables[1].groups[0].rows[i].subtotals[field='sales_price'].value}
{d.form_submissions[0].form.bom.tables[1].groups[0].rows[i+1]}
Result
Software discount -28,290.00 kr
Additional Discount -50,000.00 kr
Total discount -78,290.00 kr (bold)

The first row is removed for subtotal rows, the second row is removed for all other rows. This lets you style subtotals differently, for example in bold.

Form elements and selected options

To show the configuration itself, for example as a specification list, loop over the form elements and their selected options. This is also a nested loop:

ElementSelected option
{d.form_submissions[0].form.data.elements[i].label}{d.form_submissions[0].form.data.elements[i].options[i].label}
{d.form_submissions[0].form.data.elements[i].options[i+1]}
{d.form_submissions[0].form.data.elements[i+1]}
Result (first lines)
Solution CoWelder Basic
Robot UR12e
Do you want to deselect the robot? No
Welding method MIG
…

The element label is repeated for every selected option, so an element with three selected options produces three rows. Elements without options (free inputs such as Software discount in %) have no options list, so they produce no rows here. Print their value in a separate loop filtered on type='element-without-options'.

Filtering

Add conditions after i to only repeat the items that match. The syntax is the same as searching, but inside a loop all matching items are repeated:

{d.list[i, condition].field}
{d.list[i+1, condition]}

Examples with the example quote:

GoalLoop markers
Only the Software stepelements[i, groupLabel='Software'] … elements[i+1, groupLabel='Software']
Only elements with selectable optionselements[i, type='element-with-options'] … elements[i+1, type='element-with-options']
Skip “No” answers to yes/no questionsoptions[i, label!='No'] … options[i+1, label!='No']
Only options that cost somethingoptions[i, price>0] … options[i+1, price>0]
Only discount linesoptions[i, price<0] … options[i+1, price<0]
Only options with an imageoptions[i, image!=''] … options[i+1, image!='']
Only accessoriesoptions[i, productProperties.type='Accessories'] … options[i+1, productProperties.type='Accessories']
The first 3 itemsoptions[i, i<3] … options[i+1, i<3]
Everything except the last itemoptions[i, i!=-1] … options[i+1, i!=-1]

A complete example: list the software options with their prices.

SoftwarePrice
{d.form_submissions[0].form.data.elements[i, groupLabel='Software'].options[i, price>0].label}{d.form_submissions[0].form.data.elements[i, groupLabel='Software'].options[i, price>0].price:formatN(2)}
{d.form_submissions[0].form.data.elements[i, groupLabel='Software'].options[i+1, price>0]}
{d.form_submissions[0].form.data.elements[i+1, groupLabel='Software']}
Result
Pulse Function 7,650.00
PowerArc 2,450.00
PowerArc Pulse 6,040.00
Sequence Repeat 1,300.00
Weaving 25,840.00
Offset 13,300.00

Sorting

To sort the repeated items, put the field to sort by before i. Sorting is ascending (lowest first):

OptionPrice
{d.form_submissions[0].form.data.elements[label='Software for PowerSource'].options[price, i].label}{d.form_submissions[0].form.data.elements[label='Software for PowerSource'].options[price, i].price}
{d.form_submissions[0].form.data.elements[label='Software for PowerSource'].options[price+1, i+1]}
Result
Sequence Repeat 1300
PowerArc 2450
PowerArc Pulse 6040
Pulse Function 7650

You can sort by several fields: [groupLabel, label, i] … [groupLabel+1, label+1, i+1].

Showing each value once (distinct)

Use a field name instead of i to print every distinct value only once. The values are printed in alphabetical order. For example, list the steps (groups) used in the form:

Steps
{d.form_submissions[0].form.data.elements[groupLabel].groupLabel}
{d.form_submissions[0].form.data.elements[groupLabel+1]}
Result
Accessories
Freight
PowerSource
Quote Specification
Services
Software
Solution
Table
Torch

Product properties

productProperties is a set of name/value pairs rather than a list. Loop over it with .att for the property name and .val for its value:

PropertyValue
{d.form_submissions[0].form.data.elements[label='Robot'].options[0].productProperties[i].att}{d.form_submissions[0].form.data.elements[label='Robot'].options[0].productProperties[i].val}
{d.form_submissions[0].form.data.elements[label='Robot'].options[0].productProperties[i+1]}
Result
default-selected Default Track
solution MotionPlus
type Robot

Lists as a single line of text

When you don’t need a table, you can join the values of a list into one line of text:

Selected software: {d.form_submissions[0].form.data.elements[].options[productProperties.type='Software'].label:aggStr(', ')}

See Calculations for more.

Rules for loops

  • Every [i] needs a matching [i+1]. A missing end marker causes an error.

  • Close nested loops innermost first. For three levels you need three end rows.

  • Never mix two different lists in one loop. Each loop’s [i] and [i+1] must stay together:

    Wrong: {d.listA[i].name} {d.listB[i].name}
    {d.listA[i+1]} {d.listB[i+1]}
    Right: {d.listA[i].name}
    {d.listA[i+1]}
    {d.listB[i].name}
    {d.listB[i+1]}
  • Keep a loop within one section. In Word, a loop can’t start in the body and end in the header or footer.

  • Avoid floating images and text boxes inside loops. Set images to In line with text. See Word templates.