Thursday, April 7, 2011

CRM 2011 Javascript - attributes and controls

Based on an optionset (picklist) value I wanted to show or hide a field and make it required or not. Within CRM 4.0 I would write the script by heart. It would look something like this:

//CRM 4.0 Toggle field visible and required
if(crmForm.all.new_picklist.SelectedText == "value-A")
{
    crmForm.SetFieldReqLevel("new_showhidefield", 0);
    crmForm.all.new_showhidefield_c.style.display = 'none';
    crmForm.all.new_showhidefield_d.style.display = 'none';
}
else
{
    crmForm.SetFieldReqLevel("new_showhidefield", 1);
    crmForm.all.new_showhidefield_c.style.display = 'inline';
    crmForm.all.new_showhidefield_d.style.display = 'inline';
}

With the CRM 2011 Xrm.Page model, things have changed quite a lot. The Microsoft Dynamics CRM 2011 SDK describes the Xrm.Page Object Hierarchy. It shows there is a difference between the attributes (Xrm.Page.data.entity.attributes) and the controls (Xrm.Page.ui.controls). Trying to hide or show a field and make it required/not required made me realize that depending on functional requirements you have to figure out if you need to work with the attribute or the control (or both).

For example:
For making a field required, you need to work with the attribute
For making a field hidden, you need to work with the control

The SDK describes for both the methods supported:
Xrm.Page.data.entity attribute Methods
Xrm.Page.ui control Methods

So the CRM 2011 javascript for toggling a field visible/not visible and required or not required is this:

toggleLeadVisible = function()
{
    var Leadcontrol = Xrm.Page.ui.controls.get("new_leadid");
    var Leadattribute = Xrm.Page.data.entity.attributes.get("new_leadid");

    if(Xrm.Page.getAttribute("new_customertype").getText() == "Lead")
    {
        Leadcontrol.setVisible(true);
        Leadattribute.setRequiredLevel("required");
    }
    else
    {
        Leadcontrol.setVisible(false);
        Leadattribute.setRequiredLevel("none");
    }
}

It’s quite simple. Just know what you can do with attributes and what with controls!

Friday, February 25, 2011

Using generic functions – fieldnames- in CRM 2011

With the new webresources structure in Microsoft Dynamics CRM 2011, it has become a lot easier, or at least a lot more self evident, to use generic javascript functions instead of hard-coded scripts. Off course with CRM 3.0 and 4.0 one could write generic functions and call those functions when needed. But reality is that in a lot of situations fieldnames were hardcoded in the javascript.

For example, to add the value of Field A to Field B and set the result in Field C, both the onchange of Field A and Field B would contain the following javascript:

crmForm.all.new_fieldc.DataValue = crmForm.all.new_fielda.DataValue + crmForm.all.new_fieldb.DataValue;

With CRM 2011 it is not possible write the javascript directly on the onchange of the field, but you will have to call a function from a webresource.

Now let’s assume that you have an entity with 3 sets of 3 fields (2 source fields and 1 result field) on the form and you want to populate the 3 result fields with the result outcome of field 1 plus field 2. If you follow on the CRM 4.0 structure, the result would be a webresource with 3 similar javascript functions containing the fieldnames and the onchange events of the 6 source fields calling 1 of those 3 functions.

So how to do this in a CRM 2011 way using the ‘pass parameters to the function’? Start with creating a new javascript webresource and give it a name, in this example called ‘new_testscripts’. In this webresource, write a javascript function that contains 3 parameters. In this example is the function name ‘calcAddValues’ and the parameter names ‘sResultField’, ‘sFieldA’ and ‘sFieldB’, that represent the fieldnames to be used and write the logic as you would with hardcoded fieldnames like this:

function calcAddValues(sResultField,sFieldA,sFieldB)
{
if((Xrm.Page.getAttribute(sFieldA).getValue() != null)||(Xrm.Page.getAttribute(sFieldB).getValue() != null))
{
Xrm.Page.getAttribute(sResultField).setValue(Xrm.Page.getAttribute(sFieldA).getValue() + Xrm.Page.getAttribute(sFieldB).getValue());
}
else
{
Xrm.Page.getAttribute(sResultField).setValue(null);
}
}

