Question

how to pass comments from each approver to k2 process

  • 10 October 2008
  • 33 replies
  • 468 views

Hi


Worklist is sent to multiple approvers and each approver can fill their comments and say approve or reject. if there are multiple approvers(destination users), how to pass the comments from each approver to K2 Process?
 
 


 


33 replies

There are several ways to do this:


1.  Use a SmartObject.  I typically create one that has an autonumber key, userid, date/time, and comment, and a field used as a foreign key to the process ID or some other field that is relevant to your process.  Then on the ASPX page or InfoPath form I insert the comment into the SmartObject.  Using this approach is also really handy if you need to report on comments later.


2.  Create an ActivityInstanceDestination datafield.  Right-click on the activity and add a new data field, but don't click the Shared checkbox.  Each slot will get its own copy of the datafield.  It will be logged (if you check the box) but if you want to do something else with it you will have to do it before the activity ends.


I ususally use approach #1. 


David

I use infopath forms for my processes, and wanted a method of creating a history that could be stored in the infopath form itself.

To achieve this, I create an xml process datafield, which I copy the comments into from the Infopath form using a code activity. The code activity runs for each desination user. Once all destination users have completed the task, the next step in the process has a code activity which copies all of the comments from the process xml field, and creates entries in the infopath form.

DavidL has a fair bit more experience in K2 than me, so his method is probably better (I don't really have my head around smartobjects), however I thought you might be interested in a few different approaches.

David: Thanks for your reply. I tried second approach ( Create an ActivityInstanceDestination datafield).


I need to insert each approvers comments(ASPX Page) to sharepoint list after he/she say approve or reject, but ActivityInstanceDestination datafield value becomes empty after executing ApprovedAction.Execute().


Could you please give me more detailed explanation on approach # 2? 


 


Arduk: Thanks for your reply.I am not able to get how you are doing it. Could you please give me some more explanation? 

Hi Arduk,


I need to capture the comments of several approvers of an aspx page and pass them to K2 process. Once all the approvers approve it then I need to go with the next step.


I wanted to know how exactly you are using the xml datafield. How you are adding the comments for each user and passing them to the K2 process and storing it until all the approvers have finished with the approval. Where is the code activity written and under which event?


Could you kindly explain me with a very small example.


Thanks in advance!!!!!!!!! 


 

OK, so just to ensure we are using the same terminology, an Activity is the container in your K2 workspace, and it can contain multiple events....

First off, I create a process level xml data field "tempApprovalStatus", which has the following structure:
<approval>
  <history>
    <username></username>
    <statusSelected></statusSelected>
    <comments></comments>
  </history>
</approval>
NOTE: the History node is a repeating node.

Now I have an activity which has 2 events in it:
1. An infopath client event in it - this is the event that actually collects the information from the user
2. A Default Server Event (Code) - this activity takes the information that is collected in the form by each user, and adds a history node, including the subnodes to the xml process field. Each user that the activity is assigned to will then have a task assigned to them. When they complete the task, the code event saves their values to the process level xml field. The result is that there will be a "history" node structure for each person who completes the task

I then have a second activity which has a single code event in it. The code event reads through the "tempApprovalStatus" process xml field, and creates an xml fragment which is then inserted into my infopath form to form a repeating table.

The result is that I end up with an infopath form that stores all of the comments history within itself.

Hope this helps.

Hi Arduk,


When there are several approvers, the comments are updated for the last user only.


The tempApprovalStatus should be a Process level or Activity level field.


Can you explain how the history node is appended from each user.


Is it in the second server code activity, the  "tempApprovalStatus" field is looped for each user and the comments are extracted.


I'm able to get the comments but only for the last user.


More explanation is needed in the second point.


Kindly comment.


Thanks in advance!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


 

Please read through my previous post carefully, as it answers most of your questions - I will answer them again below, and provide a bit more detail where I can...


Q. The tempApprovalStatus should be a Process level or Activity level field
A. tempApprovalStatus is a PROCESS LEVEL xml field


Q. Can you explain how the history node is appended from each user
A. The Default Server Event (code) event that is in the first activity will run for each destination user. It is in this event that I am creating the <history> nodeset, and appending it to the tempApprovalStatus PROCESS LEVEL xml field
The basics of the code I use to do this is below:

Hope this helps.


 


// This section reads in the data from the infopath form which I am using to collect the information


System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();


xmlDoc.LoadXml(K2.ActivityInstanceDestination.XmlFields["MyIPForm"].Value); // this loads the xml from my infopath form


// Get the status that the user has selected


System.Xml.

XmlNamespaceManager nameSpaceMgr = new System.Xml.XmlNamespaceManager(xmlDoc.NameTable);


nameSpaceMgr.AddNamespace(

"my", xmlDoc.DocumentElement.GetNamespaceOfPrefix("my"));


System.Xml.

XmlNode node = xmlDoc.SelectSingleNode("//my:myfields/my:approval/my:tmpApprovalStatus", nameSpaceMgr);


String selectedStatus = node.InnerXml;


node.InnerXml =

""; // clear out the temporary status field. on the IP form


// Get the comments entered


node = xmlDoc.SelectSingleNode(

"//my:myfields/my:approval/my:tmpComments", nameSpaceMgr);


String comments = node.InnerXml;


node.InnerXml =

"";


// Get the user who actioned it.


node = xmlDoc.SelectSingleNode(

"//my:myfields/my:approval/my:tmpUserName", nameSpaceMgr);


String actionedBy = node.InnerXml;


node.InnerXml =

"";


// I now have all the values from the Infopath form for the user who has just completed the form, and so I can load in the process level xml field, and save the values to it


System.Xml.XmlDocument processStatus = new System.Xml.XmlDocument();

string docRS = K2.ProcessInstance.XmlFields["tempApprovalStatus"].Value.ToString();


// build the history to add to the tempApprovalStatus field


string newHistory = "";


newHistory += "<history>";


newHistory +=

"<username>" + actionedBy + "</username>";


newHistory +=

"<date>" + System.DateTime.Now.ToString("dd MMM yyyy hh:mm tt") + "</date>";


newHistory +=

"<statusSelected>" + selectedStatus + "</statusSelected>";


newHistory +=

"<comments>" + comments + "</comments>";


newHistory +=

"</history>";


// if there is already an entry in the history, append it, otherwise just finish building the approval history


if (docRS.Length > 0) {


processStatus.LoadXml(docRS);


string innerXML = processStatus.SelectSingleNode("/approval").InnerXml;


newHistory = processStatus.SelectSingleNode(

"/approval").InnerXml + newHistory;


}


// add the root node to the history xml fragment


newHistory =

"<approval>" + newHistory + "</approval>";


// Save back to the process variable....tempApprovalStatus - this will save the updated value back to the PROCESS LEVEL field
// for each user that completes. As this is run for each user, the result is a PROCESS LEVEL field which contains all of the comments from each user


K2.ProcessInstance.XmlFields[

"tempApprovalStatus"].Value = newHistory;



 

Hi arduk,

Can you tell us how to get this repeating xml process field into a repeating table in infopath??

to get the comments back into the infopath form in a repeating table, I have another activity (lets call this activity "CollateComments") after the user comments have been collected.

In the CollateComments activity, there is a server code event, which reads the values in from the process level xml field "tempApprovalStatus", and loads them into an xml document.
I then loop through each of the comments that have been added and build an xml fragment that is in the same format as the nodes of the repeating table in the infopath form.
I retrieve the innerxml of the repeating table, and add the new list of comments at the start (so that comments appear with the latest at the top)
I then save xml of the infopath document back to the process level field.

The key is getting the xml correct for the repeating table. The easiest way I have found to do this has been either to debug your code, and examine the xml, or to fill in a form (using Infopath to populate the repeating table) and then save it, and examine the xml format. Of course, if you have a good understanding of how infopath builds its forms, then you will probably be able to look at the structure, and know what you need to build.

Hope this helps....


Can u provide us a code snippet for that??
Please see the code snippet above - to create a repeating table, you are doing exactly the same thing, it is just that you need to load the xml of the infopath form from the process level field, add your repeating rows by adding the appropriate xml string (this will depend on how you have setup your form), and then save it back to the process level field.

i tried the same thing but i got schema errors. i'm trying to get the comments as well the user names into the repeating table.


process xml field                                Infopath xmlfield


Tracking Comments                          Trackingcomments(node)


History(repeating node)                      History(repeating node)


user(field)                                          user(field)


comments(field)                                comments(field)


 

I take it that the schema errors are in your IP form?

I found the key to working this out was to open the form in Infopath, and actually manually add some rows, and then save the form. I was then able to examine the source xml, and work out the correct string that I needed to reproduce, and then build that string within the code.

ok thank you, ill try that out
Since the process xml field doesnot have the default namespace of "my:". It does not get appended with the infopath and the process throws schema exceptions. How do i get around this. How do i add the namespace "my:" to the process xml field before copying it to the infopath??

Here is my full code that saves the info back to the infopath form. I do not claim that this is the best way or the only way to do it - there may be other methods which are more efficient, but this does work - clearly you will have to change the names of the nodes to represent your schema etc...Hope this is helpful

     // load the infopath document into an XML document so we can add new history nodes
     XmlDocument xmlDoc = new System.Xml.XmlDocument();
     xmlDoc.LoadXml(K2.ProcessInstance.XmlFields["FormSignoff"].Value); //.ProcessInstance
     // get the namespace for the infopath form
     XmlNamespaceManager nameSpaceMgr = new System.Xml.XmlNamespaceManager(xmlDoc.NameTable);
     nameSpaceMgr.AddNamespace("my", xmlDoc.DocumentElement.GetNamespaceOfPrefix("my"));
     // Get a history node from the infopath form so we can create a duplicate of it later, and add new nodes before it.
     System.Xml.XmlNode historyNode = xmlDoc.SelectSingleNode("//my:myFields/my:approval/my:history", nameSpaceMgr);
     // load the temporary status xml field which is used so that each activity instance doesn't get overwritten by the last one.
     XmlDocument tempStatus = new XmlDocument();
     tempStatus.LoadXml(K2.ProcessInstance.XmlFields["tempApprovalStatus"].Value.ToString());
     XmlNodeList approvalNodes = tempStatus.SelectNodes("/approval/history");
     foreach (XmlNode node in approvalNodes) {
      // create a new history node to be added to the infopath form
      System.Xml.XmlNode newHistoryNode = historyNode.Clone();
      // Set the values of each of the fields within this node...
      System.Xml.XmlNode tmp = newHistoryNode.SelectSingleNode("my:statusSelected", nameSpaceMgr);
      tmp.InnerText = node.SelectSingleNode("statusSelected").InnerText;
      // If not Accepted as is, set the minor changes required flag. This ensures we take the worst case.
      if (tmp.InnerText.ToLower() != "accepted as is") {
       K2.ProcessInstance.DataFields["minorChangesRequired"].Value = true;
      }
      tmp = newHistoryNode.SelectSingleNode("my:comments", nameSpaceMgr);
      tmp.InnerText = node.SelectSingleNode("comments").InnerText; ;
      tmp = newHistoryNode.SelectSingleNode("my:username", nameSpaceMgr);
      tmp.InnerText = node.SelectSingleNode("username").InnerText; ;
      tmp = newHistoryNode.SelectSingleNode("my:date", nameSpaceMgr);
      tmp.InnerText = node.SelectSingleNode("date").InnerText; ;
      tmp = newHistoryNode.SelectSingleNode("my:stepName", nameSpaceMgr);
      tmp.InnerText = node.SelectSingleNode("stepName").InnerText;
      // Insert the new history node before all other history nodes so it appears at the top.
      xmlDoc.SelectSingleNode("//my:myFields/my:approval", nameSpaceMgr).InsertBefore(newHistoryNode, historyNode);
      // move the history node to the one just inserted so we can keep adding nodes in reverse chronological order
      historyNode = newHistoryNode;
     }
     // clear the temporary status, comments and username fields
     // clear the status field
     System.Xml.XmlNode clearnode = xmlDoc.SelectSingleNode("//my:myFields/my:approval/my:tmpApprovalStatus", nameSpaceMgr);
     clearnode.InnerText = ""; // clear out the temporary status field.
     // clear the comments field
     clearnode = xmlDoc.SelectSingleNode("//my:myFields/my:approval/my:tmpComments", nameSpaceMgr);
     clearnode.InnerText = "";
     // Clear the user who actioned it.
     clearnode = xmlDoc.SelectSingleNode("//my:myFields/my:approval/my:tmpUserName", nameSpaceMgr);
     clearnode.InnerText = "";
     // Set the "Email me my comments" tick box to false
     clearnode = xmlDoc.SelectSingleNode("//my:myFields/my:emailMeMyComments", nameSpaceMgr);
     clearnode.InnerText = "false";


     // Save the changed xml back to the process field
     K2.ProcessInstance.XmlFields["FormSignoff"].Value = xmlDoc.OuterXml.ToString();
     // clear the tempApprovalStatus field
     K2.ProcessInstance.XmlFields["tempApprovalStatus"].Value = "";

thanks arduk, ill try that and let you know. Thanks for your time

i got this to work in a different approach. Thanks for yur help.. Cheers
great - I'm glad you got it working - it would be great if you posted the details - it may help someone else who runs into this in the future.

public

void Main(Project_9dc436d796d1427586e196902c623d5e.EventItemContext_fc4b075f85594985a3659d1d44477a2f K2)


{


XmlDocument InfopathTemplate = new XmlDocument();


InfopathTemplate.LoadXml(K2.ProcessInstance.XmlFields[

"TemplateName"].Value.ToString());


XmlNamespaceManager nameSpaceMgr = new XmlNamespaceManager(InfopathTemplate.NameTable);


nameSpaceMgr.AddNamespace(

"my", InfopathTemplate.DocumentElement.GetNamespaceOfPrefix("my"));


ClearCommentsTracker(InfopathTemplate, nameSpaceMgr);


XmlDocument ProcessFieldDocument = new XmlDocument();


ProcessFieldDocument.LoadXml(K2.ProcessInstance.XmlFields[

"TrackingComments"].Value);


XmlNamespaceManager nameSpaceMgrOfProcessFieldDocument = new XmlNamespaceManager(ProcessFieldDocument.NameTable);


InfopathTemplate = CreateCommentsTracker(InfopathTemplate, ProcessFieldDocument, nameSpaceMgr);


DeleteFirstTrackingCommentNode(InfopathTemplate, nameSpaceMgr);


K2.ProcessInstance.XmlFields[

"TemplateName"].Value = InfopathTemplate.InnerXml;


}


private XmlDocument CreateCommentsTracker(XmlDocument InfopathTemplate, XmlDocument ProcessFieldDocument, XmlNamespaceManager nameSpaceMgr)


{


XmlNodeList ListOfXmlNodes = ProcessFieldDocument.SelectNodes("/TrackingComments/CommentsHistory");


for (int iNumberOfNodes = 0; iNumberOfNodes < ListOfXmlNodes.Count; iNumberOfNodes++)


{


string[] strArray = new string[4];


for (int iCount = 0; iCount < 4; iCount++)


{


strArray[iCount] = ListOfXmlNodes[iNumberOfNodes].ChildNodes[iCount].InnerText;


}


InfopathTemplate = AddCommentsHistoryToInfopath(InfopathTemplate, nameSpaceMgr, strArray);


}


return InfopathTemplate;


}


private XmlDocument AddCommentsHistoryToInfopath(XmlDocument InfopathTemplate, XmlNamespaceManager nameSpaceMgr, string[] strArray)


{


XPathNavigator InfopathTemplateNavigator = InfopathTemplate.CreateNavigator();


XmlDocument doc = new XmlDocument();


XmlNode group = doc.CreateElement("my:TrackingComments",


nameSpaceMgr.LookupNamespace(

"my"));


XmlNode field = doc.CreateElement("my:UserName",


nameSpaceMgr.LookupNamespace(

"my"));


XmlNode node = group.AppendChild(field);


node.InnerText = strArray[0];


field = doc.CreateElement(

"my:Action",


nameSpaceMgr.LookupNamespace(

"my"));


node = group.AppendChild(field);


node.InnerText = strArray[1];


field = doc.CreateElement(

"my:DateAndTime",


nameSpaceMgr.LookupNamespace(

"my"));


node = group.AppendChild(field);


node.InnerText = strArray[2];


field = doc.CreateElement(

"my:ApproverComments",


nameSpaceMgr.LookupNamespace(

"my"));


node = group.AppendChild(field);


node.InnerText = strArray[3];


doc.AppendChild(group);


InfopathTemplateNavigator.CreateNavigator().SelectSingleNode(


"/my:OraclePurchasing/my:History",


nameSpaceMgr).AppendChild(doc.DocumentElement.CreateNavigator());


return InfopathTemplate;


}


private void ClearCommentsTracker(XmlDocument InfopathTemplate, XmlNamespaceManager nameSpaceMgr)


{


XPathNavigator InfopathTemplateNavigator = InfopathTemplate.CreateNavigator();


XPathNodeIterator TrackingCommentsIterator = InfopathTemplateNavigator.Select("/my:OraclePurchasing/my:History/my:TrackingComments", nameSpaceMgr);


if (TrackingCommentsIterator.Count > 1)


{


for (int iLoopCount = TrackingCommentsIterator.Count; iLoopCount > 1; iLoopCount--)


{


string strXpathOfItem = "/my:OraclePurchasing/my:History/my:TrackingComments[" + iLoopCount.ToString() + "]";


XPathNavigator TrackingCommentsNavigator = InfopathTemplateNavigator.SelectSingleNode(strXpathOfItem, nameSpaceMgr);


if (TrackingCommentsNavigator != null)


TrackingCommentsNavigator.DeleteSelf();


}


}


}


private void DeleteFirstTrackingCommentNode(XmlDocument InfopathTemplate, XmlNamespaceManager nameSpaceMgr)


{


XPathNavigator InfopathTemplateNavigator = InfopathTemplate.CreateNavigator();


XPathNavigator TrackingCommentsNavigator = InfopathTemplateNavigator.SelectSingleNode(


"/my:OraclePurchasing/my:History/my:TrackingComments[1]",


nameSpaceMgr);


if (TrackingCommentsNavigator != null)


TrackingCommentsNavigator.DeleteSelf();


}


}


 


Please correct me if u find this piece of code very amateurish.. I'm new to programming..

Hi max,


i red thru this post since i needed to capture the comments of approvers in my workflow. Am confused to follow this post max. Could you please guide me thru steps.


Thx much.

Create a process level xml repeating field as given in the previous posts. Then create a server event for gathering the comments. And another server event for showing the gathered comments on the infopath form

Please correct here if am wrong:::A process level xml field can be created in K2 object browser.


How to make this process field as repeating field?


And also do i need to place the comments textbox in IP form under Repeating Section?


Thanks

Create the structure as in the attched image.

ALSO do I need to create these fields (repeating node) on IP form same as xml fields? Right now I only have txtcomments field on IP form.


Since there are 2 parts of code snippets are provided in post by yourself & Adruk, its getting tough to map between.


Appreciate if you can you provide me with the Default Server Event (code) event that is in the first activity will run for each destination user to create the xml field nodes for each user.


Thx much.

Reply