Showing posts with label Ribbon. Show all posts
Showing posts with label Ribbon. Show all posts

Aug 30, 2023

Show/ Hide Ribbon button based on a Security role

This is a frequently asked requirement in Model driven apps. Obviously you need to use Ribbon Workbench (of Xrm Tool Box). Lets see how we achieve it.

Any button command has Display Rules and Enable Rules. Here we enhance the Enable Rule to achieve this. Actually, we write the logic in a JavaScript to match the security role by Id with logged in users security roles and return True/ false accordingly.

Suppose our JS name is AccountRibbon.js and below is the method I implement. Hope its self-explanatory. 

function IsAutherizationRoleIncluded(execCtx) {

    var currentUserRoles = execCtx._globalContext._userSettings.securityRoles;
    var roleId = "92D1FC2B-8A22-ED11-9DB1-00224818AFAC"; // Id of the role
    roleId = roleId.toLowerCase();
    // Get all the roles of the Logged in User.

    for (var i = 0; i < currentUserRoles.length; i++) {
        var userRoleId = currentUserRoles[i];
        if (userRoleId == roleId) {
            console.log("Role Found");
            return true;
        }
    }
    console.log("Role NOT Found");
    return false;
}

Now we need to associate this method to Ribbon Button as a Custom Rule as below;


Now button will be shown/ hidden based on availability of the security rule.

Jan 16, 2013

Subgrids in terms of relationship types

Subgrids are new enhanced feature in CRM 2011 forms. They can be considered as alternatives for associate views.  Yet it got something more and it caters some of the usability engineering aspects too. Though it doesn’t contain any complexity, I thought of writing this explanation.

When we add a subgrid, we basically have one main option to select; it’s to show “All records” or “Only related records”. If you need to show all records you have all the entities to choose from. If you need to have related records only, you have much shorter list. They are the entities with relationship to the entity we are customizing right now. Check the difference in below illustration.


Showing the related records is something theoretically identical to an associate view, but showing all the records is something new. This invites designers and architects to grasp a new thought. In other terms we are now allowed to have an editable reference list (of information) within a form.

Now we will from the users view point. For my example I consider an entity called “office” which got a subgrid of designations. Designation is another entity.

1) Office has no relationship with designation


When you select the subgrid/ sub record relevant ribbon button offers you buttons to do all the operations of designation entity such as Add new, Edit and Delete.  Here I don’t need to show any link to designation from anywhere else of the CRM since I am capable of handling it from the subgrid.

2) Office has a 1:n relationship with designation


In this case we see an extra button called “Add existing designation” which leads to a wizard which gives full control of adding designations to the current subgrid.

3) Office has n:n relationship with designation


Now you don’t get add new button here but other buttons such as remove.

Subgrid is a nice improvement in CRM 2011, yet they are to be carefully selected in terms of the relationship type, as required. Also, by looking at the ribbon we can identify the entity relationship behind it.

Oct 21, 2012

Rule definitions to enable buttons for All RECORDS or SELECTED REDCORDS

This is a handy way of enabling the buttons either for “all records” or “selected once”. I will explain a scenario as below;

Consider an action you need to do against the records populated in a grid/subgrid. Typically we may need to do the action in two ways;

1) We may need to select records and perform the action
2) We may need to perform action for all the records

My solution is to introduce two custom buttons with two rule definitions that work well in terms of useability engineering aspects.

When loading the grid you will see only “All records” button enabled as below. (Pic. A). Idea is, if user interested in executing the action against all record, nothing to worry, just press it.


Suppose, we need to cherry pick the records. Now as soon as we start to select records, we get the view of this. (Pic. B).



Now only “Selected records” button is enabled. There is no confusion; one can execute the action only for selected items. If user deselects everything again, ribbon buttons will get changed to previous state again. (Pic. A)

Now we will see the simple way of defining the enabling rules for both buttons.

<RuleDefinitions>
  <TabDisplayRules />
  <DisplayRules />
  <EnableRules>
    <EnableRule Id="Mscrm.Cu_Selected">
      <SelectionCountRule 
        AppliesTo="SelectedEntity" Maximum="25" Minimum="1">
      </SelectionCountRule>
    </EnableRule>
    <EnableRule Id="Mscrm.Cu_SelectedAll">
      <SelectionCountRule 
        AppliesTo="SelectedEntity" Maximum="0" Minimum="0">
      </SelectionCountRule>
    </EnableRule>
  </EnableRules>
</RuleDefinitions>

Value 25 can be changed as you wish, which marks the maximum number you can select keeping the select button enabled.

