24 October 2009

Change the lookup view on Many-to-Many relationship

The N:N lookup view in CRM 4.0 is very basic: it only shows the primary field on the lookup entity. Say if you create a N:N relationship for Account and Contact, the lookup view looks like:


It's not convenient if you want to see more details of the lookup records. So, how can we change it?
How about if we put the standard view(Active Contacts View, Contact Lookup View, etc) on the left panel - Sounds great!

The solution includes two parts:
1. A customized lookupmulti.aspx in the ISV folder;
- Used for generating the lookup view.
2. Jscript code on the referencing entity(which is 'Account' in this case )
- Used for passing parameters to the custom ASPX page;



First of all, create a new folder under the: \CRMWeb\ISV\
Save the below code as a ASPX file in that folder, so you got, for instance: \CRMWeb\ISV\lookup\lookupmulti.aspx


<!--
-- Show entity's standard View in a N:N LookupMulti dialog window.
-- Jim Wang @ October 2009
-- http://jianwang.blogspot.com
-- http://www.mscrm.cn
-->
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=utf-8">
<title>Look Up Records</title>
<script type="text/javascript">
var viewDoc = this;
var _mode = 4;
var IS_PATHBASEDURLS = true;
var ORG_UNIQUE_NAME = window.dialogArguments.split("/")[1];
var iTypeCode = window.dialogArguments.substring(window.dialogArguments.indexOf("etc=") + 4, window.dialogArguments.indexOf("&viewid="));
</script>
<script type="text/javascript" src="/_static/_common/scripts/encodedecode.js"></script>
<script type="text/javascript" src="/_static/_controls/util/util.js"></script>
<script type="text/javascript" src="/_static/_common/scripts/global.js"></script>
<script type="text/javascript" src="/_static/_common/scripts/xmlutil.js"></script>
<script type="text/javascript" src="/_static/_controls/remotecommands/remotecommand.js"></script>
<script type="text/javascript" src="/_common/windowinformation/windowinformation.aspx"></script>
<script type="text/javascript" src="/_static/_controls/lookup/lookupdialogs.js"></script>
<script type="text/javascript" src="/_static/_forms/addrelated.js"></script>
<script type="text/javascript" src="/_static/_common/scripts/details.js"></script>
<script type="text/javascript" src="/_static/_common/scripts/select.js"></script>
<script type="text/javascript" src="/_static/_common/scripts/presence.js"></script>
<script type="text/javascript" src="/_static/_controls/number/number.js"></script>
<script type="text/javascript" src="/_static/_controls/lookup/lookup.js"></script>
<link rel="stylesheet" type="text/css" href="/_common/styles/global.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_common/styles/global-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_common/styles/global-dynamic-styles.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_common/styles/fonts.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_forms/controls/form.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_forms/controls/controls.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_common/styles/dialogs.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_controls/lookup/lookupdialogs.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_common/styles/select.css.aspx?lcid=1033" />
<link rel="stylesheet" type="text/css" href="/_controls/notifications/notifications.css.aspx?lcid=1033" />
</head><body><div style="width:100%; height:100%; overflow:auto"><table style="width:100%; height:100%;" cellspacing="0" cellpadding="0"><tr><td colspan="2" class="ms-crm-Dialog-Header">
<div class="ms-crm-Dialog-Header-Title" id="DlgHdTitle">Look Up Records</div><div class="ms-crm-Dialog-Header-Desc" id="DlgHdDesc">Type the information you are looking for in the Look for box and click Find. Then, select the records you want from the Available records list and move them to the Selected records list.</div></td></tr>
<tr><td colspan="2" style="height:100%;"><div class="ms-crm-Dialog-Main" ><div id="divWarning" style="height:100%;">
<script language="JavaScript">

function initFrame() {
viewDoc = document.getElementById("frmResults").contentWindow.document;
tblResults = viewDoc.getElementById("crmGrid").InnerGrid;
if (tblResults == undefined) {
return;
}
}

