Aug
27

Auto complete text box in Dynamics CRM 4.0

We needed a drop down box in our CRM 4.0 environments for a lot of our clients. The data from the drop down should come from within CRM 4.0 to limit the dependencies to other systems.

The cleanest way we found to do this is via an autocomplete textbox which is actually jQuery.  To make it work we needed to get data from CRM through the CRM webservice. We did this with some helper objects that we found athttp://blogs.inetium.com/blogs/azimmer/archive/2009/02/01/crm-4-0-javascript-web-service-helper-objects.aspx. These objects are created to support a .Net like language to connect to the CRM webservice.

The next problem was an autocomplete function in JavaScript. For this we chose the functions from http://www.pengoworks.com/workshop/jquery/autocomplete.htm. We changed these a bit so there is no-more CSS included, because we did not want to load this (since the look and feel of CRM won’t change (for now)) and we don’t want to screw up the CRM classes that are already in place on the form.

How does it stick together?

Finally we needed to write some plumbing code to make it work.

In the onLoad event of the form you’ll need to add the following code:

function load_script (url) 
{ 
    var x = new ActiveXObject("Msxml2.XMLHTTP"); 
    x.open('GET', url, false); x.send(''); 
    eval(x.responseText); 
    var s = x.responseText.split(/\n/); 
    var r = /^function\s*([a-z_]+)/i; 
    for (var i = 0; i < s.length; i++){ 
        var m = r.exec(s[i]); 
        if (m != null) 
            window[m[1]] = eval(m[1]); 
    } 
} 

load_script("/Custom/ISV/jquery-1.3.2.min.js");
var scriptTag1 = document.createElement("<script src='/Custom/ISV/jquery.autocomplete.js' type='text/javascript'>");
var scriptTag2 = document.createElement("<script src='/Custom/ISV/crmHelpers.js' type='text/javascript'>");
var scriptTag3 = document.createElement("<script src='/Custom/ISV/ISVspecific.js' type='text/javascript'>");
var array = new Array();
array = document.getElementsByTagName("head");
array[0].insertAdjacentElement("afterBegin", scriptTag1);
array[0].insertAdjacentElement("afterBegin", scriptTag2);
array[0].insertAdjacentElement("afterBegin", scriptTag3);

As for the creation of the ISVspecific.js script I would like to link to a previous post a wrote on Client-side validation scripts. In that post you can see a way to create a script paste it in CRM without changing code in the events of every field on every form.

The contactFormLoad function from the previous post (Client-side validation scripts):

function contactFormLoad(){
    var zipoptions = {
                        "borderColor" : "#6699CC",
                        "selectedColor" : "#a7cdf0",
                        "background" : "white",
                        "getDataCallback" : GetCountries 
                     };
    $("#address1_country").autocomplete(zipoptions);
    //add you other onchange scripts here
}

The GetCountries which is assigned to getDataCallback is where the CRM service queries are performed. This should be your own implementation. I have prepared some sample code that retrieves countries from a small custom entity called new_country with the attributes new_countryid and new_name.

function GetCountries(partialCountry){
    try{
        var crmService = new CrmService("new_country", LOGICAL_OPERATOR_OR);
        crmService.AddColumn("new_name");
    
        if(partialCountry != null){
            crmService.AddFilterCondition("new_name", partialCountry + "%", CONDITION_OPERATOR_LIKE);
        }
        var result = crmService.Fetch(20);
        
        for(var i in result.Rows)
        {
            var s= "";
            for(var j in result.Rows[i].Columns)
            {
                s += result.Rows[i].Columns[j].Value;
            }
        }
                
        var ar = new Array();
        var i = 0;
        var addvalue = true;
        for (rowsNumber in result.Rows) {
            addvalue = true;
            var newval = result.Rows[rowsNumber].GetValue("new_name");
            for(resultRow in ar) {
                if(ar[resultRow] == newval) {
                    addvalue = false;
                    break;
                }
            }
            if(addvalue){
                ar[i] = newval;
                i++;
            }
        }
        return ar;
    }
    catch(er) {
        alert(er.message);
    }
}

As you can see in the above code block, the function takes one parameter, being the part you typed in the field you need the autocomplete functionality on. Then some fetch xml is generated by the crmHelper objects and the query is executed. This will only get 20 records as specified in the line crmService.Fetch(20).

The crmHelpers.js contains a method called CrmService.prototype.Fetch, which is a reworked version of the CrmService.prototype.RetreiveMultiple from the code of Andrew Zimmer.

If you would like to use the autocomplete function on a lookup field in CRM (yes, that also works :)) you need to add “_ledit” (without the quotes) to the fieldname. Example:

$("#new_countryid_ledit").autocomplete(zipoptions);
 
You can download a complete sample but don’t forget to create the new_country entity if you want to test.
Posted by Wim De Coninck | 1 Comment

May
08

Dynamics CRM4.0: Client-side validation scripts

Everybody who customized CRM in the slightest will know that adding a client side validation script on one or more fields on a form is quite tedious and repetitive work.
 
Therefor we've looked a little bit deeper at the problem here at Orbit One, to be able to serve our customers quicker, better and more reliable.
 
Let me try and scetch the situation in CRM to add 1 field validator. There are a few steps that you need to do.
  1. Open CRM
  2. Goto Settings
  3. Click on the Customization link in the menu (left side)
  4. Select the entity for which you want to validate a field
  5. Click Forms and Views in the new screen (again on the left side)
  6. double click on the Form item
  7. double click on the Field you want to validate
  8. Select the tab Events
  9. Click on the Edit button
  10. Write your validation logic in JavaScript
  11. Do not forget to enable the event with the checkbox above your validation code
  12. Click the OK button
  13. Click the OK button
  14. Click the Save and Close button on top
  15. Click the Actions button on top and select publish.

