Back to Website
Show / Hide Table of Contents

Dynamic expressions

  • 13-14 minutes to read

To extend workflow features, you can use JavaScript code in them. This code is named dynamic expressions. They allow you to manage workflows depending on widgets conditions. For example, when a customer clicks a checkbox, then the next step becomes available. Customers select something in a drop-down list, and then a design will be changed. JS code also allows customers to see the result of personalization and approve it.

Note

In a workflow file, JS code is enclosed in double curly braces {{...}}.

Let's consider how dynamic expressions work.

Concept

As you read in the Creating and editing workflows article, workflows are defined in JSON format. This format doesn't allow you to create a self-updating workflow. Nothing will happen, when you select an option, or click a button. Adding some JS code to a workflow file, you create interactions between workflow elements vars, widgets, and so on. For example, when you click an Add image button, an upload dialog box appears, and once an image is selected and uploaded, it is placed in a design.

It works because the system reads a workflow file and tracks all the changes. Although JSON elements aren't changed but dynamic expressions return the result of a function instead. This will happen every time when the customer takes an action.

Note! You can only embed a dynamic expression in the following elements:

  • Params of a widget
  • Variables
  • Name of a step

To learn more about anatomy of each of these elements, read the Widgets article and the Structure

Let's see examples of dynamic expressions.

In this code, you can see vars and a widget with a dynamic expression. In the expression, you can see a variable var1 with its string Hello and a string world!.

{
    "vars": {
        "var1": "Hello"
    },
    "widgets": [
        {
            ...
            "params": {
                "param1": "{{vars.var1 + ' world!'}}"
            }
        }
    ]    
}

The system will resolve this expression to a string and the resulting code will appear as follows:

{
    "vars": {
        "var1": "Hello"
    },
    "widgets": [
        {
            ...
            "params": {
                "param1": "Hello world"
            }
        }
    ]    
}

The same works in key: value syntax. In this example, a variable var1 will be replaced with its value MyParamName in params.

{
    "vars": {
        "var1": "MyParamName"
    },
    "widgets": [
        {
            ...
            "params": {
                "{{vars.var1}}": 123
            }
        }
    ]    
}

The result is the following.

{
    "vars": {
        "var1": "MyParamName"
    },
    "widgets": [
        {
            ...
            "params": {
                "MyParamName": 123
            }
        }
    ]    
}

You can manipulate arrays and objects in dynamic expressions. In the following example, we place an object and an array in params. .

{
    "vars": {
        "var1": {
            "a": "b"
        }
    },
    "widgets": [
        {
            ...
            "params": {
                "param1": "{{vars.var1}}",
                "param2": "{{[vars.var1, 123]}}"
            }
        }
    ]    
}

This is the result of the expression resoluiton.

{
    "vars": {
        "var1": {
            "a": "b"
        }
    },
    "widgets": [
        {
            ...
            "params": {
                "param1": { "a": "b" },
                "param2": [{ "a": "b" }, 123]
            }
        }
    ]    
}

Syntax

You've already seen that JS code is placed in double curly brackets.

{{some JS code}}

You can embed JS code inside.

{{ 1 + 1 }}

This expression can also contain a reference to a variable.

{{ vars.design }}

Referring to widgets

Let's consider how to refer to widgets and get their values.

This example illustrates two widgets: widget1 and widget2. The widget2 contains a dynamic expression. This expression refers to the widget1 and the value of its params1.

  • The $ symbol means a scope. It allows you to refer to a widget defined in the config. To do so, use the name of a widget. For example: {{ $['my-widget-name'] }}.

  • The .param1 allows you to refer to the parameter in a widget. The . symbol means that you're already in a widget you need, and go to its parameters.

{
    "widgets": [
        {
            "name": "widget1",
            "title": "My widget",
            "type": "...",
            "params": {
                "param1": 2
            }
        },
        {
            "name": "widget2",
            "type": "...",
            "params": {
                "text": "{{$['widget1'].param1}}"  
               
            }
        }
    ]        
}

Referring to widget with selected value

Let's see another case. The main conceptual feature of some widgets are values, defining these widgets. For example, the main value of the input-text widget is the text, which a customer types.

This example illustrates two widgets: my-option and my-widget. The latter widget refers to my-option to get the value.

To get a selected value, use the .selected syntax, which can be replaced by the ._ alias.

{
  "widgets": [
    {
      "name": "my-options",
      "type": "option",
      "title": "Select the option",
      "params": {
        "type": "radio",
        "title": "This is your options:",
        "values": [
          {
            "title": "Sky blue"
          },
          {
            "title": "Scarlet"
          },
          {
            "title": "Beige"
          }
        ]
      }
    },
    {
      "name": "my-widget",
      "type": "...",
      "params": {
        "text": "{{$['my-options'].selected}}"  
      }
    }
  ]      
}

