Hello JScript friends,

I'm back from vacation and back again to enjoy some interesting jscript stuff with you. Do you like JScript? I do not! However, since Web 2.0 and AJAX, it has become very popular because it makes web applications much more user friendly. And you could use jscript even for extending the client side behaviour of Microsoft Dynamics CRM. That is one more reason to use the good old trial-and-error script language. Especially AJAX techniques will help you to add validation logic on the client side of Microsoft CRM.

So what is AJAX? The magic word AJAX (Asynchronous Javascript and XML) is a web development technique for creating rich and interactive web applications, where additional data is loaded and displayed after the page is already loaded in the browser. The motivation for this technique is to make web apps more user friendly by minimizing the numbers of manual page loads and roundtrips between server and client.  AJAX has become a buzz word in 2005 but the technique is not very new. The first time I saw it was in the Navision User Portal years ago, a web based application to access the Navision ERP-System.

In this post I would like to show you how you could use AJAX-techniques together with Microsoft CRM to implement the following functionality:

1. Creating auto suggestion fields for Microsoft CRM
2. Load the data for the auto suggestion list via a web service.

Just take a look at the screenshots and you will understand what I mean. This is just a simple functionality/function but you can use this technique in many other scenarios. E.g. server side validations without a roundtrip or having a central list without using picklists.

1. Creating auto suggestion fields for Microsoft CRM

Lets start: just copy the following function into the onload event of any entity.