Save and publish the webresource. Now you can use this function on any CRM form and call it as many times as you like. To do so, first add the webresource as a library to the form. When included, go to the properties of a field and set the OnChange event by selecting the library, set the name of the function and add the fieldnames to be used in the function as a comma separated list of parameters that will be passed to the function.

Here’s an example on how to do so:

Jscript_Parameters

Advantages

Using the new Microsoft Dynamics CRM 2011 structure creates the possibility to write generic javascript functions and have them collected in one place. Calling a function is easy, just using the function name and providing some parameters when necessary will suffice. No duplicated code is required. Maintenance and future adjustments will be a lot easier. Fixing one piece of javascript code will result in a change for every instance where the code is called.

The catch: make sure that if you change an existing function for one specific requirement that there’s no other part within your Microsoft Dynamics CRM solution that breaks.

Saturday, January 16, 2010

Setting attribute description as tooltip for fields

If you want to give the end user some information about the meaning of a field, one way to do so is using tooltips. a33ik wrote a javascript that retrieves the field descriptions from the entity definition and attaches them as a tooltip.

Now it finally makes sense to fill out the description field:
DescriptionField   

Here’s the script to be placed OnLoad of the form:

SetTooltips = function()
{
var request = "" +
"<?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\">" +
GenerateAuthenticationHeader() +
" <soap:Body>" +
" <Execute xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <Request xsi:type=\"RetrieveEntityRequest\">" +
" <RetrieveAsIfPublished>true</RetrieveAsIfPublished>" +
" <EntityItems>IncludeAttributes</EntityItems>" +
" <LogicalName>" + crmForm.ObjectTypeName + "</LogicalName>" +
" </Request>" +
" </Execute>" +
" </soap:Body>" +
"</soap:Envelope>";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttpRequest.Open("POST", "/mscrmservices/2007/MetadataService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", request.length);
xmlHttpRequest.send(request);

var result = xmlHttpRequest.responseXML;


for(var i = 0; i < crmForm.all.length; i++)
if(crmForm.all[i].title != null && crmForm.all[i].title != 'undefined')
{
var fieldName = crmForm.all[i].id;

var desc = result.selectSingleNode("//EntityMetadata/Attributes/Attribute[LogicalName='" + fieldName + "']/Description/UserLocLabel/Label");
try
{
if(desc != null)
{
crmForm.all[fieldName + '_c'].title = desc.nodeTypedValue;
crmForm.all[fieldName + '_d'].title = desc.nodeTypedValue;
}
}
catch(e) {}
}
}

SetTooltips();


And here is the result:ToolTips

Enlarge Attachment Size

Here are some simple steps to enlarge the max attachment size in Microsoft Dynamics  CRM.

1. Open the web.config of MS CRM
2. Find the line  <httpRuntime executionTimeout="300" maxRequestLength="8192" />
3. Change the value of “8192” to the desired max size in kilobytes. For this example it is set to “20480”.
4. Open CRM, go to System settings. You can now change the max size of attachments in the E-mail tab.

AttachmentSize

User friendly information messages

In some situations you might want to provide the end user with some information. A way to do that is using alerts. But that is not always as user friendly as you might want.

Tanguy wrote a javascript for more user friendly messages that was improved by Si. I thought I could improve it a little further by making it more flexible and share the code with you.

Place the next function on the OnLoad event of the CRM form.

Notification = function(sNotification) {
    var elementId = 'Notifications';
    var id = 'divMessage';
    var src = document.getElementById(elementId);

    if ((sNotification == null) || (sNotification == "")) {
        src.style.display = 'none';
    }
    else {
        var newcontent = document.createElement("span");
        newcontent.id = id;

        newcontent.innerHTML = "<table><tr><td><img src='/_imgs/ico/16_info.gif' /></td><td valign='top'>" + sNotification + "</td></tr></table>";
        src.style.display = "";

        var previous = src.firstChild;
        if (previous == null || previous.attributes['id'].nodeValue != id) {
            if (src.childNodes.length == 0)
                src.appendChild(newcontent);
            else
                src.insertBefore(newcontent, src.firstChild);
        }
        else
            src.replaceChild(newcontent, previous);
    }
}