function createNew() {
openObj(iTypeCode, null, null);
}

function applychanges() {
window.returnValue = buildReturnValue(tblSelected.rows);
window.close();
}

function cancel() {
window.close();
}

function window.onload() {
if (window.dialogArguments) {
document.getElementById("frmResults").src = window.dialogArguments;
document.getElementById("frmResults").onreadystatechange = resultsReady;
}
else {
alert("No arguments found.");
return;
}
}

function resultsReady() {
if (frmResults.document.readyState == "complete") {
initFrame();
viewDoc.body.scroll = "no";
viewDoc.body.style.padding = "0px";
viewDoc.body.style.border = "1px";
viewDoc.body.firstChild.firstChild.firstChild.firstChild.bgColor = "#E3EFFF";
viewDoc.getElementById("crmMenuBar").parentNode.parentNode.removeNode(true);
viewDoc.getElementById("crmGrid").onpropertychange = function() { setTimeout(onAfterChange, 100); }

setNavigationState();
}
}

function onAfterChange() {
initFrame();
setNavigationState();
}

function removeSelected() {
var items = tblSelected.selectedItems;
for (var i = 0; i < items.length; i++) {
items[i].removeNode(true)
}

items.splice(0, items.length);
if (tblSelected.rows.length > 0) {
selectItem(tblSelected, tblSelected.rows[0], true);
}
setNavigationState();
}

function duplicateSelection(oid) {
var len = tblSelected.rows.length;
for (var i = 0; i < len; i++) {
if (tblSelected.rows[i].oid == oid) {
return true;
}
}
return false;
}

function appendItem(id, type, html, originalItem) {
var tr = tblSelected.insertRow();
tr.oid = id;
tr.otype = type;
tr.originalItem = originalItem;

var td = tr.insertCell();
td.className = "sel";
td.noWrap = true;
td.innerHTML = html;

if (tr.rowIndex == 0) {
selectItem(tblSelected, tr, false);
}
}

function appendSelected() {
initFrame();
var items = tblResults.SelectedRecords;
if (items) {
var len = items.length;
var html = "<TD class=ms-crm-List-DataCell align=middle><IMG style='CURSOR: hand' alt='Click to preview' src='/_imgs/grid/row_selected.gif'> </TD>";

for (var i = 0; i < len; i++) {
var o = items[i];

if (!duplicateSelection(o[0])) {

appendItem(o[0], o[1], o[3].innerHTML.indexOf("row_selected.gif") == -1 ? html + o[3].innerHTML : o[3].innerHTML, null);
}
}
setNavigationState();
}
}

function setNavigationState() {
if (tblResults != undefined) {
if (tblResults.SelectedRecords != null && tblResults.SelectedRecords.length != null && tblResults.SelectedRecords.length > 0) {
btnProperties.disabled = false;
btnAppend.disabled = false;
}
else {
btnProperties.disabled = true;
btnAppend.disabled = true;
}
btnRemove.disabled = (tblSelected.rows.length == 0);
tblNoRecords.runtimeStyle.display = (tblSelected.rows.length == 0 ? "" : "none");
}
}

function showProperties() {
initFrame();
var items = tblResults.SelectedRecords;
if (items == null || items.length == null) {
return;
}
if (items.length == 0) {

alert("You must select one object.");
}
else if (items.length > 1) {
alert("You must only select one object.");
}
else {
var nWidth = 560;
var nHeight = 525;
var oWindowInfo = GetWindowInformation(items[0][1]);
if (oWindowInfo != null) {
nWidth = oWindowInfo.Width;
nHeight = oWindowInfo.Height;
}

switch (Number(items[0][1])) {
case Service:
openStdWin(prependOrgName("/sm/services/readonly.aspx?objTypeCode=" + items[0][1] + "&id=" + items[0][0]), "readonly" + buildWinName(items[0][0]), nWidth, nHeight);
break;
case Workflow:
openObj(items[0][1], items[0][0]);
break;
case ImportMap:
openStdWin(prependOrgName("/tools/managemaps/readonly.aspx?objTypeCode=" + items[0][1] + "&id=" + items[0][0]), "readonly" + buildWinName(items[0][0]), nWidth, nHeight);
break;
default:
openStdWin(prependOrgName("/_forms/readonly/readonly.aspx?objTypeCode=" + items[0][1] + "&id=" + items[0][0]), "readonly" + buildWinName(items[0][0]), nWidth, nHeight);
break;
}
}
}

