Saturday, June 21, 2008

Cloning an Entity Using JavaScript

Originally wrote this piece of code for v3.0 for a client who requested a simple cloning mechanism for incidents. The idea behind the code is fairly simple because all CRM controls share a common property called DataValue.
basically all you need to do is create a simple toolbar button that opens a fresh entity page and traverse the controls DataValue.

The first requirement I stumbled on was to identify the cloning process. The problem was that In v4.0 ,by default, ms disallows the passing of unknown query string parameters. although you can change this behavior by modifying a registry settings, this would be extremely unsupported and unwise if ms would choose to delete this option on it’s next CRM version. Instead I used the window.open name parameter.


var EntWindowName = 'Cloned' + crmForm.ObjectTypeName; // e.g. ‘Clonedaccount’
window.open( EntWindowUrl , EntWindowName , EntWindowFeatures );


An alternative solution would be to add a new bit attribute to the entity form e.g. new_iscloningproc and pass a value indicating that this is a cloning process.
For example: “/userdefined/edit.aspx?new_iscloningproc=1 “(true);
MS supports this new type of parameter referencing in v4.0, take a look at the following URL for more information: URL Addressable Forms and Views

The second thing I stumbled on was getting the entity layout and URL information. I could have wrote a simple mechanism for that, however, ms already exposes a GetWindowInformation function so I decided to use that instead.


var EntUrlInfo = GetWindowInformation(ObjectTypeCode); // e.g. 1 - account
var EntWindowFeatures = 'toolbars=0,width=' + EntUrlInfo.Width + ',Height=' + EntUrlInfo.Height + ',Left=10,top=90';


The last issue I came across was to correctly set the URL format for vanilla and custom entities. Vanilla entities use a well defined folder structure e.g. "/sfa/accts/edit.aspx" ( account form ) whereas
custom entities use a logical URL e.g. “/userdefined/edit.aspx?etc=1”. ms uses the etc query string parameter to differentiate between the requested entities. In order to solve that I added a simple check on the crmForm.ObjectTypeCode.


var EtcQsParameter = (ObjectTypeCode > 9999)? '?etc=' + ObjectTypeCode : '';
var EntWindowUrl = '/' + ORG_UNIQUE_NAME + '/' + EntUrlInfo.Url + EtcQsParameter;


Here is the entire isv.config toolbar button.


<Button Icon="/_imgs/ico_18_quota.gif"
JavaScript="
function CloneEntity()
{
var ObjectTypeCode = crmForm.ObjectTypeCode;
var EntUrlInfo = GetWindowInformation(ObjectTypeCode);

var EntWindowFeatures = 'toolbars=0,width=' + EntUrlInfo.Width + ',Height=' + EntUrlInfo.Height + ',Left=10,top=90';
var EtcQsParameter = (ObjectTypeCode > 9999)? '?etc=' + ObjectTypeCode : '';
var EntWindowUrl = '/' + ORG_UNIQUE_NAME + '/' + EntUrlInfo.Url + EtcQsParameter;
var EntWindowName = 'Cloned' + crmForm.ObjectTypeName;
window.open( EntWindowUrl , EntWindowName , EntWindowFeatures );
}
CloneEntity();
" Client="Web">
<Titles>
<Title LCID="1033" Text="Clone X" />
</Titles>
<ToolTips>
<ToolTip LCID="1033" Text="Clone X" />
</ToolTips>
</Button>
<ToolBarSpacer />


Paste the code inside the entity onload event and your done.


function OnCrmPageLoad()
{
/* Indentify that this is a cloning process. */
if( window.name == 'Cloned' + crmForm.ObjectTypeName && crmForm.FormType == 1 )
{
GetOriginalEntityFields();
}
}

function GetOriginalEntityFields()
{

if( !opener )
{
alert('Missing opener');
return;
}

var re = new RegExp("INPUT|TEXTAREA|SELECT","gi");
var originalEntityFormElements = opener.document.all.crmForm.all;
var currentEntityFormElements = crmForm.all;

for( var i = 0 ; i < originalEntityFormElements.length ; i++ )
{

var OriginalElement = originalEntityFormElements[ i ];
var OriginalElementId = OriginalElement.id;

if( OriginalElementId != '' )
{
var currentElement = currentEntityFormElements[OriginalElementId];

if( typeof(currentElement.req) != "undefined" )
{
currentElement.DataValue = OriginalElement.DataValue;
currentElement.Disabled = OriginalElement.Disabled;
}
else if( re.test(currentElement.tagName) )
{
currentElement.value = OriginalElement.value;
}
}
} //end for
} //end function

OnCrmPageLoad();

CRM Custom Tooltip


Here is a simple tooltip solution I posted on ms forums.
In my opinion Microsoft CRM is more of a “search what you need” then an “achieve your goal” application So a well designed tooltip infrastructure is a good place to start.

In my case the tooltip text is hard coded. A better solution would be to load the text from the server using an Ajax call, or create a new “ntext” attribute on the form and read It’s value when the form loads.

The tooltip is positioned at the bottom of each control. Tried moving it around but it didn’t fit the page. Hope you find it useful.

This is how the tooltip looks like
tell me what you think…


Insert the code inside the entity onload event.


var TooltipPopup = null;

function OnCrmPageLoad()
{
AddToolTip( "name" , "The name of the account…" );
AddToolTip( "customertypecode" , "ToolTip ToolTip ToolTip ToolTip…" );
}

function AddToolTip( 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;
TooltipPopup = window.createPopup();
// Single line.
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:12 px tahoma'>"+control.ToolTip+"</DIV>";
TooltipPopup.document.body.innerHTML = ToolTipHTML;
var Width = control.offsetWidth;
var Height = 53;
var Position = GetControlPostion ( control );
var Left = Position.X + 1;
var Top = Position.Y + 1;
TooltipPopup.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( TooltipPopup )
TooltipPopup.hide();
}

OnCrmPageLoad();

Follow-up Task Assignment BUG


When you try to create a follow up task from an existing task and assign it to another user the assignment part is not working and you’re left “assigning” the task to your self.

Until MS releases a fix / rollup, here is a simple unsupported fix I created.

Basically MS send both current task ownerid and the new (follow-up) ownerid to the server. Removing the current ownerid node from the xml that is sent to the server does the trick.

Navigate to: [CRM Install Directory]
C:\Program Files\Microsoft Dynamics CRM\CRMWeb\_static\_controls\RelatedInformation\Category_FollowUp.htc

After line 163:


var activityXml = crmFollowUpFormSubmit.crmFormSubmitXml.value;

Insert the following js code:

var activityXmlDoc = loadXmlDocument(activityXml);
var badOwnerNode = activityXmlDoc.selectNodes("/task/ownerid").item(1);
activityXmlDoc.documentElement.removeChild(badOwnerNode);
activityXml = activityXmlDoc.xml;


Till next time,