Nov 24, 2011

Defining DisplayRule for buttons

I have shown in my previous post how to enable/disable a ribbon button according to defined criteria. Assume that we need to make it visible/invisible depending on the same rule. This is also possible with a little change of XML. Get defined our own display rule instead of enable rule. Define and call under Displayrules tag instead of Enablerules tag. Please check below code. (Please refer previous post for relevant CustomActions tag entry.)
<CommandDefinition Id="Cmd_CUSTOM_CompanyFunction">
  <EnableRules>
    <EnableRule Id="Mscrm.AvailableOnForm" />
    <EnableRule Id="Mscrm.CanSavePrimary" />
  </EnableRules>
  <DisplayRules>
    <DisplayRule Id="DR_CUSTOM_CompanyCustomer" />
  </DisplayRules>
  <Actions />
</CommandDefinition>

<RuleDefinitions>
  <TabDisplayRules />
  <DisplayRules>
    <DisplayRule Id="DR_CUSTOM_CompanyCustomer">
      <ValueRule Default="false"
                 Field="new_typeofcustomer"
                 InvertResult="false"
                 Value="100000001"/>
    </DisplayRule>
  </DisplayRules>
  <EnableRules>
    ----------
  </EnableRules>
</RuleDefinitions>

Defining EnableRule for buttons

We have discussed how to add custom buttons to ribbons in a recent post. We have used standard Enablerules for them. Enablerules are to define criteria when to enable/disable our button. If we discuss a real time scenario, I need to enable a button in account form ribbon when account is a company. Here, account can be an individual or a company, which decides by the value of one option list.  We will first see the CustomActions and CommandDefinitions entries, which are familiar already.

<CustomActions>
  <CustomAction Id="CA_CUSTOM_CompanyFunction" 
                Location="Mscrm.Form.account.MainTab.Save.Controls._children" 
                Sequence="35">
    <CommandUIDefinition>
      <Button Id="B_CUSTOM_CompanyFunction" 
       Command="Cmd_CUSTOM_CompanyFunction" 
       LabelText="Company Matters" 
       ToolTipTitle="Company related Matters" 
       ToolTipDescription="This calculate and gives you the company related information" 
       TemplateAlias="o1" 
       Image16by16="/_imgs/SFA/calculate_16.png" 
       Image32by32="/_imgs/ribbon/calculate32.png"/>
    </CommandUIDefinition>
  </CustomAction>
</CustomActions>

<CommandDefinitions>
  <CommandDefinition Id="Cmd_CUSTOM_CompanyFunction">
    <EnableRules>
      <EnableRule Id="ER_CUSTOM_CompanyCustomer" />
    </EnableRules>
    <DisplayRules>
      <DisplayRule Id="Mscrm.CanSaveAndClosePrimaryEntityType" />
    </DisplayRules>
    <Actions />
  </CommandDefinition>
</CommandDefinitions>

Now, take a close look at the enable rule ER_CUSTOM_CompanyCustomer  which is defined by me. This is to be defined with our criteria. Interesting part of ribbon is that I can define those in a special section called <RuleDefinitions>. Under this tag, we can see two sections to define two kinds of rules. Namely Display rules and Enable rules. See carefully, how I have defined my enable rule there.

<RuleDefinitions>
  <TabDisplayRules />
  <DisplayRules>
    ----------
  </DisplayRules>
  <EnableRules>
    <EnableRule Id="ER_CUSTOM_CompanyCustomer">
      <ValueRule Default="false"  
                 Field="new_typeofcustomer" 
                 InvertResult="false" 
                 Value="100000001"/>
    </EnableRule>
  </EnableRules>
</RuleDefinitions>

Here mew_typeofcustomer is the name of optionlist and 100000001 Is the value of company.



We didn’t have this kind of controlling power in CRM 4.0. This is cool.
(Read about Displayrules here.)

Nov 22, 2011

State (Statecode and statuscode) change through a JavaScript

We can find a nice code on status changes here. I tried to make it more generalised as below and it works for me.

SetStateRequest = function (_entityname, entityid, _state, _status)
{
    var requestMain = ""
    requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    requestMain += "  <s:Body>";
    requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
    requestMain += "      <request i:type=\"b:SetStateRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
    requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <c:key>EntityMoniker</c:key>";
    requestMain += "            <c:value i:type=\"a:EntityReference\">";
    requestMain += "              <a:Id>" + entityid + "</a:Id>";
    requestMain += "              <a:LogicalName>" + _entityname + "</a:LogicalName>";
    requestMain += "              <a:Name i:nil=\"true\" />";
    requestMain += "            </c:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <c:key>State</c:key>";
    requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
    requestMain += "              <a:Value>" + _state + "</a:Value>";
    requestMain += "            </c:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <c:key>Status</c:key>";
    requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
    requestMain += "              <a:Value>" + _status + "</a:Value>";
    requestMain += "            </c:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "        </a:Parameters>";
    requestMain += "        <a:RequestId i:nil=\"true\" />";
    requestMain += "        <a:RequestName>SetState</a:RequestName>";
    requestMain += "      </request>";
    requestMain += "    </Execute>";
    requestMain += "  </s:Body>";
    requestMain += "</s:Envelope>";
    var req = new XMLHttpRequest();
    req.open("POST", _getServerUrl(), false)
    // Responses will return XML. It isn't possible to return JSON.
    req.setRequestHeader("Accept", "application/xml, text/xml, */*");
    req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
    var successCallback = null;
    var errorCallback = null;
    req.onreadystatechange = function () { SetStateResponse(req, successCallback, errorCallback); };
    req.send(requestMain);
}

SetStateResponse = function (req, successCallback, errorCallback)
{
    if (req.readyState == 4)
    {
        if (req.status == 200)
        {
            if (successCallback != null)
            { successCallback(); }
        }
        else
        {
            errorCallback(SDK.SAMPLES._getError(req.responseXML));
        }
    }
}

Also need below two supportive methods.

_getServerUrl = function ()
{
    var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
    var serverUrl = "";
    if (typeof GetGlobalContext == "function")
    {
        var context = GetGlobalContext();
        serverUrl = context.getServerUrl();
    }
    else
    {
        if (typeof Xrm.Page.context == "object")
        {
            serverUrl = Xrm.Page.context.getServerUrl();
        }
        else
        { throw new Error("Unable to access the server URL"); }
    }
    if (serverUrl.match(/\/$/))
    {
        serverUrl = serverUrl.substring(0, serverUrl.length - 1);
    }
    return serverUrl + OrgServicePath;
}

_getError = function (faultXml)
{
    var errorMessage = "Unknown Error (Unable to parse the fault)";
    if (typeof faultXml == "object")
    {
        try
        {
            var bodyNode = faultXml.firstChild.firstChild;
            //Retrieve the fault node
            for (var i = 0; i < bodyNode.childNodes.length; i++)
            {
                var node = bodyNode.childNodes[i];
                if ("s:Fault" == node.nodeName)
                {
                    for (var j = 0; j < node.childNodes.length; j++)
                    {
                        var faultStringNode = node.childNodes[j];
                        if ("faultstring" == faultStringNode.nodeName)
                        {
                            errorMessage = faultStringNode.text;
                            break;
                        }
                    }
                    break;
                }
            }
        }
        catch (e) { };
    }
    return new Error(errorMessage);
}


Few examples I tried;
Deactivating an account -> SetStateRequest("account", _accountid, 1, 2)
Changing a Salesorder to submitted state -> SetStateRequest("salesorder", _salesorderid, 1, 3)
Disqualifying a lead with reason "No longer interested" -> SetStateRequest("lead", _leadid, 2, 6)

Anyway, still I don’t know whether this works for all the entities... but so far so good :-)

Nov 21, 2011

Ascentium CrmService in CRM 2011

As I mentioned earlier Ascentium CrmService is a handy way of doing Data operations through JavaScrips in CRM. Is this still applicable with CRM 2011?

Answer is yes. We managed to do the operations such as Create, Update & etc. There is a tricky part when starting, because it’s quite difficult to have a web resource for CrmService.js You can’t just open the “Text Editor” and paste the code. Instead, you will need to upload the code through “upload file” in web resource window.


Hope this will help you too..

Nov 9, 2011

Custom recalculation button for opportunity

Scenario: It is required to calculate the cost in opportunity. Usually, opportunity consists of Recalculation button to calculate few values, but not cost as I need. First I tried to find a way to fire my own JavaScript to do that with the same button, but I learned it is not a recommended attempt. So now I am trying to implement my own Button to do all the calculations (my way) and hide the existing calculation buttons.

To add/ Hide buttons I need to modify the ribbon. In order to modify the ribbon it is needed to import the customization of particular entity. For clarity I will mention below steps;

Create solution and add only the entity we need to modify the ribbon.
  1. Export it (Don’t add any other depending objects/ export as unmanaged solution)
  2. Unzip the file (it will give three XML files)
  3. Modify the customizations.xml file accordingly (which we will discuss)
  4. Zip the files again
  5. Import the file and publish
To play around with Ribbon, there are some resources to be referred. SDK has provided a XML file for each entity in sdk\resources\exportedribbonxml folder. For my example with opportunity, I can open opportunityribbon.xml. So practically, we need to open two XML files when we need to modify a ribbon.

  1. Costomization.xml (TO MODIFY)
  2. <entityname>ribbon.xml (TO REFER)
Reference file contains all the information about existing buttons, locations and their characteristics such as abilities and visibility. First we will hide the existing Recalculate buttons. When I simply search the reference file I found two button Ids for this (Mscrm.Form.opportunity.Recalculate, Mscrm.HomepageGrid.opportunity.Recalculate).

Buttons can be placed in three main ways in ribbon.

Mscrm.Form.<entityname> – button appears when form is opened for selected record.
Mscrm.HomepageGrid.<entityname> – button appears when grid is shown (multiple records)
Mscrm.SubGrid.<entityname> - Button appears when records appear within a subgid of some other entity

Now adding below entries within the <CustomActions> tag will do the hiding of both Recalculate buttons.

<HideCustomAction Location="Mscrm.Form.opportunity.Recalculate" 
   HideActionId="Mscrm.Form.opportunity.Recalculate.HideAction" />
<HideCustomAction Location="Mscrm.HomepageGrid.opportunity.Recalculate" 
   HideActionId="Mscrm.HomepageGrid.opportunity.Recalculate.HideAction" />

Now I am trying to add my own button with instructions to fire my javascript method. Add below entry within <CustomActions> and <CommandDefinitions> tags respectively.

<CustomAction Id="CA_CUSTOM_Recalculation" Location="Mscrm.Form.opportunity.MainTab.Actions.Controls._children" Sequence="8">
  <CommandUIDefinition>
    <Button Id="B_CUSTOM_Recalculation" 
            Command="Cmd_CUSTOM_Recalculation" 
            LabelText="Recalculate Job" 
            ToolTipTitle="Recalculate Totals, Costs and Margin" 
            ToolTipDescription="This recalculates Job Price, Job Cost and Job Margin" 
            TemplateAlias="o1" 
            Image16by16="/_imgs/SFA/Recalculate_16.png" 
            Image32by32="/_imgs/ribbon/recalculateopportunity32.png"/>
  </CommandUIDefinition>
</CustomAction>

<CommandDefinition Id="Cmd_CUSTOM_Recalculation">
  <EnableRules>
    <EnableRule Id="Mscrm.CanWritePrimary" />
    <EnableRule Id="Mscrm.OpportunityIsOpen" />
  </EnableRules>
  <DisplayRules>
    <DisplayRule Id="Mscrm.CanWriteOpportunity" />
  </DisplayRules>
  <Actions>
    <JavaScriptFunction Library="$webresource:new_recalculations.js" 
                        FunctionName="JobCalculationSinglejob">
    </JavaScriptFunction>
  </Actions>
</CommandDefinition>



Here CustomAction Id and Button Id are a two unique values I gave to my Custom Action and Button respectively. If you look carefully, it’s noticeable that I have given a command name and use that when I define the command later on. Though location value is bit tricky, I have checked the reference file and found the location of the existing recalculate buttons for that. “_children” postfix is to tell that button should come under that. Sequence number is a number to arrange the buttons with the existing buttons. Other buttons in same location were having numbers 5, 6 and 7 and I wanted to add my one at the end, which I put 8.

Command definition is used to define the behaviour of my button and it’s obvious to see how it calls a method within the JavaScript which has been saved as a webresource. Also I have done a cunning work here. Since I am going to perform a calculation, I copied (from reference file) the enablerules and displayrules from the rules of existing recalculation button. So my button will behave the same way.

Now I am free to do calculation within my Javascript method with my own rules!

Nov 7, 2011

Referring JavaScript files

Referring the JavaScript files in CRM 2011 is pretty straight forward. You have been given a room for doing it in customization area as below.
So this is easy. Once a file is added to this space, all the methods in it become available to use.


Anyway, when you call a method from the Ribbon, it’s not the case. You can simply mention a file and method as below. In this case our method is called CalculateCost(). We can now call only the functions in this file only.

<Actions>
  <JavaScriptFunction Library="$webresource:new_Cal.js" FunctionName="CalculateCost">
  </JavaScriptFunction>
</Actions>

If we need to access some other JavaScript method in some other file (a web resource), how we are going to achieve it? Well, I managed to do it this way. Suppose, we need to access another method from CalculateCost which is in another file called new_Utilities. This is how I am going to call them.

function InjectScript(scriptFile)
{
  var netRequest = new ActiveXObject("Msxml2.XMLHTTP");
  netRequest.open("GET", scriptFile, false);
  netRequest.send(null);
  return netRequest.responseText;
}

function CalculateCost()
{
  eval(InjectScript('../WebResources/new_Utilities.js'));

  //now all the methods in new_Utilities can be called 
}

Hope this will help.

Nov 1, 2011

Fire it in Form close

Sometime we need to do something when a form is being closed without saving. We are given provision to execute a script in loading and saving in both CRM 4.0 and 2011, but not for closing a form. Below is the section that we add usual JavaScript functions for forms events in CRM 2011;


This is a simple script that catch the event and do something for you..

window.onbeforeunload = function() 
{
    //code goes here
}