This is part 2 of a quick (but huge) post on my experiences working with SharePoint Designer 2010 workflows and Forms Services. In part 1, we used the scenario of an employee termination form, and sacked Justin Bieber. Now we want to ensure that the SharePoint user experience for sacking Justin Bieber is seamless and intuitive.
Truth be told, I have never actually heard a Justin Bieber song because we have not had a television in the house for over a year. Ignorance is bliss, but I have seen enough news reports that I still want to sack him!
In part 1, we examined the ability of SPD2010 to leverage InfoPath for tailoring forms used by workflows. We then covered creating a workflow utilising the Start Approval Process action, which enables us to do a couple of cool things without custom programming.
Now we are onto the next two steps.
Step 2: Dealing with WrkTaskIP.aspx
As we covered in part 1, WrkTaskIP.aspx will render the link to the InfoPath form that triggered the task via the line <% SPHttpUtility.NoEncode(m_pageDescription,Response.Output); %>
3: <td width="10" valign="center" style="padding: 4px;">
4: <img IMG SRC="/_layouts/images/Workflows.gif" alt=&lt;%SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(GetLocString("WrkTask_PageTitle")),Response.Output);%&gt;/>
7: <% SPHttpUtility.NoEncode(m_pageDescription,Response.Output); %>
11: <InfoPath:XmlFormView id="XmlFormControl" runat="server" style="width:100%;"/>
Our second step is to make a copy of WrkTaskIP.aspx. I imaginatively called my copy WrkTaskIPFS.aspx (FS meaning Forms Services). I then simply removed the entire <table> section from the above code, leaving behind only the code that embeds the InfoPath form (<InfoPath:XmlFormView id="XmlFormControl" runat="server" style="width:100%;"/>)
The next step is where things get a little tricky. I really don’t want to go off onto too much of a tangent here except to say that you should read up on the relationship between site content types and list content types, as well as the content type propagation issues encountered when using XML Based Content types (which the workflow created tasks are).
We need to modify the task content type created by the workflow action to refer to the WrkTaskIPFS.aspx file. Take care to determining what the content type name is. This is because sometimes the name of the content type created when the workflow is published is not what you expect – especially if you have set up previous Start Approval Process workflow actions before now. So if you are unsure of what content type the workflow created, just trigger the workflow and check the content type of the task created. In my dodgy example here, the content type name is “Approval”.
Now that we have found the content type we get to the tricky bit that I alluded to earlier. You might think that we just select the site content type in SharePoint Designer and change the reference to the display and edit forms right? After all, I show below that SharePoint Designer happily lets you do it?
If you make the change from WrkTaskIP.aspx to WrkTaskIPFS.aspx for the edit and display form setting, and then refer to the task list and click to edit one of the tasks, you will be disappointed. As you can see, we are still referring to the old form page because the link has not disappeared.
The problem is that changing the display and edit form on the Approval site content type has not propagated to the Approval content type applied to the Tasks list. I tried to propagate the settings using a Gary Lapointe stsadm extension, but it seems that extension deals with column propagation and not all properties.
The workaround is to apply the same setting change to the task content type applied to the tasks list. In SharePoint Designer, choose the Tasks list (or whatever task list you have associated with your workflow), and scroll down to display the content types assigned to it.
Click on the content type of interest (in my case “Approval”), and you see essentially the same content type properties screen you see when editing the site content type. The breadcrumb is the giveaway that you are working with a list content type and not the site content type. Change the reference to WrkTaskIPFS.aspx as shown below.
Now I can click on my task to sack Justin Bieber and we have removed the default link as intended.
Okay, now that we have dealt with the hyperlink to the form, lets add the hyperlink into the InfoPath form associated with the task.
Part 3 – Dealing with InfoPath
Now we need to add a reference back to the original employee termination form. So lets get back to our workflow and check it’s properties where we can get to the forms generated. I click on “TerminateEmployee” and we are back in InfoPath.
I am going to put the hyperlink in a new row, right above the status header. To do this, I need to insert a row above status and then enter a nice descriptor.
Now let’s deal with the hyperlink. Here is yet another trap for younger players. Your first instinct might be to add a hyperlink control to the form. If you try this, it will not behave like a form you created from scratch. A screenshot below shows what happens.
Unlike an InfoPath form that is created from scratch, this form is bound to the data provided by the workflow action. Therefore if you try and drop on a control to the form, you will be prompted to bind to a field of the existing data source. This is not how we are going to do this, but I will go through the motions anyway.
Insert a hyperlink control, and from the fields listed, choose the one called Related Content
Amazed by your cleverness, you excitedly publish the form and check out the task! Yeah baby! you say to yourself! I have my hyperlink.
You now click on the hyperlink to see the magic. At this point one of two things will happen. If you happened to use a space in the original form name, then you will get a 404 error because the hyperlink control is dumb and not escaping the space in the URL. If you did not use a space in the name, you will get prompted to open the form in the native InfoPath client.
We are back to where we started. So we are foiled – or are we?
Part 3a – A clever workaround
Clearly, utilising the built in Related Content field with the InfoPath hyperlink control is kind of naff. So let’s have another crack at it. Return to your task form and delete the hyperlink control you just created. What we will do this time is add an additional field to the InfoPath data source. In InfoPath, take a look at the data pane. At the bottom is a link “Show advanced view”. Click it and we can take a look at the data source in more detail.
The next step is a lesson in Olympic class Microsoft usability eliteness (NOT!). Right click on the myFields dropdown and choose Add. Name the field URL and select Hyperlink as the data type as shown below.
Click OK and your data source should now show our newly created field called URL (my:URL). Click on this field and from the drop down and choose Properties. (We are going to set the default value of this hyperlink to our original form, but append the &OpenIn=Browser to the URL).
Click the fx button and lets make a formula. If you have played with InfoPath formulas, you might think that all we have to do is use the concatenate function on the Related Content field with the string “?OpenIn=Browser” as shown below.
Workflowlink, by the way, is the reference to the original Related Contnet field in the InfoPath data source. Just click Insert Field or Group and find Related Content
So will it work? Actually many times it will. So either drag this completed field to the form to create the hyperlink control, or insert the hyperlink control from the ribbon and bind to the field. This time our new field (my:URL) is in the list to choose from when binding the hyperlink. (oh - make sure you are in advanced view, not basic view to choose your URL field).
So having done this, you will have a primed hyperlink control on the form. Publish the form and let’s have a look at the effect when a new workflow task is created…
Looking positive, but what happens when we click it?. In my example, we get a nasty 404. If you look closely, this is because I have a space in the URL name. *sigh*, InfoPath certainly doesn’t make it easy does it? So we need to revisit our little formula to deal with the unencoded space.
This is the part of the blog post where I feel dirty. InfoPath does not have a string replace function, and ideally what I want to do is replace any occurrence of a space “ “ with an encoded space “%20”. The built in translate() function will not suffice because it will only translate one character for another, not one character for three. 🙁
For the geeks, InfoPath uses the XPATH 1.0 spec, however XPATH 2.0 was released in 2007 and does have a replace function. What a pity InfoPath 2010 does not appear to use XPATH 2.0.
So in my case, I will create a formula to handle one space only. Yeah I know this is lame, but given that you have control of the filename generated for InfoPath forms when you make a submit connection, this is not an overly big deal – just bloody annoying. If you avoid spaces in filenames altogether, you can you my easier concatenate formula above.
I have dealt with the space, by using two built in substring formulas in combination with the concat function as shown below.
concat(substring-before(WorkflowLink, " "), "%20", substring-after(WorkflowLink, " "), "?OpenIn=Browser")
What I do here is search for the space and return all text before it, add a %20 and then return all text to the right of the space to create my URL.
Now lets republish and take a look. Aha! now it works. We can now view the form in the browser, have control over where the link is placed on the form and can manipulate the form URL.
Taking a breath
So if we step back and review what we have done.
We changed to the newer, cooler, “Start an approval process” workflow action. By the way, in case its not clear amongst the screenshot-hell that is an InfoPath post, the reason we did this was because the older workflow actions do not provide a reference to the Related Content/Workflowlink field. Yup, that’s right. The Collect Data from a User, Assign a Form to a Group and Assign a To-do Item workflow actions do not appear usable with the technique I described above.
That is a minor point anyway, because the new SP2010 specific workflow actions are much more powerful and flexible and should be used. We have barely scratched the surface in these posts and I encourage you to have a good look at the new process actions.
We then made a copy of WrkTaskIP.aspx and then changed the settings of the task content type to use the copy (WrkTaskIPFS.aspx). We also encountered a glimpse of the content type setting propagation issues that developers have to contend with on a daily basis. We modified the copy to not provide a reference to the original form, and instead, modified the task form using InfoPath to provide a hyperlink to the original form, manipulating the URL, ensuring that it will open in the browser.
Although I would not rate this as the cleverest of clever workarounds, its not too bad and certainly won’t be the kind of action that would prevent a future service pack being applied.
Bonus Part 4
So did you think this is the end? No sir. I am going to show you one other neat trick that this approach can enable. To explain, let’s do a little more form pimping. If we return to our employee termination form, I am going to add a picture button that has a rule to submit for form and then switches the form view to a “thank-you please come again” type thing. Below is the new form with the sexy “terminate” button and the new view after it is submitted.
This is a very common InfoPath scenario, and the problem is that if the user re-opens this form, they can again, click the submit button and the rules will be re-run. This normally would necessitate some logic in the form to test if the form has been submitted already. Adding logic to the submit process on a form is not a hard thing to do, but if you check the InfoPath toolbar that is enabled by default, there is a print preview button. I wonder what happens when we click it? 🙂
Interesting eh? No toolbar and no button. This is exactly what I want to happen when a user clicks on the task link to review the original termination application.
So how about we change the URL to load up the print preview of the browser enabled form? If we examine the URL generated for this form it uses Print.FormServer.aspx instead of FormServer.aspx. If you load up any browser enabled InfoPath form, you can remove the toolbar and buttons by calling Print.FormServer.aspx instead.
Thus if we take this
and change to this:
… we achieve our goal.
So how about we generate this as the default value for our URL field on the task form. The Related Content URL supplied from SharePoint is the full path to that content. e.g.
When we look at the Print.FormServer.aspx URL above and break it up into its components, we need to:
- Create the site collection URL
- Create the string "_layouts/Print.FormServer.aspx?XmlLocation=/"
- Grab the relative path to the form
- Create the string "&OpenIn=Browser"
Creating the site collection URL is easy as their is a built in formula called SharePointSiteCollectionUrl(). Additionally, creating the relative path can be done by taking the Related Content field (called WorkflowLink) and removing the host part of the URL. Once again built-in formulas come to the rescue here. SharePointServerRootUrl() will return the hostname of the site and substring-after() will return everything to the right of the matching substring. Therefore the formula
will strip the hostname from the URL to the form, leaving us with the relative path.
Stringing that all together into a concatenation creates the following formula.
concat(SharePointSiteCollectionUrl(), "_layouts/Print.FormServer.aspx?XmlLocation=/", substring-after(WorkflowLink, SharePointServerRootUrl()), "&OpenIn=Browser")
to render a sample URL of:
But the space…
Those of you that have not fallen asleep at this point might notice that we have re-introduced our problem with the non encoded space. That can be dealt with using the substring-before and substring-after formula that we used earlier. Assuming the space is in the form name, we build a string up to the form name, use substring-before to grab everything prior to the space and substring-after to grab everything after.
The resulting formula looks like this.
concat(substring-before(concat(SharePointSiteCollectionUrl(), "_layouts/Print.FormServer.aspx?XmlLocation=/", substring-after(WorkflowLink, SharePointServerRootUrl())), " "), "%20", substring-after(WorkflowLink, " "), "&Openin=browser")
Ugly enough for you? 🙂
The reality is that spaces are deadly here and you are better off utilising form logic that does not use spaces and stick to the simpler formula. The example above might be interesting from a learning point of view, but there are a number of ways that this formula could trip up and produce a malformed URL (what if there is no space in the filename for example?)
Notwithstanding that InfoPath formulas suck in terms of needed functions, we now have a task form that will link to the original InfoPath form and display the form in print-preview mode. All in all not a bad effort, and an interesting exploration into what InfoPath can do.
I would have liked to make the URL friendlier of course, and given that this is InfoPath we are talking about, I fully expect someone to post a comment telling me the elegant, perfect way to do what I have done here 🙂
Thanks for reading