Custom Dynamics CRM 2011 ToolTips

Dynamics CRM 2011 does not support custom tooltips or any other built-in method for delivering helpful information to the user when filling out a form. The code snippet below will popup a tooltip and display a custom tooltip on as many of the CRM form fields as you like. Just attach this code to you CRM form onload event.

function OnLoad() {
CreateToolTip("img_equipmentneeded", "Provide a brief history of why the services or equipment are needed.");
}

var Tooltip = null;

function CreateToolTip(controlId, toolTip) {
var control = document.getElementById(controlId);

control.ToolTip = toolTip;

control.attachEvent("onmouseover", ShowToolTip);

control.attachEvent("onfocus", ShowToolTip);

control.attachEvent("onmouseout", HideToolTip);
}

function ShowToolTip() {
var control = event.srcElement;

Tooltip = window.createPopup();

var ToolTipHTML = "<DIV style='width:100%;height:100%;border:1px solid gray;background-color: #d8e8ff;filter: progid: DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#cecfde);padding-left:2px;font:12px tahoma'>" + control.ToolTip + "</DIV>";

Tooltip.document.body.innerHTML = ToolTipHTML;

var Width = control.offsetWidth;

var Height = 30;

var Position = GetControlPostion(control);

var Left = Position.X + 1;

var Top = Position.Y + 1;

Tooltip.show(Left, Top, Width, Height, null);
}

function GetControlPostion(control) {
var Position = new Object();

var controlHeight = control.offsetHeight;

var iY = 0, iX = 0;

while (control != null) {
iY += control.offsetTop;

iX += control.offsetLeft;

control = control.offsetParent;
}

Position.X = iX + screenLeft;

Position.Y = iY + screenTop + controlHeight;

return Position;
}

function HideToolTip() {
if (Tooltip)

Tooltip.hide();
}

OnLoad();

 

Advertisements

Dynamic JSON Converter and CRM 2011

The Dynamics CRM 2011 REST API is a powerful integration option for XRM developers.  In a past post I attempted to demonstrate a simplified example of a console app grabbing some SQL data from a non-CRM source and posting that data to CRM using the REST API.  Retrieving data back from CRM is just as straightforward but since it returns as JSON you need a strategy to handle that. I wanted to find a way I could deserialize JSON on the fly dynamically, below are my results.  I have greatly parsed down the example, hopefully making it easier for readers to comprehend my approach but at the end of this post I will include the full DynamicJsonConverter class for your use.

The JavaScriptSerializer class is used internally by the Dynamics CRM 2011 asynchronous communication layer to serialize and deserialize the data that is passed between the browser and the Web server. You cannot access that instance of the serializer. However, this class exposes a public API. Therefore, you can use the class when you want to work with JavaScript Object Notation (JSON) in managed code.

To serialize and deserialize types that are not natively supported by JavaScriptSerializer, implement custom converters by using the JavaScriptConverter class. Then register the converters by using the RegisterConverters method which is where our DynamicJsonConverter class comes in.

Step 1) First we get some data from a XRM proxy service based on the concepts found here Dynamics CRM 2011 REST API

publicdynamic GetListofEntities(string entityName, string entityField)

        {

         

   XRMServiceClient xrm = newXRMServiceClient();

          string json = xrm.GetAllEntititiesSpecifyFields(entityName, entityField);

 

 

 

Step 2)

We declare a JavaScriptSerializer dynamic object and register our custom converter.

          var serializer = newJavaScriptSerializer();

          serializer.RegisterConverters(new[]{newJsonExtension.DynamicJsonConverter()});

 

          dynamic data = serializer.Deserialize(json, typeof(object));

 

          return data.Items;

         }

 

 

 

The sample code above would then be implemented by code similar to below.  In this example (Step 3 below) we are just passing in a email address of a contact and getting back the corresponding contact entity record from CRM.  Next we can store our contact in local variables or as a class which I also show below.

Notice here that using these methods we are able to use dot notation to access entity attributes (entityRecord.telephone1).  That’s pretty cool considering our GetListofEntities method above has no concept of a contact or any of the specific attributes, it only knows how to fetch JSON based data and convert it. Even the keyword Items is dynamic and resolved only at runtime.

