Tuesday, April 7, 2009

SharePoint folder integration

CRM and SharePoint are natural companions - both are web-based applications, both use the same workflow engine (WinWF), and SharePoint provides a natural completion to CRM's abilities by providing document management. A great way to use this - documents attached to a specific CRM entity, such as agreements on an account, or perhaps customer-delivered documentation attached to specific opportunities. Most recently, I was asked to integrate with CRM campaigns, and use SharePoint as a location to store the creative, graphics, etc. that go along with a specific campaign. This way, when a user accesses a campaign in CRM, all of the relevant files will simply appear in an additional tab on the form, hosted inside an iFrame.

In this case, I decided to store the documents for the campaign inside folders in the SharePoint document library - a folder for each campaign, all within a single library. I also decided that users won't need to access these folders from outside of CRM, so the name of each folder is simply the campaignid GUID to avoid duplicates or other problems. I also set up a special page to use in the iFrame that eliminates the SharePoint "chrome", giving me a bit more screen real estate to display the actual documents.

So now to create the folders dynamically - I need to ensure a folder is created for each campaign automatically. The "standard" method for doing this was grabbed from someone else (sorry, I don't have a link) and involves using an ActiveX FileSystemObject control. This method accesses SharePoint via UNC path, checks to see if a folder by the right name is in the library, and creates one if it is not there. An example is here:


var theIncidentId = crmForm.ObjectId;
var oShell = new ActiveXObject("Scripting.FileSystemObject");

if (! oShell.FolderExists("\\\\Netshare\\IncidentAttachments\\"+ theIncidentId) )
oShell.CreateFolder("\\\\Netshare\\IncidentAttachments\\"+ theIncidentId )
else{
}


Of course, you can use this code to access any fileshare - this is certainly not limited to SharePoint only. However, the fact that SharePoint document libraries are accessible via UNC means you can use this same functionality with SharePoint and it works great...

...Unless you can't get UNC access to SharePoint. This might occur for a few reasons - if SharePoint is not installed on port 80, or maybe if you use a proxy between the end computer and SharePoint, or if using an IFD, or if you are using SharePoint Online. In my case, I couldn't get to SharePoint for an unknown reason via UNC, so rather than troubleshooting SharePoint (which I don't really have control over) I decided to look into an alternative - using the SharePoint web services.

My inspiration came from here: http://blogs.msdn.com/crm/archive/2006/10/23/creating-folders-in-sharepoint-document-libraries.aspx . Their example used a callout, but this proved that the functionality was available in SharePoint. I had another example that involved finding a user's roles using a call to the CRM webservices (one example is here - there are others) . I realized that both ends of the process were there - I simply needed something to bridge the gap and access SharPoint via webservice to create the new folder. Lucky for me, I was able to find a helpful blog by Darren Johnstone that had created all of the javascript needed to talk to SharePoint - I just needed to modify it a bit and use it with CRM. Since Mr. Johnstone had broken the real work out into his classes, I had to do a bit of reverse-engineering to pull it back into an in-line function to make it work with CRM. The end result creates the folder in SharePoint with the webservices just like the FileSystemObject would, but now it will work with SharePoint anywhere! The only issue I haven't worked out yet is how to CHECK for the folder before attempting to create it, so right now I just let it error itself out (SharePoint won't create a duplicate folder) and ignore it. This could become a larger problem later, but it works for me for right now.
The final code is below:

// Only run code on Update forms
if(crmForm.FormType == 2)
{
var objectId = crmForm.ObjectId;
objectId = objectId.replace(/[{}]+/g,'');

// The service URL should go to the site with the document library
var varServiceUrl = "http://sharepoint/SiteName/Marketing/_vti_bin/lists.asmx";

// This function takes the doc library name and the name of the new folder
// You can also pass a third parameter to specify a root folder
var res = createFolder("CRMIntegration", objectId);

// Error catching, which I am currently ignoring
// if (res.status == 200)
// {
crmForm.all.IFRAME_SPDocs.src = "http://sharepoint/SiteName/Marketing/CRMIframe/CampaignCreative.aspx?RootFolder=%2fSiteName%2fMarketing%2fCRMIntegration%2f" + objectId + "&FolderCTID=&View=%7bE2F9780A%2d0546%2d4254%2d92EA%2d31B1A584914E%7d";
// }
// else {

// alert("error creating folder: " + res.statusText);
// }
}


// Functions to support SharePoint folder creation

// Creates batch XML file for new folder
// (Includes optional rootFolder parameter - see Darren Johnstone's blog for usage)
function createFolder(listName, folderName, rootFolder)
{
var batch;
batch = " if (rootFolder != null)
{
batch += " RootFolder='" + rootFolder + "'";
}
batch += ">";
batch += ""
+"1"
+"" + folderName + ""
+"
"
+"
";
return updateListItems(listName, batch);
}

// Delivers message to SharePoint
// (Eliminated dependance on javascript classes)
function updateListItems(listName, updates)
{
var oXMLHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
var result = null;
var resultName;

oXMLHttpRequest.open("POST", varServiceUrl, false);
oXMLHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
oXMLHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/sharepoint/soap/UpdateListItems");

var packet = ["",
"",
"",
"",
"",
listName,
"
",
"",
updates,
"
",
"
",
"
",
"
"
].join("");

oXMLHttpRequest.send(packet);

resultName = "UpdateListItems";
var resBatch;
var status;
var statusText;

status = oXMLHttpRequest.status;
statusText = oXMLHttpRequest.statusText;

if (status == 200)
{
// Check for SharePoint error code
resBatch = oXMLHttpRequest.responseXML.getElementsByTagName(resultName);

var codeEl = oXMLHttpRequest.responseXML.getElementsByTagName('ErrorCode');

if (codeEl != null && codeEl.length > 0)
{
var spStatus = parseInt(codeEl[0].childNodes[0].nodeValue);

if (spStatus != 0)
{
status = 0-spStatus; // Note we make this -ve to prevent confusion with the HTTP code

var messageEl = oXMLHttpRequest.responseXML.getElementsByTagName('ErrorText');
if (messageEl != null && messageEl.length >= 0)
{
statusText = messageEl[0].childNodes[0].nodeValue;
}
}
}
}

result = {
status : status,
statusText : statusText,
responseXML : oXMLHttpRequest.responseXML,
responseText : oXMLHttpRequest.responseText,
resultNode : (resBatch == null || resBatch.length == 0 ? null : resBatch[0])
};
return result;
}

1 comment:

  1. A well known payday brisk loan firm will likewise avow straightforwardly what sorts of augmentations can be conceivable. A mainstream payday fast loan firm will likewise confirm transparently what sorts of expansions can be conceivable and which measure of augmentations charges and the most extreme number of expansions relevant for your payday brisk loan which you are permitted to take.
    http://www.shop12monthloans.co.uk/

    ReplyDelete