Now you can call this function from anywhere on the form. For example on the exchange of accountnumber:

if(crmForm.all.accountnumber.DataValue != null)
{
    Notification("Accountnumber = "  + crmForm.all.accountnumber.DataValue);
}
else
{
    Notification();
}

Which results in the next information message:

InformationMessage

Sunday, November 15, 2009

GoogleMaps within MSCRM

Here’s a way to have GoogleMaps integrated with Microsoft Dynamics CRM.

The aim in this example is as follows: On the account form show the route to the account, having:
- address1 as the destination point
- a flexible starting point (home, office or otherwise)

GMapsMSCRM

The setup used is as follows:
- a new tab named ‘GMaps’
- a new section on the tab 
- a new picklist field named ‘new_gmstartpointselect’ values ‘Office’, ‘Home’ and ‘Otherwise’
- a new nvarchar field named ‘new_gmstartpoint’
- a new IFRAME named ‘IFRAME_GMaps’

Make sure you have the following attributes on the account form to set destination address:
- address1_line1
- address1_line2 (used for housenumber in this example)
- address1_city

In everyday use end users may want to use different starting points. In this example I assumed that the user might want to depart from a) a predefined office address or b) from the end users home address or c) any other address.

To use the end user address, make sure to fill out address1 on the user form.  EndUserAddress

On the picklist field ‘new_gmstartpointselect’ a javascript is used to determine the starting point:

if(crmForm.all.new_gmstartpointselect.SelectedText == "Home")
{
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\">" +
GenerateAuthenticationHeader() +
" <soap:Body>" +
" <RetrieveMultiple xmlns=\"
http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <query xmlns:q1=\"
http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
" <q1:EntityName>systemuser</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>address1_line1</q1:Attribute>" +
" <q1:Attribute>address1_line2</q1:Attribute>" +
" <q1:Attribute>address1_postalcode</q1:Attribute>" +
" <q1:Attribute>address1_city</q1:Attribute>" +
" <q1:Attribute>address1_country</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>systemuserid</q1:AttributeName>" +
" <q1:Operator>EqualUserId</q1:Operator>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:Criteria>" +
" </query>" +
" </RetrieveMultiple>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "
http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);

var resultXml = xmlHttpRequest.responseXML;
var entityNode = resultXml.selectSingleNode("//RetrieveMultipleResult/BusinessEntities/BusinessEntity");

var line1Node = entityNode.selectSingleNode("q1:address1_line1");
var line2Node = entityNode.selectSingleNode("q1:address1_line2");
var postalcodeNode = entityNode.selectSingleNode("q1:address1_postalcode");
var cityNode = entityNode.selectSingleNode("q1:address1_city");
var countryNode = entityNode.selectSingleNode("q1:address1_country");

var line1 = (line1Node == null) ? "" : line1Node.text;
var line2= (line2Node == null) ? "" : line2Node.text;
var postalcode = (postalcodeNode == null) ? "" : postalcodeNode.text;
var city = (cityNode == null) ? "" : cityNode.text;
var country = (countryNode == null) ? "" : countryNode.text;

if((line1 == null) || (line1 == ""))
    {
        alert("End User Home address undefined");
    }
    else
    {
        crmForm.all.new_gmstartpoint.DataValue = line1 + "  " + line2 + ", " + city + ", " + country;
        crmForm.all.new_gmstartpoint.FireOnChange();
    }
}

if(crmForm.all.new_gmstartpointselect.SelectedText == "Office")
{
    crmForm.all.new_gmstartpoint.DataValue = "Amsterdamsestraatweg 1, Utrecht, Nederland";
    crmForm.all.new_gmstartpoint.FireOnChange();   
}

if(crmForm.all.new_gmstartpointselect.SelectedText == "Otherwise")
{
    crmForm.all.new_gmstartpoint.DataValue = null;
    crmForm.SetFieldReqLevel("new_gmstartpoint", 1);
}

The first part of this script retrieves the end users address when ‘Home’ is chosen. When ‘Office’ is chosen, a predefined address (Amsterdamsestraatweg 1, Utrecht, Nederland) is used. The script sets the value of ‘new_gmstartpoint’ is set and the onchange is fired. When ‘Otherwise’ is chosen, the end user will have to fill out the starting point manually.

