Sunday, October 11, 2009

CRM 4.0 Make your announcements come to life




CRM has a nice feature (or at least a decent idea) called announcements which allows you to integrate and share textual messages with all CRM users.
The main issue with announcements is that the body or description field does not interprets hyper text.

Now wouldn’t it be nice to be able to integrate reach html messages into CRM using announcements. Although you can easily create your own reach text announcement mechanism and integrate it into CRM site map I actually find it more appealing leveraging an existing feature.

In order to force the announcement page support HTML is made a simple modification to the home / homepage / home_news.aspx. now, since modifying CRM files is unsupported consider the ramification before actually doing so.

The home_news.aspx page contains html that looks like this:

<body>

<table width="100%" height="100%">
<tr>
<td>
<table width="100%" height="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
<div style="width:100%;height:100%;overflow-y:scroll;padding:10px; border: 1px solid #cccccc; background-color:#ffffff;">
<table width="100%" height="100%" cellspacing="2" cellpadding="3" border="0" style="table-layout:fixed;">
<col width="20"><col>
<% =RenderAnnouncements(false) %>
<tr height="100%" colspan="2"><td> </td></tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>


If you take a closer look at the generated html (using iedevtoolbar) you’ll notice that the announcement messages are rendered as table rows. I wanted to make as little change to this page as possible and still support HTML. In order to do so I used a simple technique which wraps the inner body content (table element) in a textarea and reads the textarea value into a new span element as innerHTML.

Here is how the page looks like after the modification is made:

<body>

<textarea style="display:none" id="anntext">
<table width="100%" height="100%">
<tr>
<td>
<table width="100%" height="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
<div style="width:100%;height:100%;overflow-y:scroll;padding:10px; border: 1px solid #cccccc; background-color:#ffffff;">
<table width="100%" height="100%" cellspacing="2" cellpadding="3" border="0" style="table-layout:fixed;">
<col width="20"><col>
<% =RenderAnnouncements(false) %>
<tr height="100%" colspan="2"><td> </td></tr>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</textarea>

<span id="annhtml"></span>
<script> 
window.document.all.annhtml.innerHTML = document.all.anntext.value;
</script>

</body>


One thing that you need to minded of is that the html you paste in the announcement body should be a one liner. This is because the line breaks are interpreted as br elements.

That’s all, enjoy your new announcement feature.

CRM 4.0 cloning using entity mapping




In the past I wrote a few posts about cloning an entity using client side scripting. Most of the posts implemented complex script especially to non developers. I wanted to show you a simple cloning technique that leverages CRM built in features and can also work for CRM online.

The benefits of using this technique are:
1. Gain full control over which attributes are cloned
2. Ability to change which attributes are cloned without adding / changing your code.
3. Usage of a very simple script that does not need to be changed when reused.
4. Ability to easily track the parent record from which the cloned entity originated.

Following is the list of built in CRM features I am going to utilize in the post:
1. Creation of CRM form toolbar button
2. Creation of entity relationship.
3. Creation of mapping between related entities.
4. Adding simple script to the cloned entity onload handler.

So how can we actually make the above features work to our advantage. The main feature that lies in the heart of this technique is the ability to create a self referencing relationship between the an entity to itself and use the mapping wizard to tell CRM which attributes we what to pass from the parent record. Once we have that all that remains is to understand how CRM uses the mapping on the client side much like when you create a child contact record from within an account from.

If you take a close look at the URL that is used by CRM when you create a child related record you’ll notice that the URL uses a specific format that tells CRM what is the parent record id and type. Once you learn how to replicate this behavior you’re half way implementing this solution.
The following script which you’ll eventually need to paste in the entity onload event handler shows how to construct the child record or in our case the cloned record url:


Clone = function()
{
var cloneUrl = location.pathname + "?";
cloneUrl += "_CreateFromType=" + crmForm.ObjectTypeCode +
cloneUrl += "&_CreateFromId=" + crmForm.ObjectId +
cloneUrl += "&etc=" + crmForm.ObjectTypeCode + "#";

var cloneFeatures = 'toolbars=0,status=1,width=' + document.body.offsetWidth + "height=" + document.body.offsetHeight;

window.open(cloneUrl,'',cloneFeatures);
}


Once we have the script in place we need to add a toolbar button that will fire the actual cloning process.
Following is a sample clone button xml which you should add to your isv.config


<Entity name="gi_test">
<ToolBar ValidForCreate="0" ValidForUpdate="1">
<Button Icon="/_imgs/ico_18_debug.gif" JavaScript="Clone();">
<Titles>
<Title LCID="1033" Text="Clone" />
</Titles>
<ToolTips>
<ToolTip LCID="1033" Text="Clone" />
</ToolTips>
</Button>
<ToolBarSpacer />
</ToolBar>
</Entity>


Next you’ll need to create a self referencing relationship. To do that open the entity customization and select 1:N relationship. Then select create new relationship. Inside the relationship form choose the same entity on both sides of the relationship. You should select not to display the left navigation link. The bellow image encapsulates the process of creating the self referencing relationship.



Once the relationship is saved you’ll see a mapping link on the relationship form. Select the mapping link to open the mapping wizard.
Add as much mapping as required. Before you publish the entity consider the final step.



Finally, if you wish to track the originating record you only need to add the entity lookup (parent test in this case) that was created as a result of the self referencing relationship. You can also set the lookup field as read only so users can’t change it manually.



That’s it! publish your entity and you’re done.

Saturday, October 10, 2009

CRM 4.0 Creating Inline Toolbar and Buttons


Here is a nice usability feature that I really like. Currently CRM 4.0 only supports adding functional buttons via form toolbar. This suffices most of the time and mainly on strait forward data input forms. But as CRM takes giant leaps toward becoming a xRM platform, as an application architect and designer, you bow to search for more flexible ways to convey the system to the end user.

The following post presents a simple and effective way of adding an inline toolbar buttons at the section level. This is especially useful when creating complex data entry forms like designers and wizards that require multi-step / section oriented logic. It is also much more simpler to add a button to the form then going through the entire isv.config process.

Adding an inline toolbar to the form is pretty simple and involves 2 steps.
The first step is to add a new text field to the form, where you want the toolbar to appear (e.g. gi_toolbar) and hide it’s label through the form field customizations (i.e. double click on the field and uncheck display label on form checkbox).

Here is how it looks after the above step is completed:



The final step is to add the following code to the entity onload event handler and add an OnCrmPageLoad function which creates a new instance of InlineToolbar and adds the necessary buttons.

The end result looks like this:





function InlineToolbar(containerId)
{
var toolbar = this;
var container = document.all[containerId];

if (!container)
{
return alert("Toolbar Field: " + containerId + " is missing");
}

container.style.display = "none";
container = container.parentElement;

toolbar.AddButton = function(id,text,width,callback,imgSrc)
{
var btn = document.createElement("button");
var btStyle = new StyleBuilder();
btStyle.Add( "font-family" , "Arial" );
btStyle.Add( "font-size" , "12px" );
btStyle.Add( "line-height" , "16px" );
btStyle.Add( "text-align" , "center" );
btStyle.Add( "cursor" , "hand" );
btStyle.Add( "border" , "1px solid #3366CC" );
btStyle.Add( "background-color" , "#CEE7FF" );
btStyle.Add( "background-image" , "url( '/_imgs/btn_rest.gif' )" );
btStyle.Add( "background-repeat" , "repeat-x" );
btStyle.Add( "padding-left" , "5px" );
btStyle.Add( "padding-right" , "5px" );
btStyle.Add( "overflow" , "visible" );
btStyle.Add( "width" , width );

btn.style.cssText = btStyle.ToString();
btn.attachEvent("onclick",callback);
btn.id = id;

if (imgSrc)
{
var img = document.createElement("img");
img.src = imgSrc;
img.style.verticalAlign = "middle";
btn.appendChild(img);
btn.appendChild(document.createTextNode(" "));
var spn = document.createElement("span");
spn.innerText = text;
btn.appendChild(spn);
}
else
{
btn.innerText = text;
}

container.appendChild(btn);
container.appendChild(document.createTextNode(" "));

return btn;
}

toolbar.RemoveButton = function(id)
{
var btn = toolbar.GetButton(id)
if (btn)
{
btn.parentNode.removeChild(btn);
}
}

toolbar.GetButton = function(id)
{
return document.getElementById(id);
}

function StyleBuilder()
{
var cssText = new StringBuilder();
this.Add = function( key , value ){cssText.Append( key ).Append( ":" ).Append( value ).Append( ";" );}
this.ToString = function(){return cssText.ToString();}
}

function StringBuilder()
{
var parts = [];
this.Append = function( text ){parts[ parts.length ] = text;return this;}
this.Reset = function(){parts = [];}
this.ToString = function(){return parts.join( "" );}
}
}

/* Start Script Execution */
function OnCrmPageLoad()
{
window.GeneralToolbar = new InlineToolbar("gi_toolbar");
GeneralToolbar.AddButton("btnReset","Reset","15%",Reset_Click);
GeneralToolbar.AddButton("btnLookup","Lookup","10%",Lookup_Click);
//GeneralToolbar.RemoveButton("btnLookup");
GeneralToolbar.AddButton("btnAddNote","Create Note","16px",AddNote_Click,"/_imgs/ico_16_5_d.gif");
}

function Reset_Click()
{
alert('Reseting Fields...');
}

function Lookup_Click()
{
alert('lookup records...');
}

function AddNote_Click()
{
alert('Add new note');
}