Step 3)

foreach (var entityRecord in GetListofEntities(“contact”, “emailaddress1”))

{

first = newContact() { FirstName = entityRecord.firstname };

last = newContact() { LastName = entityRecord.lastname };

phone = newContact() { Phone = entityRecord.telephone1 };

 

}

 

publicclassContact : IEntity

  {

publicstring FirstName { get; set; }

publicstring LastName { get; set; }

publicstring Phone { get; set; }

  }

 

 

Here’s the JSONConverter class in sound working order.  However the example code above is not necessarily Intended for direct use.

using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Dynamic;

using System.Linq;

using System.Text;

using System.Web.Helpers;

using System.Web.Script.Serialization;

namespace JSONConverter

{

    publicclassJsonExtension

    {

        publicclassDynamicJsonConverter : JavaScriptConverter

        {

            publicoverrideobject Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)

            {

                if (dictionary == null)

                    thrownewArgumentNullException(“dictionary”);

 

                return type == typeof(object) ? newDynamicJsonObject(dictionary) : null;

            }

 

            publicoverrideIDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)

            {

                thrownewNotImplementedException();

            }

 

            publicoverrideIEnumerable<Type> SupportedTypes

            {

                get { returnnewReadOnlyCollection<Type>(newList<Type>(new[] { typeof(object) })); }

            }

 

            privatesealedclassDynamicJsonObject : DynamicObject

            {

                privatereadonlyIDictionary<string, object> _dictionary;

 

                public DynamicJsonObject(IDictionary<string, object> dictionary)

                {

                    if (dictionary == null)

                        thrownewArgumentNullException(“dictionary”);

                    _dictionary = dictionary;

                }

 

                publicoverridestring ToString()

                {

                    var sb = newStringBuilder(“{“);

                    ToString(sb);

                    return sb.ToString();

                }

 

                privatevoid ToString(StringBuilder sb)

                {

                    var firstInDictionary = true;

                    foreach (var pair in _dictionary)

                    {

                        if (!firstInDictionary)

                            sb.Append(“,”);

                        firstInDictionary = false;

                        var value = pair.Value;

                        var name = pair.Key;

                        if (value isstring)

                        {

                            sb.AppendFormat(“{0}:\”{1}\””, name, value);

                        }

                        elseif (value isIDictionary<string, object>)

                        {

                            newDynamicJsonObject((IDictionary<string, object>)value).ToString(sb);

                        }

                        elseif (value isArrayList)

                        {

                            sb.Append(name + “:[“);

                            var firstInArray = true;

                            foreach (var arrayValue in (ArrayList)value)

                            {

                                if (!firstInArray)

                                    sb.Append(“,”);

                                firstInArray = false;

                                if (arrayValue isIDictionary<string, object>)

                                    newDynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);

                                elseif (arrayValue isstring)

                                    sb.AppendFormat(“\”{0}\””, arrayValue);

                                else

                                    sb.AppendFormat(“{0}”, arrayValue);

                            }

                            sb.Append(“]”);

                        }

                        else

                        {

                            sb.AppendFormat(“{0}:{1}”, name, value);

                        }

                    }

                    sb.Append(“}”);

                }

 

                publicoverridebool TryGetMember(GetMemberBinder binder, outobject result)

                {

                    if (!_dictionary.TryGetValue(binder.Name, out result))

                    {

                        // return null to avoid exception.  caller can check for null this way…

                        result = null;

                        returntrue;

                    }

 

                    var dictionary = result asIDictionary<string, object>;

                    if (dictionary != null)

                    {

                        result = newDynamicJsonObject(dictionary);

                        returntrue;

                    }

 

                    var arrayList = result asArrayList;

                    if (arrayList != null && arrayList.Count > 0)

                    {

                        if (arrayList[0] isIDictionary<string, object>)

                            result = newList<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => newDynamicJsonObject(x)));

                        else

                            result = newList<object>(arrayList.Cast<object>());

                    }

 

                    returntrue;

                }

            }

        }

    }

}

 

 

 

 

 

 

