Sunday, March 30, 2014

XDocument Merge XML Documents

In some instances, we may have the need to extend the information in a XML payload by merging multiple documents into one. When using the System.Xml.Linq.XDocument class, there is no method to allow us to do a merge. On this article, we extend the XDocument class to provide the Merge capabilities.

XML Documents:

We first start by showing two XML documents that can be combined into one. The base requirement with this approach is that both documents are similar with the exception of one Element that contains some unique information that is not contained on the other XML.

XML with Annual Data
XML with YTD Data
Merged XML

<Finance>
  <Company>
    <Name>OG-BIT</Name>
  </Company>
  <Annual>
    <col1>100</col1>
    <col2 />
  </Annual>
</Finance>


<Finance>
  <Company>
    <Name>OG-BIT</Name>
  </Company>
  <YTD>
    <col1>100</col1>
    <col2>200</col2>
  </YTD>
</Finance>


<Finance>
  <Company>
    <Name>OG-BIT</Name>
  </Company>
  <YTD>
    <col1>100</col1>
    <col2>200</col2>
  </YTD>
  <Annual>
    <col1>100</col1>
    <col2 />
  </Annual>
</Finance>


The company and annual information needs to be maintained.

From this XML, we need to extract the YTD information

The outcome should be a document with both reports and with single company information.

Extension Methods:

An approach to provide this feature is to create extension methods that implement the merge. This is done by adding the following static class:

    /// <summary>
    /// extension methods to enable the merge of xml documents
    /// </summary>
    internal static class XDocumentExtension
    {
        /// <summary>
        /// merge an element from one document to the root of another
        /// </summary>
        /// <param name="refDoc"></param>
        /// <param name="doc"></param>
        /// <param name="elementName"></param>
        internal static void Merge(this XDocument refDoc, XDocument doc, string elementName)
        {
            if (doc != null && !String.IsNullOrWhiteSpace(elementName))
            {

                //find the element by name
var segment = doc.Descendants().Where(elm => elm.Name.LocalName= elementName).Select(elm => elm).FirstOrDefault();
                //if found add to the original document at the root
                if (segment != null)
                    refDoc.Root.Add(segment);           
            }
        }

        /// <summary>
        /// merge an xml string into a XDocument reference
        /// </summary>
        /// <param name="refDoc"></param>
        /// <param name="xml"></param>
        /// <param name="elementName"></param>
        internal static void Merge(this XDocument refDoc, string xml, string elementName)
        {
            if (!String.IsNullOrWhiteSpace(xml) &&                              !String.IsNullOrWhiteSpace(elementName))
            {
                XDocument doc = XDocument.Parse(xml);
                refDoc.Merge(doc, elementName);
            }
        }

        /// <summary>
        /// returns the xml string with header declaration
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        internal static string ToStringWithDeclaration(this XDocument doc)
        {
            return String.Format("{0}{1}", doc.Declaration, doc.ToString());
        }       
    }

This is the description for this class:

Method
Description
Merge (XDocument, string)
Merges two XDocument references by finding an element with the LocalName equal to the parameter value.
Merge (string, string)
Loads the XML string into an XDocument reference and calls the Merge method.
ToStringWithDeclaration
Gets the merged XML string with the Declaration header when found (i.e. <?xml>)

Example:

The following example uses the extension methods to merge the documents and returns the merged XML string:

public static void main()
{
    string xml1 = @"<Finance><Company><Name>OG-BIT</Name></Company><YTD><col1>100</col1><col2>200</col2></YTD></Finance>";
    string xml2 = @"<Finance><Company><Name>OG-BIT</Name></Company><Annual><col1>100</col1><col2/></Annual></Finance>";

   XDocument doc = XDocument.Parse(xml1);
   doc.Merge(xml2, "Annual");
   string xml = doc.ToStringWithDeclaration();
}

This simple example illustrates an approach of how this can be done when using the XDocument class. There are other ways of achieving the same with other classes.


Thanks for reading.

Sunday, February 23, 2014

MSCRM Page Navigation with Account Parameters using JavaScript

When adding a page navigation link, we have the options to set the URL to use to a web resources or an external link. When using either one, we may want to be able to pass a parameter on the URL, but  this option is not available from the customization form interface. To address this, I use our old good friend JavaScript to handle the injection of parameters on the URL.

The first step is to add the URL with a query string parameter that contains a key value or token that we can replace. In this example, we are using an external URL with a parameter on the Account entity form. We should add a navigation link with the following information:

  • Link Label:  My Site
  • Link URL:   http://www.mysite.com?acccountId=accid

We should note that the accid parameter is the token that we are using to replace and inject the actual account id from the form. We should also note the label on the link because it is used to find the actual form element. You may ask why not just use the id, and the answer is that we do not have an option to define our own id which is created by the platform (Dynamics), and we do not know its value until the page is rendered. For the purpose of this article, we are using the label parameter to match the name that was used for the link label.

function SetNavLink(label){

    try{
        var navLinkRef = document.querySelectorAll('a[title="View ' + label + '"]');
        if (navLinkRef != null) {
            var navLink = navLinkRef[0];
            var link = navLink.getAttribute('onclick');
            var guid = Xrm.Page.data.entity.getId();
            link = link.replace('guid', guid);           
            navLink.onclick = function () {
                eval(link);             
            };                       
        }

    }catch(e){}

}

The above function uses the label to get a reference of the control by using the querySelectorAll function. This function returns an array of elements that match that query selector for an anchor tag with a specific title. In our case, we should be using something unique for the label. so the array should contain one element. If this is not the case, we should try to change the label to something more unique.