This is how an equivalent definition with ._ will look like.

{
  ...
  "widgets": [
    {
      "name": "my-widget",
      "type": "...",
      "params": {
        "text": "{{$['my-options']._}}"  
      }
    }
  ]    
}

Referring to widget itself

Sometimes you need to refer to another parameter of the same widget. To avoid cycle dependency, you can use the self alias.

In this example, you can see a widget with two params. The param1 has a boolean value, and the param2 gets the value of param1.

{
    "widgets": [
        {
            "name": "widget1",
            "title": "My widget",
            "type": "...",
            "params": {
                "param1": true,
                "param2": "{{self.param1}}"
            }
        }
    ]        
}

Referring to variables

As you have already learned, you can refer to variables defined in a separate block in a workflow file. Read the Structure article to know about defining variables.

{
    "vars": {
        "name": "my-string"
    },

    "widgets": [
        {
            "name": "widget-name",
            "type": "...",
            "params": {
              "param1": "{{vars.name}}"
            }
        }
    ]
}

You can also refer to variables containing arrays or compound objects.

Variables can be used not only in widget parameters. For example, you can manage the name of a step.

{
  "vars": {
    "condition": true,
    "ifTrue": "True",
    "ifFalse": "False"
  },
  "steps": [
    {
      "name": "{{ vars.condition ? vars.ifTrue : vars.ifFalse}}",
      "mainPanel": {...}
      }
    },
    {
      "name": "{{ !vars.condition ? vars.ifTrue : vars.ifFalse}}",
      "mainPanel": {...}
    }
  ]
}

Referring to attributes

As in case of variables, you can refer to attributes by using the product object. The following example is taken from a real workflow file.

{
    "attributes": [
        {
            "name": "Design",
            "type": "single-asset",
            "assetType": "design"
        }
    ],
    "vars": {
        "design": "{{product.attributes?.find(x=>x.name==='Design')?.value}}",
    }
}

Expressions

