Quick Primer on InfoPath and SharePoint 2010 Time Zones

InfoPath functions such as Today() and Now() pick up the time zone based on the settings of the SharePoint server.  By default the web is set to the time zone of the server, which is thereby the default of any new sites created as well as new user profiles created.  For global organizations with users around the world, there are situations where you will want the automated date/time stamps to display the local time for the user rather than the server time.  This article will show you how to set that up.

The time zone of the web can be changed in central administration.  The time zone of the site can be changed by going to site settings / regional settings.  In addition a user can change their personal time zone settings by going to “my settings / my regional settings”. 

The InfoPath form I created to test the scenario has 2 fields.  The first is set by default using the now() function when the form is created, the second is set by an action on form load, again using the now() function.  The first field is also used to name the form when it’s first submitted.  Using the magic of VMs, I set the time zone of the host machine and the site settings to be Hawaii time zone (GMT-10).  I then have a separate test user whose regional settings I set to Pacific (GMT-8) and am using the browser on a machine whose time zone is set to EST (GMT-5).

On the machine whose time zone is set to Hawaii, I created an instance of the InfoPath form.  Note that not only are the two fields set to the current Hawaii time but also the Created and Modified dates show as Hawaii time (Figure 1).

  Image5

Figure 1: Form in Hawaii time zone from machine in Hawaii time zone.

If I log into SharePoint as the same user but from the host machine whose time zone is set to EST I still see the exact same results.  Note the machine’s time is 12:35. (Figure 2)

  Image6

Figure 2: Form in Hawaii time zone from machine in Eastern time zone

Now, by logging into SharePoint as the user who changed the regional settings to Pacific time, I see something very different (Figure 3).  Note that the Created and Modified dates in the SharePoint list are adjusted to my regional settings which are Pacific time zone.  And when I open the form the refresh date time uses the now() function which uses my regional time zone setting to set that date/time.

  Image7

Figure 3: Form in Hawaii time zone from machine in Eastern time zone as user in Pacific time zone.

Conclusion

If you need all the date/time stamps to appear the same regardless of the user’s location, then either override or leave the default regional settings for your web and make sure the users do not change the regional settings of the web.  If the users want to see the information converted to their own time zone, they should change their regional settings to their own time zone.

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).

Image3

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.

Image4

Using SQL Reporting Services Report Viewer Web Part to display Access Services report.

As you may or may not be aware when an Access database is published to a SharePoint site the reports in the database are converted into SQL Reporting Services reports.  Great, you think… that should mean you can host the report elsewhere in the SharePoint higherarchy simply by using the SQL Reporting Services Report Viewer Web Part.  And in fact, you can, it's just a little tricky getting it configured.  Certainly not anything you can't overcome but hopefully by posting this information it'll help save some time.

So first, we need to create the page that we're going to put our report on.  Next, you're going to want to insert a SQL Reporting Services Report Viewer Web Part.  And finally, we need to configure it, which includes setting the location of the report as well as any report aparameters.

Configuring the Web Part

The first step is to set the report that the Web Part should display.  To do that you need the URL to the rdl file that represents the Access report.  To get that, browse to the SharePoint site hosting the Access application.  Next, manually change the URL after the site name to show all site content.  You do this by appending "_layouts/viewlsts.aspx" after the site which should look something like the following where "Access Services Demo" is the name of my SharePoint site:

ViewListURL

Now in the listing of the site's contents you'll see a document library called Report Definitions.  Browse to that library and you'll see all your Access Reports defined there.  Now, go back to the web part properties.  As shown below click on the ellipses of the Report parameter and then put in the URL of the report definition document library.

ReportViewer

When you click the green arrow it will load a list of the reports available in that library.  Select the one you want to display and click OK.

SetReportLocation

Now, back in the web part properties expand the parameters section and click on Load Parameters.