Dynamics CRM 2011 REST API

Dynamics CRM 2011 offers a multitude of integration options. There are number of additional items exposed to enable delivery of more dynamic integrated soluions. CRM 2011 supports both Early and Late-bound interfaces allowing access to business data in CRM 2011. Additonally, Microsoft has introduced REST endpoint as an alternative to SOAP-based Web Services that allows executing requests using a service that is based on URI. This new interface allows developing client applications using ASP.NET AJAX or Microsoft Silverlight as well as other technologies such as Drupal, PHP, Ruby on Rails,etc. In this post, we will highlight some tips that should give you a good starting point to work from.

Sample Project

Challenge: To explore this let’s use a fictional company we will call Northwind Real Estate. Northwind would like to capture leads and applications on their public website and feed them in to their new Dynamics CRM 2011 back-office system. Northwind has had an ASP.NET website for years and it runs on the .NET 3.x Framework. Unfortunately the CRM 2011 SDK requires .NET 4.0 so we can’t use traditional CRM SDK methods to integrate Northwind’s public facing website with CRM.

Solution: Utilizing CRM 2011 REST API, Northwind can continue to operate their Web site in the .NET 3.x Framework and yet consume CRM REST interface to interact with their new CRM 2011 back-office system. Bottemline, we should be able to achieve the following steps:

  1. Get some data that should be sent to CRM 2011
  2. Format the data in to JavaScript Object Notation (JSON)
  3. Pass it to a method which will make the REST call

GetData is just a simple SQL DataReader method example that grabs some columns from our database and passes the results to second method that formats the results to JSON format.

static void GetData() {
string connectionString = GetConnectionString();
string queryString =
"SELECT FirstName, LastName, Email, PrimaryPhone FROM Leads;";
using (SqlConnection connection = new SqlConnection(connectionString)) {
SqlCommand command = connection.CreateCommand();
command.CommandText = queryString;
try {
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read()) {
string json = Buildjson(reader[0].ToString(), reader[1].ToString(), reader[2].ToString(), reader[3].ToString());
HttpPost(json);
}
reader.Close();
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}
}

Next create some simple JSON Objects

A JSON object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).

static private string Buildjson(string firstName, string lastName, string email, string phone) {
  string json = "{\'new_firstname\':\'" + firstName + "\'";
  json += ",\'new_lastname\':\'" + lastName + "\'";
  json += ",\'new_email\':\'" + email + "\'";
  json += ",\'new_phone\':\'" + phone + "\'";
  json += "}";
  return json;
}

Now we are ready to look at the code that executes the REST call

The HttpWebRequest object in .NET allows us to POST to the CRM 2011 REST API (we could use cURL for a similar PHP function), it’s important to choose a content type of “application/json” and a method of POST as some of the other REST methods don’t seem to be fully supported.

static string HttpPost( string json) {
   //The service call will require a URL, active CRM account name, password and domain
   string domain = ConfigurationManager.AppSettings["domain"];
   string url = ConfigurationManager.AppSettings["restEndPoint"] + endpoint.ToString();
   string user = ConfigurationManager.AppSettings["crmUser"];
   string pwd = ConfigurationManager.AppSettings["crmPWD"];
     // HttpWebRequest exposes common HTTP header values sent to the Internet resource //as properties, set by methods, or set by the system;
   HttpWebRequest req = WebRequest.Create(new Uri(url)) as HttpWebRequest;
   req.Method = "POST";
      // Set the content type of the data being posted.
   req.ContentType = "application/json";
   // Set credentials to use for this request.
   req.Credentials = new NetworkCredential(user, pwd, domain);
     // Encode the parameters as form data:
   byte[] formData = UTF8Encoding.UTF8.GetBytes(json);
   req.ContentLength = formData.Length;
   // Send the request:
   using (Stream post = req.GetRequestStream()) {
   // Write out the response just for testing and debugging
     post.Write(formData, 0, formData.Length);
   }
   // Pick up the response:
   string result = null;
   using (HttpWebResponse resp = req.GetResponse() as HttpWebResponse) {
     StreamReader reader = new StreamReader(resp.GetResponseStream());
     result = reader.ReadToEnd();
   }
   return result;
}

