How to implement Convert Lead feature in CRM 4.0


I’ve been dwelling in the world of MS CRM 4.0 for more than a month now, it’s a pretty decent product. In terms of development on CRM, it’s not exactly a pleasant experience. Because it’s such a closed and inflexible ecosystem, there’s only ever 1-2 ways to do something. After doing stuff for a while, it gets pretty repetitive and monotonous. However there was one piece of work that I had to complete which proved to be a little tricky, and I’m going to share with you in this post.

Essentially, I had to implement a behavior that is close to the “Convert Lead” feature in CRM. It was not as straightforward as I though it would be, and here’s the breakdown of things you need to do in order to implement the entire feature.

  1. Create a custom aspx / html page (ideally done entirely using JScript, because any postback on the dialog web page causes issues)
  2. Write JScripts on custom page to convert, deactivate the current record and validation.
  3. Modify ISV Config file to add a button into the toolbar.
  4. Add an Onload JScript to hide the button when entity is inactive. (there’s not in-built way in the ISV Config to do this, which really sucks)

Let’s use a fictitious entity for this example, called new_entity. So here goes.

Custom Web Page

ConvertPopup

One problem I had at first was to get a stylesheet that will give me the same look and feel of CRM. If you google around for it, you will be very much disappointed like me. I have no idea why the CSS is not shared, leaving developers like myself having to figure it out. Fortunately there is a standard template (download link at end of post) you can use from the CRM 4.0 SDK, and here’s the CSS.

BODY
{
font-size: 11px;
margin:	0px;
border: 0px;
cursor: default;
scrollbar-3dlight-color:#6699CC;
scrollbar-arrow-color:#567DB1;
scrollbar-base-color:#D6E8FF;
scrollbar-darkshadow-color:#6699CC;
scrollbar-face-color:white;
scrollbar-highlight-color:#D6E8FF;
scrollbar-shadow-color:#D6E8FF;
overflow: hidden;
background-color:#E3EFFF;
}

TD
{
font-size: 11px;
}

BODY, INPUT, SELECT, TEXTAREA
{
font-family : Tahoma, Verdana, Arial;
}

BUTTON
{
font-family: Tahoma;
font-size: 11px;
height: 20px;
width: 84px;
text-align: center;
cursor: pointer;
border: 1px #3366CC solid;
background-color: #CEE7FF;
background-image: url('/_imgs/btn_rest.gif');
background-repeat: repeat-x;
border: 1px #3366CC solid;
padding-left: 5px;
padding-right: 5px;
}

TD.ms-crm-Dialog-Header
{
background-color:#6693CF;
color:			#ffffff;
border-bottom:	1px solid #6693CF;
height:			51px;
vertical-align:	top;
padding:		5px;
}

DIV.ms-crm-Dialog-Header-Title
{
font-weight:	bold;
font-size:		13px;
padding-left:	5px;
}

DIV.ms-crm-Dialog-Header-Desc
{
padding-top:	4px;
padding-left:	5px;

}

Here’s the HTML template you can use.

<html>
<head>
<title>[DIALOG TITLE GOES HERE]</title>
	<link rel="stylesheet" type="text/css" href="styles/dialog.css" />

<script type="text/javascript" language="javascript">

	function ISV_ApplyChanges()
	{
		// TODO: Add your "save", "apply" or any other functionality here
		alert("Do Work Here");
	}

	function ISV_Cancel()
	{
		// TODO: Add your "cancel" or "close" code here
		window.close();
	}

</script>

</head>
<body>
<table style="width:100%; height:100%;" cellspacing="0" cellpadding="8">
<tr>
<td class="ms-crm-Dialog-Header">
<div class="ms-crm-Dialog-Header-Title">
					[DIALOG TITLE GOES HERE]</div>
<div class="ms-crm-Dialog-Header-Desc" id="DlgHdDesc">
					[DIALOG DESCRIPTION TEXT AND INSTRUCTIONS GO HERE]</div></td>
</tr>
<tr>
<td style="border-bottom: 1px solid #999999;" valign="top">
<div id="divFill">
					[HTML CONTENT GOES HERE]</div></td>
