Sometimes during workflow development, the tools we have discussed up to this point will not be enough to implement the required logic. Typically, this occurs whenever you need to manipulate the raw input data, e.g., before comparing it in an expression or before saving it in a context variable. In these cases, the option to use our embedded JavaScript (JS) Engine in the workflow markup should offer a viable solution.

Any use of JavaScript in a workflow should always be inside a <![CDATA[ ... ]]> tag to avoid XML parser errors. A JS scope is opened and closed using ?{ ... }?. You can access context variables from inside the JS scope using context.variable_name. To access event data you would similarly use e.g., event.field:

<setvar id="add_digit_from_speech_command">
    <context_of>workflow</context_of>
    <context_update>
        <param name="number" type="long"><![CDATA[ ?{ context.number + Number(event.command.substr(event.command.length - 1, event.command.length)) }? ]]></param>
    </context_update>
</setvar>

As you can see in the above example, you can write arbitrary native JS. This can also span multiple lines and more complex handling. You will see some more examples in the next article.

Embedded Functions

There are a few embedded functions that you can use directly in the workflow XML without opening a JS context. Some of these examples are regularly used in specific use cases:

  • exists: exists(#{shelve})
  • toUppercase: toUppercase( '#{material_name}' )
  • toLowercase: toLowercase( '#{material_name}')
  • trim: trim( #{material_name} )
  • contains: contains( #{first_code}, #{second_code})
  • startsWith: startsWith( #{first_code}, #{second_code})
  • endsWith: endsWith( #{first_code}, #{second_code})
  • equals: equals( #{first_code}, #{second_code})
  • substring: substring( #{material_name}, 0, 3)

The example below contains the toUpperCasetrim,and substring functions.

<rule id="speak_material">
    <expression><![CDATA[ #{event:command} == toUpperCase('#{material_name}') ]]></expression>
    <actions>
        <setvar id="set_material_type">
            <context_update>
                <param name="material_type" type="string">trim(substring(#{material_name}, 0, 3))</param>
            </context_update>
        </setvar>
    </actions>
</rule>

The above is a typical example, because speech commands always get emitted in all caps. If you want to compare a speech command to some content in your workflow, you should always transform it to uppercase as well.

Tips & tricks: Do not use white space for better code readability within a param tag of type string, as it would become part of the string you are manipulating. For example, <param name="material_type" type="string">trim(substring(#{material_name}, 0, 3)) </param> would result in e.g. MAT_ (where _ is white space) even though you used trim.

Example

Let's look at a few more examples of use cases where the JS engine comes in handy:

Dynamically accessing data

Sometimes you need to access data dynamically (based on the content of another context variable):

    <setvar id="set_step">
        <context_update>
            <param name="step" type="object"><![CDATA[ ?{context.steps[context.current_step_index]}? ]]></param>
        </context_update>
    </setvar>

Conditional data manipulation

While you could also create multiple actions and rules to implement this, JS allows for brevity:

    <setvar id="set_text_color">
        <context_update>
            <param name="label_text_color" type="string"><![CDATA[ ?{ (context.urgent)? 'red' : 'black' }? ]]></param>
        </context_update>
    </setvar>

Generation and manipulation of objects

JavaScript also helps you to generate or manipulate objects. In the example below, we want to enable the worker to use speech commands to express an amount of 0-99. Each speech command needs to be added to the speech grammar separately, but using JavaScript we can shorten this down to:

<action id="add_amount_speech_commands" type="speech_modify_commands_in_grammar">
    <param name="slot">wf_editor_slot</param>
    <param name="commands"><![CDATA[ ?{
        Array.apply(null, {length: 100})
             .map(Number.call, Number)
             .map(function(e) { 
                    return { 'name': 'AMOUNT ' + (e), 'description': `AMOUNT [0-99]`}
                }
        )}? ]]></param>
    <param name="modification">add_commands</param>
</action>

In expressions

There are also use cases where JS helps in expressions. In the below example we process an event that gets emitted when a third-party device is disconnected. We use JS to search a string of MAC addresses for the MAC of the disconnected device.

<rule id="device_vanished">
    <expression><![CDATA[ #{event:command}== 'DEVICEVANISHED' && ?{context.mac.search(event.mac)>-1}?]]></expression>
    <actions>
        <action ref="back_to_start"/>
    </actions>
</rule>

Note: The usage of JavaScript is limited for expressions. The content of all expressions will be trimmed to remove all line breaks and whitespaces that could break the logic. Therefore, you cannot use multiple lines of JavaScript in expressions.

Wildcard widget

Another typical use case for JS is the wildcard widget. Since we have not introduced lay-outing yet, we will not look at an example here but the widget documentation has one.

📌Assignment

In the current version of our component, the output of our choice is all uppercase and is shown as such in the UI of the next component. Let's make it look a bit nicer:

  • Use JavaScript or embedded functions to save the resulting choice in a more natural capitalization.

 Download Component (Pre-Assignment)

Solution

Below is an exemplary solution:

<rule id="choice_made">
    <expression><![CDATA[ #{event:command} == 'APPLE' || #{event:command} == 'PEAR' ]]></expression>
    <actions>
        <setvar id="save_choice">
            <context_of>workflow</context_of>
            <context_update>
                <param name="choice" type="string"><![CDATA[ ?{ event.command.slice(0,1) + event.command.slice(1).toLowerCase() }? ]]></param>
            </context_update>
        </setvar>
        <action ref="show_confirmation_dialog"/>
    </actions>
</rule>  

 Download Component (Post-Assignment)

In the next lesson, we will finally look into how to design the user interface of a component.