Pages

Friday, November 20, 2009

Embedding Advanced Find Views in Entity Forms (Version 6)

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);
    }
  }
}