Pages

Tuesday, April 7, 2009

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

[UPDATE: See version 6 of the code in this post at http://crmentropy.blogspot.com/2009/11/embedding-advanced-find-views-in-entity.html]

Well, it's been interesting seeing my code deployed on the CRM system here at work. I've run into the following bug that has been corrected in this newest version:

  1. The Run Workflow button on the embedded view would not function properly, because of the modal dialog function hack; corrected by adjusting the modal dialog override to return a value, and refrain from refreshing the grid.

Version 5:

/// 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 variables 
  var httpObj = new ActiveXObject("Msxml2.XMLHTTP"); 
  var url = SERVER_URL + "/AdvancedFind/fetchData.aspx"; 
  var iFrame = document.getElementById(iFrameId); 
  var win = iFrame.contentWindow; 
  var doc = iFrame.contentWindow.document; 
  
  // Provide a global function within the parent scope to avoid XSS limitations 
  // in updating the iFrame with the results from our HTTP request 
  PushResponseContents = function (iFrame, httpObj, entityTypeId) { 
    var win = iFrame.contentWindow; 
    var doc = iFrame.contentWindow.document; 
    var m_iFrameShowModalDialogFunc = null; 
    var m_windowAutoFunc = 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.scroll="no"; 
    
    // Should we overwrite the functionality of the "New" button? 
    if ((typeof(entityTypeId) != "undefined") && (entityTypeId != null)) { 
      var buttonId = "_MBopenObj" + entityTypeId; 
      var newButton = doc.getElementById(buttonId); 
      
      if (newButton != null) { 
       eval("newButton.action = 'locAddRelatedToNonForm(" + entityTypeId + ", " + crmForm.ObjectTypeCode + ", \"" + crmForm.ObjectId + "\",\"\");'");
      } 
    } 
    
    // Swap the showModalDialog function of the iFrame 
    if (m_iFrameShowModalDialogFunc == null) { 
      m_iFrameShowModalDialogFunc = win.showModalDialog; 
      win.showModalDialog = OnIframeShowModalDialog; 
    } 
    
    if (m_windowAutoFunc == null) { 
      m_windowAutoFunc = win.auto; 
      win.auto = OnWindowAuto; 
    } 
    
    // Configure the automatic refresh functionality for dialogs 
    function OnIframeShowModalDialog(sUrl, vArguments, sFeatures) { 
      var returnVar = m_iFrameShowModalDialogFunc(sUrl, vArguments, sFeatures); 

      if (sUrl.search(/OnDemandWorkflow/) < 0) { 
        doc.all.crmGrid.Refresh(); 
      } 
      
      return returnVar; 
    } 
    
    function OnWindowAuto(otc) { 
      doc.all.crmGrid.Refresh(); 
      m_windowAutoFunc(otc); 
    } 
  } 
  
  // Without a null src, switching tabs in the form reloads the src 
  iFrame.src = null; 
  
  // Preload the iFrame with some HTML that presents a Loading image 
  var loadingHtml = "" 
    + "<table height='100%' width='100%' style='cursor:wait'>" 
    + " <tr>" 
    + " <td valign='middle' align='center'>" 
    + " <img alt='' src='/_imgs/AdvFind/progress.gif' />" 
    + " <div /><i>Loading View...</i>" 
    + " </td>" 
    + " </tr>" 
    + "</table>"; 
  
  doc.open(); 
  doc.write(loadingHtml); 
  doc.close(); 
  
  // 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) { 
      parent.PushResponseContents(iFrame, httpObj, entityTypeId); 
    } 
  } 
  
  // Set it, and forget it! 
  httpObj.send(params); 
}