Create SharePoint Document Set (and set metadata) using REST

A quick post today to augment what’s out there in the “Googleverse”.  I needed to create a Document Set in client side code, and went out to find the appropriate calls to make that happen.  To update the metadata on the folder you create (which is all a Document Set really is under the covers), you simply make an “almost” normal list item update call.  So the following is the various “functions” you need and how to string them together to do this task.  As you read through, I’ll point out in the code where other older posts on this topic steer you wrong.

WARNING, this code is not optimized for best practices but is generalized for reuse. As sample code, it may not work in all scenarios without modification.
NOTE: this code requires jQuery to execute the AJAX calls and the promise
NOTE: The use of odata=verbose is no longer required and better practices would suggest that it should not be used in production. See this post from my partner Marc Anderson more information.

This first function is what is used to create the document set folder. The function uses the folderName parameter as the title of the Document Set

The following code is a generic update function, we’ll use it to update our Document Set’s metadata after its been created. In other posts out there, you’ll see the url of the AJAX call set to the folder.__metadata.uri. Unfortunately, that uri is no longer valid as a way to update the metadata and the call will fail. Also, when updating list items there’s a standard “type” that defines the object your updating, with our Document Set this type is different than a generic list item, and so I’m passing it in from our calling function. It can partially be retrieved from the folder creation response’s metadata, but it’s not exactly correct and the call will fail.

NOTE: the list’s display name in this case has no spaces or odd characters, if yours does you will need to escape those characters when creating the list type, for example a list containing an “_” you would use the following code: “SP.Data.” + list.replace(‘_’, ‘_x005f_’) + “ListItem”

So now we have functions that do the work for us we just need to call them. In this case I’m showing the code encapsulated in a function that does the calls but returns a promise to the calling function so that the caller can be notified when the document set has been created completely.

The call to createDocSet includes the Document Set’s content type, this can be retrieved from the URL of the Content Type definition page. Also note in this code that you need to do a bit of manipulation of the eTag if you’re going to pass it. You technically could use a wildcard instead of extrapolating the eTag, but for completeness I’ve included it.

InfoPath – Show links to documents in a library from the form

I had an interesting client issue recently where the users were copying and pasting the contents of entire e-mail messages into a text box inside an InfoPath form.  Although useful in that the information was captured with the form, the implementation was tedious at best when trying to find information potentially buried there.  Long story short by putting the e-mail messages in a document library that was linked to the InfoPath form library by the ID I was able to have a parent-child relationship.  The issue then was how to display those e-mails from within the form. 

Obviously the ideal solution was to be able to actually preview them, but barring the funds to take on a challenge like that, and very little hope that the exercise would be fruitful it was decided to try and at least show a list of the "messages" that were stored in the document library related to the form that was open.  Sounds simple I'm sure, and in the end it was fairly straight forward but a few little gotcha's had to be worked around.

My first thought was to make a secondary data connection to the document library in question.  Unfortunately, I figured out rather quickly that getting the name of the file or the path to the file wasn't happening.  Enter REST services for SharePoint… i.e. /_vti_bin/listdata.svc

The first step was to validate the URL for the REST service that I would add as a datasource in the InfoPath form.  To do this I used the URL http://sp2010/test/_vti_bin/listdata.svc where sp2010/test is the path to the site that housed the document library.  What resulted was this:

listdata.svc screenshot

If you then modify the URL to put /TestDocuments after the service call… in other words: http://sp2010/test/_vti_bin/listdata.svc/TestDocuments you will see the contents of the Test Documents library.  There are a lot of references on the web for quering REST data so I won't go into it here suffice to say that the best way to figure out what you need to query is to view the source of the resulting page.  So when I showed the contents of the Test Document library I saw the following:

Test Document listing

