I know I know, I am way behind on my InfoPath Integration series that I started several weeks back, but still, I'd like to go off on a tangent here on a somewhat tricky methodology for accomplishing something that is probably pretty common in a lot of workflow scenarios. That scenario is this: Lets say we have a list of zero to many items, where it is unknown how many items may be in said list but we need to do some sort of workflow process on each item in the list. Specifically, we will use an InfoPath template with a repeating table as our example. In this InfoPath template, the user may add as many rows as they like to the repeating table which we will call 'Child' for simplicity sake. Each 'Child' row has a 'Name' and a 'Description' field. In our workflow process, we need to loop through each 'Child' row and create a smart object for it, so it is persisted to a data store.
I am going to make a couple of assumptions here before I get started with my example. I am assuming there is an InfoPath form with the 'Child' repeating table present as defined above, and there is a simple smart object defined with a 'Name' and 'Description' property. I am also assuming that the InfoPath form is integrated with the process which we will be using as an example, with the process being started on submission of the Infopath form.
Given our assumptions, the first step to simulating a For-Each loop is to drag out a Smart Object event which is going to be our action taken on each iteration of the loop. Map this Smart Object event to our 'Child' smart object's 'Create' method. Leave the input and output parameters blank for now, we'll need to extract them in our loop. What we are doing here is providing the action to take place on every iteration of the loop, which in this case is to create a 'Child' smart object:
Now we need to go about telling the activity that it is to be executed for each child row in the InfoPath form. It is not very intuitive to do this, which is why I'm blogging about it in great detail. I'll try and explain each step as best I can. The first step is to bring up the activity's 'Destination Rule' wizard:
Once the rule wizard starts, we actually need to GO BACK a screen (Click the 'Back' button), so we can tell the wizard we want to run in advanced mode:
Now that we are in advanced mode, click 'Next' and we are presented with a list of options for specifying when instances of this activity should be created. We want to specify 'Plan per slot (no destinations)', which means that this activity is not going to an actual destination and that we will be creating x number of instances of the activity:
Click 'Next' and we are presented with a special screen based on our previous selection of 'no destinations'. In this screen, we are being asked to specify how many instances of the activity we will be creating. The first option assumes we know the exact number of slots to create, which we don't since we are relying on the InfoPath repeating table to tell us this. The second option allows us to specify any repeating field (be it a repeating xml node or a smart object list) to iterate through, creating an instance of the activity for each iteration. This is exactly what we want, so select this option. In the field entry, specify the repeating 'Child' node from our InfoPath form which instructs it to create an activity instance for each row in this repeating table:
Click 'Finish' to finalize the destination rule wizard. What would happen now if we ran this process is that for each 'Child' row inserted into the InfoPath form, there will be a blank smart object record inserted into the Smart Box database. Why will the smart object values be blank? We never mapped the 'Create' method's input parameters to the values being extracted from the child node iteration. Now there is good news, bad news and worse news here: The good news is that all of the data from the child node of the current iteration is available in the 'ActivityDestinationInstance.InstanceData' field:
The bad news is that, as you can see, there is no design-time context for the underlying data in this field so we can't use the interface to map values to the Create method. No problem, we can always create our data fields manually in server code, parsing the individual data fields from the InstanceData into our own data fields. Here is the worse news. The InstanceData field concatenates the data of its repeating node into one giant non-delimited string, making it very difficult to extract individual values. I am currently working with the product support team to see if there is a better way to get at our iteration data, but for now this is only a viable solution if you either have just one child value or if you can make assumptions on data locations and sizes in your InstanceData. I'll post an update here if and when I learn a more effective way to get at the iteration data.
UPDATE - forgot to come back and revisit this (among other things), but the best approach I found to getting the appropriate data for your current iteration is to keep a process instance data field as an index, incrementing it in a server code event. Within that same event (or another one, I guess), you can use that index to get the node directly from the xml data field for that index and stick it into an activity instance xml node to use in your smart object's 'Create' method. Here is a quick example of what my server code event within the for-each activity looks like ('Policy' is the name of the repeating node):
System.Xml.XmlDocument docSource = new System.Xml.XmlDocument();
docSource.LoadXml(K2.ProcessInstance.XmlFields[
"Client Services Request Form"].Value);System.Xml.XmlNamespaceManager nsMgr = new System.Xml.XmlNamespaceManager(docSource.NameTable);
nsMgr.AddNamespace(
"my", docSource.DocumentElement.GetNamespaceOfPrefix("my"));int policyIteration = ((int)K2.ProcessInstance.DataFields["Policy Iteration Index"].Value);
System.Xml.
XmlNode ndePolicy = docSource.SelectNodes("//my:Policy", nsMgr)[policyIteration];K2.ActivityInstanceDestination.XmlFields["Policy"].Value = ndePolicy.OuterXml;
K2.ProcessInstance.DataFields[
"Policy Iteration Index"].Value = policyIteration + 1;