Dynamic expressions can also use some JavaScript expressions through the {{#...}} syntax. Let's see the examples.

Conditions (#if/#elseif/#else)

The #if/#elseif/#else expression adds a conditional logic to your workflow. It looks like this:

{
    "{{#if somevalue}}": "value-1",
    "{{#elseif anothervalue}}": "value-2",
    "{{#else}}": "value-3"
}

Depending on the condition passed to the expression, it will return different result. In the example above, if somevalue is true, the entire object containing this expression will be replaced by value-1.

If somevalue is false, but anothervalue is true, then the object will be resolved to value-2.Finally, if both the conditions are false, the result will be value-3.

You may use any JavaScript instead of somevalue or anothervalue, just like in regular dynamic expressions. In the following example, the result will depend on the var1 value.

{
    "vars": {
        "var1": ...
    },
    "widgets": [
        {
            ...,
            "params": {
                "param1": {
                    "{{#if vars.var1 > 0}}": "var1 is positive",
                    "{{#elseif vars.var1 = 0}}": "var1 is zero",
                    "{{#else}}": "var1 is negative"
                }
            }
        }
    ]
}

Let's see the result. if vars.var1 is 10, the first condition will be true and will return the var1 is positive string.

{
    "vars": {
        "var1": 10
    },
    "widgets": [
        {
            ...,
            "params": {
                "param1": "var1 is positive"
            }
        }
    ]
}

If vars.var1 is 0, the second condition is met:

{
    "vars": {
        "var1": 0
    },
    "widgets": [
        {
            ...,
            "params": {
                "param1": "var1 is zero"
            }
        }
    ]
}

If vars.var1 is -10, the value will be taken from the third expression:

{
    "vars": {
        "var1": -10
    },
    "widgets": [
        {
            ...,
            "params": {
                "param1": "var1 is negative"
            }
        }
    ]
}

It is possible to omit {{#elseif}} or use many of them.

You can also omit {{#else}}, however, when the condition is false, the result will be an undefined value, which is almost always undesired.

Each as

The each expression returns an array formed from element values.

"names": {
   "{{ #each [{first: 'John', last: 'Lennon'}, {first: 'Paul', last: 'McCartney'} as person ] }}": "{{ person.first + ' ' + person.last }}"
}

The result will be as follows:

"names": [ "John Lennon", "Paul McCartney" ]

Let's see another example.

"names": {
   "{{ #each [{id: 0, name: 'Chicago Bears'}, {id: 1, name: 'Washington Redskins'}] as item }}": {
         "id": "{{ team.id }}",
         "title": "{{ team.name }}",
         "price": 0
    }
}

This expression will be resolved to the names array.

"names": [
  {
     id: 0,
     title: "Chicago Bears",
     price: 0
  },
  {
     id: 1,
     title: "Washington Redskins",
     price: 0
  }
]

Let's see how this expression can be used in a real workflow file. In the following example, the slider widget displays proof images and is used at the Approval step.

{
    "name": "preview",
    "type": "slider",
    "params": {
        "style": {
            "--au-widget-background": "#eee",
            "--au-widget-padding": "8px"
        },
        "direction": "tile",
        "rows": 1,
        "columns": "{{Math.min(2,$['editor'].proofImageUrls.length)}}",
        "containerColor": "#eee",
        "images": {
            "{{#each $['editor'].proofImageUrls.map(s=>s[0]) as imageUrl }}": {
        "url": "{{imageUrl}}"
            }
        }
    }
}

In this example, slider shows the result of personalization. It doesn't know how many proof images have been created.

"images": {
    "{{#each $['editor'].proofImageUrls.map(s=>s[0]) as imageUrl }}": {
        "url": "{{imageUrl}}"
    }
}

Another example illustrates how you can populate an array of option widget values with values defined in vars.

{
    "vars": {
        "list": {
             ['Apples', 'Orange', 'Pear']
        }
    },
    "widgets": [
        {
            "type": "option",
            "name": "my-options",
            "params": {
                "type": "list",
                "title": "Select an item",
                "values": {
                    "{{#each vars.list }}": {
                        "title": "{{`value=${item}, index=${index+1}`}}", 
                        "index": "{{index}}",
                        "value": "{{item}}"
                    }
                }
            }
        }
    ]
}

With as

This expression allows you to create a local variable that will be available in the following part of code.

"{{#with vars.layouts.find((layout)=>layout.id === $['layouts']._.id) as curLayout}}": {
   "name": "{{curLayout.name}}",
   "teamLimit": "{{curLayout.teams}}"
}

When any changes occur, such a variable will be automatically reevaluated. In the following example from a real workflow file, the productSize will be changed as soon as you change the browser width or height.

{
    "vars": {"sizes": ["Small", "Medium"]},
    ...
    "widgets": [
        ...
        {
            ...
            "params": {
                "...": {
                    "{{ #with $['height-value']._ <= 387 || $['width-value']._ <= 387 ? 'Small' : $['height-value']._ <= 1000 && $['width-value']._ <= 1200 ? 'Medium' : 'Large' as productSize }}": {
                        "title": "{{ productSize }}",
                        "isSupportedSize": "{{ vars.sizes.some(x => x == productSize) }}"
                    }
                }
            }
        }
    ]
}

You can also use this expression in nested objects.

"props": {
    "{{#with 1 + 1 as myValue}}": {
        "value2": {
            "{{#with 2 + 2 as myValue}}": "{{myValue}}"
        },
        "value3": "{{myValue}}"
    }
}

Escape

The #escape expression allows you to apply escaping and use double curly brackets as is without the resolution.

For example, you must use it in the Design Editor for VPD products with interpolated strings. When you enclose such strings in double curly brackets, the system will try to resolve {{UserName}} as a dynamic expression, but should not.

{
    "Header": "Hello {{#escape UserName}}"
}

The result will be as a string.

{
    "Header": "Hello {{UserName}}"
}

This expression is constantly used in products, where you add variable text to. Once a product is designed, these text elements will be located, and a list of their values will populate in a table. Then, a single product will be rendered for every table row.

The example of VDP product with text fields.

This is how you can define a Variable Text button in the editor widget to implement this scenario.

{
    "translationKey": "Variable Text",
    "translationKeyTitle": "Add a variable text to the canvas",
    "iconClass": "fa fa-check-square-o",
    "action": "text",
    "itemConfig": {
        "name": "Custom Text",
        "text": "{{#escape Custom Text}}",
        "isVariable": true,
        ...
    }
}

Merge

This expression merge objects.

{
    "{{#merge}}": [
        {
            "name": "John",
            "company": "FooBar, Inc."
        },
        {
            "age": 35
        },
        {
            "name": "John Silver",
            "pictures": ["1.jpg", "2.jpg"]
        }
    ]
}

This expression will result in the following object.

{
    "name": "John Silver", // note, we have overwritten the name
    "company": "FooBar, Inc.",
    "age": 35,
    "pictures": ["1.jpg", "2.jpg"]
}

Concat

The #concat expression concatenates several arrays into one.

In this example, there are one array with two arrays inside. The concat expression unites two arrays in one.

{
    "{{#concat}}": [[1,2,3], [4,5,6]]
}

This results in [1, 2, 3, 4, 5, 6].

You may have an array that contains a nested array and an item inside.

{
    "{{#concat}}": [[1,2,3], 100]
}

In this case, #concat returns the following array: [1,2,3,100].

Now, let's look at an example from a real workflow file. This is a part of the editor widget defining toolbox buttons.

"Toolbox": {
    "buttons": {
        "{{#concat}}": [
            [
                {
                    "translationKey": "Toolbox.TEXT",
                    "translationKeyTitle": "Toolbox.TITLE_ADD_TEXT",
                    "iconClass": "cc-icon-add-text",
                    "buttons": [
                        "Text",
                        "BoundedText"
                    ]
                },
                {
                    "translationKey": "Toolbox.SHAPE",
                    "translationKeyTitle": "Toolbox.TITLE_ADD_SHAPE",
                    "iconClass": "cc-icon-add-shape",
                    "buttons": [
                        "Line",
                        "Rectangle",
                        "Ellipse"
                    ]
                },
                "QrCode",
                {
                    "action": "Image",
                    "translationKeyTitle": "Upload an Image",
                    "iconClass": "cc-icon-uploadable",
                    "tabs": [
                          "My files"
                    ]
                }
            ]
        ]
    }
}

Flatten

The #flatten expression transforms an array to object properties. Why you may need this?

Let's imagine that you have a list of baseball teams:

{
    "vars": {
        "teams": [ "Los Angeles Dodgers", "New York Yankees", "Houston Astros"]
    }
}

Now let's assume that you need to prepare the following structure, for example, to send it to the server using the ajax widget.

{
    "Team 1": {
        "name": "Los Angeles Dodgers"
    },
    "Team 2": {
        "name": "New York Yankees"
    },
    "Team 3": {
        "name": "Houston Astros"
    },
    "Heading": "2023 MBL",
    "Background": "green-field-01.jpg"
}

You may try to implement this through a combination of #merge, #each or use complicated logic which would involve a lot of #if statements. However, it would be quite challenging.

The #flatten comes to rescue. You can write something like this:

{
    "{{#flatten}}": {
        "_": {
            "{{#each vars.teams as team}}": {
                "{{`Team ${index + 1}`}}": {
                    "name": "{{team}}"
                }
            }
        },
        "Heading": "2023 MBL",
        "Background": "green-field-01.jpg"
    }
}

It will yield with the same result as described above.

The #flatten scans the object in the right side of the expression. If it meets a regular property which holds a string, like Heading or Background in our example, it leaves them in a result object as is. However, if it meets a property holding an array of objects, it will pull each property of each element and add it to the resulting object.

The name of a property containing such arrays is ignored, that's why you may write whatever you want, for example, _ or any other string.

Any non-objects in such array are ignored.

The #flatten processes only one level. If it meets an object that contains arrays, they are copied to the result as is.

Function

You can implement handling the onClick, onChange, onActivate, and other events in your workflow file. They will execute code only when an event happens. For example, you add code, which is executed after clicking a button.

The syntax is the following:

{
    "onClick": "{{#function <some js expression> }}"
}

It's important that <some js expression> returns a value. For example, this expression is true.

{
    "onClick": "{{#function $['editor'].getProofImages() }}"
}

The following expression returns an error because console.log is a statement and doesn't return a value.

{
    "onClick": "{{#function console.log('Hello world') }}"
}

If you want to implement several actions in a handler, you can write them as an array of functions.

{
    "onClick": [
        "{{#function <some js expression 1> }}",
        "{{#function <some js expression 2> }}"
    ]
}

The system will execute them one by one. If a function is asynchronous, the next function will run after the previous function is executed.

Debugging

When you are writing a workflow file, something can go wrong. You can use the following keywords for troubleshooting.

@log

You can add @log to the beginning of a dynamic expression to output its value:

The usage of @log in a browser console.

{
    "name": "my-checkbox",
    "type": "checkbox",
    "params": {
        "prompt": "This is my checkbox.",
        "value": false
    }
},
{
    "name": "my-button",
    "type": "button",
    "params": {
        "text": "Click me",
        "enabled": "{{ @log $['my-checkbox']._}}"
    }
}

Every time the system resolves this expression, its result appears in the browser console. To open this console, press F12 and click the Console tab.

@debug

The @debug keyword works like a breakpoint for a dymanic expression. You can add it to the beginning of a dynamic expression. Every time the system implements a dynamic expression, a browser will stop at this Java Script code. You can debug the code in the debugger window.

The usage of @debug keyword in a browser console.

{
    "name": "my-checkbox",
    "type": "checkbox",
    "params": {
        "prompt": "This is my checkbox.",
        "value": false
    }
},
{
    "name": "my-button",
    "type": "button",
    "params": {
        "text": "Click me",
        "enabled": "{{ @debug $['my-checkbox']._}}"
    }
}

Now you can learn how to use dynamic expressions in widgets. To do so, read the Widgets articles.

Was this page helpful?
Thanks for your feedback!
Back to top Copyright © 2001–2022 Aurigma, Inc. All rights reserved.
Loading...
    Thank for your vote
    Your opinion is important to us. To provide details, send feedback.
    Send feedback