In Access Services all reports have the following parameters:

  • AccSrv_SiteId: This is the GUID of the root site collection.
  • AccSrv_SiteZone: Default
  • AccSrv_WebId: This is the GUID of the site hosting the Access application
  • AccSrv_TimeOffset: TimeZone offset
  • AccSrv_Where: A where clause for the report
  • AccSrv_DataLCID: The Data Language Reference ID (1033 is the English Default)
  • AccSrv_CollationLCID: The collation Language Reference ID (2070 is the English Default)

I have found that for most reports setting the AccSrv_SiteId and AccSrv_WebId are all that is necessary for the report to generate properly.  However, under certain circumstances where there was summation and grouping in the report I've had to set the DataLCID value to 1033 (the default for English).  All the other values can be left to use the report default.

Note: There are many ways to get the GUID for a site but I find the quickest way is to use a tool like SharePoint Manager 2010. 

If you have the need to adjust the Where parameter of the report keep in mind that you need to pass the entire where clause as you would type it in the filter parameter of the report if you were working in Access.  You also must include a beginning "=" sign. 

For instance the following "Where" clause limits the report to those records where the value of the [Quarter] field is equal to 1.  (Even though [Quarter] is a numeric field I still have to pass it as a string).

WhereClause

And then here's what the report looks like when rendered:

Report

If you want to pass the Where clause in using a Query String filter you can do that but you'll first need to modify the parameter of the report by going back to the Report Definition library, and modifying the parameters of the report so that the AccSrv_Where parameter is changed to "Prompt" instead of hidden.  The only downside is that the report shows the user the where clause when it's rendered but this can be overcome by using a little JQuery script to hide the <div> that shows in the report.

ManageParameters

Note also in the above screen shot that one of the option is "Edit in Report Builder".  If you don't have a complex report you can modify the report definition using this SQL Report Builder tool, which is downloaded for you automatically.  This was how I initially determined the values of some of the parameters by showing them in the header and then running the report from within the Access UI on the SharePoint site.

Setting an Application Page Title from code

I’m currently dealing with a requirement where I need to the set an application page’s title from the code behind.

By default the form has a ContentPlaceHolder control with a
ContentPlaceHolderID="PlaceHolderPageTitle"
which in the master page is the text that will fall in the header's title tag. Unfortunately, <asp:Content> controls are not added to the hierarchy at runtime so I can’t access it directly.

There are good ways to modify this title on the client side with Javascript but client side code doesn’t really suit my needs as the title will come from some custom manipulation that’s better done on the server. It did cross my mind that I could do this minipulation and then post it back in script to have it update but it seemed kind of like taking the long way around. Anyway, long story short I found a way to access that title in the Page_Load event but if anyone out there has any better ideas I’d love to hear them.

Maintain file version history when moving/copying files between SharePoint sites

I’m working on a requirement to copy files from one document library to another document library in a sub-site. I figured this wouldn’t be horribly difficult to do but turns out it’s not as simple as you might think and for some reason, although I found many questions about how to do it, I found very few answers. From the things I’ve read out there it’s apparently relatively easy if you’re moving documents within the same site… apparently the Move method works and I read where someone suggesting using SPExport and SPImport. I also found a post by Ivan Sanders that said that you can use Site Actions > Manage Content and Structure > Choose the Library > Choose the docs >Use the Actions drop Down Click onMove > Choose the new Location to accomplish the move on this thread. Unfortunately, none of these solutions fully met my needs.

Ok, so onward and upward… I ended up started from a good post by ‘Dink’ on Copying documents between libraries with metadata – including version history which was a great starting point but seemed like it could be optimized a bit and was posted back in 2007, so I hoped that there might be potential improvements in 2010. As it turns out there is, in the form of additional overload methods for SPFileCollection.Add

So here’s my code snippet. I’ll point out that this snippet is in a function that takes two parameters SPListItem itmSource which is the source item to move, and SPDocumentLibrary libDest which is the target document library.

Add BCS Secondary Fields to a custom list definition

