Thursday, February 12, 2009

Client Side Scripting - Customer fields again

SUNNWARE 10.02.2009


received quite a few emails recently pointing out that the customer field sample doesn't work or doesn't always work, so I finally decided to try another approach and came up with a new implementation, which is more elegant and smaller than the old one and hopefully more reliable as well. As usual, this is an unsupported customization.

In the meantime I received a modified version of the original sample, which I post in this article as well. 

The OnLoad code

You need an account lookup and a contact lookup field on your form to run this code. Some properties of the account lookup are modified in such a way that CRM treats it as a real customer field. It's also important to set the default value when initializing the "customer" (account) field with a contact, to prevent unwanted warning messages ("Do you really want to navigate away from this ....") when closing the form.

 

// Change "sw_accountid" and "sw_contactid" to the field names you are using.


var accountLookup = crmForm.all.sw_accountid;


var contactLookup = crmForm.all.sw_contactid;


 


// Set the available lookup types to account and contact.


accountLookup.lookuptypes = "1,2";


accountLookup.lookuptypenames = "account:1,contact:2";


 


// Set the icons to use for the account and contact.


accountLookup.lookuptypeIcons = "/_imgs/ico_16_1.gif:/_imgs/ico_16_2.gif";


 


// If there is an existing value stored in the contact lookup, then pass it to the "customer" lookup.


if (contactLookup.DataValue != null) {


 


   // Set the default value of our customer field to the contact. If you don't do this, CRM assumes


   // that the customer was changed and will display a warning messaging when closing the form


   // without saving.   


   accountLookup.DefaultValue = contactLookup.DataValue;


   accountLookup.DataValue = contactLookup.DataValue;


 


   if (typeof (accountLookup.DataValue[0].data) != "undefined") {


      // For some reason, the data property of the DataValue is set to an empty string, while the


      // data property of the DefaultValue is undefined. The data property is part of the comparison


      // CRM does when looking for modified fields. In order for CRM to truly believe that our


      // customer field wasn't changed - though we did - this data property has to be set accordingly.


      accountLookup.DefaultValue[0].data = accountLookup.DataValue[0].data;


   }


}


 




 



The OnSave code



The OnSave code simply stores the selected customer in the account and contact lookups. When using an OnChange event in the sw_accountid field, make sure to differentiate if the event was fired due to the user selecting a record or the OnSave modifying it. A simple way to do it, is setting a global variable in the OnSave event and comparing that value in OnChange.





// Change "sw_accountid" and "sw_contactid" to the field names you are using.


var accountLookup = crmForm.all.sw_accountid;


var contactLookup = crmForm.all.sw_contactid;


 


// If there is no value selected in "customer" lookup, then clear the contact lookup.


if (accountLookup.DataValue == null) {


   contactLookup.DataValue = null;


}


 


// Otherwise check the lookup type and copy the selected value to the contact lookup if appropriate.


else {


   var customer = accountLookup.DataValue[0];


 


   // A type code of 1 represents an account.


   if (customer.type == "1") {


      // If it is an account, then clear the contact lookup.


      contactLookup.DataValue = null;


   }


 


   else {


      // A contact was selected, so copy the value to the contact lookup and clear the account lookup.


      contactLookup.DataValue = accountLookup.DataValue;


      accountLookup.DataValue = null;


   }


}




 



Modified version of the original sample



As reported by some others, Alejandro Cesetti (ale [dot] cesetti [at] gmail.com) had problems with the original code, and sent me an email explaining the problem and the fix:



Reproducing the bug



When you save the entity having the “custom” Customer attribute with an Account being selected everything is ok. Then, if you open the same instance of the entity, and edit it changing the value of the Customer from an Account to a Contact, after you save your changes, the value shown in the Customer field is still the Account value and, what is more, the account relationship in DB isn’t updated to null keeping the relationship to the account.



How did I fix it



What I did was to hide the Account field as the Contact field and add another field in the place where I want the Customer field to be shown. Then the ExchangeLookups function will exchange the new lookup field with the Customer and vice versa instead of the Account field.



OnLoad Code





/******************************************************************************


* Global variables required to setup the customer field. You have to replace


* the values of AccountFieldName and ContactFieldName when using attribute


* names different than the one used in this sample.


*****************************************************************************/


AccountFieldName = "hud_accountid";


ContactFieldName = "hud_contactid";


CustomerFieldName = "hud_customerid";


CreatedByName = "createdby";


AccountLookup = crmForm.all.item(AccountFieldName);


ContactLookup = crmForm.all.item(ContactFieldName);


CreatedByLookup = crmForm.all.item(CreatedByName);


CustomerLookup = null;


CreatedByInnerHTML = null;


AllFieldsAvailable = (AccountLookup != null) && (ContactLookup != null);


 


/******************************************************************************


* GetLookupFieldHtml builds the inner HTML of a lookup control. Used to


* dynamically create the customer lookup.


*****************************************************************************/


