Friday, February 27, 2009

Questions for Convergence anyone?

I will be attending Convergence in New Orleans next month and I will be working hard to talk with as many of the experts as I can and getting as much info as I can from the sessions as I can. However, I've found that going to events like this is much more effective if I have some questions in mind to try to get answered. I have some questions of my own that I will be asking but Convergence but I thought it would beneficial to the community to see what you want to know about even though you may not be able to attend.

If anyone out there isn't attending but has a question that is general enough for the product, road map, etc I'd love to try to find out for you. I can't guarantee that I'll be able to get every question answered but I'll do my best to take your questions and post them with answers/responses direct from Microsoft. Also, I happen to leverage the Scribe tool a fair amount and I will be meeting with them so I will even take Scribe questions relating to migrating/integrating with MSCRM.

My goal is to post multiple times a day while I'm at Convergence so stay tuned during Convergence week for lots of updates from the Microsoft world.

David Fronk
Dynamic Methods Inc.

SRS 2008 (SQL 2008) Export to WORD

Just one more reason to upgrade SQL to 2008, SRS out of the box has the ability to export directly to Word. I've seen SRS render it's exports to PDF in funky ways and you're stuck with however SRS exports since you can't modify your PDF's. However, with the export to Word things are much cleaner and you can even tweak the document a bit before you send to PDF from Word.



Great out of the box functionality,

David Fronk
Dynamic Methods Inc.

Friday, February 20, 2009

Autofill Account from Contact (custom fields)

There are many times where I have come across requests to fill in fields automatically based on other fields in order to help cut down the time for data entry and also to help keep data integrity higher. One of the great ways to do this is to help in filling out "related" lookup fields. For instance, on an Opportunity a lot of companies can't always decide whether they want to see an Account or an Account in the Customer field. Or, management decides on Accounts and then the end users put Contacts in the Customer field because that's what makes sense to each role. However, this is a disaster for reports and getting the correct metrics at the end of a month, quarter, or year. A majority of the time it just makes more sense to show both Account and Contact fields on the Opportunity.

There is a script that can be used to help fill in one of the lookups for your end users. If you have them fill in the Contact field first you can figure out who that Contact works for by looking up their Parent Customer field. Sure, you could do this in a Plugin/Callout, but why do that when you can have the end user see it all filled out right in front of them? It makes a big difference to the end users. There are only a couple of lines that should have to be modified:

1. lookupItem = crmForm.all.[insert your Contact field here].DataValue;
2. crmForm.all.[your Account field].DataValue = larrray;

Outside of that if you shouldn't have to change anything if you are querying an Account via the ContactId. If you want to change the query that's up to you. Last thing before I post the script, this uses the 2006 WSDL, so don't expect this to work come CRM 5.0. We might get lucky and it could still work, but just know that it might not.

Here's the script:

var lookupItem = new Array;
lookupItem = crmForm.all.new_clientcontactid.DataValue;
var clientid;
if (lookupItem != null)
{
clientid = lookupItem[0].id;

var prodxml = GetClientInfo();
var companyid = "";

function GetClientInfo()
{
var xml = "" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<soap:envelope soap="\" href="http://schemas.xmlsoap.org/soap/envelope/">http://schemas.xmlsoap.org/soap/envelope/\</a>" xmlns:xsi=\"<a href="http://www.w3.org/2001/XMLSchema-instance/">http://www.w3.org/2001/XMLSchema-instance\</a>" xmlns:xsd=\"<a href="http://www.w3.org/2001/XMLSchema/">http://www.w3.org/2001/XMLSchema\</a>">" + "<soap:body>" + " <query q1="\" href="http://schemas.microsoft.com/crm/2006/Query/">http://schemas.microsoft.com/crm/2006/Query\</a>" xsi:type=\"q1:QueryExpression\" xmlns=\"<a href="http://schemas.microsoft.com/crm/2006/WebServices/%22%3e">http://schemas.microsoft.com/crm/2006/WebServices\"></a>" + "<q1:entityname>contact</q1:EntityName>" +"<q1:columnset type="\">" + "<q1:attributes>" + "<q1:attribute>parentcustomerid</q1:Attribute>" + "</q1:Attributes>" + "</q1:ColumnSet>" + "<q1:distinct>false</q1:Distinct>" + "<q1:criteria>" + "<q1:filteroperator>And</q1:FilterOperator>" + "<q1:conditions>" + "<q1:condition>" + " <q1:attributename>contactid</q1:AttributeName>" + "<q1:operator>Equal</q1:Operator>" + "<q1:values>" + "<q1:value type="\">" + clientid + "</q1:Value>" + "</q1:Values>" + "</q1:Condition>" + "</q1:Conditions>" + "</q1:Criteria>" + "</query>" + "</soap:Body>" + "</soap:Envelope>" + "";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2006/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","<a href="http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple">http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple</a>");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);

var resultXml = xmlHttpRequest.responseXML;
//alert("resultxml = " + resultXml);

var entityNodes = resultXml.selectNodes("//RetrieveMultipleResult/BusinessEntities/BusinessEntity");

for (var i = 0; i < entityNodes.length; i++)
{
var entityNode = entityNodes[i];

var companyidnode = entityNode.selectSingleNode("q1:parentcustomerid");

companyid = (companyidnode == null) ? null : companyidnode.text;
}

/** get the name of the company **/
var xml1 = "" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<soap:envelope soap="\" href="http://schemas.xmlsoap.org/soap/envelope/">http://schemas.xmlsoap.org/soap/envelope/\</a>" xmlns:xsi=\"<a href="http://www.w3.org/2001/XMLSchema-instance/">http://www.w3.org/2001/XMLSchema-instance\</a>" xmlns:xsd=\"<a href="http://www.w3.org/2001/XMLSchema/">http://www.w3.org/2001/XMLSchema\</a>">" + "<soap:body>" + " <query q1="\" href="http://schemas.microsoft.com/crm/2006/Query/">http://schemas.microsoft.com/crm/2006/Query\</a>" xsi:type=\"q1:QueryExpression\" xmlns=\"<a href="http://schemas.microsoft.com/crm/2006/WebServices/%22%3e">http://schemas.microsoft.com/crm/2006/WebServices\"></a>" + "<q1:entityname>account</q1:EntityName>" +"<q1:columnset type="\">" + "<q1:attributes>" + "<q1:attribute>name</q1:Attribute>" + "</q1:Attributes>" + "</q1:ColumnSet>" + "<q1:distinct>false</q1:Distinct>" + "<q1:criteria>" + "<q1:filteroperator>And</q1:FilterOperator>" + "<q1:conditions>" + "<q1:condition>" + " <q1:attributename>accountid</q1:AttributeName>" + "<q1:operator>Equal</q1:Operator>" + "<q1:values>" + "<q1:value type="\">" + companyid + "</q1:Value>" + "</q1:Values>" + "</q1:Condition>" + "</q1:Conditions>" + "</q1:Criteria>" + "</query>" + "</soap:Body>" + "</soap:Envelope>" + "";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2006/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","<a href="http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple">http://schemas.microsoft.com/crm/2006/WebServices/RetrieveMultiple</a>");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml1);

var resultXml1 = xmlHttpRequest.responseXML;

var entityNodes = resultXml1.selectNodes("//RetrieveMultipleResult/BusinessEntities/BusinessEntity");

for (var j = 0; j < entityNodes.length; j++)
{
var entityNode = entityNodes[j];

var namenode = entityNode.selectSingleNode("q1:name");

var companyname = (namenode == null) ? null : namenode.text;
}

var larray = new Array;
var l = new Object();
l.id = companyid;
l.typename = 'account';
l.name = companyname;
larray[0] = l;
crmForm.all.new_clientcompanyid.DataValue = larray;

return resultXml;
}
}

Please note, I tested this in CRM Online and it doesn't work. Perhaps the 2006 WSDL isn't exposed with CRM Online, I'm not 100% sure, but just be aware that this script won't work there. Sorry.

David Fronk
Dynamic Methods Inc.

Friday, February 13, 2009

Change Activity History to "All" instead of "Last 30 Days"

While there have been other bloggers out ther who have blogged about this topic (Stunnware, CustomerEffective, etc) this request is made so often and the answer isn't always the easiest to find...so my hope is that if more of us MSCRM bloggers blog about then it should be easier for everyone to find and use. This code snippet comes from Stunnware (here's the link).

Just take the code below and put it in your onLoad script and you should be all set.

Enjoy!

/**************************************************************
* Change the default view of a view selection combo box
**************************************************************/
SetDefaultView = function(viewCombo, viewName, appGrid) {

/* If the view has already been set, we don't need to do it again. */
if (viewCombo.value != viewName) {

/* Set the new view */
viewCombo.value = viewName;

/* Call RefreshGridView to run the code in the DHTML control.
* Without this call, only the selection in the combo box changes,
* but not the content of the grid */
appGrid.RefreshGridView();
}
}

/**************************************************************
* Event handler. Called whenever the ready state of the
* areaActivityHistoryFrame changes.
**************************************************************/
areaActivityHistoryFrame_OnReadyStateChange = function() {

/* Waiting until the frame has finished loading */
if (this.readyState == "complete") {

/* This is the frame we're interested in */
var frame = document.frames("areaActivityHistoryFrame");

/* And this is the view combo box */
var viewCombo = frame.document.getElementById("actualend");

/* This is the AppGridFilterContainer control we need to refresh the view */
var appGrid = frame.document.getElementById("AppGridFilterContainer");

/* The view combo box uses a style sheet that references a HTML
* control. We have to wait until the htc file is loaded,
* otherwise the call to FireOnChange in the SetDefaultView
* method will fail. */
if (viewCombo.readyState == "complete") {

/* If the control already has finished loading, we can
* directly set the new view. */
SetDefaultView(viewCombo, "All", appGrid);
}

else {
/* Otherwise we have to register another event handler
* waiting until all of the include files used by the
* combo box are loaded as well. */
viewCombo.onreadystatechange = function() {
if (this.readyState == "complete") {
SetDefaultView(this, "All", appGrid);
}
}
}
}
}

