Thursday, July 11, 2013

Visual Studio to add a Geolocation Site Column in SharePoint 2013

There is a new field type named Geolocation in SharePoint 2013, which allows us to easily integrate location and map functionality into SharePoint lists.

However this field is not available for the end user to choose in Create Column window. It must be added through code. PowerShell is one option. Here I use a Site Column and a Content Type and then use an Element to bind it to the pages library.

Site Column:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 
  <Field
       ID="{9218A4E1-12F1-40B7-B7AD-9A048B6CB6DF}"
       Name="MyLocation"
       DisplayName="My Location"
       StaticName="MyLocation"
       Type ="Geolocation"
       Required="TRUE"
       Group="My Site Columns">
  </Field>
</Elements>

Content Type:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Article Page (0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D) -->
  <ContentType ID="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00D2536686E7AA47789656CD7F6E8C9A35" Name="MyContentType" Group="Custom Content Types" Description="My Content Type" Inherits="TRUE" Version="0">
    <FieldRefs>
      <FieldRef ID="{9218A4E1-12F1-40B7-B7AD-9A048B6CB6DF}" DisplayName="My Location" Required="TRUE" Name="MyLocation" />
    </FieldRefs>
  </ContentType>
</Elements>

Content Type binding to the Pages library:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ContentTypeBinding
   ContentTypeId="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00D2536686E7AA47789656CD7F6E8C9A35"
   ListUrl="$Resources:osrvcore,List_Pages_UrlName;"/>
</Elements>

First, deploy above elements with a feature and then, create a page using “MyContentType” content type. Then select the Edit Properties option of the created Page item.


My location filed will be displayed in the edit properties window as follows. In there we have two options to provide the location details.


That is either by specifying the location coordinates (Longitude and Latitude) or by using the current location (my location). Selecting current location option will make a client-side call to determine location based on the IP information of the device making the call. It does not require GPS.


Once the list item is saved, we can see a globe symbol for the location and by clicking on it, we can see the Bing map view for the location with scroll and zoom features.


In addition there is a new view template provided for creating custom Map Views for the lists.


Note: Geolocation needs connection to the Bing Maps service. This requires us to register and request an API key from http://bingmapsportal.com. Even though Geolocation will work without this, there will be a message shown about Bing Maps key across the center of the map.  Once we get a key, we can apply it in PowerShell with the following command and it needs to be done once for the farm:

     Set-SPBingMapsKey -BingKey $yourKey

More information on Geolocation field in MDSN is here.

Tuesday, July 9, 2013

SharePoint Person or Group Field – Populate with Current User as the Default Value

I’m having a user type field in one of my publishing page layouts and I wanted to default it to current user. So when an author creating a page, user field will automatically populated with the current user. If author decided to change the value and set it to another user, new value will be retained.


I found this post: Set SharePoint PeoplePicker Fieldmark 2 in Andy Bonner's Blog, where the provided script is working fine in SharePoint 2010, but not in SharePoint 2013.

Here is the slightly modified version of the script for SharePoint 2013:

<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("fillDefaultValues");

function fillDefaultValues() {
  fillPeoplePickerWithCurrentUser(1);
  fillPeoplePickerWithCurrentUser(2);
}

function fillPeoplePickerWithCurrentUser(pickerNo) {
  var currentUser = getCurrentUser();
  if (currentUser != null) {
    var pp = getPickerImputElement(pickerNo);
    if (pp != null) {
      if (pp.innerHTML == '') {
        pp.innerHTML = currentUser;
      }
    }
  }
}

function getPickerImputElement(pickerNo) {
  var result = '';
  var divs = document.getElementsByTagName('DIV');
  var j = 0;
  for (var i = 0; i < divs.length; i++) {
    if (divs[i].id.indexOf('UserField_upLevelDiv') > 0) {
      j++;
      if (j == pickerNo) {
        result = divs[i];
        break;
      }
    }
  }
    return result;
}

function getCurrentUser() {
    var tags = document.getElementsByTagName('a');
    for (var i = 0; i < tags.length; i++) {
        if (tags[i].id == 'zz4_Menu') {
            var userName = tags[i].innerHTML;
            userName = userName.substr(0, userName.indexOf("<"))
            return userName;
        }
    }
}
</script>

Thursday, July 4, 2013

SPListItem.Update() - The file has been modified by SHAREPOINT\system

SPListItem.Update() - The file has been modified by SHAREPOINT\system

Sometimes it is necessary to have multiple solutions like event handlers, timer jobs, workflows, etc on SharePoint list items, which might work concurrently.

For example I’m working on a SharePoint project in which I create publishing pages using a PowerShell script, based on a XML file like below:


In the PS script I create a publishing page, set the properties as specified in the XML file, and then perform a $item.Update()

$pWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web)
$page = $pWeb.AddPublishingPage($PageUrl,$Layout)
$item = $page.ListItem             
$item["Title"] = $PageTitle;
$item["Page Content"] = $PageContent;
$item.Update()


I also had an Event Receiver bind to the same pages library, which will update a field in the content type in ItemAdded and ItemUpdated events.

public override void ItemAdded(SPItemEventProperties properties)
{
  using (SPWeb web = properties.OpenWeb())
  {
    try
    {
      Guid pagesLibraryId = Microsoft.SharePoint.Publishing.PublishingWeb.GetPagesListId(web);
      SPList pagesLibrary = web.Lists[pagesLibraryId];
      SPListItem item = properties.ListItem;

      if (item.ContentType.Name.Equals("NewsRelease"))
      {
        item[NEWSCLASSIFICATIONFIELD] = NEWS_RELEASE;
        item.Update();
      }                   
    }
    catch (Exception e)
    {}
  }
}

But when I run the PS script I always get the error: Exception calling "Update" The file has been modified by SHAREPOINT\system.

Reason:
When the page is created in PS, event receiver also fires and updates the list item. Then when PS try to update the lists item SharePoint throws an error since it is trying to perform an operation on a SPListItem that has been modified.

The Solution:
Instead of using SPListItem.Update() method, I used SPListItem.SystemUpdate(false) method in both PowerShell script and in the event receiver.

PowerShell script:     $item.SystemUpdate($false)
Event receiver:        item.SystemUpdate(false);

SPListItem.SystemUpdate() method updates the database with changes made to the list item without changing the Modified or Modified By fields. By passing false as the parameter, SPListItem.SystemUpdate(bool incrementListItemVersion) method will not even increment the item version.

------------ Update -------------

By default SharePoint event receiver ItemAdded executes asynchronously. We can make it synchronous by adding Synchronization tag in the event receiver Elemeny.xml file. This will also fix the issue mentioned in above case.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="850">
      <Receiver>       
        <Synchronization>Synchronous</Synchronization>
      </Receiver>     
  </Receivers>
</Elements>