When working with BCS data and creating an external data column in a list or library it is often desirable to show some or all of the secondary fields. In the SharePoint UI this is a simple task, completed by checking off the fields to capture for viewing, searching, filtering, and sorting later.

 bcssecondaryfields1

However, when you’re building your list or library using a custom list definition schema.xml file this is not as simple. After struggling a bit trying to code the XML myself, it dawned on me that I could mock up the field using the UI and then extract the properly formated field tags by getting the schemaxml proerty of the list.

 bcssecondaryfields2

 

There are various ways to do this but I’ve found the simplest way it to use a tool like SharePoint Manager

  1. Browse to the list/library definition using SharePoint Manager
  2. Copy and paste the SchemaXML property into a text file
  3. Save the text file with an XML extention
  4. Open the XML file in Visual Studio (or some other XML editor). If using Visual Studio, select “Format Document” from the Edit/Advanced menu (Ctrl+E, D)
  5. Scroll down to find the external data field you created through the UI. You’re looking for a <Field> tag with a the Type attribute equal to “BusinessData” The next <Field> tag with Type=”Note” will be the hidden field that stores the key value for the selected value. All the additional <Field> tags that follow will be the secondary fields you selected in the UI.
 bcssecondaryfields3

If you copy and paste those Field tags into your list definition schema file and remove the “SourceID” attribute, the list definition will generate a list with the appropriately defined external data column including the secondary fields.

To dig a little deeper note that you could potentially do this by hand. Creating the field tags for the hidden primary key field and the additional secondary fields isn’t all that difficult.

Where this all get’s tricky is trying to figure out how to format the SecondaryFieldBdcNames, SecondaryFieldWssNames, SecondaryFieldsWssStaticNames attributes of the primary “BusinessData” field tag. Below is our example field tag for the primary business data field.

<Field Type=BusinessData DisplayName=Order Required=FALSE ID={5a261e1a-e157-436c-83a2-fda125d72266} StaticName=Order0 BaseRenderingType=Text Name=Order0 ColName=nvarchar3 RowOrdinal=0 Version=6 Group=“” SystemInstance=MSSExternal EntityNamespace= http://sharepoint/bidemo EntityName=Order BdcField=OrderNumber Profile=/_layouts/ActionRedirect.aspx?EntityNamespace=http%3A%2F%2Fjturner%2Dsrv08r2%2Fbidemo&amp;EntityName=Order&amp;LOBSystemInstanceName=MSSExternal&amp;ItemID= HasActions=True SecondaryFieldBdcNames=15%2014%2015%208%20CustomerRegion%20CustomerState%20CustomerTarget%20Product%2011 RelatedField=Order_ID SecondaryFieldWssNames=33%2033%2033%2027%20Order%5Fx003a%5F%5Fx0020%5FCustomerRegio%20Order%5Fx003a%5F%5Fx0020%5FCustomerState%20Order%5Fx003a%5F%5Fx0020%5FCustomerTarge%20Order%5Fx003a%5F%5Fx0020%5FProduct%2012 RelatedFieldBDCField=“” RelatedFieldWssStaticName=Order_ID SecondaryFieldsWssStaticNames=33%2033%2033%2027%20Order%5Fx003a%5F%5Fx0020%5FCustomerRegio%20Order%5Fx003a%5F%5Fx0020%5FCustomerState%20Order%5Fx003a%5F%5Fx0020%5FCustomerTarge%20Order%5Fx003a%5F%5Fx0020%5FProduct%2012 AddFieldOption=AddToDefaultContentType, AddFieldToDefaultView/>

If you take the value of SecondaryFieldBdcNames attribute for instance and decode it you get:
15 14 15 8 CustomerRegion CustomerState CustomerTarget Product 11
The text CustomerRegion, CustomerState, etc all make sense, they are the root BDC field names. However the numbers that proceed and follow the field names allude me completely.

Regardless, I hope this workaround helps anyone out there who too was struggling to define secondary fields in a custom list definition.