So, I decided to take another cue from Adi on the implementation of embedding Advanced Finds from his blog. My previous version would initialize and load the AF in the IFrame on the form's load. This caused trouble, in that if the IFrame was on a tab other than the first, the tab's code would cause the src attribute of the IFrame to load, and replace the AF content.
Originally, I had coded around this by setting the src to null, which effectively prevented the AF from being erased. However, even though my code loads the AF view asynchronously (a distinct advantage over Adi's code), it still adds unnecessary overhead to the form when any of its entities were opened.
So, falling back on Adi's technique of hooking into the onreadystatechange handler, I've reverted to the platform behavior of loading only if the tab containing the IFrame is displayed (including the first tab). The added bonus is that since I'm no longer overwriting the src attribute, the domain of the IFrame doesn't change, reducing the tricky cross-frame scripting permissions needed from IE to make it work.
Additionally, I added a few more optional parameters to the function that allows the "New" button on the Advanced Find view to establish child records connected to a parent record other than the one holding the view.
Version 6:
/// 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 be 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 /// relatedTypeId (Optional) The Object Type ID for the related entity on which to establish new /// records. Dependent on entityTypeId. Defaults to crmForm.ObjectTypeCode /// relatedObjectId (Optional) The Object ID for the related entity on which to establish new records. /// Dependent on entityTypeId. Defaults to crmForm.ObjectId function EmbedAdvancedFindView (iFrameId, entityName, fetchXml, layoutXml, sortCol, sortDescend, defaultAdvFindViewId, entityTypeId, relatedTypeId, relatedObjectId) { // Initialize our important variables var url = SERVER_URL + "/AdvancedFind/fetchData.aspx"; var iFrame = document.getElementById(iFrameId); // 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) { if ((typeof(relatedTypeId) == "undefined") || (relatedTypeId == null)) { relatedTypeId = crmForm.ObjectTypeCode; } if ((typeof(relatedObjectId) == "undefined") || (relatedObjectId == null)) { relatedObjectId = crmForm.ObjectId; } eval("newButton.action = 'locAddRelatedToNonForm(" + entityTypeId + ", " + relatedTypeId + ", \"" + relatedObjectId + "\",\"\");'"); } } // 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); } } // Set the onreadystatechange handler of the IFrame to overwrite the contents dynamically iFrame.onreadystatechange = function() { if (iFrame.readyState == "complete") { var doc = iFrame.contentWindow.document; var httpObj = new ActiveXObject("Msxml2.XMLHTTP"); iFrame.onreadystatechange = 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); } } }