// Function for adding suggestion functionality
// for text input fields in Microsoft CRM
// textfield: document.getElementById('address1_country')
// method: a function, that returns an array of strings 
// preload: true or false
function SuggestionTextBox(textfield, method, preload)
{   
// max items in the suggestion box
    
var maxItems 6;
    
    this
.suggestionList = new Array();
    this
.suggestionListDisplayed = new Array();
    
  
    var 
actual_textfield textfield;
    var 
actual_value '';
    
    var 
selectedNumber 0;
    var 
countMatches 0;
    
    if 
(preload)
    {
        
// load the data via external method
        
this.suggestionList method();
    
}
    
    
// attach this function to the textfield
    
textfield.attachEvent("onfocus", initTextfield);
 
    
// init textfield and attach necessary events
    
function initTextfield()
    {
        
// when leaving the field we have to clear our site    
        
textfield.attachEvent("onblur", resetTextfield);
        document
.attachEvent("onkeydown", keyDown);
    
}

    
function resetTextfield(e)
    {
        
//when leaving the field, we have to remove all attached events
        
document.detachEvent("onkeydown", keyDown);
        
textfield.detachEvent("onblur",resetTextfield);
    
}

    
function keyDown(e)
    {
        keyCode 
e.keyCode;
        

        switch 
(keyCode)
        {
            
case 9case 13:
                
// enter & tab key
                
if (countMatches > 0)
                {
                   
                    
                    actual_textfield.
value = suggestionListDisplayed[selectedNumber];
                    
                    if 
(document.getElementById('suggestion_table') != null)
                    { 
                       
document.body.removeChild(document.getElementById('suggestion_table'));
                    

                    
                }
                
                
                
break;
            case 
38:
                
//pressing up key
                
if(selectedNumber > && countMatches > 0)
                {
                    selectedNumber--
;
                    
createSuggestionTable();
                
}
                
                
return false;
                break;
            case 
40:
                
// pressing down key
                
if(selectedNumber < countMatches-&& countMatches > && selectedNumber < maxItems)
                {
                    selectedNumber++
;
                       
createSuggestionTable();
                
}
                
                
return false;
                break;                
            default
:
                
// do not call the function to often
                
setTimeout(
                            
function()
                            {
                                executeSuggestion(keyCode)
                            },  
200 /* in ms */
                           
);
                break;
        
}
    }

    
function executeSuggestion(keyCode)
    {
        selectedNumber 
0;
        
countMatches 0;
        
        
actual_value textfield.value;
        
//todo add keyCode
        
    
        // get all possible values from the suggestionList
        
        
if (!preload)
        {
            
// load the data via external method
            // todo add some caching function
            
this.suggestionList method();
        
}
        
        
// using regular expressions to match it against the suggestion list
        
var re = new RegExp(actual_value, "i");
        
        
//if you want to search only from the beginning
        //var re = new RegExp("^" + actual_value, "i");
                
        
countMatches 0;
        this
.suggestionListDisplayed = new Array();
        
        
// test each item against the RE pattern
        
for (i 0i < this.suggestionList.lengthi++)
        {
            
// if it matche add it to suggestionListDisplayed array
            
if (re.test(this.suggestionList[i]) && actual_value !'')
            {
                
this.suggestionListDisplayed[countMatches] = this.suggestionList[i];
                
countMatches++;
                
                
// if there are more values than in maxItems, just break
                
if (maxItems == countMatches)
                    
break;
            
}
        }
        
        
if (countMatches > 0)
        {
            createSuggestionTable()
;
        
}
        
else
        
{
            
if (document.getElementById('suggestion_table'))
            { 
                
document.body.removeChild(document.getElementById('suggestion_table'));
            

        }
    }
    
   
    
function createSuggestionTable()
    {
        
        
if (document.getElementById('suggestion_table'))
        { 
            
document.body.removeChild(document.getElementById('suggestion_table'));
        

        
        
// creating a table object which holds the suggesions
        
table = document.createElement('table');
        
table.id 'suggestion_table';
        
        
table.width actual_textfield.style.width;
        
table.style.position'absolute';
        
table.style.zIndex '100000';

        
table.cellSpacing '1px';
        
table.cellPadding '2px';


        
topValue 0;
        
objTop actual_textfield;
        while
(objTop)
        {
            topValue +
objTop.offsetTop;
            
objTop objTop.offsetParent;
        
}
        
        table.style.top 
= eval(topValue + actual_textfield.offsetHeight) + "px";

        
leftValue 0;
        
objLeft actual_textfield
        
while(objLeft)
        {
            leftValue +
objLeft.offsetLeft;
            
objLeft objLeft.offsetParent;
        
}

        table.style.left 
leftValue + "px";
        
        
table.style.backgroundColor '#FFFFFF';
        
table.style.border "solid 1px #7F9DB9";
        
table.style.borderTop "none";
        
        document
.body.appendChild(table);
        
        
// iterate list to create the table rows        
        
for ( i 0i < this.suggestionListDisplayed.lengthi++)
        {
                row 
table.insertRow(-1);
                
                
row.id 'suggestion_row' + (i);
                
column row.insertCell(-1);
                
column.id 'suggestion_column' + (i);
                
                
                if 
(selectedNumber == i)
                {
                    column.style.color 
'#ffffff';
                    
column.style.backgroundColor '#316AC5';
                
}
                
else
                
{
                    column.style.color 
'#000000';
                    
column.style.backgroundColor '#ffffff';
                
}
                
                column.style.fontFamily 
'Tahoma';
                
column.style.fontSize '11px';
                
column.innerHTML = this.suggestionListDisplayed[i];
                
        
}
    }
    
    
// return object
    
return this;
}

Next, we have to call the function for every textfield where we want to add this behavior.

var = function ListOfCountries()
    {
        
return new Array('Afghanistan','Albania','Algeria','American Samoa','Andorra','Angola','Anguilla','Antigua and Barbuda','Argentina','Armenia','Aruba','Australia','Austria','Azerbaijan','Bahamas','Bahrain','Bangladesh','Barbados','Belarus','Belgium','Belize','Benin','Bermuda','Bhutan','Bolivia','Bosnia-Herzegovina','Botswana','Bouvet Island','Brazil','Brunei','Bulgaria','Burkina Faso','Burundi','Cambodia','Cameroon','Canada','Cape Verde','Cayman Islands','Central African Republic','Chad','Chile','China','Christmas Island','Cocos (Keeling) Islands','Colombia','Comoros','Conch Republic','Congo, Democratic Republic of the (Zaire)','Congo, Republic of','Cook Islands','Costa Rica','Croatia','Cuba','Cyprus','Czech Republic','Denmark','Djibouti','Dominica','Dominican Republic','Ecuador','Egypt','El Salvador','Equatorial Guinea','Eritrea','Estonia','Ethiopia','Falkland Islands','Faroe Islands','Fiji','Finland','France','French Guiana','Gabon','Gambia','Georgia','Germany','Ghana','Gibraltar','Greece','Greenland','Grenada','Guadeloupe (French)','Guam (USA)','Guatemala','Guinea','Guinea Bissau','Guyana','Haiti','Holy See','Honduras','Hong Kong','Hungary','Iceland','India','Indonesia','Iran','Iraq','Ireland','Israel','Italy','Ivory Coast (Cote D`Ivoire)','Jamaica','Japan','Jordan','Kazakhstan','Kenya','Kiribati','Kuwait','Kyrgyzstan','Laos','Latvia','Lebanon','Lesotho','Liberia','Libya','Liechtenstein','Lithuania','Luxembourg','Macau','Macedonia','Madagascar','Malawi','Malaysia','Maldives','Mali','Malta','Marshall Islands','Martinique (French)','Mauritania','Mauritius','Mayotte','Mexico','Micronesia','Moldova','Monaco','Mongolia','Montserrat','Morocco','Mozambique','Myanmar','Namibia','Nauru','Nepal','Netherlands','Netherlands Antilles','New Caledonia (French)','New Zealand','Nicaragua','Niger','Nigeria','Niue','Norfolk Island','North Korea','Northern Mariana Islands','Norway','Oman','Pakistan','Palau','Panama','Papua New Guinea','Paraguay','Peru','Philippines','Pitcairn Island','Poland','Polynesia (French)','Portugal','Puerto Rico','Qatar','Reunion','Romania','Russia','Rwanda','Saint Helena','Saint Kitts and Nevis','Saint Lucia','Saint Pierre and Miquelon','Saint Vincent and Grenadines','Samoa','San Marino','Sao Tome and Principe','Saudi Arabia','Schengen countries','Senegal','Serbia and Montenegro','Seychelles','Sierra Leone','Singapore','Slovakia','Slovenia','Solomon Islands','Somalia','South Africa','South Korea','Spain','Sri Lanka','Sudan','Suriname','Svalbard and Jan Mayen Islands','Swaziland','Sweden','Switzerland','Syria','Taiwan','Tajikistan','Tanzania','Thailand','Timor-Leste (East Timor)','Togo','Tokelau','Tonga','Trinidad and Tobago','Tunisia','Turkey','Turkmenistan','Turks and Caicos Islands','Tuvalu','Uganda','Ukraine','United Arab Emirates','United Kingdom','United States','Uruguay','Uzbekistan','Vanuatu','Venezuela','Vietnam','Virgin Islands','Wallis and Futuna Islands','Yemen','Zambia','Zimbabwe');
    
}
    
var obj SuggestionTextBox(document.getElementById('address1_country'), f, true);

 

We will call the function "SuggestionTextBox" and passing three parameter:

  1. The textbox we would like to extend in this case 'address1_country'
  2. A function which returns an array of strings, in this case f, which returns a list of countries
  3. The last parameter controls if the list of values will be loaded just one time or if false, everytime the user is pressing a key. So for long lists the second option might be better. So if true, the list should contain all possible values and if false it should contain only the result for the given input of the textbox.

For demoing purpuses I just created the function 'f', which returns some countries. So copying the above code into the onload event handler of the entity. Thats it, just save your form and publish your changes. Now you can open the form and by typing the first character the auto suggestion box will appear.

2. Load the data for the auto suggestion list via a web service.

Now that we have a great suggestion functionality we can now start to make the list a little bit more dynamic. So instead of fixed function which returns static list of items, we could load them every time by using a web service.

Web Service function, which returns a list of values:

using System;
using 
System.Web;
using 
System.Web.Services;
using 
System.Web.Services.Protocols;

[WebService(Namespace "http://webservices/")]
[WebServiceBinding(ConformsTo 
WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    
public Service () {

        
//Uncomment the following line if using designed components 
        //InitializeComponent(); 
    
}

    [WebMethod]
    
public string GetListOfCountries()
    {
      
// it might be a good idea to get the values out of a database or from Microsoft CRM
        
string[] arrayCountries = new string[] { "Afghanistan","Albania","Algeria","..."};
        return string
.Join(",,", arrayCountries);
    
}
}

Last step, we will call the web service and init our suggestion text box. Add the following code to your onload event handler:

var countries = new Array();
var 
obj;

var 
= function GetListOfCountries()
{
    
return countries;
}

function CallWebService()
{

    
var objHttp;

    
// create an XmlHttp instance
    
objHttp = new ActiveXObject("Microsoft.XMLHTTP");

    
// Create the SOAP Envelope
    
strEnvelope "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
            
" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" +
            
" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
            
"  <soap:Body>" +
            
"    <" "GetListOfCountries" " xmlns=\"http://webservices/\">" +
            
"    </" "GetListOfCountries" ">" +
            
"  </soap:Body>" +
            
"</soap:Envelope>";

    
//<?xml version="1.0" encoding="utf-8"?>
    //<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    //  <soap:Body>
    //      <GetListOfCities xmlns="http://webservices/" />
    //  </soap:Body>
    //</soap:Envelope>

    // Set up the post
    
objHttp.onreadystatechange = function()
    {
        
// a readyState of 4 means we're ready to use the data returned by XMLHTTP
        
if (objHttp.readyState == 4)
        {
            
// get the return envelope
            
var szResponse objHttp.responseText;
                        
            var 
startTag "<string xmlns=\"http://webservices/\">";
            var 
endTag "</string>";
            var 
cities;

            var 
valueStart 0;
            var 
valueEnd 0;

            
//Parsing the returned XML
            
valueStart objHttp.responseXML.xml.indexOf(startTag, valueEnd) + startTag.length;
            
valueEnd objHttp.responseXml.xml.indexOf(endTag, valueEnd+1);

            var 
tmpCountries objHttp.responseXML.xml.substring(valueStart, valueEnd);

            
countries tmpCountries.split(',,');


            
obj SuggestionTextBox(document.getElementById('address1_country'), f, true);

        
}
    }

    
var szUrl;
    
szUrl "http://website/WebServices/Service.asmx/GetListOfCountries";

    
// send the POST to the Web service
    
objHttp.open("POST", szUrl, true);
    
objHttp.setRequestHeader("Content-Type""application/x-www-form-urlencoded");
    
objHttp.send(strEnvelope);
   
}

CallWebService()
;

Thats it. Do you like it? Please let me know if that works fine for you or if you made any improvements. I'd love to hear from you!

Other interesting blogs regarding this topic:

Accessing Web Services From CRM Forms
http://blogs.msdn.com/arash/archive/2006/02/01/521565.aspx

Address Validation in Three Easy Steps!
http://www.invokesystems.com/cs/blogs/mscrm/archive/2006/06/21/38.aspx

Working with CRM Form Lookup Controls Programmatically
http://blogs.msdn.com/arash/archive/2006/03/28/562846.aspx

Finally there: Show and hide fields based on the users role!
http://ronaldlemmen.blogspot.com/2006/05/finally-there-show-and-hide-fields.html

Inserting City and State Automatically based upon Zip Code
http://blogs.msdn.com/midatlanticcrm/archive/2006/06/22/Inserting_City_and_State_Automatically_based_upon_Zip_Code_Microsoft_CRM_3.aspx