jQuery to the Rescue: Writing a Survey ID to a List on Response Creation (w/o Workflow)
Guest Author: Jim Bob Howard
SharePoint surveys don’t fire a workflow. You can create one, but it will never fire.
And the reason is fairly understandable. When should it fire? When is the survey item created? When is it changed? To understand how difficult it is answer those questions, I need to take a moment to explain how surveys work.
Surveys can take two forms, branched or unbranched. An unbranched survey looks a lot like any other list and functions almost exactly like it (except for workflows).
But, a branched survey is a completely different proposition.
Unbranched vs. Branched Surveys
When you have a branched survey, the questions are presented a few at a time, depending on the answers from previous questions. This is great, especially when you have a requirement that looks like this:
Do you like ice cream? If yes, what flavor?
In other words, if the answer is No, I’m not going to ask you what flavor is your favorite.
The way SharePoint accomplishes this is that it starts with a NewForm.aspx, just like other lists, but it only shows the first branch of the survey. When Next is clicked, the answers to the first branch are saved and an ID is assigned to the survey. The next page is displayed using the EditForm.aspx and passing in (a) the ID of the survey, and (b) the first question of the next branch. This happens each time Next is clicked, until the last branch, when a Finish button is displayed rather than Next.
At any point, if the user clicks Cancel from an EditForm.aspx page, he or she is prompted with a question whether the entire survey should be deleted (recycled, actually) or not. So, built into the creation of the survey item is also the possibility that it will be destroyed before all questions are answered, but after some data has already been saved.
Typically, with other lists, a create workflow is fired when NewForm.aspx is saved, that is, when the item ID is created. The problem with doing that with a branched survey is that the workflow may need required fields that come later in the survey and so are not available when NewForm.aspx is saved.
Likewise, with other lists, a change workflow is fired when something changes and the EditForm.aspx is saved. Again, with a survey, EditForm.aspx is used in the creation of the item, so a save from it doesn’t necessarily mean something has changed; it may be the initial creation of the data for that column.
Why would you want a workflow on a Survey?
Similar to the Collect Data from a User workflow action I documented in Understanding SharePoint Journal’s Bonus to Issue 4, it’s nice to spawn a new task for the user to do that can be handled asynchronously to the initial item creation. With Collect Data, the workflow creates a Task item and assigns it to the user. But a Task can’t have branching, so if you need that, a Survey is a better option.
In both cases, it’s pretty useful to be able to include a lookup column in both items (Initiating item and Task, or Initiating item and Survey response) that point to each other. With the former, you can fire a workflow on creation of the Task to write its ID to the initiating item. With the latter, you can’t.
Real World Use Case
I was recently asked to create an intake list for Work-related injuries and illnesses. The simple set-up allows employees to report when an injury or illness occurs at work, and allows HR to see those reports for its legal documentation.
But, because the company is a healthcare provider, bloodborne pathogen exposure (BPE) is one of the possible types of injury/illness that can occur. And if it does occur, there’s an entirely separate set of additional questions that need to documented. Many of these are similar to our ice cream question above: if yes, then answer these additional clarifying questions; if no, move on to the next question.
These additional questions were perfectly suited for a survey. But, the answers have to be tied to the original injury intake report.
So, how can we get these two to talk to each other?
jQuery and SharePoint’s Web Services to the Rescue
Using Marc Anderson’s jQuery Library for SharePoint Web Services on Codeplex, we can get the information we need, when we need it, and write it out where it needs to go.
The basic steps are:
- If it’s a BPE, email the user a survey response link which includes the Injury Report’s ID in the querystring
- Add jQuery to the survey’s NewForm.aspx to fill the lookup on the survey with the ID of the report from the querystring
- Add jQuery to the survey’s EditForm.aspx to update the Injury Report with the survey ID
Step 1. Since our Injury Report is a list that can execute a workflow, it’s easy to send an email to the user that includes a link to the BPE survey NewForm.aspx. But we also want to pass the ID of the Injury Report to the BPE survey and we can do that through the querystring. All we have to do is add "?ReportID=" and the Current Item’s ID field to the anchor tag’s HREF. (e.g. http://{site}/{subsite}/Lists/BPESurvey/NewForm.aspx?ReportID={ID}. If this isn’t clear, see my separate post on Building a Hyperlink in an Email in SPD.)
Step 2. We’ll use Javascript and jQuery to capture the ReportID and update our Report lookup column in the BPE surveywith it. Of course, that means you’ll need to add a Lookup column as the first question (or at least in the first branch) of the survey, so that it can link back to the Injury Report. This will create a dropdown list as the first question. We’re going to use this column to connect the two, but we’re going to hide it. So, make it required and no one will be able to submit a survey with supplying a ReportID once we add a little jQuery to it.
Adding jQuery to NewForm.aspx
- Point your browser to your survey’s NewForm.aspx (you don’t need the ReportID in the querystring yet).
- Right-click on the page and choose View Source
- While viewing the source, type Ctrl-F and find the label text for your Lookup field. It should look something similar to this:
- Notice that the select control has a title that matches your label (i.e. Injury Report). That’s what we’re going to be searching on with jQuery.
- Add ?PageView=Shared&ToolPaneView=2 to the URL in your browser to put it in edit mode.
- Click Add a Web Part and add the Content Editor Web Part
- Click on the title bar of the Content Editor Web Part (CEWP) and drag it below the Survey form
- Click on the CEWP’s edit menu and choose Modify Shared Web Part
- Click on the Source Editor… button
- Paste the following into the Webpage Dialog:
<TR> <TD valign="top" width="90%" class="ms-formlabel"> Injury Report </TD> </TR> <TR> <TD valign="top" width="90%" class="ms-formbodysurvey"> <!-- FieldName="Injury Report" FieldInternalName="ReportID" FieldType="SPFieldLookup" --> <span dir="none"><select name="ctl00$m$g_a31f7024_8309_422f_9bba_84a452812e43$ctl00$ctl01$ctl00$ctl00$ctl00$ctl04$ctl00$Lookup" id="ctl00_m_g_a31f7024_8309_422f_9bba_84a452812e43_ctl00_ctl01_ctl00_ctl00_ctl00_ctl04_ctl00_Lookup" title="Injury Report"> <option selected="selected" value="0">(None)</option> <option value="10">Migraine</option> <option value="9">Needle stick</option> <option value="15">Sprained ankle</option> <option value="14">Threw back out</option> </select><br/></span> </TD> </TR>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script> // version 1.4 will be out in a month or so <script type="text/javascript"> $(document).ready(function() { $("input[value='Save']").hide(); // hides the Save buttons (find out why here) $("table.ms-formtoolbar tr:first").hide(); // hides the top toolbar (scroll through the comments on previous link to see why) var rptItem = $("select[title='Injury Report']"); // sets a variable to the select control with our ID in it rptItem.hide(); //hides the select control fillDefaultValues("Injury Report","ReportID"); // fills the select control with the data from the querystring var rptText = $("select[title='Injury Report'] option:selected").text(); // sets a variable to the text of the selected item rptItem.after(rptText); // prints the text of that variable after the select control (which is now hidden) }); function fillDefaultValues(fieldName,value) { // Notice that you call fillDefaultValues, passing it: a) the Display Name of the column, and b) the querystring key JSRequest.EnsureSetup(); var qs = JSRequest.QueryString[value]; var x = $('select[title="'+fieldName+'"]'); if (typeof(qs)=="undefined") {x.val(0)} else {x.val(qs)}; x.change(function () { x.val(qs); }) .change(); } </script>
fillDefaultValues captures the querystring value and puts it in the control matching the title supplied
So far, what we’ve done is pass the Injury Report’s ID to the BPE survey response via an email link. Steps 1 and 2 complete.
Step 3. Here’s where we really put Marc’s library to work. We’re going to use his web services library to update the item in the Injury Report list by writing the ID of this BPE survey response to the BPE Survey lookup field (make sure you’ve created that column) in the Injury Report. Note: We’ll do this on the EditForm.aspx, since the ID of the survey response isn’t available on NewForm.aspx (it hasn’t been created yet).
Follow the steps above for adding a CEWP to the EditForm.aspx and getting the basic jQuery shell in place. From there, we’ll start putting the pieces in place to make the final connection between these two items. Include the snippets for hiding the select control and displaying the text, as well as hiding the Save button and top toolbar(when we come back to the record in edit mode, we’ll want that same functionality to be in place). fillDefaultValues is not needed on the EditForm.aspx, however, because the connection will have already been made.
Looking through the library, we want to use the UpdateListItems operations of the Lists Web Service.
$().SPServices({ operation: "UpdateListItems", async: false, // This ensures that processing waits for the response listName: "Injury Report", // Use the Display Name of your Source List updates: "<Batch OnError='Continue'>" + "<Method ID='1' Cmd='Update'>" + "<Field Name='SurveyID'>" + SurveyID + "</Field>" + // The first SurveyID is the StaticName of the lookup column in the Injury Report; // The second SurveyID is a variable we'll define in a minute "<Field Name='ID'>" + ReportID + "</Field>" + // ID is the column name of the ID of Injury Report // ReportID is another variable we'll define shortly "</Method>" + "</Batch>", completefunc: function(xData, Status) { // We could put any post-processing or error handling here } }); });
So, we need to declare a couple of variables to make the above work.
SurveyID. Since we’re on EditForm.aspx, the easiest place to get this is from the querystring. The following code snippets will do that for us:
We need to declare this variable (bold line below) before the SPServices call to UpdateListItems, so we’ll put it right after we display the Report Title:
. . . rptItem.after(rptText); var SurveyID = getQuerystring('ID'); // ID is found in the page URL (e.g. ../EditForm.aspx?ID=1) }); . . .
The code snippet for getQuerystring should go last before the <script> tag closes.
. . . }); function getQuerystring(key, default_) { if (default_==null) default_=""; key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); var regex = new RegExp("[\\?&]"+key+"=([^&#]*)"); var qs = regex.exec(window.location.href); if(qs == null) return default_; else return qs[1]; } </script>
ReportID. The ReportID is not accessible from this page unless we’re on the first branch of the survey. When the response is first recorded, the first time we’re on EditForm.aspx is on the second branch, so we need to get the ReportID some other way.
SPServices to the rescue again, in the form of the GetListItem operation. This helpful service allows us get the XML for any list item using common SQL syntax. First, we’ll declare the variable, making it null. (This gives it a scope outside the jQuery call to SPServices.) For easy maintenance, we’ll just declare right after the SurveyID declaration:
. . . rptItem.after(rptText); var SurveyID = getQuerystring('ID'); var ReportID = null; }); . . .
Now, we’ll call GetListItem before we call UpdateListItems, so that ReportID has data by the time we call UpdateListItems. Paste the following jQuery snippet (with edits) right before the UpdateListItems section:
$().SPServices({ operation: "GetListItems", async: false, listName: "BPE Survey", //Again, use the Display Name here CAMLQuery: " <Query> <Where> <Eq>" + " <FieldRef Name='ID' /> " + " <Value Type='Integer'>" + SurveyID + "</Value> " + // This is the variable we declared earlier "</Eq> </Where> </Query> ", completefunc: function(xData, Status) { $(xData.responseXML).find("[nodeName= z:row]").each(function() { ReportId = $(this).attr("ows_ReportID"); // Notice ows_ReportID; ReportID is StaticName of the Lookup column in the Survey list; // Add the "ows_" to the front of that and you will have the XML attribute you need. }); } });
Conclusion
That’s it. Here are the complete snippets we created (with notes left in to help you make the changes necessary for your implementation).
NewForm.aspx CEWP source:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script> <script src="/js/jquery.SPServices-0.4.7.min.js" type="text/javascript"></script> // Download Marc's code here http://spservices.codeplex.com; // Save it in a SharePoint directory on your site and modify the previous line accordingly <script type="text/javascript"> $(document).ready(function() { $("input[value='Save']").hide(); $("table.ms-formtoolbar tr:first").hide(); var rptItem = $('select[title="Injury Report"]'); // Use title shown in source in step 3 rptItem.hide(); fillDefaultValues("Injury Report","ReportID"); // See function below for syntax on this one var rptText = $('select[title="Injury Report"] option:selected').text(); // Use title shown in source in step 3 rptItem.after(rptText); }); function fillDefaultValues(fieldName,value) { // Notice that you call fillDefaultValues, passing it: a) the Display Name of the column, and b) the querystring key (i.e. NewForm.aspx?ReportID=1) JSRequest.EnsureSetup(); var qs = JSRequest.QueryString[value]; var x = $('select[title="'+fieldName+'"]'); if (typeof(qs)=="undefined") {x.val(0)} else {x.val(qs)}; x.change(function () { x.val(qs); }) .change(); } </script>
EditForm.aspx CEWP source:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script> <script src="/js/jquery.SPServices-0.4.7.min.js" type="text/javascript"></script> // Download Marc's code here http://spservices.codeplex.com; // Save it in a SharePoint directory on your site and modify the previous line accordingly <script type="text/javascript"> $(document).ready(function() { $("input[value='Save']").hide(); $("table.ms-formtoolbar tr:first").hide(); var rptItem = $('select[title="Injury Report"]'); // Use title shown in source in step 3 rptItem.hide(); var rptText = $('select[title="Injury Report"] option:selected').text()); // Use title shown in source in step 3 rptItem.after(rptText); var SurveyID = getQuerystring('ID'); var ReportID = null; $().SPServices({ operation: "GetListItems", async: false, listName: "BPE Survey", // Use Display Name of your survey list CAMLQuery: "<Query><Where><Eq>" + "<FieldRef Name='ID' />" + "<Value Type='Integer'>" + SurveyID + "</Value>" + "</Eq></Where></Query>", completefunc: function(xData, Status) { ReportID = $(xData.responseXML).find("[nodeName='z:row']").attr("ows_ReportID"); // Prepend 'ows_' to the StaticName of the Lookup column in your survey list } }); $().SPServices({ operation: "UpdateListItems", async: false, listName: "Injury Report", // Use Display Name of your source list updates: "<Batch OnError='Continue'>" + "<Method ID='1' Cmd='Update'>" + "<Field Name='SurveyID'>" + SurveyID + "</Field>" + // Use StaticName of the Lookup column in your source list "<Field Name='ID'>" + ReportID + "</Field>" + "</Method>" + "</Batch>", completefunc: function(xData, Status) { } }); }); function getQuerystring(key, default_) { if (default_==null) default_=""; key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); var regex = new RegExp("[\\?&]"+key+"=([^&#]*)"); var qs = regex.exec(window.location.href); if(qs == null) return default_; else return qs[1]; } </script>
I may make it sound really easy, but figuring all out wasn’t that straightforward. If you’d like to follow the dialog and thought process that actually led to this solution, it’s all archived here: http://spservices.codeplex.com/Thread/View.aspx?ThreadId=76735. If you run into any trouble, check there first. I probably made the same mistake and Marc helped me figure it out. If you still have trouble, post comments there. If you have enhancement tips, tricks, traps, and/or suggestions, post them here.
Enjoy!

Jim Bob Howard
Jim Bob Howard is a web designer / web master in the healthcare industry. He has been working with SharePoint only since March 2009 and enjoys sharing what he has learned. You can email him at [email protected].
- jQuery to the Rescue - Automate All Day Event
- jQuery to the Rescue - Default Text Based on Radio Button Click
- jQuery to the Rescue: Writing a Survey ID to a List on Response Creation (w/o Workflow)
Jim,
Nice post. I had a similar request from one of my clients and I used an InfoPath form to gather the intial requirements and submit to a SharePoint list. The Infopath would have optional sections with surveys built into it. Based on the users selection the form would only display the survey which was needed – but the user would not see this survey until after he/she submits the form for the first time. A custom workflow was created on the form library which was triggered based on the users answers and send an email back to the user to complete additional information (survey). The user would take the survey and resubmit the form back to the form library alerting the back office of the update. No code required