CRM 4.0 Master Schema Management and Customization Change Control

 Recently I found a CRM 4.0 Schema comparison tool that I find makes life much easier when I’m playing the role of Schema Master.  Managing schema changes is in my opinion a arduous and very manual process.  It is  not advisable to use TFS for schema file comparisons and definitely not for merging, this is because TFS does line by line comparison not XML tag comparisons.  The Customization Comparison Utility lets you compare the customization files between two Microsoft Dynamics CRM systems and the Configuration Data Utility lets you transfer custom configuration data from one Microsoft Dynamics CRM system to another.   You can download the solution file here

The process I recommend for the proper management and change control practices   essential for the ongoing life cycle of a Microsoft CRM Dynamics Organization Database Schema.  The diagram below represents the three typical scenarios that the Schema Master may encounter on a frequent basis.  The three scenarios are Jr. Developer or Third Party Developer interactions, Senior Developer Interactions and Multi-Developer Entity Edits.  Using the Customization Comparison Utility along with this process will ensure the integrity of your CRM schema and save you from a lot of pain.

  

Master Schema Scenarios

Scenario 1

Under scenario 1 a junior developer or a third-party or outside vendor may need to promote changes to the CRM Master Schema.  In this circumstance the Schema Master would likely be responsible for heavy validation of the schema changes.

1.       The schema items impacted are exported from the developers environment .

2.       The schema items impacted are documented in a standard SharePoint change-log.

3.       The schema export file is checked in to the weekly schema build folder.

4.       The Schema Master reviews the change-log and the may make manual or automated adjustments to the Master Schema.

Scenario 2

Under scenario 2 a senior developer may need to promote changes to the CRM Master Schema in this circumstance would likely be responsible for validation of the schema changes.

1.       The schema items impacted are exported from the developers environment .

2.       The schema items impacted are documented in a standard SharePoint change-log.

3.       The schema export file is checked in to the weekly schema build folder.

4.       The Schema Master reviews the  change-log and the may make manual or automated adjustments to the Master Schema.

Scenario 3

Under scenario 3 multiple developers may need to promote changes to the CRM Master Schema impacting the same entity.  In this circumstance the developers would likely be responsible for creating and validating a single schema export file.

1.       The schema items impacted are exported from the developers environment .

2.       The schema items impacted are documented in a standard SharePoint change-log.

3.       The schema export file is checked in to the weekly schema build folder.

4.       The Schema Master notifies the developers of potential conflicts or collisions.

5.       The developers create a single schema export file and check it in to TFS.

6.       The Schema Master reviews the change-log and the may make manual or automated adjustments to the Master Schema.

The TFS Role

TFS is used as a repository for the incremental schema edits that are proposed as candidates to the Schema-Master.  It’s critical that developers only submit specific entity schema segments rather than the full CRM schema, this is because TFS analyzes code line by line rather than in the XML  tag format the CRM schema model uses.  Also because the environment that the CRM schema is exported from can alter the format and order of the XML.  For these reasons it will be incumbent upon the Schema-Master to understand where the schema segments are coming from and to identify the risks associated with the submitting party.

The SharePoint Role

A standard SharePoint form should be used to capture the schema changes being submitted by each developer.  The SharePoint form should capture at minimum:

               

o    ID Number

o    Entity, Workflow or Role Name

o    Attribute

o    Deployment Comments

o    Impact Analysis Comments

The ID Number shoul

 

d always be references within the TFS check-in comments   and included in the release and deploy notes sent to the Deployment team for each build.

 

 

 

 

 
 
 
 
 
 
 

HTML Editor for Microsoft Dynamics CRM 4.0

Until Microsoft Dynamics 5.0 to comes out the only way to get a HTML or Rich Text editor capability in Microsoft Dynamics CRM 4.0 is to roll your own or use a third party application.  I have had tremendous success using the TinyMCE  editor.  click here to download The editor has a plethora of features available including everything from spell check to word count and is available in a number of  build format and examples including;   jQuery, JavaScript, PHP, .Net and JSP.  I prefer to use the jQuery build for my Microsoft Dynamics CRM 4.0 implementations.