Once we are getting that one element, we get the onclick property. This is important because the platform uses a JavaScript call   (function LoadIsvArea) to enable the navigation instead of the HREF property. Reading the attribute, help us ensure that we are maintaining the same function call that was generated by the platform. The string that is returned by this property contains our target URL.

Our next step is to get the actual account id. This is done with the help of XRM JavaScript framework that is native to the Dynamics CRM Platform. To get any entity main GUID, we just need to call this function within the context of the entity form:

Xrm.Page.data.entity.getId();

For our example, this returns the Account GUID which we then can use to replace accid parameter. The final step is to assign a new onclick handler to the form element with a function that evaluates/executes the contents of our link string. If we add a JavaScript debugger, we will be able to see that our external link now reflects the Account GUID of the current loaded record.

I hope this can show you an approach to dynamically add parameters to a navigation link, and I hope that for the next release Microsoft allows us to add context parameters when adding navigation links without having to use JavaScript or edit the Site Map.

Thursday, February 13, 2014

MSCRM Create Password Field with JavaScript

Dynamics CRM does not currently provide the ability to add password fields to a form. With this script, we show a way to make an input field on an Entity Form to be of type “Password”.

OGBIT.PasswordField = {
    Init: function (fields) {

        var ids = fields.split(';');
        for (var idx in ids) {
            var id = ids[idx];
            var control = document.getElementById(id);
            if (control && typeof (control) != "undefined") {
                control.setAttribute("type", "password");
                OGBIT.PasswordField.Show(id, true);
            }                           
        }       
    },
    Show: function (id, cmd) {
        var control = Xrm.Page.getControl(id);
        if (control)
            control.setVisible(cmd);
    }
}

The Init function takes a semi-colon delimited string with field ids. For each field id, we set the attribute of the input field to type password. This automatically masks the text in the field with asterisks (*), and the actual character values are not affected. Since there is a delay between the JavaScript changing the field type and the form showing the values, we need to initially hide the field on the form. This prevents the users from seeing the actual text. This Script uses the Xrm JavaScript framework to get the field reference and setting the visibility attribute to true right after changing the field type attribute. See the Show function for more details.

To make this work on CRM, you can add this script as a web resource and configure this script to execute on the OnLoad event of the entity form.  We can then add the string with the field names that should be set to type password. This string should map to the fields parameter of the Init function.
We should note that this approach has a limitation. If you look at the html source, you will see the actual password value. If this is not desirable, additional work needs to be done. An approach can be to never include the actual field on the form and to handle the update via the OData web services.

I hope you find this useful.

Thursday, January 30, 2014

MSCRM Get User Information with JavaScript

When working with Dynamics CRM forms, we can use the XRM client object model to query the fields and controls on the page. A common field on every form is the current user id. This however only provides the internal system id or GUID. When we need more information about the user, we can use the ODATA web services to get additional information.

This script provides us with a helper function to enable us to find additional information about the current logged on user.

if (typeof (OGBIT) == "undefined") { OGBIT = {}; }
OGBIT.UserInfo = {   
    Get: function () {

        var userInfo = {};
        if (self.userName == '') {
        var serverUrl = window.location.protocol + "//" + window.location.host + "/" + Xrm.Page.context.getOrgUniqueName();
           
            var userId = Xrm.Page.context.getUserId();
            var req = serverUrl + "/xrmservices/2011/OrganizationData.svc/SystemUserSet(guid'{0}')?$select=EmployeeId,FullName,InternalEMailAddress,SystemUserId,DomainName".replace('{0}', userId);
            try {
                $.ajax({
                    type: "GET", contentType: "application/json; charset=utf-8", datatype:"json",async:false,
                    url: req,
                    beforeSend: function (XMLHttpRequest) {
                        XMLHttpRequest.setRequestHeader("Accept", "application/json");
                    },
                    success: function (data, textStatus, XmlHttpRequest) {
                        userInfo = data.d;
                        userInfo.status = true; //dynamic property
                    },
                    error: function (XmlHttpRequest, textStatus, errorObject) {
                        userInfo.status = false;
                        //add error handling

                    }
                });
            } catch (e) { }
        }
        return userInfo;
    }
}

The script first calls the getUserId method of the page context. This returns the system id which we can then use to make an ODATA request to get the additional user information. The ODATA query to get the user information has this format:

SystemUserSet(guid'{0}')?$select=EmployeeId,FullName,InternalEMailAddress,SystemUserId,DomainName

The SystemUserSet allows us to query the System User entity.  We query for the current user with the guid’{0}’ parameter. The $select query option is used to indicate what additional fields should be returned. Those fields are listed below:

Parameters:
Field Name
Description
EmployeeId
employee id
FullName
First and last name
InternalEMailAddress
Email
SystemUserId
Internal ID or GUID
DomainName
Username with this format:
DomainName\Username

The following snippet can be used to test the script. With this function, we just call the Get method and assign the returning information into local variables.

function GetUserInfo() {
    if (typeof (OGBIT.UserInfo) != 'undefined') {
        var info = OGBIT.UserInfo.Get();
        if (info.status) {
            var empId = info.EmployeeId;
            var name = info.FullName;
            var email = info.InternalEMailAddress;
            var userName = info.DomainName;
            var id = info.SystemUserId;
        } else {
            alert('Unable to find user');
        }
    }
}


I hope this Script helps you get more understanding on how to interact with MS CRM.