Looping in Workflows

Overview

Loops are used when you must repeat a set of the same nodes a specific number of times.

Note: Task loops do not have the concept of a for loop or do while loop. Instead, iterations of a loop happen essentially all at once. For that type of functionality, consider using a recursive routine.

Loops are comprised of two different nodes, Loop Head and Loop Tail.

Loop Head

The Loop Head node controls how often the loop is processed and what data is used with the XML or JSON parameters. The Loop Head is also referred to as Loop Begin in some documentation.

The Loop Head node includes the following parameters:

  • Data Source: The source containing the data to create each task in the loop.
  • Loop Path: The XPath or JSONPath statement that indicates what data records to process.
  • Variable Name: The local variable name represents the data used in loop tasks. This optional parameter is sometimes used to identify a variable from the XML or JSON to be used in each loop iteration, but builders typically use the output of the loop head node (<%=@results['Loop Head']['Value']%>) from the parameter list instead.
LoopHead1

Example of a Loop Head node

Loop Tail

The Loop Tail controls where the end of the loop is, and when the loop is exited. The Loop tail is also referred to as Loop End in some documentation.

Settings in the Loop Tail are the same as a Join node.

LoopTail1

Example of a Loop Tail node

Type selection determines when the Loop Tail node completes and progresses farther through the tree. The following options are available:

  • All: Every loop instance processes to the Loop Tail completely.
  • Any: No other instances will move forward once the first loop instance is completed.
  • Some: You must specify how many loop instances must be completed before the Loop Tail completes.

Connecting

Along with the connections within the Loop Head and the Loop Tail, the Loop Head and the Loop Tail must be directly connected. This connection tells the engine which nodes are between the Loop Head and Loop Tail and includes those nodes in an individual loop instance.

LoopExample

Example of a simple loop

In this loop, you can see that the Loop Head is connected directly to the Loop Tail, along with the node that runs as part of the loop.

Examples

Let's create a loop using the loop configuration above and a list of names as the Test Input value. In this example, the loop would process three times, passing "han solo", "darth vader", and "leia organa" as individual outputs.

Loop Head

In XML, the Test Input value for the Loop Head might look like this:

   <user>
    <id>han solo</id>
    <id>darth vader</id>
    <id>leia organa</id>
   </user>

In JSON, it might look like this:

["han solo","darth vader","leia organa"]

For both types, we will use the output of the Test Input node (<%=@results['Test Input']['Value']%>) as the Data Source value.

Loop Path

The Loop Path should use XSL for XML content (in this example, we would use an XSL value of //user/id).

For JSON, we would use a Loop Path of $.[*].

Note: This online path evaluator can help you find the correct path you are looking for.

Because the Loop Tail is set to All, the loop will run for every value defined in the Data Source.

Run Results

After running the loop, we received the following results:

The important thing to notice on this run is that after the tasks for the initial Loop Head and Loop Tail, there are individual Loop Head tasks and triggers for each instance of the loop run. The tasks are differentiated by the index number of their execution (for example, the first is [0]).

Using Loop Values

Accessing Node Results From Within the Loop

In the Task Tree, results from a node can be accessed and used by another node or a connector.  If both nodes are within a loop, the results of the source node will return a value from only within the context of that same loop iteration. 

Content of Echo Node ParameterResult
<%=@results['Get Email'].inspect%>{“output”=>”[email protected]”}
<%=@results['Get Email']['output']%>[email protected]

Accessing Node Results From Outside the Loop

When accessing the results from outside the loop, the same code that was used inside the loop, the source node returns more complex Hashof values.  The hash now contains the values from each loop iteration.

Content of Echo Node ParameterResult
<%=@results['Get Email'].inspect%>{0=>{"output"=>"[email protected]"}, 1=>{"output"=>""}, 2=>{"output"=>"[email protected]"}, 3=>{"output"=>"[email protected]"}}
<%=@results['Get Email']['output']%>empty

It is also important to note that if you have a routine inside the loop, its output isn't quite the same as a handler's. Outside the loop, a routine's results would also seem empty without the inspect, but with the inspect they look as follows:

Content of Echo Node ParameterResult
<%=@results['Individual Approval Routine'].inspect%>{0=>{"Run Id"=>16430, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33544}, 1=>{"Run Id"=>16431, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33545, "Approval Response"=>"Approve", "Denial Comments"=>nil, "Approver Login ID"=>"aramey"}, 2=>{"Run Id"=>16432, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33546}}

So they must be accessed slightly differently (as referenced below).

Using the Results

In the examples above, the only immediately usable results are returned from <%=@results['Get Email']['output']%> and only when accessing a result from within the loop.  When used outside the loop no value is returned.

Additional efforts are needed to access the results from outside the loop, but there is also increased usefulness. Knowing that it returns a hash, we can use Ruby methods to evaluate it and manipulate it as we would any other Ruby hash. Now, other Ruby Methods can be used to manipulate the result set.

All nodes return a string; therefore, an Array will be converted to a string resulting from a node. The inspect method, used frequently below, converts the array into a string representation, which is identifiable as an array before the node naturally converts it to a string. The quotation marks and greater than/less than symbols are encoded when viewed in the Task Management Console. The examples have decoded the symbols back to the original values to better display the results.

Ruby MethodContent of Echo Node ParameterDescriptionResult
inspect<%=
@results['Get Email'].inspect
%>
Using the inspect method reveals that @results['Get Email'] returns a hash that combines all values for the Get Email node from within the loop.{0=>{"output"=>"[email protected]"}, 1=>{"output"=>""}, 2=>{"output"=>"[email protected]"}, 3=>{"output"=>"[email protected]"}}
collect<%=
@results['Get Email'].collect{ |key,values|
values['output']
}.inspect
%>
The collect method invokes block once for each element of self. Creates a new array containing the values returned by the block.  The contents of the block use "values['output']" and return the "output" value for each element.["[email protected]", "[email protected]", "", "[email protected]"]
collect with out inspect<%=
@results['Get Email'].collect{|key,values|
values['output']
}
%>
Without the inspect to create a string representation of the array the values in the array are combined into one value w/o and separator.[email protected]@[email protected]
compact<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.inspect
%>
By using an if statement in the block to ignore the empty array objects and then using the compact method, any empty results are removed from the results.["[email protected]", "[email protected]", "[email protected]"]
join<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.join('; ')
%>
The inspect's results are not useful in a Task tree to other nodes. By using the join method, a list of all results joined with a value of "; ".[email protected]; [email protected]; [email protected]
first<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.first
%>
Returns the first value in the hash.  If a result comes from a deferred node, this can be used to get the value of the node which was first completed.[email protected]
include?<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.include?('[email protected]')
%>
include? can be used to evaluate is a specific value is included in the results.true
count<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.count('[email protected]')
%>
The count method can be used to count the amount of times when a specific value exists in an array1
length<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.length
%>
The length method can be used to display the length of the array.3