</tr>
<tr height="20">
<td style="border-top: 1px solid #ffffff;" align="right">
				<button onclick="ISV_ApplyChanges();" >OK</button>
				&nbsp;
				<button onclick="ISV_Cancel();" >Cancel</button></td>
</tr>
</table>
</body>
</html>

I recommend that you perform your validation (if any), update / create of entity using JScript. I’ve had some unexpected problems performing server-side calls and having postbacks. You will find many examples of performing CRUD using XML soap message in JScript in the SDK. Here’s an example of deactivating your entity when the user successfully perform the conversion.

function DeactivateEntity() {
    var opener = window.dialogArguments;
    var crmForm = opener.document.crmForm;

    // Prepare the SOAP message.
    var xml = "< ?xml version='1.0' encoding='utf-8'?>" +
      "<soap :Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'" +
      " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
      " xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +
      opener.GenerateAuthenticationHeader() +
      "</soap><soap :Body>" +
      "<execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
      "<request xsi:type='SetStateDynamicEntityRequest'>" +
      "<entity><id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + crmForm.ObjectId + "</id>" +
      "<name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>new_entity</name></entity>" +
      "<state>Inactive</state>" +
      "<status>2</status>" +
      "</request>" +
      "</execute>" +
      "</soap>" +
      "";
    // Prepare the xmlHttpObject and send the request.
    var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
    xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
    xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xHReq.setRequestHeader("Content-Length", xml.length);
    xHReq.send(xml);
    // Capture the result.
    var resultXml = xHReq.responseXML;
    // Check for errors.
    var errorCount = resultXml.selectNodes('//error').length;
    if (errorCount != 0) {
      alert("Unable to process your request. Please contact System Administrator.");
    }
  }

Which brings me to the next biggest problem. How do you get access to the CRMForm DOM elements and the GenerateAuthenticationHeader() JS function from your modal page? A good way I found is to use window.showModalDialog function, and from your modal page you can conveniently grab a context of the parent form using window.dialogArguements.

The first two lines of the function is the important bit, where I grab a reference to the parent form that opens this custom webpage as a modal dialog. This allows us to make use of the JS functions built into CRM to generate an authentication header using opener.GenerateAuthenticationHeader(), as well as being able to access the parent DOM to grab elements like the ID of the parent entity using crmForm.ObjectId.

Adding a Toolbar Button in ISV Config

Next we need to add a button into the toolbar of the entity you want it to show up in. First export a copy of the ISV Config file from CRM, and modify the XML.

  <importexportxml generatedBy='OnPremise'>
    <entities></entities>
    <roles></roles>
    <workflows></workflows>
    <isvconfig>
    <configuration version="3.0.0000.0">
      <root />
      <entities>
        <entity name="new_entity">
          <toolbar>
            <button Icon="/_imgs/ico/16_convert.gif" ValidForCreate="0" ValidForUpdate="1"
               JavaScript="
                if (crmForm.IsDirty) {
                  alert('Changes have been made to this record. Please save first.');
                }
                else {
                 var centerWidth = (window.screen.width - 300) / 2;
                 var centerHeight = (window.screen.height - 300) / 2;

                 var sFeatures = 'dialogHeight:300px;dialogWidth:300px;dialogTop:' + centerHeight
                                        + 'px;dialogLeft:' + centerWidth + 'px;';
                 var returnvalue = window.showModalDialog('/ISV/Convert.aspx', self, sFeatures);
                 if (returnvalue)
                 {
                  window.opener.location.reload(); // refresh dashboard
                  // refresh the current url.
                  var currentUrl = window.location.href;
                  if (currentUrl.indexOf('#') > -1) {
                    currentUrl = currentUrl.replace('#', '');
                  }
                  else {
                    currentUrl += '#';
                  }
                    window.location.href = currentUrl;
                  }
                }
              ">
              <titles>
                <title LCID="1033" Text="Convert" />
              </titles>
              <tooltips>
                <tooltip LCID="1033" Text="Convert" />
              </tooltips>
            </button>
          </toolbar>
        </entity>
      </entities>
      </configuration>
    </isvconfig>
    <languages>
      <language>1033</language>
    </languages>
  </importexportxml>