function GetLookupFieldHtml(name, tabIndex, lookupTypes, lookupTypeNames, lookupTypeIcons, reqLevel) {


 


   var html = "<table class=\"ms-crm-Lookup\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"table-layout:fixed;\">" +


        "<tr>" +


        "<td>" +


        "<div ime-mode=\"auto\" class=\"ms-crm-Lookup\" tabindex=\"" + (parseInt(tabIndex) + 1) + "\"></div>" +


        "<label class=\"ms-crm-Hidden-NoBehavior\" for=\"" + name + "_ledit\"></label>" +


        "<input class=\"ms-crm-Hidden-NoBehavior\" ime-mode=\"auto\" type=\"text\" tabindex=\"" + tabIndex + "\" id=\"" + name + "_ledit\" maxlength=\"1000\"/>" +


        "</td>" +


        "<td width=\"25\" class=\"Lookup_RenderButton_td\">" +


        "<img src=\"/_imgs/btn_off_lookup.gif\" id=\"" + name + "\" class=\"ms-crm-Lookup\" req=\"" + reqLevel + "\" style=\"ime-mode:auto\" lookuptypes=\"" + lookupTypes + "\" lookuptypenames=\"" + lookupTypeNames + "\" lookuptypeIcons=\"" + lookupTypeIcons + "\" lookupclass=\"BasicCustomer\" lookupbrowse=\"0\" lookupstyle=\"single\" defaulttype=\"0\" autoresolve=\"1\" showproperty=\"1\" resolveemailaddress=\"0\">" +


        "<a href=\"#\" onclick=\"previousSibling.click();\" tabindex=\"-1\"></a>" +


        "</td>" +


        "</tr>" +


        "</table>";


 


   return html;


}


 


/******************************************************************************


* A sample event handler. As the customer lookup does not exist on the form,


* you cannot add an OnChange event handler in the CRM client. This method is


* used as an alternative.


*****************************************************************************/


function Customer_OnChange() {


   alert("Customer changed");


}


 


/******************************************************************************


* Helper function. Retrieves a field by its name.


*****************************************************************************/


GetField = function(name) {


   return crmForm.all.item(name);


}


 


/******************************************************************************


* Helper function. Retrieves the id of the TD element hosting a lookup control.


*****************************************************************************/


GetLookupCellId = function(name) {


   return name + "_d";


}


 


/******************************************************************************


* Helper function. Retrieves the TD element hosting a lookup control.


*****************************************************************************/


GetLookupCell = function(name) {


   return GetField(GetLookupCellId(name));


}


 


/******************************************************************************


* ExchangeLookups replaces an existing lookup control with another one. Used


* to replace the account lookup with the customer lookup and vice versa.


*****************************************************************************/


ExchangeLookups = function(lookupToShowId, lookupToHideId, newLookupHtml) {


   var prevCell = GetLookupCell(lookupToHideId);


   var prevCellIndex = prevCell.cellIndex;


   var row = prevCell.parentNode;


 


   row.deleteCell(prevCellIndex);


 


   var newCell = row.insertCell(prevCellIndex);


   newCell.id = GetLookupCellId(lookupToShowId);


   newCell.innerHTML = newLookupHtml;


}


 


/******************************************************************************


* CreateCustomerField creates the customer lookup and replaces the account


* lookup on the form.


*****************************************************************************/


function CreateCustomerField() {


   var customerValue;


 


   if (AccountLookup.DataValue != null) {


      customerValue = AccountLookup.DataValue;


   }


   else {


      customerValue = ContactLookup.DataValue;


   }


 


   var accountLookupEditField = GetField(AccountFieldName + "_ledit");


   var tabIndex;


 


   //If the account field supports the auto-resolve feature, then the tab-index is stored on the text field


   //of the lookup control


   if (accountLookupEditField == null) {


      tabIndex = AccountLookup.getAttribute("tabIndex");


   }


 


   //otherwise we use the same tabIndex as in the 3.0 implementation.


   else {


      tabIndex = accountLookupEditField.getAttribute("tabIndex");


   }


 


   //Standard values for a customer field


   var lookupTypes = "1,2";


   var lookupTypeNames = "account:1,contact:2";


   var lookupTypeIcons = "/_imgs/ico_16_1.gif:/_imgs/ico_16_2.gif";


   var reqLevel = AccountLookup.getAttribute("req");


   var customerIdHtml = GetLookupFieldHtml(CustomerFieldName, tabIndex, lookupTypes, lookupTypeNames, lookupTypeIcons, reqLevel);


 


   //    ExchangeLookups(CustomerFieldName, AccountFieldName, customerIdHtml);


   ExchangeLookups(CustomerFieldName, CreatedByName, customerIdHtml);


 


   CustomerLookup = GetField(CustomerFieldName);


   CustomerLookup.DataValue = customerValue;


   //  Here we are able to attach events to the customer field 


   //    CustomerLookup.onchange = Customer_OnChange;


}


 


/******************************************************************************


* Main code: If the required fields are avaialable on the form, the account


* lookup is replaced with a customer lookup. The CreatedByInnerHTML is saved to


* restore the original state later (see OnSave).


*****************************************************************************/


if (AllFieldsAvailable) {


   CreatedByInnerHTML = GetLookupCell(CreatedByName).innerHTML;


 


   CreateCustomerField();


}




 



OnSave Code





/******************************************************************************


* If the required fields are available on the form, the CreatedBy


* lookup was replaced with a customer lookup in the OnLoad event. To properly


* save the values, we have to restore the original state, meaning that the


* customer lookup has to be removed and the CreatedBy lookup has to be added


* back.


*****************************************************************************/


if (AllFieldsAvailable) {


   var customer = CustomerLookup.DataValue;


 


   ExchangeLookups(CreatedByName, CustomerFieldName, CreatedByInnerHTML);


 


   if (customer == null) {


      AccountLookup.DataValue = null;


      ContactLookup.DataValue = null;


   }


 


   else if (customer[0].type == "1") {


      AccountLookup.DataValue = customer;


      ContactLookup.DataValue = null;


   }


 


   else {


      AccountLookup.DataValue = null;


      ContactLookup.DataValue = customer;


   }


} 


 


 


No comments: