Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. 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.

Oct 14, 2022

Refresh Sub Grid once Async operation is completed

This is a recent challenge came on my way. I had to refresh a sub grid of a form after an operation (say a field value change or Save). Challenge is Sub Grid values are being modified asynchronously through a server side logic. In summery, I have to delay the refresh till that happens.

If I elaborate this further, I have a field (i.e. Amount) in the form of Service entity. Once it is updated the server side logic will trigger and synchronously populate a field (i.e. Item Amount) of the child Service Item entity, which is the child record type of the grid.

While we need to run this Java Script on Save. Below is the code in my on Save script and I will explain each of the pieces  come with the functions being called.

var formContext = executionContext.getFormContext();
var dirtyAttributes = CheckDirtyFieldsOnForm(formContext);
if (dirtyAttributes != null && dirtyAttributes.length > 0 
   && dirtyAttributes.includes("new_amount")
   && formContext.getAttribute("new_amount") != null 
   && formContext.getAttribute("new_amount").getValue() > 0) {
	var childId = RetrieveChildRecordToBeUpdate(formContext);
	   if (childId != null) {
		IsServiceItemAmountSet(formContext, childId, 1);
	   }
      }

This is the first function, which identifies all the fields going to be updated. This returns and array where we need to check if Amount field is among them.

function CheckDirtyFieldsOnForm(formContext) {
  var attributes = formContext.data.entity.attributes.get();
  var dirtyAttributes = [];
  if (attributes != null) {
	for (var i in attributes) {
	   if (attributes[i].getIsDirty()) {
		dirtyAttributes.push(attributes[i].getName());
	      }
	   }
	}
  return dirtyAttributes;
}

If it returns Amount field, we know we need to refresh the grid. Lets retrieve the Service Item Id which we are interested in. (Note: even if there are more than one line items, selecting one is enough since we are going to use this item to check if async operation is completed)

function RetrieveChildRecordToBeUpdate(formContext) {
  var gridContext = formContext.getControl("grid_serviceitems");
    if (gridContext == null || gridContext == "undefined")
       return;

	var complGrid = gridContext.getGrid();
	var complRows = complGrid.getRows();
	var rowCount = complRows.getLength();
	if (rowCount == 0) {
			return;
	}
	for (var i = 0; i < rowCount; i++) {
	  var rowEntity = complRows.get(i).getData().getEntity();
        // Can add some logic if you have filter criteria for 
        // select line item
	  return rowEntity._entityId.guid;
	}
	return;
 }

Since we have Service Item Id, now we have to keep on checking by retrieving Service Item details through server calls (i.e. WebApi) if its updated. In our scenario what I need to check is if Item Amount is populated. Once it happen, we can refresh the grid since we know Sync operation is completed and refreshed grid will show the new values.

This is the recursive code that keep on checking if update has taken place.

function IsServiceItemAmountSet(formContext, Id, index) {
   Xrm.WebApi.online.retrieveRecord("new_serviceitem", Id, "?$select=new_itemamount").then(
      function success(result) {
	   if (result.new_lineamount != null && result.new_lineamount > 0) {
		formContext.getControl("grid_serviceitems").refresh();
		return true;
	   }
	   else {
		 return false;
	   }
	 },
	 function (error) { }
   ).then(function (isSuccess) {
	 if (!isSuccess && index < 5) {
	    index++;
	    setTimeout(function () {
		IsChildClaimAmountSet(formContext, Id, index);
	    }, 2000);
       }
   });
}

This function do recursive WebApi calls in 2 second intervals. Anyway, I am using an index to count these calls, since I need to prevent infinite loops in case of failure in service side logic. Hence maximum no of attempts is limited to 5. I assume within 10 seconds Async operation would have been completed.

Jun 17, 2020

Client side Retrieval using Xrm.WebApi - Handling Lookups

One of the previous posts I provided sample codes for different client side operations using Xrm.WebApi. Click this to find it.

Anyway, one of the tricks I couldnt post was how to retrieve a Lookup which is little tricky. Lets check it using with examples.

Here I am trying to retrieve Full Name and Email Address as below.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1

It works fine. Check my result.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1)/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Smith","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8"}

Suppose, I need to retrieve a lookup field value. For example, Created By name. What I feel is just adding it as another field. So I am adding to the end of he field list.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1,createdby

Hmm.. here comes the confusion. I don't get any values for Created By lookup field. I am sure I spelled correctly, otherwise I would get a error message about unknown field.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1,createdby)/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Smith","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8"}

Well, this is the trick WebApi just doesn't return values from other entities. Lookup value is physically in related entity. So you have to extend the query with Expand keyword.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1&$expand=createdby($select=fullname,systemuserid)

Here we get the result like a charm.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1,createdby(fullname,systemuserid))/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Bailey","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8","createdby":{"@odata.etag":"W/\"29915606\"","fullname":"Paul Wicks","systemuserid":"267874e4-645c-e911-a870-000d3a6a065c","ownerid":"267874e4-645c-e911-a870-000d3a6a065c"}}

In summery, make sure you link entity with Expand command to retrieve lookup values.

Simple!

Apr 22, 2020

Passing FetchXml to Xrm.WebApi

Previously we explained how to do basic operations using Xrm.WebApi. Please refer this for relevant post.

What we didn't mention there is passing of FetchXml. Pass a FetchXml to retrive a specific set of records is a very useful operation. Lets check below scenario.

In this fictitious system, we have custom entity called Office that is associated to Account and contains two custom fields called Territory and Type. While we are in Account, suppose we need to select all the associate offices where Territory is Asia-Pacific and Type is Regional. So, this is pretty realistic requirement. If we try to do only with basic operations, it may be complex and would require many server-calls which is not ideal.

This is a scenario that easily achieved with FetchXml. You can easily download the FetchXml through Advanced Find as shown below;


Find below code snippet to understand how we pass this to WebApi;

And this online tool help you with converting your FetchXml to a string that could be used in Java Script: https://www.ashishvishwakarma.com/FetchXmlFormatter/

function onAccountLoad(executionContext) {

    formContext = executionContext.getFormContext();
    var accountId = formContext.data.entity.getId();

    var fetchXML = new String();
    fetchXML += "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>";
    fetchXML += "    <entity name='su_office'>";
    fetchXML += "        <attribute name='su_officeid' />";
    fetchXML += "        <attribute name='su_name' />";
    fetchXML += "        <attribute name='createdon' />";
    fetchXML += "        <order attribute='su_name' descending='false' />";
    fetchXML += "        <filter type='and'>";
    fetchXML += "           <condition attribute='su_mainaccountid' operator='eq' value='" + accountId + "' />";
    fetchXML += "            <condition attribute='su_territoryid' operator='eq' value='{A25642F7-61EF-E411-80EB-C4346BACE124}' />";
    fetchXML += "            <condition attribute='su_type' operator='eq' value='100000001' />";
    fetchXML += "        </filter>";
    fetchXML += "    </entity>";
    fetchXML += "</fetch>";

    Xrm.WebApi.online.retrieveMultipleRecords("su_office", "?fetchXml=" + encodeURIComponent(fetchXML)).then(
        function success(result) {
            var eligibleOffices = result.entities;
            if (eligibleOffices != null && eligibleOffices.length > 0) {

                for (i = 0; i < eligibleOffices.length; i++) {
                    // Do the logic
                    alert(eligibleOffices[i].su_name);
                }
            }
        },
        function (error) {
        }
    );
}

Hope this is helpful.

Sep 26, 2019

Passing parameters to JavaScripts

Click this to see how parameters can be passed to Plug-ins.

Passing parameters to JavaScript is possible in Dynamics 365. When calling a particular method, we can see a way to pass whatever the parameters as below, apart from execution context as below.


Anyway, I find its pretty good since we can pass complex objects as Jason objects such as below;

{
    "type": "external",
    "isTaxInclude": true,
    "industryCode": 500,
        "profile": {
        "authority": "ABC Corp",
            "proficencyLevel": "Advanced",
            "jobCodes": [
                "GGG11",
                "ABX00",
                "XXY87"
            ],
        "noOfContractors":120
    }
}

If you go to debug (ad text debugger; to the code and perform operation after pressing F12) mode you will see how easy to access the different attributes with the JavaScript. Check how I see it once above Json object is passed;



If you are working with much complex Json objects, JsonPathFinder or JSONPath Finder chrome extension can help you sort the different values.

Passing parameters to JavaScript like this is particularly useful in below scenarios;

1) Same script to be used in different Forms with different parameters
2) Same script to be used in different Entities with different parameters

Anyway, one thing to keep in mind is these parameters can't be different in different environments. This is because, if a Solution is deployed with same Form, these parameters are being overridden with what is in the Solution. In other terms, this technique is NOT suitable to keep environmental specific variables.

One way to keep environmental variables is by checking the URL to determine the environment and load the variable accordingly. 

Aug 16, 2017

Code snippet to add Lookup filter

This is just a simple JavaScript to filter lookup based on existing field value in the form.

In this scenario, we will assume we needs to set Payment Method to a lookup field (i.e. new_paymentmethodid). Its user-friendly if we can filter the lookup values to show only the payments of contact (i.e. new_contactid). This code snippet will work for this.

PaymentMethodFilter = function () {
    Xrm.Page.getControl("new_paymentmethodid").addPreSearch(addPaymentMethodFilter);
}

addPaymentMethodFilter = function () {
    var contact = Xrm.Page.getAttribute('new_contactid');
    if ((contact == null) || (contact.getValue() == null) || (contact.getValue()[0] == undefined)) return;
    var paymentMethodFilter = "<filter type='and'><condition attribute='new_paymentowner' uiname='" + contact.getValue()[0].name + "' operator='eq' uitype='contact' value='" + contact.getValue()[0].id + "' /></filter>";
    Xrm.Page.getControl("new_paymentmethodid").addCustomFilter(paymentMethodFilter, "new_paymentmethod");
}

Now call PaymentMethodFilter method for onChange of new_contactid field.

May 23, 2017

Web API - Retrieve records in JavaScript

It is high time to use WEB API for JavaScript calls, though no one is recommending one particular library for that. In fact, underlying argument has two sides;

1) Using a Library - can be tricky since Microsoft can change WEB API and no guarantee new version of Library will be out
2) Without a Library - Coding sequential lines will not manageable in long run

When take both in to account we decided we will have our own light library (easily updated as necessary).

In Order to do this, we found a cool tool that create JS codes for WEB API. This is simply a Dynamics Solution which needs to be deployed first as any other solution. Then you will see the button that launches the tool;


So we generated record Retrieve operation and made below small library for Retrieval of single record in general manner. (We plan to extend this to other CRUD operations).

We made same method to be used to below scenarios;

1) Retrieve record by Id
2) Retrieve record by Criteria

So this is the simple Library. I hope this is manageable than a heavily customized and heavy one.

function RetrieveEntityById(id, entityName, fieldsArry, filterStr) {
    var RetrieveStr = RetrieveStringBuilder(id, entityName, fieldsArry, filterStr);
    return RetrieveEntityByIdWebApi(RetrieveStr);
}

function IdBracketRemover(id) {
    return (id.indexOf('{')>-1) ? id.substring(1, id.length - 1) : id;
}

function RetrieveStringBuilder(id, entityName, fieldsArry, filterStr) {
    var Str;

    if (id != null)
    { Str = entityName + '(' + IdBracketRemover(id) + ')' + '?$select='; }
    else
    { Str = entityName + '?$select='; }

    for (i = 0; i < fieldsArry.length; i++)
    { Str = Str + fieldsArry[i] + ','; }
    Str = Str.substring(0, Str.length - 1);
    if (filterStr != null)
        Str = Str + '&$filter=' + filterStr;
    return Str;
}

function RetrieveEntityByIdWebApi(RetriveString) {
    var result;
    var req = new XMLHttpRequest();
    req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/" + RetriveString, false);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 200) {
                result = JSON.parse(this.response);
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send();
    return result;
}

Please find the ways of calling the method for two different scenarios as mentioned. Check how parameters become null in different scenarios.

// Query basded on Id
// string : "/customeraddresses(1A170E1D-91AE-4965-8631-0FB9270504D7)"
var fieldsArry = ['city', 'country'];
var Id = '{1A170E1D-91AE-4965-8631-0FB9270504D7}';
var AddressEnt = RetrieveEntityById(Id, 'customeraddresses', fieldsArry, null);
if (AddressEnt != null) {
    alert(AddressEnt["city"]);
}


// Query basded on condition (Parent ID and Prefred = true
// string : "/customeraddresses?$filter=_parentid_value eq 19139FC0-DC44-4E88-A793-924F1F90B08F 
//          and  smsmt_ispreferred eq true"
var fieldsArry = ['city', 'country'];
var ParentId = '{19139FC0-DC44-4E88-A793-924F1F90B08F}';
var filterStr = 'new_ispreferred eq true and  _parentid_value eq ' + IdBracketRemover(Id);
var AddressEnt = RetrieveEntityById(null, 'customeraddresses', fieldsArry, filterStr);
if ((AddressEnt != null) && (AddressEnt.value[0] != null)) {
    alert(AddressEnt.value[0].city);
}

For the moment, this seems to be catering the need. If I develop the other operations, I will post them. Anyway, it is the CRM Rest Builder generated this code for me, what I have done is making little changes with parameters to create correct string to be passed.

Special thanks to my colleague Biplab Singha for guiding to this approach.

Post Note;
Noticed assigning a lookup value after retrieving through this could be little tricky. Please find below code snippet of assigning lookup value (coming from entity called new_shareholders and lookup field called new_shname) to primarycontact;


var shEnt = RetrieveEntityById(null, 'new_shareholders', fieldsArry, filterStr);
if ((shEnt != null) && (shEnt.value[0] != null)) {
    var constLookup = new Array();
    constLookup[0] = new Object();
    constLookup[0].id = shEnt.value[0]['_new_shname_value'];
    constLookup[0].name = shEnt.value[0]['_new_shname_value@OData.Community.Display.V1.FormattedValue'];
    constLookup[0].entityType = shEnt.value[0]['_new_shname_value@Microsoft.Dynamics.CRM.lookuplogicalname'];
    Xrm.Page.getAttribute('primarycontact').setValue(constLookup);
}

Mar 20, 2017

Track Underage contact based on birthday

Dynamics 365 doesn’t give a functionality to identify under age contacts though it captures the birthday by default. Here is a simple code snippet to switch a flag based on age. We can now simply distinguish under age (<18) contact easily through a view.

SetUnderAgeFlag = function() {
  var birthDate = Xrm.Page.getAttribute('birthdate').getValue();
  if (birthDate  != null)
  {
      var today = new Date();
      var nowyear = today.getFullYear();
      var nowmonth = today.getMonth();
      var nowday = today.getDate();             

      var birthyear = birthDate.getFullYear();
      var birthmonth = birthDate.getMonth();
      var birthday = birthDate.getDate();

      var age = nowyear - birthyear;
      var age_month = nowmonth - birthmonth;
      var age_day = nowday - birthday;

      if ( (age < 18) || ((age==18) && (age_month<0)) || ((age==18) && (age_month==0) && (age_day <0)) )
      {         
         Xrm.Page.getAttribute('new_under18flag').setValue(true);
      }
      else
      {
         Xrm.Page.getAttribute('new_under18flag').setValue(false);
      } 
      Xrm.Page.getAttribute('new_under18flag').setSubmitMode('always');  
  }   
}

Feb 26, 2015

Issue with datetime formats in OData retrievals

We now use OData Service for client side data retrievals. Yet, there is a bit of complexity when dealing with date time fields.  I usually use REST methods of XrmServiceToolkit library. When I try to retrieve datetime field and populate in some form field, I simply couldn’t do since what I retrieved was something different. Something like “/Date(1420588331000)/”. What is this?

This is a time encoded to a single integer which increases every second. This is UNIX timestamp. Though this is a logical representation of time which can be very helpful when dealing with complex time calculations, in our operation this can be frustrating.

We need to transfer this to date time which can be identified by JavaScript. So below code snippet can be helpful.

//Method

dateFormatUnixStamp: function (_uString) {
    var _date = null;
    if (_uString != null) {
        _date = new Date(parseInt(_uString.slice(_uString.indexOf("(") + 1, _uString.indexOf(")"))));
        _date.setDate(_date.getDate());
    }
    return _date;
}

//Calling

//Suppose _Ent is a entity we retrived through the service
//and we are assigning it to new_startdate of current form

var _startDate = dateFormatUnixStamp(_Ent.new_StartDate);
Xrm.Page.getAttribute(new_startdate).setValue(_startDate);

These two blogs also provides much helpful code samples in this regards;

https://rajeevpentyala.wordpress.com/2011/08/21/read-odata-service-date-field-in-crm-2011/
http://blogs.catapultsystems.com/rhutton/archive/2014/01/30/crm-2013-handling-odata-date-field-value.aspx

Thank you Deepak Mehta for the help.

Aug 6, 2014

Code Snippet: Retrieve data with oData query

Just though of sharing a code snippet on retrieving data via oData query.

RetrieveOfficeLocationsByAccount: function()
{
    var _serverUrl = Xrm.Page.context.getServerUrl();
    var _accountID = Xrm.Page.data.entity.getId();
    var _oDataEndpointUrl = _serverUrl + "/XRMServices/2011/OrganizationData.svc/";
    var _offices = RetriveOfficesCollection(_oDataEndpointUrl, _accountID);

    if (_offices != null) 
    {
        alert(_offices.results.length);

        for (var j = 0; j < _offices.length; j++) 
        {
            if (_offices[j].new_Location != null) 
            {
                alert(_offices[j].new_Location.Value);
            }
        }
    }
}

RetriveOfficeCollection: function(_oDataEndpointUrl, _accountID) 
{
    var _offices = null;
    _oDataEndpointUrl += "new_officeSet?$select=new_name,new_Location&$filter=new_AccountId/Id eq (guid'" + _accountID + "')&$orderby=new_name";
    var service = getRequestObject();

    if (service != null) {
        service.open("GET", _oDataEndpointUrl, false);
        service.setRequestHeader("X-Requested-Width", "XMLHttpRequest");
        service.setRequestHeader("Accept", "application/json, text/javascript, */*");
        service.send(null);

        var requestResults = eval('(' + service.responseText + ')').d;
        if (requestResults != null && requestResults.results.length > 0) {
            _offices = requestResults;
        }
    }
    return _offices;
}

getRequestObject: function () 
{
    if (window.XMLHttpRequest) 
    {
        return new window.XMLHttpRequest;
    }
    else 
    {
        try 
        {
            return new ActiveXObject("MSXML2.XMLHTTP.3.0");
        }
        catch (ex) 
        {
            return null;
        }
    }
}

There are two important notes about this code.

If you build the query correctly, you should be able to browse through your browser and see the list of records. It’s always good to check your query this way before proceeding, which will save a lot of time.

Other one is a note of caution. If you check the field names in the code, you may realize they are the schema names! (You see capital letters!.. example: new_Location not new_location). Usually, we use Name (which is all low case in CRM 2011) in most of the codes including Plug-ins.

Mar 26, 2014

REST Functions from XrmServiceToolkit

Now we are in a situation, that REST calls should do the basic client side functions.

In fact, XrmServiceToolkit (http://xrmservicetoolkit.codeplex.com/) becomes handy. So it’s a library you need to add to your CRM form consist of three files as below;


XrmServiceToolKit website provides some sample coding. Yet, I thought of making them bit more user-friendly and simple as below;

function Retrieve() {
    var re;
    XrmServiceToolkit.Rest.Retrieve(
              "{EE81D2A9-E28E-E311-96DF-D89D6765B238}",
              "AccountSet",
              null, null,
              function (result) {
                  re = result;
                  alert("success");
              },
              function (error) {
                  alert("failed");
              },
              false
          );
    //debugger;
    alert(re.Name);
    alert(re.AccountId);
}

function Delete() {
    XrmServiceToolkit.Rest.Delete(
                "{32815A55-19AF-E311-BF0E-D89D6765B238}",
                "AccountSet",
                function () {
                    alert("successfully deleted");
                },
                function (error) {
                    alert("failed to delete");
                },
                false
            );
}

function Update() {

    var account = {};
    account.Name = "SO and Company A1";
    account.Address1_AddressTypeCode = { Value: 3 }; //Address 1: Address Type = Primary
    account.Address1_City = "Wentworthville";
    account.Address1_Line1 = "153 Dunmore Stret";

    XrmServiceToolkit.Rest.Update(
        "{EE81D2A9-E28E-E311-96DF-D89D6765B238}",
        account,
        "AccountSet",
        function () {
            alert("successfully Updated");
        },
        function (error) {
            alert("failed to Update");
        },
        false
    )

}

function Create() {
    var account = {};
    account.Name = "SO and Company B1";
    account.Address1_AddressTypeCode = { Value: 3 }; //Address 1: Address Type = Primary
    account.Address1_City = "Wentworthville B";
    account.Address1_Line1 = "153 Dunmore Stret B";

    XrmServiceToolkit.Rest.Create(
                    account,
                    "AccountSet",
                    function (result) {
                        accountId = result.AccountId;
                        alert("successfully Created. Acc ID : " + result.AccountId);
                    },
                    function (error) {
                        alert("failed to Create Account");
                    },
                    false
    );

}

Hope this is helpful.

Jul 30, 2013

Setting regarding object in JavaScript

When programmatically creating a task/activity, I found it is little tricky the way we need to assign regarding object. For the benefit of others, though of sharing the code snippet. I am using Ascentium CrmService for my client side operations.

var beEnt = new BusinessEntity("task");
beEnt.attributes["regardingobjectid"] = "F5E42F4A-8539-E011-8602-005056A61424"; //GUID of record
beEnt.attributes["regardingobjectid"].name = "ABC and Company"; //name of the record
beEnt.attributes["regardingobjecttypecode"] = "account"; //type of record
beEnt.attributes["subject"] ="sample subject";
beEnt.attributes["description"] = "Sample body";
oService.Create(beEnt);

Jun 25, 2013

Issues of using checkbox in CRM 2011

In a previous post I explained difference of using onChange and onClick in CRM 4.0

Now it has come the time to talk the same thing against CRM 2011. As same as 4.0, even in CRM 2011 we got only the onChange in native manner. As we all know the problem is you need to click somewhere else after clicking the checkbox to execute the event.  So we need to implement onClick to accomplish this. It is bit similar to  4.0, but there is a complication.

For some crazy reason, standard Xrm model that works for 2011 doesn’t work here and have to use the 4.0 style code. Other issue is execution take cyclic pattern and our code gets executed twice! So we need to change the focus as soon as we finish the code. Please check the working code (this should go in onload event);

MyCheckbox = function()
{
    crmForm.all.new_sample.onclick = function()
    {
       //Code
       //Change focus to another field  
    }
}

Issues are not finish yet! Practically, we might need to do something according to the value of the relevant check box. Biggest confusion comes here. Values given are completely opposite. When you check the checkbox you might get false instead of true and vice versa. Why this happens?

I am trying to understand it this way. (please correct me If I am wrong) This is not an error. We are executing the code on “click”. This doesn’t mean we have changed the value in the time we execute the code. Of course what we see is the "changed" situation through the form. Actually code happens for previous value of the check box. I think this is the reason Microsoft doesn’t provide this event in their native framework.

If we understand this, we are good to proceed with our work without any trouble. Only thing is do the opposite when playing around with the value of the checkbox upon onClick.

var _checkbox = Xrm.Page.getAttribute("new_sample").getValue();
if (_checkbox == true)
{ 
  //code for false 
}
else
{
  //code for true 
}

This is bit confusing.. but manageable.

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.

May 14, 2012

Filtered lookup in CRM 2011: a dynamic lookup view

Filtered lookup in CRM 2011 is quite different than CRM 4.0. It contains more code, but result could be fruitful. I learn this as a different approach. This actually is imposing of a custom view which is temporary and dynamic. I like the idea. “addCustomView” is the method that enables us to do this. We will see how I do this.

Assume I have a custom entity called office. My office entity contains two main lookups. One is for account and other one is for contact. Obviously, I need to have a filter for contact lookup which will show only the contacts of selected account (if account lookup field is not null). In this approach, we are just creating a view and make it default when loading the lookup window.

Below is the method I use for this. This code shows you how it gives more control over the new view... we decide its name, criteria and also layouts that contains the widths of each column to show. Guid is any new Guid.

function ContactLookupFilter(accountid, accountname)
{     
var _viewId = "{10CACCF3-AC63-46FE-920B-DFEF53BCDE33}";
var _entityName = "contact";
var _viewDisplayName = "Contacts of Account : " + accountname;

var _fetchXml = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>" +
                "<entity name='contact'>" +
                "<attribute name='fullname' />" +
                " <attribute name='contactid' />" +
                " <attribute name='parentcustomerid' />" +
                " <attribute name='address1_city' />" +
                " <attribute name='address1_telephone1' />" +
                " <attribute name='telephone1' />" +
                " <attribute name='emailaddress1' />" +
                "<filter type='and'>" +
                "<condition attribute='parentcustomerid' operator='eq' value='"+accountid+"'/>" +
                "</filter>" +
                "<order attribute='fullname' descending='false' />" +
                "</entity>" +
                "</fetch>";

var _layoutXml = "<grid name='resultset' object='1' jump='name' select='1' icon='1' preview='1'>" +
                 "<row name='result' id='contactid'>" +
                    "<cell name='fullname' width='250' />" +
                    "<cell name='parentcustomerid' width='150' />" +
                    "<cell name='address1_city' width='150' />" +
                    "<cell name='address1_telephone1' width='100' />" +
                    "<cell name='telephone1' width='100' />" +
                    "<cell name='emailaddress1' width='150' />" +
                  "</row>" +
                  "</grid>";

var lookupControl = Xrm.Page.ui.controls.get('office_contactid');
lookupControl.addCustomView(_viewId, _entityName, _viewDisplayName, _fetchXml, _layoutXml, true);
}

Here is the code to call this from the loading event.

function office_OnLoad()
{
 var attrs =  Xrm.Page.data.entity.attributes;
 if (attrs.get("office_accountid").getValue() != null)
 {
 var _aid  =attrs.get("office_accountid").getValue()[0].id; 
 var _aname  =attrs.get("office_accountid").getValue()[0].name; 
 ContactLookupFilter(_aid, _aname);
 }
}

Are we done now? Some explained this is it, but I propose you to call this method from the OnChange event of the account lookup as well. Let me explain why? If we call this method only in loading, this custom view will load for the contacts of account which is currently assigned to the form. If user changes the account (and not save yet) prior to filling the contact lookup, our view is still showing the contacts of the account which was present when loading. But now we got different account on the form... Hope you understand the scenario...

Now check how its look likes... Can you see how our new view have got the custom name we gave. Very descriptive; since we have selected “SO and Company” as the account, we can see that as a part of the view name.


See how this dynamic view is listed along with other views that give the flexibility of selection.

May 10, 2012

Handle Save, Save & close and unload of CRM form

This is a tricky situation one can come across in CRM 4.0 development. Leaving the form can happen in three ways. Those are SAVE, SAVE AND CLOSE, UNLOAD
We can write JavaScript for all three occasions.

UNLOAD

If you put below code in onLoad event, you can execute a script on unload.

// Put this in onload
window.onbeforeunload = function()
{
    alert("unload event")      
}

SAVE & SAVE AND CLOSE

Saving is straight forward in CRM form, but you can distinguish two ways of saving by event mode. Try below script in save event.

var _mode = event.Mode;
alert("Save event .. with mode : " +_mode);

Different modes could be easily distinguished as blow.



SAVE - 1
SAVE AND CLOSE – 2

Now my real focus is to do something when we leave a form with any of above three methods. Best example would be Quote entity can have total fields which depend on quotedetail entities under it (quotedetai is a child of quote). If we need them to be accurate at any given moment... we might need to do the calculations (unless we do it in plug-ins) whenever any of above events occur.

Way above event occurs are quite messy.
Please check what I found. Different combination of events trigger depends on circumstance.

SAVE (without change to form data) - Save event
SAVE (with change to form data) - save event, unload event
SAVE AND CLOSE (without change to form data) - save event, unload event

SAVE AND CLOSE (with change to form data) - save event, unload event

So, if we place the calculation event in all the three events, nothing will go wrong but in some occasions, it will run twice. Still I don’t know how to solve this simply. What we could do is maintaining a hidden check box (Boolean field) to say whether form needs recalculation. We should be smart enough to update this field even when child records are changed, if they influence the calculations.

Then we can add our own calculation methods to all three events, but calculation should always check the Boolean field before execution. In this way, we can avoid happening of calculation twice for no reason.

I am glad to hear if someone got better approach for this.

Apr 16, 2012

Hide default toolbar items and separator

It is usually required to hide some of the default tool bar items appear under each entity depending on the requirement. “Look Up Address...” is one such item. Quote or Order like entities got Recalculation button by default and etc.

In those cases, we know how to hide them by identify those tags with their tooltips (titles) in UI. For example below code will hide Print button of the quote. See how we got it from its title. This is something we know.

//print button
    var lis1 = document.getElementsByTagName('LI');
    var j = 0;
    while (j < lis1.length)
    {
        if (lis1[j].getAttribute('title') == 'Print Quote for Customer')
        { lis1[j].outerHTML = '<SPAN> <SPAN> <SPAN> <SPAN> </SPAN> </SPAN> </SPAN> </SPAN>'; }
        j = j + 1;
    }

Anyway, if you do above exercise for few buttons, you will notice that separator symbol (i.e. |) is still appearing there and it could create some messy look as below.


In this case, there is another script to hide them too. Below is the script for that. This hides all the separators. I think that’s better than keeping untidy separators in the tool bar.

//seperator
    var lis2 = document.getElementsByTagName('img');
    var k = 0;
    while (k < lis2.length)
    {
        if (lis2[k].getAttribute('id') == 'mnu_hSpacerGrid')
        { lis2[k].outerHTML = '<SPAN> <SPAN> <SPAN> <SPAN> </SPAN> </SPAN> </SPAN> </SPAN>'; }
        k = k + 1;
    }

Mar 25, 2012

Issue of using Onload JavaScript for “create form” of Quote

This is just a simple warning. We all use onload JavaScript for any entity and we simply know how to handle them separately for Create form and Update form, when necessary.  For example, below is a popular way of doing something (for onLoad) just for “create form”.

var CRM_FORM_TYPE_CREATE = 1;     
var CRM_FORM_TYPE_UPDATE = 2;

if (crmForm.FormType == CRM_FORM_TYPE_CREATE)
{
  //some script create form onload
}

Now I am going to use this for Quote entity. Can you guess the issue related to this attempt?

Quote can be created from other entities; for example from Opportunity. This script will not work when we create a quote from opportunity. That’s because, when quote is being created from opportunity, quote is first created in the background before pop-up a form for user. What we really see is an Update form not a create form. In such cases, you may move in to plug-ins than Java Scripts. Actually, you don’t get a create event. Quite tricky! Isn’t it?