Below I have included a code sample you can use in your custom iFrame.  Accessing the raw HTML source during the CRM onSave event was a little bit tricky, here’s the code for that.

crmForm.all.IFRAME_Accomplishment.contentWindow.tinymce.getInstanceById('elm1').getBody().innerHTML

You can either make a call to the CRM SDK and save this value to the database or use javascript and simply copy to a hidden  nText  field located  directly on the form next to the iFrame.  All we have to do is save and load the raw HTML and the HTML editor does the rest.

I use a FetchXML function to load the raw HTML…

$('#elm1').val(GetContacts());

The GetContacts function simply uses FetchXML to retrieve the value of a nText attribute I have added to the Contacts entity.

Here’s a page that instantiates a HTML editor in a iFrame for use in Microsoft Dynamics CRM 4.0.

<head>
<title>Full featured Microsoft Dynamics CRM 4.0 tinyMCE example using jQuery plugin by ExtremeCRM.net</title>

<!-- Load jQuery -->
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
 google.load("jquery", "1");
</script>

<!-- Load TinyMCE -->
//****** REMEMBER TO CHANGE THIS PATH TO THE PROPER ISV LOCATION  *******//
<script type="text/javascript" src="../jscripts/tiny_mce/jquery.tinymce.js"></script>
<script type="text/javascript">
 $().ready(function() {
 $('textarea.tinymce').tinymce({
 // Location of TinyMCE script
 //****** REMEMBER TO CHANGE THIS PATH TO THE PROPER ISV LOCATION  *******//
 script_url : '../jscripts/tiny_mce/tiny_mce.js',

 // General options
 theme : "advanced",
 plugins : "pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,advlist",

 // Theme options
 theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect",
 theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
 theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
 theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak",
 theme_advanced_toolbar_location : "top",
 theme_advanced_toolbar_align : "left",
 theme_advanced_statusbar_location : "bottom",
 theme_advanced_resizing : true,

 // Example content CSS (should be your site CSS)
 //****** REMEMBER TO CHANGE THIS PATH TO THE PROPER ISV LOCATION  *******//
 content_css : "css/content.css",

 // Drop lists for link/image/media/template dialogs
 template_external_list_url : "lists/template_list.js",
 external_link_list_url : "lists/link_list.js",
 external_image_list_url : "lists/image_list.js",
 media_external_list_url : "lists/media_list.js",

 // Replace values for the template plugin
 template_replace_values : {
 username : "Some User",
 staffid : "991234"
 }

 });

 //Here we load the input textbox named elm1.  This input text box will be overloaded by the tinyMCE editor at runtime.
 //See the GetContacts function below.
 $('#elm1').val(GetContacts());

 });
</script>
<!-- /TinyMCE -->