I placed my custom webpage in the ISV folder of the CRMWeb folder. Using window.showModalDialog, we can get a returnvalue from the dialog window. Therefore in your custom webpage, you will need to set window.returnvalue = true when your page successfully does the conversion; if value is false, just do nothing and close the modal form using window.close(). In order to refresh the CRM Form after the record has been deactivated, we retrieve the URL using window.location.href and perform some manipulation. If you hit Ctrl+N on the open CRM Form, you will see something like this.

ConvertPopupUrl

If you look at the address bar, you will notice that there’s a # appended at the end of the url. Therefore, in order to refresh the page, we need to remove the # and set the window.location.href to the modified URL. In case you’re wondering, I’ve tried using window.location.reload() but I get unexpected behaviors. I suspect reloading the page with reload() initiates a HTTP POST, which is not desirable. Once you’re done, upload the modifed XML back into CRM.

Onload JScript to hide the Button

In the ISV Config, there’s a ValidForCreate and ValidForUpdate to indicate when the button will appear, but there isn’t one to hide for Inactive record. Annoying! Anyways, we will have to use a JScript to handle that. Credit goes to my colleague (thanks Karl) who provided me with the JScript, here’s the logic for the OnLoad function.

  var Spacer = {Right : 1, Left : 2, Both : 3, None : 4}
  var Display = {Show : "inline", Hide : "none"}
  if (crmForm.FormType == DISABLED_FORM_MODE)
  {
    //Configure Display when the form loads.
    ConfigureToolbarDisplay();

    //Configure the display each time a user manually changes the window width size.
    attachEvent("onresize",ConfigureToolbarDisplay);
  }

  function ConfigureToolbarDisplay()
  {
    // use button title as input
    ShowHideToolbarButton("Convert" , Spacer.Left, Display.Hide);
  }  

  function ShowHideToolbarButton(btnTitle , spacer , state)
  {
    if(isNullOrEmpty(document.all.mnuBar1))
        return;
    if(isNullOrEmpty(btnTitle))
        return;
    if(isNullOrEmpty(spacer))
        spacer = ToolbarSpacer.None;
    if(isNullOrEmpty(state))
        state = ButtonDisplay.Hide;

    //Get all toolbar buttons
    var toolBarButtons = document.all.mnuBar1.rows[0].cells[0].childNodes[0].childNodes;

    //Loop through each button
    for (var i = 0 ; i &#60; toolBarButtons.length ; i++)
    {
        var button = toolBarButtons[i];
        if(button.title.match(btnTitle) != null)
        {
            button.style.display = state;
            switch(spacer)
            {
                case Spacer.Right:
                    ShowHideSpacer(button.nextSibling);
                    break;
                case Spacer.Left:
                    ShowHideSpacer(button.previousSibling);
                    break;
                case Spacer.Both:
                    ShowHideSpacer(button.nextSibling);
                    ShowHideSpacer(button.previousSibling);
                    break;
            }
            return;
        }
    }

    function ShowHideSpacer(btnSpacer)
    {
        if(!isNullOrEmpty(btnSpacer))
        {
            btnSpacer.style.display = state;
        }
    }

    function isNullOrEmpty(obj)
    {
        return obj == null || typeof(obj) == "undefined" || obj == "";
    }
  }

So that’s about it, quite a lot of work to implement a rather straightforward feature. I reckon most of the work is in the custom webpage with JScript you need to write and styling the page. It’s a pity I could not attach any screenshots of the work I did as it’s part of my current project and it would be inappropriate to do so. In this post, I have only covered the most important aspects of implementing such a feature , so if you have any queries, leave a comment and I will be happy to help.

Download VS2008 C# CRM WebPage Template here.

Share this post:
Advertisements

4 Responses to “How to implement Convert Lead feature in CRM 4.0”

  1. arman Says:

    Very interesting blog. Is there a possible to implement custom search with the Jscript and allowing the result in the Grid to be checked by the User ?

  2. Marwa Says:

    Hey .. i used your code that hide ISV buttons, but i made some modifications to check on user role and status field and as per the results Show/Hide buttons ..
    It worked perfect on form load and on maximize , but when i restore back, all buttons are shown as if the code isn’t working!
    Any help to catch onRestore event??

  3. Bugnar Tudor Says:

    Great Post. Very helpful for developing custom CRM dialogs that need to appear like the real thing :).


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: