Friday, September 26, 2008

Decorating CrmGrid Columns


This How-To Post is about decorating grid columns. Once you understand the mechanics behind the code the rest is a simple (or not) manifest of your / customer’s imagination. You should consider the ramifications of using this type of functionality since this might not upgrade to the next version.

The general idea is to override a sitemap link, contacts SubArea link in the workplace Area customers Group in this case, with a new url which contains an IFRAME to the original / Desired CrmGrid.

The JavaScript code in the new aspx page has 2 functionalities:
1. Handles the IFRAME and CrmGird loads and between Refreshes.
2. Contains the code you provide to Decorate the column cells.

So basically if you need to clone this solution for other purposes you need only change the second (2) part.

Steps to follow:
1. Export the sitemap from customization (Settings → customization → export customizations).
2. Save the exported sitemap.zip and extract its contents.
3. Open the constomization.xml file in Visual Studio or Notepad.
4. Locate the contacts (nav_conts) SubArea Node. Here is the actual sitemap fragment


<Group Id="Customers"
ResourceId="Group_Customers"
DescriptionResourceId="Customers_Description">

<SubArea Id="nav_accts"
Entity="account"
DescriptionResourceId="Account_SubArea_Description" />

<SubArea Id="nav_conts"
Entity="contact"
DescriptionResourceId="Contact_SubArea_Description"
Url="/ISV/GridVIews/Contacts.aspx" />
</Group>


5. Add Url attribute to the following url: /ISV/GridVIews/Contacts.aspx
6. Import the customization.xml file back to crm (Settings → Customization → Import Customizations)
7. Add a new aspx under the isv folder, I put it inside ISV/GridViews folder. The aspx does not need a code behind.

Note! If you need to run server side code then you must create your own IIS application to be able to compile your code.

8. Copy the following page html to the Contacts.aspx and Save.
9. In order for the sitemap changes to take effect close and reopen crm in IE.



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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<script language="javascript">

attachEvent( "onload" , OnPageLoad );
//IFrame ID
var contsGrid;
//IFrame document object
var iframeDoc;
//Views picklist
var SavedQuerySelector;
//The grid object
var crmGrid;

/*
Load the contacts grid view,
You may change the IFRAME url to suit your needs
*/
function OnPageLoad()
{
contsGrid = document.all.contsGrid;
var contsUrl = "/" + top.ORG_UNIQUE_NAME + "/_root/homepage.aspx?etc=2";
contsGrid.src = contsUrl;
contsGrid.onreadystatechange = OnGridViewReady;
}

/*
When the IFRAME is ready then:
Attach to the grid refresh and selector change events.
Since the grid is already loaded call it for the first time.
*/
function OnGridViewReady()
{
if( contsGrid.readyState != "complete" )
return;
iframeDoc = contsGrid.contentWindow.document;

//make sure the selector exists
SavedQuerySelector = iframeDoc.all.SavedQuerySelector;
if( SavedQuerySelector )
iframeDoc.all.SavedQuerySelector.attachEvent( "onchange" , OnGridReadyChangeLayout );

//make sure the grid exists
crmGrid = iframeDoc.all.crmGrid;
if( crmGrid )
{
iframeDoc.all.crmGrid.attachEvent( "onrefresh" , OnGridReadyChangeLayout );
//change the layout for the first time
OnGridReadyChangeLayout();
}
}

/*
This function (callback) is called every time the
grid view refreshes either by the selector or refresh button.

IF the Data( InnerGrid ) is not ready then the setTimeout is called.
*/
function OnGridReadyChangeLayout()
{
if( !crmGrid.InnerGrid )
return setTimeout( OnGridReadyChangeLayout , 100 );

/* Put your implementations under this line */
DecorateEmailColumn();
}

/*
This is an example of how to transform the email column into an
Active mailto link

IF the Column you want to Decorate is found then:
FOR each row in the grid
IF the column has text
1. SAVE the column text
2. CLEAR the column inner NOBR element html
3. CREATE a link element
4. APPEND the link element to the NOBR element

*/
function DecorateEmailColumn()
{
var emailColIndex = crmGrid.InnerGrid.FindColumnIndex("emailaddress1");
if( emailColIndex == -1 )
return;

for( var i = 0 ; i < crmGrid.InnerGrid.AllRecords.length ; i++ )
{
emailCell = crmGrid.InnerGrid.AllRecords[ i ][3].cells[ emailColIndex ];
if( emailCell.innerText == "" ) continue;

var emailText = emailCell.innerText;
emailCell.childNodes[0].innerHTML = "";
var link = iframeDoc.createElement("<A style='text-decoration:underline'>");
link.href = "mailto:" + emailText;
link.innerText = emailText;
emailCell.childNodes[0].appendChild( link );
}
}

</script>
</head>
<body scroll="no" style="margin:0px">
<iframe id="contsGrid"
src="about:blank"
style="width:100%;height:100%"
frameborder="0"
scrolling="no">
</iframe>
</body>
</html>

9 comments:

Abhijeet said...

Hi Adi,



I have done this with Ajay's tricky code i.e. call function like



window.setTimeout(addLink, 300);



few problems are still there ...



when I go for search links disappear, and when I cancel search and it come back to
original view with no links to email column. and sometimes screen reamins with search result on cancel.


here is the code...

http://forums.microsoft.com/Dynamics/ShowPost.aspx?PostID=3937803&SiteID=27


Thanks,

Abhijeet

Ralf said...

Hello Adi,

thx for offering us your great code example!
It was a big hint for me to mark inactive datasets in a grid view.
But I am still very new to .NET and writing aspx files (former ASP developer),
so my problem is, when I want to store some js functions in seperated files for reusing,
I do not manage to include these files into the aspx-file.
I tried using the simple "script src=" html-tag or do load these files with ajax!
Can you please refer an article with a sample code or another source which helps me learning this (Book, SDK, etc.)?
I did not find something in the CRM SDK or in the MSDN!

I appreciate any help!

Regards,
Ralf

Ralf said...

Hi Adi,

I should not work sundays...
I was always using the wrong path for the include (forgot to use /ISV/GridViews/)!

Thx anyway! Your code helped me until I had my blackout.
I am sure you have already done this, setting events for the quickfind. If not, here is my dev based on your apporach:
//make sure the quick find exists
QuickFind = iframeDoc.all.findCriteria;
if (QuickFind) {
iframeDoc.all.findCriteriaButton.attachEvent("onclick", OnGridReadyChangeLayout);
iframeDoc.all.findCriteria.attachEvent("onkeypress", function(keyPressed) { if (keyPressed.keyCode == 13) setTimeout(OnGridReadyChangeLayout, 10); });
}

Regards from Germany,
Ralf

Mira Fahmy said...

i need to hide the entire column , not just the values

Adi Katz said...

Row 85: crmGrid.InnerGrid.AllRecords[ i ][3] points to the current row (TR)

So you can:
var r = crmGrid.InnerGrid.AllRecords[ i ][3].style.display = 'none'

Hemangi said...

Hi,

I want to modify the footer of the grid with total number of records for that view and not for the current page.

Can you please help?

Thanks,
Hemangi

Anonymous said...

Hi Adi,

did you manage to get this running in CRM 2011?
I am struggling with the crmGrid, the object is always empty!

Regards
Ralf

didi said...

Custom page on grid gives an error, when we do click on 'Advanced Find' from outlook client. Error -'OpenStdwin' function undefined

this function is refered to Global.js file.

how do i resolve this issue?

Melisa Marzett said...

Thanks for letting us know more information about decorating CrmGrid columns! The steps provided in this entry are easy to follow - it's a merit of our distinguished term paper writing service!