/* Set a new onclick event for the History navigation element
* This is where we register the onreadystatechange event handler */
if (document.getElementById('navActivityHistory') != null) {
document.getElementById('navActivityHistory').onclick = function() {
loadArea('areaActivityHistory');
document.frames('areaActivityHistoryFrame').document.onreadystatechange = areaActivityHistoryFrame_OnReadyStateChange;
}
}


David Fronk
Dynamic Methods Inc.

Friday, February 06, 2009

Grid Buttons...a better way

I've received some comments on my previous post regarding grid buttons in CRM and using the getSeleted('crmGrid') method via Jscript within the ISV.config file and how it is risky since it is an undocumented method and is very subject to change. One of the other limitations with that method is that all of the GUID's that are retrieved are placed in the URL query string. While that easy to access I quickly found out that there is a character limit on how many characters you can put into that query string. Using the getSelected('crmGrid') method really only allows you access to about 50 records or less. If you select more it will look like your button and code worked, when in fact it never did anything to the select records that push the count beyond 50.

I have seen mentioned of using "window.dialogArguments" to gain access to what is selected on the grid. I looked for documentation, posts, anything and I found minimal information on how to use this within MSCRM. MSCRM 3.0 had zero documentation on this. So, I was reluctant to even try the 4.0 SDK. But surprisingly enough there is a great article in the 4.0 SDK. It's titled "Walkthrough: Capturing the GUID Values of Records Selected in a Grid". It steps you through the set up of a button and webpage and even gives you code for your webpage. However, a lot of the coding I do ends up in the codebehind page (.cs page) which resides on the server. And the code the SDK gives is all Jscript...and getting Jscript to talk to server-side code isn't seemless, you have to make them talk to each other. And the SDK left that part out completely. So, I am posting a way that I have figured out on how to make this work from a webpage and still being able to use codebehind to access strongly the typed development via the webservices of MSCRM. If someone else has any other way that they have accomplished this I'd love to see what you've done, because either everyone knows how to do this and isn't sharing because it's so easy, or few people know about it and those who do are keeping it to themselves, or I'm just not looking in the right places.

The above mentioned walkthrough will get the GUID's via Jscript but not make the GUID's available to the server-side code. Please note that the article mentions that you MUST set up your button tag in the ISV.config file as WinMode 1 or 2. 0 WILL NOT WORK. This is key in the passing of data from one webpage to another, so be aware of that. Once that's set up , in order to pass data from script to server-side code the use of a hidden field can be used. Below is the HTML page (minus the codebehind references) used to get all of the GUID's when a button from the grid is clicked:

<html>
<head>
<title>GUIDs for records seleced in Grid</title>
<script type="text/jscript">
function listselecteditems() {
var placeholder = document.getElementById("test");
var hiddenfield = document.getElementById("HiddenField1");
var sGUIDValues = "";
var selectedValues;
//Make sure window.dialogArguments is available.
if (window.dialogArguments) {
selectedValues = new Array(window.dialogArguments.length - 1);
}
else {
placeholder.innerText = "window.dialogArguments is not available.";
return
}
selectedValues = window.dialogArguments;
if (selectedValues != null) {
for (i = 0; i < selectedValues.length; i++) {
sGUIDValues += selectedValues[i] + "\n";
}
//placeholder.innerText = sGUIDValues;
var hidden = sGUIDValues;
form1.HiddentField1.value = hidden;
form1.submit();
}
else {
placeholder.innerText = "No records were selected.";
}
}
</script>
</head>
<body onload="listselecteditems()">
<form id="form1" runat="server">
<div runat="server" id="test">
<input id="HiddentField1" type="hidden" runat="server" />
</div>
</form>
</body>
</html>


Take note of the "hidden input". That is a textbox where I put all of the GUID's. This tag MUST have the runat="server" option or else your codebehind will never see the control. But that control is the key to getting access to the GIUD's in your server-side (codebehind) code. One other comment, the form1.submit(); must also be present or else what is on the page never goes to the server. And since your server-side code is…on the server, the values will never be read. So, submit the values to the server and you'll be ready to access your GUID's from your codebehind.

Once you have that you can do whatever you need to with the GUID's. Here's an example page load that I did:

protected void Page_Load(object sender, EventArgs e)
{
CrmService crmService = new CrmService();
crmService.Credentials = System.Net.CredentialCache.DefaultCredentials;
string rawString = HiddentField1.Value.ToString();
rawString = rawString.TrimEnd();
string[] ids = rawString.Split(new char[] { '\n' });
int r = 0;
while (r < ids.Length) {
ids[r] = ids[r].Replace("{", "").Replace("}", "");
try {
if (ids[r] != null && ids[r] != "") {
//Use a method to do whatever you need to off of the GUID's found
//Update a given field, create a related object, etc.
updateSomething(crmService, ids[r]);
}
}
catch { //Error code }
r++;
}
}


Once you have the GUID's in your codebehind, the rest is up to you and your imagination, or the needs of your system. That should get you going with what you need.

Happy coding!

David Fronk
Dynamic Methods Inc.