</head>
<body>

 <form id="form1" runat="server">
 <div>
 <h3>Full featured example using jQuery plugin</h3>

 <p>
 This example shows how TinyMCE can be used from Microsoft Dynamics CRM 4.0  using jQuery. The jQuery plugin will also attach it's self to various jQuery methods to make it more easy to get/set editor contents etc.
 </p>

 <!-- Gets replaced with TinyMCE, remember HTML in a textarea should be encoded -->
 <div>
 <textarea id="elm1" name="elm1" rows="15" cols="80" style="width: 80%">
 &lt;p&gt;
 This is some example text that you can edit inside the &lt;strong&gt;TinyMCE editor&lt;/strong&gt;.
 &lt;/p&gt;
 &lt;p&gt;
 ExtremeCRM provides developers coding examples, insights, concepts and options for developing customizations for Microsoft CRM 4.0.  The primary contributor, Brenden Smith is a Microsoft MCP and Microsoft CRM 4.0 Lead developer for a federal agency in Washing D.C.  If you would like to be a contributor for extremeCRM.net
 &lt;/p&gt;
 </textarea>
 </div>

 <!-- Some integration calls -->
 <a href="javascript:;" onmousedown="$('#elm1').tinymce().show();">[Show]</a>
 <a href="javascript:;" onmousedown="$('#elm1').tinymce().hide();">[Hide]</a>
 <a href="javascript:;" onmousedown="$('#elm1').tinymce().execCommand('Bold');">[Bold]</a>
 <a href="javascript:;" onmousedown="alert($('#elm1').html());">[Get contents]</a>
 <a href="javascript:;" onmousedown="alert($('#elm1').tinymce().selection.getContent());">[Get selected HTML]</a>
 <a href="javascript:;" onmousedown="alert($('#elm1').tinymce().selection.getContent({format : 'text'}));">[Get selected text]</a>
 <a href="javascript:;" onmousedown="alert($('#elm1').tinymce().selection.getNode().nodeName);">[Get selected element]</a>
 <a href="javascript:;" onmousedown="$('#elm1').tinymce().execCommand('mceInsertContent',false,'<b>Hello world!!</b>');">[Insert HTML]</a>
 <a href="javascript:;" onmousedown="$('#elm1').tinymce().execCommand('mceReplaceContent',false,'<b>{$selection}</b>');">[Replace selection]</a>

 <br />
 <input type="submit" name="save" value="Submit" />
 <input type="reset" name="reset" value="Reset" />
 </div>
</form>
<script type="text/javascript">
if (document.location.protocol == 'file:') {
 alert("The examples might not work properly on the local file system due to security settings in your browser. Please use a real webserver.");
}
</script>
</body>
</html>

Microsoft CRM 4.0 Look and Feel Kit

I notice confusion out there about CSS styles to achieve a native CRM look and feel, for reasons I’m not sure of many folks can’t find the CSS template provided in the CRM SDK which can be downloaded here. Additionally the CSS template that is included by Microsoft doesn’t have much in it, for example there aren’t any CSS styles for grids.

For those of you new to CRM what were really discussing here is iFrames embedded in to your CRM entity form page so the need to match up to CRM to present seamless integration and a clean user interface is important.

I’m going to post the CSS template and images I use in my projects and as I add more styles to it I will update it from time to time.

Click here to download the CRM Look and Feel Kit

Attaching Javascript code to a CRM Toolbar Button

Attaching Javascript code to a tool bar button can be very ugly.  Generally the code needs to be included within the CRM, ISV Config  XML file.  It’s pretty easy to add a button via the ISV Config.  Click Here for more information on adding custom buttons to a Microsoft Dynamics CRM Entity form, toolbar.

The red highlighted Javascript tag below shows you where you would typically place your JavaScript or JavaScript function call but this isn’t the best place for the JavaScript.  I have seen huge JavaScript routines embedded in to the tag whci makes debugging and readability a painful exercise.  Even if you call a JavaScript function and leave the tag nice and cleas as shown here that Javascript function has to be maintained outside of the CRM Model.

<Entities>
        <Entity name=”new_customentity”>
          <ToolBar ValidForCreate=”0″ ValidForUpdate=”1″>
            <ToolBarSpacer></ToolBarSpacer>
            <Button Icon=”/_imgs/ico_18_debug.gif” JavaScript=”
DuplicateRecord (10016);”>
              <Titles>
                <Title LCID=”1033″ Text=”Duplicate Record” />
              </Titles>
              <ToolTips>
                <ToolTip LCID=”1033″ Text=”Copies a record based on this open record.” />
              </ToolTips>
            </Button>
            <ToolBarSpacer />
          </ToolBar>
        </Entity>
      </Entities>
 

The solution is pretty simple.

If we change the structure of our JavaScript function a bit we can simply place it in a Entity’s form OnLoad Event.  The key syntax is simply  MyFunction = function() .

Here is a working example of a function I wrote which calls a web service to copy the  current open record and create a new one based on it,

The CRM Form OnLoad Event JavaScript

