Showing posts with label subgrid. Show all posts
Showing posts with label subgrid. Show all posts

Saturday, July 2, 2011

CRM 2011 - Adding scrollbars to a sub-grid

The sub-grid control in CRM 2011 is fantastic and it has replaced a lot of script I use to write. Unfortunately, as of Roll Up 2 you can only specify the number of record to display on a sub-grid. This in turn determines how many records are on each page of the sub-grid and how the sub-grid appears when it is rendered on a form. Well if your users are like mine, they do not want to page through the the sub-grid at 5,10, or X records a page. No, what they want is to have the paging set to 250 records per page and scroll through the records. While you can have a sub-grid display 250 records at a time, you can not constrain it to a specific height and force it to scroll.

Well to get around this issue you can use the script below which sets the RecordsPerPage and maxrowsbeforescroll parameters of the sub-grid and adjusts the height of #SUB-GRID ID#_divDataArea div element of the sub-grid. The RecordsPerPage and maxrowsbeforescroll parameters and passed to the web service that renders the sub-grid and controls how many reds care visible at a given time in a sub-grid when it is rendered. Please let me know if you find any issues of have a have a better method for addressing this particular issue.

//Warning: This code below is not supported by Microsoft

//Place this code at the start of your script
function setSubgridHeight(grdID, noRecordsDisplay, noRecordsPerPage, forceRefresh) {
/// <summary>
/// Causes sub-grid to scroll by setting the number of records to be displayed and 
/// </summary>
/// <param name="grdID" type="string">
/// ID of sub-grid *required
/// </param>
/// <param name="noRecordsDisplay" type="int">
/// Controls height of sub-grid and how many recors are displayed
/// Before applying scrolling
/// </param>
/// <param name="noRecordsPerPage" type="int">
/// Determines number of records to display per page.  If not provided
/// The value defined in the form editor will be used
/// </param>  
/// <returns type="nothing" />
  if (!IsNull(grdID)) {                
    var tbl = $("#" + grdID);

    //Adjust the sub-grid page size if desired, otherwise this is controlled by the form editor
    if (!IsNull(noRecordsPerPage)) {
      //Get reference to divGridProps element of desired grid.
      //Microsoft stores property infomrmaiton about the sub-grid within this element
      var divProps = $("#divGridProps", tbl);
      $("#recsPerPage", divProps).attr("value", noRecordsPerPage);  //Controls number of rows returned by data provider and sets the page size for the sub-grid
    }

    //Get reference to divGridParams element of desired grid.
    //Microsoft stores parameter infomrmaiton about the sub-grid within this element  
    var divParams = $("#divGridParams", tbl);  

    noRecordsDisplay = (IsNull(noRecordsDisplay) ? "5" : noRecordsDisplay);  //Default to 5 visible records at a time

    $("#RecordsPerPage", divParams).attr("value", noRecordsDisplay);  //Controls number of rows displayed in output table
    $("#maxrowsbeforescroll", divParams).attr("value", noRecordsDisplay);  //Force scroll bar to display if more records included than desired to display
  
    //Clear all of the empty TR that are added to fill the table height
    var tb = $("#" + grdID + "_d").parent().parent();
    $("tr", tb).filter(function(index) { return $(this).html() == ""; }).remove();

    //Now resize the data area of the grid
    $("#" + grdID + "_divDataArea").css("height", ((noRecordsDisplay - 1) * 25) + "px");
  
    //Refresh the grid if desired
    if(forceRefresh) { document.getElementById(grdID).control.refresh(); }
  } else {
    throw Error.argument("grdID", "grdID is null or undefined")
  }
} 

