Friday, March 20, 2009

Creating a Network Path Text Control




Dynamics supports several types of text fields. One type which is not available in the current version is a text field that supports a UNC path (\\) or a network drive (Y:) to a shared folder on the server or on the user local machine.

The following example converts a regular text field into a network path control. The control works in conjunction with an Explorer page which contains an IFRAME to the actual network share.

The Explorer(.aspx) should be deployed in the ISV folder. When the user double clicks on the field value a window is opened pointing to the shared folder. The control also provides a comfortable API for managing extra query parameters, changing control style, changing explorer window features and setting an initial network path. The last can be a handy feature if you require the explorer to point to an account personal folder within a shared file system tree.

Here is the Explorer.aspx implementation:


<%@ Page Language="C#" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Dynamics File Explorer</title>
<style>
.header
{
background-color:#639ace;
padding-left:10px;
color:White;
font-size:x-large;
font-family:arial;
height:30px;
}

.iframe
{
border:1px solid #94b2de;
height:100%;
width:100%;
}

body
{
margin:0px;
border:0px solid;
}
</style>
</head>
<body scroll="no">
<table border="0" cellpadding="1" cellspacing="0" style="width:100%;height:100%">
<tr>
<td class="header">File Explorer</td>
</tr>
<tr>
<td>
<iframe class="iframe" src="<%=Request["path"] %>"></iframe>
</td>
</tr>
</table>
</body>
</html>


The following script should be added to the entity onload event:


// JScript File
function OnCrmPageLoad()
{
var networkPath = new NetworkPath("fax");
//Example Add query string parameter
networkPath.Params.Add("test","1");
//Example Remove query string parameter
networkPath.Params.Remove("test");
//Example change control default style
networkPath.Style.Add("color","red");
//Transform the text field into a network path field
networkPath.Transform();
}

function NetworkPath(baseControlId)
{
nk = this;
nk.ID = baseControlId;
nk.Control = document.all[nk.ID];
/* Default Path */
nk.Path = "\\\\127.0.0.1\\Files"; //default path
nk.Window = null;
/* File Explorer Features (window.open)*/
nk.Features = new Dictionary(",","=");
/* If you wish to extend the Explorer.aspx and add more querystring parameters */
nk.Params = new Dictionary("&","=");
/* Network path control additional styling rules */
nk.Style = new Dictionary(";",":");

nk.Transform = function()
{
if (nk.Control)
{
/* Assign default path if control value is empty */
if (nk.Control.DataValue == null)
{
nk.Control.DataValue = nk.Path;
}

nk.Control.title = "Double Click to Open Network Explorer";
/* Default Window Settings */
if (!nk.Features.Exist("width"))
{
nk.Features.Add("width",700);
}
if (!nk.Features.Exist("height"))
{
nk.Features.Add("height",400);
}
if (!nk.Features.Exist("resize"))
{
nk.Features.Add("resize","yes");
}
if (!nk.Features.Exist("toolbar"))
{
nk.Features.Add("toolbar","no");
}
if (!nk.Features.Exist("menubar"))
{
nk.Features.Add("menubar","no");
}
if (!nk.Features.Exist("titlebar"))
{
nk.Features.Add("titlebar","no");
}
/* Attach validation to change and save events */
nk.Control.attachEvent( "onchange" , nk.Validate );
crmForm.attachEvent( "onsave" , nk.Validate );

/* Adjust control style */
nk.Style.Add("text-decoration","underline");
if (!nk.Style.Exist("color"))
{
nk.Style.Add("color","blue");
}
nk.Style.Add("cursor","hand");
nk.Control.style.cssText += ";" + nk.Style.ToString();

/* Add double click functionality */
nk.Control.ondblclick = nk.Open;
}
}

nk.Validate = function()
{
if (nk.Control.DataValue == null)
{
return true;
}

/* validate \\ unc path or a netwrok drive Y: */
var regex = new RegExp("^(\\\\|[a-zA-Z]:)");
if (!regex.exec(nk.Control.DataValue))
{
alert("Invalid Network Path or Drive");
return (event.returnValue = false);
}

return true;
}

nk.Open = function()
{
if (nk.Control.DataValue != null)
{
nk.Window = window.open( SERVER_URL + "/isv/explorer.aspx?path=" + nk.Control.DataValue + nk.Params.ToString() , "" , nk.Features.ToString());
}
}

nk.Close = function()
{
nk.Window.close();
}

/* Key Value Pair Collection */
function Dictionary(sep,delim)
{
this.list = [];
this.Seperator = sep;
this.Delimiter = delim

this.Add = function(key , value)
{
this.list[key] = value;
}

this.Remove = function(key)
{
this.list[key] = "";
}

this.Exist = function(key)
{
return this.list[key] != null && this.list[key] != "";
}

this.ToString = function()
{
var result = new StringBuilder();
result.Append(this.Seperator);
for(var key in this.list)
{
if (this.list[key]!="")
{
result.Append(key).Append(this.Delimiter);
result.Append(this.list[key]).Append(this.Seperator);
}
}
var tmp = result.ToString();
return tmp.substring(0,tmp.length-1);
}
}

function StringBuilder()
{
this.data = [];

this.Append = function(text)
{
this.data[this.data.length] = text;
return this;
}

this.Reset = function()
{
this.data = [];
}

this.ToString = function()
{
return this.data.join("");
}
}
}