</script>

<table cellspacing="0" cellpadding="0" width="100%" height="100%">
<td>
<table height="100%" width="100%" id="tblFind" cellpadding="0" cellspacing="0">
<tr height="20">
<td>Available records:</td>
<td></td>
<td>Selected records:</td>
</tr>
<tr>
<td width="45%">

<iframe scrolling="no" class="ms-crm-Dialog-Lookup-Results" id="frmResults" ></iframe>

</td>
<td width="60" align="center">
<button id="btnAppend"disabled="disabled" style="width: 40px;" onclick="appendSelected()" title="Add the selected record">>></button>
<p>
<button id="btnRemove" disabled="disabled" style="width: 40px;" onclick="removeSelected();" title="Remove the selected record"><<</button>
</td>
<td>
<div id="rtnObjList" class="ms-crm-Dialog-Lookup-Objects" onkeydown="listKeyDown(tblSelected)" onfocusin="focusSelectedItems(tblSelected, true);" onfocusout="focusSelectedItems(tblSelected, false);">

<table hidefocus="true" tabindex="0" id="tblSelected" cellpadding="2" cellspacing="0" width="100%" onclick="clickItem( this )" ondblclick="removeSelected()"></table>

<table class="ms-crm-Dialog-Lookup-InlineMsg" id="tblNoRecords">
<tr>
<td class="ms-crm-Dialog-Lookup-InlineMsg" align="center">No records have been selected yet.</td>
</tr>
</table>
</div>
</td>
</tr>
<tr height="20" style="padding-top: 10px;">
<td colspan="3" nowrap>
<button id="btnProperties" disabled="disabled" onclick="showProperties();" class="ms-crm-Button" Title="View the selected record's properties" >Properties</button><span style="width: 5px;"></span>
<button id="btnNew" onclick="createNew();" class="ms-crm-Button" Title="Create a new record">New</button>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div></div>
<div id='divFillBg' style='display:none;position:absolute;top:80px;left:20px;height:23px;width:355px;background-color:#ffffff;'> </div>
<div id='divFill' style='display:none;position:absolute;top:80px;left:20px;height:23px;width:0px;filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#00ff00, EndColorStr=#00cc00);'> </div>
<div id='divStatus' style='display:none;position:absolute;top:80px;left:19px;height:23px;width:357px;'><img alt='' src='/_imgs/statusbar.gif' height='23' width='357'></div></td></tr><tr><td class="ms-crm-Dialog-Footer ms-crm-Dialog-Footer-Left"> </td>
<td class="ms-crm-Dialog-Footer ms-crm-Dialog-Footer-Right"><button id="butBegin" onclick="applychanges();" class="ms-crm-Button">OK</button> <button id="cmdDialogCancel" onclick="cancel();" class="ms-crm-Button">Cancel</button></td></tr></table></div>

</body>
</html>



Put the below code into Account.Onload(), you need to replace the: nnId, lookupTypeCode, lookupViewId to yours.


var nnId = "new_account_contact"; // entity N:N relationship id
var lookupTypeCode = 2; // entity type code
var lookupViewId = "A2D479C5-53E3-4C69-ADDD-802327E67A0D"; // the view id of referenced entity

var lookupSrc = "/" + ORG_UNIQUE_NAME + "/ISV/lookup/lookupmulti.aspx";
var lookupArg = "/" + ORG_UNIQUE_NAME + "/_root/homepage.aspx?etc=" + lookupTypeCode +"&viewid=" + lookupViewId;

var lookupEntityTypeCode;
var navId = document.getElementById("nav" + nnId);
if (navId != null)
{
var la = navId.onclick.toString();
la = la.substring(la.indexOf("loadArea"), la.indexOf(";"));

navId.onclick = function()
{
eval(la);

var areaId = document.getElementById("area" + nnId + "Frame");
if(areaId != null)
{
areaId.onreadystatechange = function()
{
if (areaId.readyState == "complete")
{
var frame = frames[window.event.srcElement.id];
var li = frame.document.getElementsByTagName("li");

for (var i = 0; i < li.length; i++)
{
var action = li[i].getAttribute("action");
if(action != null && action.indexOf(relId) > 1)
{
lookupEntityTypeCode = action.substring(action.indexOf("\(")+1, action.indexOf(","));
li[i].onclick = CustomLookup;
break;
}
}
}
}
}
}
}

function CustomLookup()
{
var lookupItems = window.showModalDialog(lookupSrc, lookupArg, "dialogWidth:800px; dialogHeight:600px;");

if (lookupItems) // This is the CRM internal JS funciton on \_static\_grid\action.js
{
if ( lookupItems.items.length > 0 )
{
AssociateObjects( crmFormSubmit.crmFormSubmitObjectType.value, crmFormSubmit.crmFormSubmitId.value, lookupEntityTypeCode, lookupItems, true, null, nnId);
}
}
}


Notice:

24 comments:

Mark Chaffee said...

Jim,

This is great if I can get it to work, but how do I find the View Id?

Jim Wang said...

Hi Mark,

To get the View Id,

First, Disable the IE security setting: "Allow websites to open windows without address or status bars."

Then open the View from the entity, you should be able to see the viewid from the address bar.

Cheers,
Jim Wang

Peter Kolvenbach said...

Hi Jim,

i have one question to the following Part:
if(action != null && action.indexOf(relId) > 1)
{
lookupEntityTypeCode = action.substring(action.indexOf("\(")+1, action.indexOf(","));
li[i].onclick = CustomLookup;
break;
}
with which value should relId be set?

Thanks in Advance

Peter Kolvenbach

Jim Wang said...

Hi Peter,

The relId is the relationship ID of the N:N entities.

Steven said...

Hi Jim,

Great post.

I'm trying to apply this to a relationship between Case and Product and have managed to get the 3 variables set:

var nnId = "pias_incident_product"; // entity N:N relationship id

var lookupTypeCode = 1024; // entity type code//

var lookupViewId = "A066D18A-8A4A-44EE-B78E-9A870DC799DD";


Although I've assumed relId = nnId.

When I add my products and click OK, I get a CRM error.

Any ideas what could cause this problem?

Thanks,

Viv

Stuart Fawcett said...

Excellent article, i get the same issue as Steven. and made the same assumption.
Also on other N:N relationships the "NEW" & "Properties" buttons are not active - do you know what might be up with these?
Many thanks for your advice.

SquareCircuit said...

I ran your example, but I couldn't get the lookup as shown in your blog. Do I have do something else besides what is already mentioned in your post.

Thank you
Syed

Stuart Fawcett said...

I Solved the problem of the New button not working, its because the variable - LOCID_UI_DIR - is undeclared and this error's but a catch stop the error being seen on screen.
I added var LOCID_UI_DIR = "LTR"; after the other declarations in the openStdWin function in the Global.js file.
Note this type of missing variable error also seems to appear in some reported printer bugs that are fixed by patching CRM 4 to the latest versions.
So maybe patching my CRM will solve this error anyway.
To solve the properties button issue i lazily just but a javascript "alert" inline saying please double click the item of interest.

Stuart Fawcett said...

Hi SquareCircuits, reread steven's comments its all in there:

The values i ended up with (for product to incident) was:
var nnId = "new_incident_product"; // entity N:N relationship id
var relId = nnId;
var lookupTypeCode = 1024; // entity type code for product
var lookupViewId = "84444444-6A44-4744-4442-0C74AE844444"; // the view id of referenced entity

Wei said...

Could you guys explain to me th line of code?

var areaId = document.getElementById("area" + nnId + "Frame");

Is it manually added on to the page?

Thanks

Tom

Wei said...

It seems like working on N:1 or 1:N relationship well.

Did somebody try to use it in N:1 or 1:N relationship?

Tom

buks said...

Hello Jim,
Thanks for this post, it works pretty well. There is one thing (isn't there always?) that does not seem to work. In your own screen shot the pulldown for the view is empty. If you start a search the default view is selected instead of the tailored view for the n:n. Is there a way to solve this?
Thanks already and in advance.
Ben.

Gavin said...

Hi Jim,

Related to Many to Many and Advanced Find but not exactly this topic... but I figured if anyone had an opinion it would be you!

Have you come across any solutions to accommodate the following scenario? Would appreciate your comments!

- Many to Many relationship between custom Entity called "Category" and "Contact"
- Want to be able to find all Contacts that are associated to both of the categories "Health" and "Medical"

Because of the way the query structure works when in Adv Find, and the related entities, I just can't figure out a way to do this! Besides actually using a custom field to 'tag' records and doing bulk edits after the first search. But this isn't really realistic because the numbers of records are going to be multiple thousands....

Any thoughts would be appreciated!

Cheers
Gavin

catzgama said...

hi jim, as selected by default logs, view active or inactive? of lookupmulti

catzgama said...

hi jim, as selected by default logs, view active or inactive? of lookupmulti.


Thanks,

Daniela said...

Hey Jim
Great post.
Do you know if this is unsupported by CRM?
Have you heard of any situation when a company had problems with support because of this customization?
thanks
Daniela

nurul said...

Hi, tried this, its work, somehow when u try run report for selected records,the context was erased. any idea how can i get the value of the the records like it suppose to? thanks

Cristiano said...

Hi, please, I need help. The function below displays the following error in tblResults:

Error in javascript: tblResults Object required


function initFrame() {
viewDoc = document.getElementById("frmResults").contentWindow.document;
tblResults = viewDoc.getElementById("crmGrid").InnerGrid;
if (tblResults == "undefined") {
return;
}
Someone can help me?

Naveed Saqib said...

Thanks Jim worked like a charm.

markpittsnh said...

Hello Jim,

Your solution works when I specify the 'active ...' view GUID. Can you tell me why using the 'associated ...' view does not work? I receive an error about the 'innerGrid not being defined or null'.

Another question. I am also using your fantastic CRMExecute plugin approach to filtering lookups. My question is, can I combine the customization to change the view with filtered lookup customization?

Cheers!

Oliver Foster said...

This looks brilliant - is there any way to make it work with a partylist, like the link to requiredattendees on appointment activities?

Nelson André said...

Hello,

How do I find the lookupTypeCode = 2; // entity type code ?

Nelson André said...
This comment has been removed by the author.
Nelson André said...

Hello all,

I think I followed all steps but nothing new happens.

What I wanted to do was exactly what is in this post : add more fields (from the parent Account) to the Contacts results lookup window.

1 - created the N:N relation
2 - Kept the entity type code 2 - Account
3 - Set the View Id to the id of my Contacts Lookup View
4 - created the aspx page in the mentioned folder

var nnId = "mc_account_contact"; // entity N:N relationship id
var lookupTypeCode = 2; // entity type code
var lookupViewId = "A9AF0AB8-861D-4CFA-92A5-C6281FED7FAB"; // the view id of referenced entity
var relId = nnId;