Steps 7 through 14 are the steps you'll need to do for every field if you do the validations in bulk.

What we do now is the following. We add some code to the OnLoad event of the form as following:

  1. Open CRM
  2. Goto Settings
  3. Click on the Customization link in the menu (left side)
  4. Select the entity for which you want to validate a field
  5. Click Forms and Views in the new screen (again on the left side)
  6. Double click on the Form item
  7. Click on the Forms Properties button on the right side
  8. Click on the Edit button
  9. Write code here:
    var scriptTag = document.createElement("<script src='/Custom/validation.js' type='text/javascript'>" );
    var array = new Array();
    array = document.getElementsByTagName("head");
    array[0].insertAdjacentElement("afterBegin", scriptTag);
  10. Do not forget to enable the event with the checkbox above your validation code
  11. Click the OK button
  12. Click the OK button
  13. Click the Save and Close button on top
  14. Click the Actions button on top and select publish.
Now that we have done this, lets take a look at the validation.js file. It should load every validation we have, so lets start by adding something that will make the onload of document trigger:
var oldOnload = document.onload;
if(oldOnload != undefined && oldOnload != null && typeof(oldOnload) == "function"){
 if(crmForm.crmFormRootElem.value == "contact"){
  document.onload = function(){ oldOnLoad(); contactFormLoad(); }();
 }
} else {
 if(crmForm.crmFormRootElem.value == "contact"){
  document.onload = function(){ contactFormLoad(); }();
 }
}
 
What does this code do ??? Well, the onload function is stored in the oldOnload variable. Then we check if the oldOnload function is actually a defined, if it is not null and that the type is a function. If this is the case there already was an onload function, so we do not want to lose that one. So what happens next is we create a new document.onload function that calls the oldOnLoad and then our own onload function contactFormLoad. If there is not already an onload function for the document we just use our contactFormLoad.
 
The contactFormLoad function looks like this:
function contactFormLoad(){
 overloadOnChange("address1_telephone1", address1_telephone1_onchange);
 overloadOnChange("emailaddress1", emailaddress1_onchange);
 overloadOnChange("websiteurl", websiteurl_onchange);
 
 hookOnSave(contactFormSave);
}
 
As you can make up from the code above, we're going to validate a phonenumber, an email address and a website url.
 
Then we are going to make sure the onsave also gets triggered so all our validation logic is run at the save, just to make sure.
 
The overloadOnChange method: where most of the magic happens.
 
function overloadOnChange(fieldName, newOnChangeFunction){
 if(fieldName == null)
  return;
 var control = crmForm.all[fieldName];
 if (control != null){
  if(typeof(newOnChangeFunction) == "function"){
   if (typeof(control.onchange) == "function"){
    if (control.onchange != null) {
     var oldOnChangeFunction = control.onchange;
     control.onchange = function() { newOnChangeFunction(); oldOnChangeFunction(); };
    } else {
     control.onchange = function(){ newOnChangeFunction(); };
    }
   } else {
    control.onchange = function(){ newOnChangeFunction(); };
   }
  }
 }
}
 
This is a chunck of code that can be found inside CRM itself.
They use this to hook the clientside code to the fields if and only if the event is enabled checkbox is checked.
I don't want to check every checkbox on every field I want to validate. That's way to repetitive. So in the code above we flipped the positions of our new eventhandler and there old eventhandler, so that ours is triggered before theirs.
This seems like good practice to us because that way we can still use the validation out of the box in CRM like everybody does, but we can also use our method... So this is actually only added value. Nothing breaks in the inner workings of CRM.
 
 
And the hookOnSave part is this:
function hookOnSave(onsaveFunction){
 if (crmForm != null) {
  if(typeof(crmForm.onsave) == "function"){
   var oldOnsave = crmForm.onsave;
   if(oldOnsave != null) {
    crmForm.onsave = function(){ onsaveFunction(); oldOnSave(); };
   } else {
    crmForm.onsave = function(){ onsaveFunction(); };
   }
  } else {
   crmForm.onsave = function(){ onsaveFunction(); };
  }
 }
}
 
Again with the old function that's being saved and so forth and so on ... just as in the previous function we'll do our onsave trigger followed by CRM's own onsave.
 
In our onsave method - contactFormSave - we trigger our functions again:
 
function contactFormSave(){
 try{
     TryFireOnChange("address1_telephone1");
     TryFireOnChange("emailaddress1");
     TryFireOnChange("websiteurl");
     event.returnValue = isFormValid();
 }
 catch (e) {
     displayError("crmForm", "onsave", e.description);
 }
}
 
Followed by the TryFireOnChange. This function has been added because we've had a minor issue in migrating the script. One of the fields in our staging environment was missing in the production environment. So when the field you try to validate the field but it doesn't exist then the catch get hit and nothing happens.
 
function TryFireOnChange(fieldName){
 try {
  if(document.crmForm.all[fieldName] != undefined) {
   document.crmForm.all[fieldName].FireOnChange();
  }
 }
 catch (e){}
}
 
I am not going to elaborate on the validation functions itself because this is not really the scope of this post.
 
 
The PRO's to this approach are pretty huge for us:
  • we can migrate the validations from our staging environment to the production environment by just copying one file.
  • we can just edit the file instead of having to click a bunch of times in the CRM web interface
  • we can still do everything through the CRM web interface
  • we can manage the one file in our source control more easily and track everything better.
I hope you enjoyed reading this, and that it makes your work a little bit easier.
 
 
Posted by Wim De Coninck | Leave your feedback

Contact us - Raas Van Gaverestraat 83, 9000 Gent, Belgium - Tel. +32 (9) 330.15.00 - Privacy Statement - Sitemap - Sign In Developed with Microsoft Office SharePoint Server 2007