DuplicateRecord = function() {

    try {
        var oXmlHTTP = new ActiveXObject(“Msxml2.XMLHTTP”);
       oXmlHTTP.Open(“POST”, “/somepath/ DuplicateRecord Service.asmx/ DuplicateRecord?mso-spacerun: yes”>      + crmForm.ObjectId, false);
        oXmlHTTP.setRequestHeader(“Content-Type”, “application/x-www-form-urlencoded”)
        oXmlHTTP.Send(‘jobId=’ + crmForm.ObjectId);
        var newId = oXmlHTTP.responseXML.selectSingleNode(“string”).text;
   }
    catch (e) {
        debugger;
        return null;
    }
}

 

The ISV Config  XML Tag

<Button Icon=”” Url=”” PassParams=”1″ WinParams=”” WinMode=”0″ JavaScript=” DuplicateRecord ();”>
              <Titles>
                <Title LCID=”1033″ Text=” Duplicate This Record ” />
              </Titles>
              <ToolTips>
                <ToolTip LCID=”1033″ Text=”Copy Record” />
              </ToolTips>
            </Button>
 

Conclusion

I like this approach because now all our JavaScript is still maintained within the CRM Entity’s and for a number of good reasons that’s where a Microsoft Dynamics CRM  application should store the JavaScript.  Because its with the entity it will get exported with your model and other customizations.

Grab those lookup values

CRM uses the Primary-Attribute (not to be confused with primary key) for many UI features and functions such as the Associated Views and the Lookup View and the default field for Primary-Attribute is Name.  Unfortunately you can’t have a lookup field or picklist for your Primary Attribute so in the example below even though we select a Trip from the Trips lookup the Name (primary attribute) would need to be filled in manually. 

 In this example imagine you have an Entity called Trips and an entity called Trip Packages.  A Trip Package is related to a Trip and it is a required value.  When you select a Trip from the Trip lookup you want to set the value of the Primary-Attribute/Name so that when Trip packages are searched for they will always contain the name of the base Trip.

 The solution is simple but I’ve seen some wild examples that are much more complex.

Here’s the one line of code to be added to the lookup field controls OnChange Event.

crmForm.all.new_name.DataValue = crmForm.all.new_authorid.DataValue[0].name;

 You could easily concatenate other fields to the Name/ Primary-Attribute as well.  I might add trip date and sponsor name for example.  I would use the same code with the appropriate control names to grab those lookup values:

crmForm.all.new_name.DataValue = crmForm.all.new_sponsorid.DataValue[0].name + crmForm.all.new_authorid.DataValue[0].name + crmForm.all.new_tripdate.DataValue;

 I know we can make this example more elegant with the use of variables and string functions but let’s leave it simple for easy digestion.

 Happy coding!

Ever need to debug a web service from the production server rather than Visual Studio?

Ever need to debug a web service from the production server rather than Visual Studio?  Well it’s a pain to do so because for security reasons Microsoft disables the parameter inputs and replaces it with this message.

Test

The test form is only available for requests from the local machine.

The solution for this is simple, add the following tags to the   <system.web> section of the web.config.

        <webServices>

          <protocols>

            <add name=”HttpGet”/>

            <add name=”HttpPost”/>

          </protocols>

        </webServices>

Now when you load the asmx page it will allow you to enter the required parameters to run your web service.

Microsoft CRM Code Injector for HTTP Handling

crmlogo1

A HTTP Handler intercepts HTTP requests as they are preformed for a particular application; in this case it is a Microsoft Dynamics CRM application. Our HTTP Handler performs code injection in the form of JavaScript; there are 5 parts to the project.

1. Web.config setup

2. Base filter classes

3. Element Extension

4. JavaScript logic files

5. CSS extension files

Let’s take a look at what each one of these 5 parts do.

Web.Config setup

By adding an entry to the <httpModules> section of the Web.Config for the CRM application something like \Inetpub\crmroot\Web.Config each time a HTTP request is preformed we can preform logic via a DLL we will build from the Base filter classes and Element Extension logic (Items 2 and 3 above).  To configure this entry we would insert a line in the <httpModules> section like the following:

<httpModules>

<add name=”ExtensionModule” type=”eCRM.Customize.ElementExtension, eCRM.Extension” />