//To use you would write something like 
setSubgridHeight(#SUB-GRID ID#, 10, null, false);

Wednesday, June 22, 2011

CRM 2011 - How to filter the 'Add Existing' button on a sub-grid. (A.K.A. filtered sub-grid look up)

Update 07 September 2011

If you are looking for a simpler solution that only allows for changing the fetchXML used by the lookup control check out James's post at CRM 2011 Change SubGrid FetchXML.


I had hoped that Microsoft would provide support for applying custom views to the lookup used by sub-grids, since you can use the Xrm.Page.ui.addCustomView method to apply a custom view to a lookup control. Unfortunately, the Xrm.Page.ui.addCustomView method only works for lookup controls. So for the past three full days I have spent my time trying to understand how the addCustomView method works so I could extend it to support sub-grids. The code below does this; however, IT HAS BEEN TESTED MINIMALLY and thus may not address your particular case. If you find issues or improve this please let me know so I can share it with the rest of the community.

//Warning: This code below is not supported by Microsoft

//Place this code in the onload event of a form containing a sub-grid that you wish to filter

window.attachEvent("onload", function() {
  function locAssocObj_custom(iType, sSubType, sAssociationName, iRoleOrdinal, additionalParams, showNew, showProp, defaultViewId, customViews, allowFilterOff, disableQuickFind, disableViewPicker, viewsIds) {
    var lookupItems = LookupObjects(null, "multi", iType, 0, null, additionalParams, showNew, showProp, null, null, null, null, defaultViewId, customViews, null, null, null, null, allowFilterOff, disableQuickFind, disableViewPicker, viewsIds, false);
    if (lookupItems) lookupItems.items.length > 0 && AssociateObjects(crmFormSubmit.crmFormSubmitObjectType.value, crmFormSubmit.crmFormSubmitId.value, iType, lookupItems, iRoleOrdinal == 2, sSubType, sAssociationName)
  }

  if (Mscrm.GridRibbonActions.addExistingFromSubGridAssociated) {
    Mscrm.GridRibbonActions.addExistingFromSubGridAssociated_org = window.Mscrm.GridRibbonActions.addExistingFromSubGridAssociated;

    window.Mscrm.GridRibbonActions.addExistingFromSubGridAssociated = function(gridTypeCode, gridControl) {
      if (IsNull(gridControl)) {
        throw Error.argument("value", "gridControl is null or undefined")
        return;
      }

   //Check if a filtered grid.  If not call default method      
      if (IsNull(gridControl._element.isFiltered) || !gridControl._element.isFiltered) {
        Mscrm.GridRibbonActions.addExistingFromSubGridAssociated_org(gridTypeCode, gridControl)
      } else {
        var e = document.getElementById(gridControl._element.id);

        var showNew = (IsNull(e.showNew) ? null : e.showNew);
        var showProp = (IsNull(e.showProp) ? null : e.showProp);
        var allowFilterOff = (IsNull(e.allowFilterOff) ? null : e.allowFilterOff);
        var disableQuickFind = (IsNull(e.disableQuickFind) ? null : e.disableQuickFind);
        var disableViewPicker = (IsNull(e.disableViewPicker) ? null : e.disableViewPicker);
        var customViews = (IsNull(e.customViews) ? null : e.customViews);
        var defaultViewId = (IsNull(e.defaultViewId) ? null : e.defaultViewId);
        var viewsIds = null;

        var $v_0 = gridControl.getParameter("relName"), $v_1 = gridControl.getParameter("roleOrd"), $v_2 = false;

        switch (gridTypeCode) {
          case Mscrm.EntityTypeCode.List:
            switch ($v_0) {
              case "campaignactivitylist_association":
                window.parent.locAssocObjCampaignActivity(gridTypeCode, "", $v_0, $v_1);
                break;
              case "campaignlist_association":
                locAssocObjCampaign(gridTypeCode, "subType=targetLists", $v_0, $v_1);
                break;
              case "listlead_association":
                window.parent.locAssocObjLead(gridTypeCode, "", $v_0, $v_1);
                break;
              case "listcontact_association":
                window.parent.locAssocObjContact(gridTypeCode, "", $v_0, $v_1);
                break;
              case "listaccount_association":
                window.parent.locAssocObjAccount(gridTypeCode, "", $v_0, $v_1);
                break;
              default:
                $v_2 = true;
                break
            }
            break;
          case Mscrm.EntityTypeCode.Campaign:
            switch ($v_0) {
              case "campaignlist_association":
                locAssocObjList(gridTypeCode, "subType=targetLists", $v_0, $v_1);
                break;
              case "campaigncampaign_association":
                locAssocObjCampaign(gridTypeCode, "", $v_0, $v_1);
                break;
              default:
                $v_2 = true;
                break
            }
            break;
          case Mscrm.EntityTypeCode.Product:
            switch ($v_0) {
              case "productsubstitute_association":
                locAssocObjProduct(gridTypeCode, "", $v_0, $v_1);
                break;
              case "productassociation_association":
                locAssocObjProduct(gridTypeCode, "", $v_0, $v_1);
                break;
              case "campaignproduct_association":
                locAssocObjCampaign(gridTypeCode, "", $v_0, $v_1);
                break;
              case "competitorproduct_association":
                window.parent.locAssocObjCompetitor(gridTypeCode, "", $v_0, $v_1);
                break;
              default:
                $v_2 = true; break
            }
            break;
          default:
            $v_2 = true;
            break
        }
        if ($v_2) {
          var $v_3 = locAssocObj_custom;
          var additionalParams = null;
          //$v_3(gridTypeCode,"",$v_0,$v_1, additionalParams)     
          $v_3(gridTypeCode, "", $v_0, $v_1, additionalParams, showNew, showProp, defaultViewId, customViews, allowFilterOff, disableQuickFind, disableViewPicker, viewsIds);
        }
      }
    }
  }
});

function addSubgridCustomView(subgridID, entityTypeCode, displayName, fetchXML, layoutXML, filterType, isDefault) {
/// <summary>
/// Adds a custom view to lookup page used by a subgrid
/// </summary>
/// <param name="subgridID" type="string">
/// ID of sub-grid to add custom view to
/// </param>
/// <param name="entityTypeCode" type="int">
/// The entity type code returned by the fetch statement
/// </param>
/// <param name="displayName" type="string">
/// The name to use for the view
/// </param>
/// <param name="fetchXML" type="string">
/// The fetch XML to be used by the view
/// </param>
/// <param name="layoutXML" type="string">
/// The layout XML to be used by the view.
/// If layoutXML == null the layout from the default view of the entity will be used
/// </param>
/// <param name="filterType" type="int">
/// Type of custom view.  Default is 0
/// </param>
/// <param name="isDefault" type="boolean">
/// The layout XML to be used by the view.  
/// If layoutXML == null the layout from the default view of the entity will be used
/// </param>
/// <returns type="nothing" />
 if (IsNull(subgridID)) { throw Error.argument("value", "ID of sub-grid to filter not provided") ; return; }
 if (IsNull(entityTypeCode)) { throw Error.argument("value", "Entype code of returned object by fetchXML not provided"); return; }
 if (IsNull(fetchXML)) { throw Error.argument("value", "FetchXml not provide for custom view on sub-grid"); return; }
 if (IsNull(layoutXML)) { throw Error.argument("value", "LayoutXml not provided for custom view on sub-grid"); return; }

 var grd = document.getElementById(subgridID);

 //Check if the sub-grid has any customViews already.
 var customViews = (IsNull(grd.customViews) ? new Array() : grd.customViews);

 var oScriptlet = new ActiveXObject("Scriptlet.TypeLib");
 var viewId = oScriptlet.GUID.toString().substr(0, 38);  //Call substr to address issue of trailing white space
 
 //Create an object to hold the customView's information
 var customView = new Object();
   customView.fetchXml = fetchXML;
   customView.id = viewId;
   customView.layoutXml = layoutXML;
   customView.name = (IsNull(displayName) || displayName == "" ? "Filtered Lookup" : displayName);
   customView.recordType = entityTypeCode;
   customView.Type = (!IsNull(filterType) ? filterType : 0);

 //Add the customView object to the array of customViews
 customViews.push(customView);      

 //Add the array of custom views to the sub-grid
 grd.customViews = customViews;

 //Set this view as the default if desired
 if (isDefault) { grd.defaultViewId = viewId; }
}

var fetchXML = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>";
 fetchXML += "<entity name='account'>";
 fetchXML += "<all-attributes />";
 fetchXML += "<filter type='and'>";
 fetchXML += "<condition attribute='name' operator='like' value='test%'/>";
 fetchXML += "</filter>";
 fetchXML += "</entity>";
 fetchXML += "</fetch>";

//Build the layout to use in the lookup.  Keep in mind that you will need to know the Object Type Code
//and the primary key of object displayed
var layoutXML = "<grid name='resultset' object='1' jump='name' select='1' icon='1' preview='1'>";
 layoutXML += "<row name='result' id='accountid'>";
 layoutXML += "<cell name='name' width='300' />";
 layoutXML += "</row>";
 layoutXML += "</grid>";
   
//Apply custom view / lookup filter to the grid
addSubgridCustomView("#GRID_ID#", #OBJECT_TYPE_CODE#, "Test", fetchXML, layoutXML, 0, true);