Related articles;
How to proceed with All RECORDS (i.e. Scenario A) for Advanced Find results : Click here
How to proceed with SELECTED RECORDS (i.e. Scenario B) : Click here

Oct 14, 2012

Custom Group in Entity Ribbon

It is useful to know how to implement a new group in the ribbon and also to put buttons inside it. Before starting to explain an example I am happy to remind you a basic rule again.

Ribbon modifications can be done in main three ways to distinguish how they should appear in to user. They can be explained as below;

Mscrm.Form.<entityname>change appears when form is opened for selected record.
Mscrm.HomepageGrid.<entityname>change appears when grid is shown (multiple records)
Mscrm.SubGrid.<entityname> - change appears when records appear within a subgid of some other entity OR appears in the result pane of the advanced find

OK, I am here going to do the sample for Subgrid (so my tag naming format will be like Mscrm.SubGrid.<entityname>). In my example I am going to consider a custom entity called new_insurance.

My scenario is to give few buttons to renew insurance. Here I am just implementing one button to create a quote, but I put it in a Group called “Renewal Methods” so I can have other optional buttons for the same need.

This is the main custom action tag. Noticeably, it contains a button tag as we already know. So we can have many as required.

<CustomAction Id="Mscrm.SubGrid.new_insurance.RenewalGroup.CustomAction" 
              Location="Mscrm.SubGrid.new_insurance.MainTab.Groups._children" 
              Sequence="110">
  <CommandUIDefinition>
    <Group Id="Mscrm.SubGrid.new_insurance.RenewalGroup.Group" 
           Command="Mscrm.SubGrid.new_insurance.RenewalGroup.Command" 
           Title="Subscription Renewal" 
           Sequence="51" 
           Template="Mscrm.Templates.Flexible2">
      <Controls Id="Mscrm.SubGrid.new_insurance.RenewalGroup.Controls">
        <Button Id="B_CUSTOM_QuoteCreate" 
                Command="Cmd_CUSTOM_QuoteCreate" 
                LabelText="Create Quote" 
                ToolTipTitle="Create Quotes" 
                ToolTipDescription="Create quotes for renewal subscriptions" 
                TemplateAlias="o1" 
                Image16by16="/_imgs/SFA/ReviseQuote_16.png" 
                Image32by32="/_imgs/SFA/ReviseQuote_32.png" />
      </Controls>
    </Group>
  </CommandUIDefinition>
</CustomAction>

Now we need to add two more custom action tags to define the appearance of the group we are introducing here.

<CustomAction Id="Mscrm.SubGrid.new_insurance.RenewalGroup.MaxSize.CustomAction" 
              Location="Mscrm.SubGrid.new_insurance.MainTab.Scaling._children" 
              Sequence="120">
  <CommandUIDefinition>
    <MaxSize  Id="Mscrm.SubGrid.new_insurance.RenewalGroup.MaxSize" 
              GroupId="Mscrm.SubGrid.new_insurance.RenewalGroup.Group" 
              Sequence="21" 
              Size="LargeLarge" />
  </CommandUIDefinition>
</CustomAction>

<CustomAction Id="Mscrm.SubGrid.new_insurance.RenewalGroup.Popup.CustomAction" 
              Location="Mscrm.SubGrid.new_insurance.MainTab.Scaling._children" 
              Sequence="140">
  <CommandUIDefinition>
    <Scale Id="Mscrm.SubGrid.new_insurance.RenewalGroup.Popup.1" 
           GroupId="Mscrm.SubGrid.new_insurance.RenewalGroup.Group" 
           Sequence="85" 
           Size="Popup" />
  </CommandUIDefinition>
</CustomAction>

Now this is nothing new, we just define a JavaScript method to pass the selected ids when button is clicked.

<CommandDefinition Id="Cmd_CUSTOM_QuoteCreate">
  <EnableRules>
    <EnableRule Id="Mscrm.Cu_Selected" />
  </EnableRules>
  <DisplayRules />
  <Actions>
    <JavaScriptFunction Library="$webresource:new_createquote.js" FunctionName="CreateQuote">
      <CrmParameter Value="SelectedControlSelectedItemIds"></CrmParameter>
    </JavaScriptFunction>
  </Actions>
</CommandDefinition>

Then we define a command definition, actually without any rule since group is to be just viewed.

<CommandDefinition Id="Mscrm.SubGrid.new_insurance.RenewalGroup.Command">
  <EnableRules  />
  <DisplayRules />
  <Actions />
</CommandDefinition>

Then we define a command definition, actually without any rule since group is to be just viewed.
Finally we have an enable rule for the button which is quote smart. In fact, button get enabled only when at least one records selected and not more than 20 records selected. We change this as required.

<EnableRule Id="Mscrm.Cu_Selected">
  <SelectionCountRule 
    AppliesTo="SelectedEntity" Maximum="20" Minimum="1">
  </SelectionCountRule>
</EnableRule>

Once implement, we will see the subgrid Ribbon this;

Oct 4, 2012

Advanced find – Select all the records of all the pages

Ribbon of advanced find result view

 
Ribbon of the result pane of advanced find view is same as Subgrid ribbon. Yep it is! So whatever the ribbon modifications you do with the format of  “Mscrm.SubGrid.<entityname>” is being applied for this ribbon. So you are free to do anything with any custom button by executing a Javascript as I explained in my previous post. Check it here.

Now we know, we can pass different parameters to our method, but obviously, favourite would be the passing of Ids of selected records. So we are good to do any operation for those records.

Reading the current grid VS reading the full result set

Anyway, none of these parameters leads us to read all the records of all the pages of the result. Our operations are restricted to current grid. Current grid could hold maximum records of 250, with least 25, depending on your settings.


If our result consists of more records and we need to do a batch operation for all of them we are not helped. One approach would be to save the view after selecting the criteria and again read the relevant fetch from SavedQueryBase. Problem of this approach is one has to save it manually with a predetermined name. (Name is to be used when querying by the programme)

Now this is a way of reading the relevant Fetch XML without saving, from the result pane. I urge this is an unsupported method, but it works.

var _XML = document.getElementById("fetchXml");
alert(_XML.value);

Now we have the fetch used to read the data for advanced find, so we are ready to do any operation for entire result set.

Sep 27, 2012

Read Selected Guids from Subgrid

Through the article I posted about a custom recalculation button, I explained the way of introducing a custom buttons to the ribbon. Check it here.

When it comes to the subrid, most important requirement is to read the selected Ids. We will see how we pass those in to a Java script through the custom button.

This is the simple way we need to pass selected Ids as a parameter for the calling method. (Read the previous article if you need entire entry for custom button)

....
<Actions>
<JavaScriptFunction 
  Library="$webresource:new_createquote.js" 
  FunctionName="CreateQuote">
<CrmParameter Value="SelectedControlSelectedItemIds"></CrmParameter>
</JavaScriptFunction>
</Actions>
....

Will see how those Ids are grasped from the method.

function CreateQuote(value)
{
    alert(value);
}

Below is the result which shows the selected ids as an array.



Quite simple, yet important.

Note:
There are few more parameter types we can pass to JavaScript methods from ribbon as required. Please check them below;
  • SelectedEntityTypeCode : A number representing the unique type of entity for a record selected in a grid. The Entity type code will vary between deployments.
  • SelectedEntityTypeName : A string representing the unique name of the entity for a record selected in a grid.
  • FirstSelectedItemId : Provides one GUID identifier as a string for the first item selected in a grid.
  • SelectedControlSelectedItemCount : The number of selected items in a grid.
  • SelectedControlSelectedItemIds : A string array of GUID Id values for all selected items in a grid.
  • SelectedControlAllItemCount : A string array of GUID Id values for all selected items in a grid.
  • SelectedControlAllItemIds : A string array providing the GUID Id values for all items displayed in a grid.
  • SelectedControlUnselectedItemCount : The number of unselected items in a grid.
  • SelectedControlUnselectedItemIds : A string array of GUID Id values for all unselected items in a grid.
Read more information about CRM CrmParameter type here.

Jul 16, 2012

Disable ribbon button through onload JavaScript

CRM 2011 is giving full control of custom button through ribbon concept over ISV configuration concept of CRM 4.0. Here I have mention how to enable/disable button according to rules we define.

Here, rule is a straightforward one. For example, if we need to disable a button depending on logged in user’s team, I am still unable to address through a rule definition. Till I find it I found a way of doing it through onload JavaScript.

Ribbon item behaves according to a defined style. What I do is manipulating the style according to my need. Here I need to disable a particular button if the user is not in “Admin” team.
First I grab the id of the button. I assume you know how to do that by F12 function as shown below.


Now replace the standard style of that button by standard style of the disabled button as required.

if (!userIsInTeam("Admin"))
{
 if (window.top.document.getElementById('<ID>'))
 {
 var _id = window.top.document.getElementById('<ID>');
 var str = _id.outerHTML;
 var str1 = str.replace("ms-cui-ctl-large", "ms-cui-ctl-large ms-cui-disabled");
 _id.outerHTML = unescape(str1);
 }
}

I urge this is not the best way, but this works.
If I manage to find a way to define a rule in XML, I am publishing it. If you find first, please share it for me too.

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 :-)