</httpModules>

eCRM.Extension is the name of the DLL we will create and eCRM.Customize.ElementExtension is the actual name of the IHttpModule class within our project.

Base filter classes

There are two classes that make up the support for our ElementExtension.  The ElementExtension will essentially inject a CSS behavior into the page being processed by the HTTP server (IIS).  The first part is BaseFilter.cs  an abstract class which is simply a System.IO.Stream, and the second part is ElementInsertFilter.cs which inherits the BaseFilter and does the actual work of inserting the CSS behavior.  There isn’t likely any changes that will need to be made to the BaseFilter abstract class, but there might be modifications needed for the ElementInsertFilter.  The ElementInsertFilter is basically a map that tells the code injector logic where to inject the CSS link or, in some cases but not our sample here, you might inject HTML after the <BODY> tag or in a certain <DIV> tag etc….

//..find the </head> element

match = new Regex( “</head>”, RegexOptions.IgnoreCase ).Match( this._buffer.ToString() );

if( match.Success )

{

//..insert the stylesheet element just before the </head> element

this._buffer.Insert( match.Index, this._element );

}

In the previous partial code snippet, we instruct our code injector to insert the CSS link just before the closing </head> tag.  It would be possible to inject JavaScript directly without using the CSS element. It is also possible to inject HTML.  If this were the case you would need to modify the ElementInsertFilter filter with additional logic used to determine where to inject the HTML or JavaScript.

Element Extension

The  ElementExtension.cs simply analyzes the HttpContext passed in to determine the file path.  In the following structure and in the class in the sample project you will need to add one string constant and one FilePath check per element or page you wish to handle.

//Define a string constant to hold the path and file name of our CSS document.

string sfa_accts_edit = “<link rel=\”stylesheet\” type=\”text/css\” href=\”/_oth/HTC/sfa_accts_edit.css\”>” + Environment.NewLine;

HttpContext context = ( ( HttpApplication ) sender ).Context;

//If the path is what we are looking for pass the CSS string to the ElementInsertFilter class.

if (context.Request.FilePath.StartsWith(“/sfa/accts/edit.aspx”, System.StringComparison.OrdinalIgnoreCase))

{

context.Response.Filter = new ElementInsertFilter(context.Response.Filter, sfa_accts_edit);

}

JavaScript logic files

An HTC is an HTML file that contains script and a set of HTC-specific elements that define a component.  We are using HTC files here to store the JavaScript behavior we want our HTTP handler to inject at runtime.    You will need to include the component tag at the beginning of your HTC file script.

<public:component lightweight=”true”>

<public:attach event=”ondocumentready” onevent=”Init()” />

</public:component>

The component tag tells the page processor that we want the JavaScript code logic to be loaded when the Initialization or Init() event is fired.

We might do a simple onload of the form even like this:

function Init()

{

window.onload=OnDocLoad();

}

In another case we might attach an event to a control on the page like this:

function Init()

{

window.document.forms( “crmForm” ).elements( “customertypecode” ).attachEvent( “onchange”, OnCustomerTypeChange );

}

In both cases, we attach a  JavaScript function we have created and attach it to a form or control event.  In this example, we are going to hide the built-in CRM help menus so we will attach to the main form onload event or   window.onload and call the following function:

function OnDocLoad()  

{

var helpMenu = document.getElementById(‘mnu_about’);

helpMenu.style.display = ‘none’;

var helpMenu = document.getElementById(‘mnu_helpUpdates’);

helpMenu.style.display = ‘none’;

var helpMenu = document.getElementById(‘mnu_crmLive’);

helpMenu.style.display = ‘none’;

var helpMenu = document.getElementById(‘mnu_adminGuide’);

helpMenu.style.display = ‘none’;

}

CSS extension files

The CSS file is very simple and has one small, but critical job.  It adds a behavior which attaches the HTC file to the page request being handled by the web server (IIS).  This is done with just a couple line in the CSS file.

body

{

behavior: url(activities_attachment_edit.htc);

}

As you can see the injector concept is a much better option than modifying CRM base pages.

Regards,

Brenden MVP