Then when I viewed the source of the page I could see that to filter for the Form field I would need to use "FormId" (I found out the hard way that this seems to be case sensitive as FormID didn't work).


Ergo, my final url to show all the documents filtered by the Form field (in this case where that value was 1) would be:
http://sp2010/test/_vti_bin/listdata.svc/TestDocuments?$filter=FormId eq 1

Ok, now I needed to be able to connect to the data from InfoPath.  That's a simple enough process, simply add a data connection for a REST Service.  Use the URL from above, but do not have it retrieve data by default.

 Now you have a couple options about how you form the URL.  If you want to linked document to open in the browser you're going to have to jump through some serious hoops.  Certainly doable though and I'll have more on that in a moment.

If it's ok that the document opens in the client side applications than the solution is much simpler, all you'll need to do is create a link to the document using the following XPath function: concat(xdServerInfo:get-SharePointSiteUrl(), m:properties/ns1:Path, "/", m:properties/ns1:Name)

So step by step, here's how you'll finish configuring your InfoPath form:

1. Insert a repeating section and bind it to the "entry" of the REST service.

2. Add a hyperlink control into the repeating section and set it's "Link To" data source value to concat(xdServerInfo:get-SharePointSiteUrl(), m:properties/ns1:Path, "/", m:properties/ns1:Name).  You can also set the "Display" data source to m:properties/ns1:Name.

3. If you haven't already done so, create a secondary service to the same SharePoint list that you're submitting the InfoPath form to and get the ID value.  Make sure you're only getting the value for the current form, and that you're getting it automatically on form load.

4. Create an FormLoad action event (or a Action rule on a button) and set it to "Change REST URL".  Set the URL for the REST call to: concat(xdServerInfo:get-SharePointSiteUrl(), "_vti_bin/listdata.svc/TestDocuments?$filter=FormId eq ", max(ID)).  This will then load the list of documents to display in the form.

Voila, a list of document hyperlinks shown in the InfoPath form.

Open Documents In Browser

Now, if opening the documents using the client application isn't enough here's the good, the bad and the hack you need to put together to have your hyperlink open the document in the browser window.  First, an example function:

concat(xdServerInfo:get-SharePointSiteUrl(), "_layouts/", concat(substring("Word", 1, contains(m:properties/ns1:Name, "docx") * string-length("Word")), substring("xl", 1, contains(m:properties/ns1:Name, "xlsx") * string-length("xl"))), "Viewer.aspx?id=", m:properties/ns1:Path, "/", m:properties/ns1:Name)

Told you it was going to be ugly.  This section of the function
concat(substring("Word", 1, contains(m:properties/ns1:Name, "docx") * string-length("Word")), substring("xl", 1, contains(m:properties/ns1:Name, "xlsx") * string-length("xl")))
is what decides which viewer you need to use.  The example above only handles .xslx and .docx documents… you'll need to expand the function to support other types of documents.

Now the hack.  This kills me actually.  The hyperlink control in InfoPath does not support XPath functions as the "Link To" value.  Ergo, you can't just put this function in the hyperlink control and have it work nice and neat.  The work around I came up with involves using a rule to set the value of one of the fields in the REST secondary datasource to the XPath for the hyperlink and then referencing that field from the hyperlink's "Link To" data source property. 

I know, this is ugly, but I figured since we're only going to be reading data I can use one of the fields I don't need to display to the users as a holding area for this information.  If you wanted to, the much cleaner solution would be to write code behind and populate a repeating field in the main section of the InfoPath form with the hyperlink values and probably the display value too.  However, code behind wasn't acceptable in my scenario.

Ok, so here's what I did.

5. Go to the REST secondary data source, expand entry and select m:properties.

6. Add an action rule to "Set a fields value" and then set a field from the properties area (I used CopySource) to the XPath value shown above.  What will happen is that as the data loads the CopySource field will be populated with the hyperlink you want that particular entry to use to load the document in the browser.

7. Replace the "Link To" data source value of the hyperlink control you added in Step 2 with the field you set the value of in step 6.  You should have already set the "Display" data source, but if not go ahead and do it now.

Voila, publish the form and the list of documents will be displayed for the user that show the linked document in the browser when clicked on.