OnCrmPageLoad();

Thursday, March 19, 2009

Displaying a lookup with related entity fields




As you all know a CRM lookup displays its related entity primary field. Although this can not be changed using existing customizations; in most cases that suffices. However, there are occasions where you want to display more information in order to avoid opening the related entity form. One solution which I posted about was the lookup preview which builds a preview window for each lookup DataValue. I personally think it’s a great solution and we also have a wizard that facilitates the creation of the preview for us. This post offers a different solution which utilizes a plug-in that retrieves the extra information you wish to display and injects it inside the lookup text. The drawback of this solution is that the lookup can only occupy a certain amount of space. So you should consider expanding the lookup colspan before you use it.

The solution makes use of the post retrieve message on the incident entity. My goal in this demo is to show how to extend the customer lookup on the incident form so if you select an account the customer lookup will display the account name , number and primary contact and if you select a contact then the customer lookup displays the salutation , job title and company fields.

The solution is static but might give you a head start when other requirements of similar nature are in need.


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;

namespace LookupTextPlugIn
{
public class LookupRetrieveHandler : IPlugin
{
#region IPlugin Members

public void Execute(IPluginExecutionContext context)
{
try
{
LookupRetrieveProxy plugin = new LookupRetrieveProxy(context);
plugin.Execute();
}
catch
{
}
}

#endregion
}

public class LookupRetrieveProxy
{
private IPluginExecutionContext Context;
private Customer Customer;
private DynamicEntity CustomerInfo;

public LookupRetrieveProxy(IPluginExecutionContext context)
{
this.Context = context;
}

public void Execute()
{
if (!Context.OutputParameters.Contains(ParameterName.BusinessEntity))
{
return;
}

DynamicEntity entity = (DynamicEntity)Context.OutputParameters[ParameterName.BusinessEntity];

if (!entity.Properties.Contains("customerid"))
{
return;
}

this.Customer = (Customer)entity.Properties["customerid"];
if ((this.CustomerInfo = RetrieveCustomer()) != null)
{
this.Customer.name = ChangeCustomerName();
}
}

private DynamicEntity RetrieveCustomer()
{
ColumnSet customerColumns = new ColumnSet();
switch (this.Customer.type)
{
case "account":
customerColumns.AddColumn("accountnumber");
customerColumns.AddColumn("primarycontactid");
customerColumns.AddColumn("telephone1");
break;
case "contact":
customerColumns.AddColumn("salutation");
customerColumns.AddColumn("jobtitle");
customerColumns.AddColumn("parentcustomerid");
break;
}

ICrmService Service = this.Context.CreateCrmService(true);

TargetRetrieveDynamic targetRetrieve = new TargetRetrieveDynamic();
targetRetrieve.EntityId = this.Customer.Value;
targetRetrieve.EntityName = this.Customer.type;

RetrieveRequest retrieveRequest = new RetrieveRequest();
retrieveRequest.ColumnSet = customerColumns;
retrieveRequest.ReturnDynamicEntities = true;
retrieveRequest.Target = targetRetrieve;

RetrieveResponse retrieveResponse = (RetrieveResponse)Service.Execute(retrieveRequest);
return retrieveResponse.BusinessEntity as DynamicEntity;
}

private string ChangeCustomerName()
{
StringBuilder lookupText = new StringBuilder();
lookupText.Append(this.Customer.name).Append(" ");

switch (this.CustomerInfo.Name)
{
case "account":
lookupText.Append(this.GetProperty("accountnumber")).Append(", ");
lookupText.Append(this.GetProperty("primarycontactid")).Append(", ");
lookupText.Append(this.GetProperty("telephone1"));
break;
case "contact":
lookupText.Append(this.GetProperty("salutation")).Append(", ");
lookupText.Append(this.GetProperty("jobtitle")).Append(", ");
lookupText.Append(this.GetProperty("primarycustomerid"));
break;
}

return lookupText.ToString();
}

private String GetProperty(string propName)
{
if (!this.CustomerInfo.Properties.Contains(propName))
{
return "";
}

Object property = this.CustomerInfo.Properties[propName];

if (property is String)
{
return property.ToString();
}
else if (property is Customer)
{
return ((Customer)property).name;
}
else if (property is Lookup)
{
return ((Lookup)property).name;
}

//not supproted
return String.Empty;
}
}
}

Friday, March 13, 2009

CRM 4.0 Associated View Record / Page Counter




Our Record and Page Counter now supports Associated Views. This AVCR extension complements the RCO solution and provides full record counting coverage. If you require such functionality it is now part of our solution.

The Counter is available for All entities except Activities and All views that fire the RetrieveMultiple event.