On the ‘new_gmstartpoint’ field is a second javascript that will check if both starting point and destination are filled. If so, the innerHTML of the IFRAME will be replaced by the code needed to show GoogleMaps.

if((crmForm.all.new_gmstartpoint.DataValue == null) ||(crmForm.all.new_gmstartpoint.DataValue == ""))
{
    alert("Startingpoint undefined");
}
else if ((crmForm.all.address1_line1.DataValue == null) ||(crmForm.all.address1_line1.DataValue == "")||(crmForm.all.address1_city.DataValue == null)||(crmForm.all.address1_city.DataValue == ""))
{
    alert("Destination undefined");
}
else
{
    document.getElementById("IFRAME_GMaps_d").innerHTML = "<iframe id=IFRAME_GMaps_d class=ms-crm-Custom width='100%' height='100%' frameborder='0' scrolling='no' marginheight='0' marginwidth='0' src='
http://maps.google.com/maps?f=d&amp;source=s_d&amp;saddr=" + crmForm.all.new_gmstartpoint.DataValue + "&amp;daddr="  + crmForm.all.address1_line1.DataValue +  "  " +crmForm.all.address1_line2.DataValue + ", " + crmForm.all.address1_city.DataValue +   "&amp;hl=nl&amp;output=embed'></iframe>";
}

If the end user wants to, he or she can just change the value of starting point and the map will refresh.

To clear the starting point fields when saving the account, just put the next line on the onsave event of the form:

crmForm.SetFieldReqLevel("new_gmstartpoint", 0); crmForm.all.new_gmstartpoint.DataValue = null;
crmForm.all.new_gmstartpointselect.DataValue = null;

Some final remarks: to view the actual directions and duration, it is possible to click the Google logo in the lower left-hand corner. This will open a new screen.

It is possible to add fields for more accurate address specification.
Integration with Google Maps will not work when offline.
This blog post does not deal with any licensing  or cost issues. Please check Google for more info.

 

Friday, November 6, 2009

Unit set automatically with Default Unit

When setting up a new product in the product catalog, Default Unit is a system required field.

DefUnit

Great! So when you choose the product on the the opportunity/quote/order/invoice-product screen the Default Unit is automatically filled in in the Unit field… NOT!

UnitNotSet

As most users will want to choose the default unit, let’s set it automatically. To do so, add the next script to the OnChange event of the product field:

GetAttributeValueFromID= function(sEntityName, sGUID, sAttributeName,sOrganizationName)
{
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\">" + GenerateAuthenticationHeader() +
" <soap:Body>" +
" <RetrieveMultiple xmlns=\"
http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <query xmlns:q1=\"
http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
" <q1:EntityName>" + sEntityName +"</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>" + sAttributeName + "</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>"+ sEntityName + "id</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>"+
"<q1:Value xsi:type=\"xsd:string\">" + sGUID + "</q1:Value>" +
"</q1:Values>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:Criteria>" +
" </query>" +
" </RetrieveMultiple>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction","
http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var resultXml = xmlHttpRequest.responseXML;
if (resultXml != null)
{
var names = resultXml.selectNodes("//BusinessEntity/q1:" + sAttributeName);
if (names != null)
{
if (names.length > 0)
{
return names[0].text;
}
}
//return "*Error*";
}
}

//Set unit with default unit of product using CRM webservice
if(crmForm.all.productid.DataValue != null)
{
    var ProductSelected = new Array;
    ProductSelected = crmForm.all.productid.DataValue;

    var UnitId = "";
    var UnitDescription = "";
    UnitId = GetAttributeValueFromID("product",ProductSelected[0].id,"defaultuomid");
    UnitDescription = GetAttributeValueFromID("uom",UnitId,"name");

    var InputUnitId = new Array();
    InputUnitId[0] = new LookupControlItem (UnitId, 1055, UnitDescription);
    crmForm.all.uomid.DataValue = InputUnitId;
}
else
{
    crmForm.all.uomid.DataValue = null;
}

 

This will retrieve the default unit from the product and fill out the unit field as the product is set.

UnitSetAuto

In this way, the end user still has the posibillity to choose another unit if necessary. But if not, is saves a couple of clicks!