Pages

Tuesday, March 10, 2009

Microsoft CRM: Embedding Advanced Find Views in Entity Forms (Version 2)

[UPDATE: See Version 3 of the code in this post at http://crmentropy.blogspot.com/2009/03/microsoft-crm-embedding-advanced-find_12.html]

So, I discovered a way to hack the functionality of the New button on the Advanced Find grid to establish new records that are related to the record housing the view. So without further ado, I give to you, the updated version of my EmbedAdvancedFindView() function.

Version 2:

/// Summary: 
/// Provides a mechanism for replacing the contents of any Iframe on an entity form 
/// with any Advanced Find view. 
/// 
/// Param Description 
/// ---------- ------------------- 
/// iFrameId The id established for the target Iframe 
/// entityName The name of the entity to be found by the Advanced Find 
/// fetchXml FetchXML describing the query for the entity 
/// layoutXml LayoutXML describing the display of the entity 
/// sortCol The schema name of the entity attribute used for primary sorting 
/// sortDescend "true" if sorting the sortCol by descending values, or "false" if ascending
/// defaultAdvFindViewId The GUID of an Advanced Find View for the entity; may that of a saved view 
/// entityTypeId (Optional) The Object Type ID for the entity. Setting this causes the system 
/// to overwrite the functionality of the "New" button to establish related records

function EmbedAdvancedFindView (iFrameId, entityName, fetchXml, layoutXml, sortCol, sortDescend, defaultAdvFindViewId, entityTypeId) { 
  // Initialize our important objects 
  var iFrame = document.getElementById(iFrameId); 
  var httpObj = new ActiveXObject("Msxml2.XMLHTTP"); 
  var url = "/AdvancedFind/fetchData.aspx"; 
  
  // Compile the FetchXML, LayoutXML, sortCol, sortDescend, defaultAdvFindViewId, and viewId into 
  // a list of params to be submitted to the Advanced Find form 
  var params = "FetchXML=" 
    + fetchXml 
    + "&LayoutXML=" 
    + layoutXml 
    + "&EntityName=" 
    + entityName 
    + "&DefaultAdvFindViewId=" 
    + defaultAdvFindViewId 
    + "&ViewType=1039" // According to Michael Hohne over at Stunnware, this is static 
    + "&SortCol=" 
    + sortCol 
    + "&SortDescend=" 
    + sortDescend; 
  
  // Establish an async connection to the Advanced Find form 
  httpObj.open("POST", url, true); 
  
  // Send the proper header information along with the request 
  httpObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
  httpObj.setRequestHeader("Content-length", params.length); 
  
  // Function to write the contents of the http response into the iFrame 
  httpObj.onreadystatechange = function () { 
    if (httpObj.readyState == 4 && httpObj.status == 200) { 
      // Shorthand to the document of the Iframe 
      var doc = iFrame.contentWindow.document; 
      
      // Without a null src, switching tabs in the form reloads the src 
      iFrame.src = null; 
      
      // Write the contents of the response to the Iframe 
      doc.open(); 
      doc.write(httpObj.responseText); 
      doc.close(); 
      
      // Set some style elements of the Advanced Find window 
      // to mesh cleanly with the parent record's form 
      doc.body.style.padding = "0px"; 
      doc.body.style.backgroundColor = "#eaf3ff"; 
      
      // Should we overwrite the functionality of the "New" button? 
      if ((typeof(entityTypeId) != "undefined") && (entityTypeId != null)) { 
        var buttonId = "_MBopenObj" + entityTypeId; 
        var newButton = doc.getElementById(buttonId); 
        
        eval("newButton.action = 'top.locAddObjTo(" + entityTypeId + ", " + crmForm.ObjectTypeCode + ", \"" + crmForm.ObjectId + "\");'"); 
      } 
    } 
  } 
  
  // Set it, and forget it! 
  httpObj.send(params); 
}

You'll notice that there's an additional parameter over the last version. entityTypeId should set as a string representation of the entity type code assigned to the record described by entityName. The code then overwrites the functionality of the New button with a call to top.locAddObjTo(). This function, I've found, takes 3 parameters in the following order: the entity type of the target record, the entity type of the related record, and the GUID of the related record. The caveat to this hacking is that upon completion of the new record, the grid does not automatically refresh to display the new record.

This may be a limitation to using the code for your purposes, so take it with a grain of salt. As for correcting this particular annoyance, there seems to be little option. As hard as I try, I cannot seem to reference the parent window, or grid, from the new record. All the normal javascript pointers (parent, top, opener) don't reference it. This doesn't mean a reference doesn't exist--I just haven't found it. The reverse seems also true: I can't grab a handler to the spawned window from any object/variable within the parent.

In light of this, I had toyed around with the idea of establishing a dual functionality for the New button, so that it would open a new record window and also start an invisible polling process to check the number of records for the view by using SOAP calls to the WSDL API. If the total number of records changed during a particular poll, then this invisible process would call crmGrid.Refresh().

I'm all for hacking, but to me that seems a bit overzealous and ugly. Although it may work, I'm not convinced it's the best approach. So, for the moment I'm leaving the annoyance in and training people to manually refresh the grid after they've established a new, related record. If anyone reading this blog has some insight on how I might better accommodate the automatic grid refresh, please leave a comment.