Custom Core Results Web Part in Grid Format, with Context Menus & Sorting/Filtering

This post is about creating a custom core results web part which will render the search results similar to list view UI i.e. using grid view with pagination, context menus, & column sorting / filtering.  Most of the business users demand this kind of UI, as they expect to have a consistent user interface while working with documents.  Moreover, it’s much usable when the document actions are available in the search results itself.  The user can search for some documents and can do the same set of actions what they can do with the document’s library list view.

Problem

  • Search results should enable the user to checkout/check inand perform other related actions for the document
  • User’s should be able to sort & filter on the search result columns
  • The UI for the search results should be similar to the UI of a document library / list library

 Solution

  • Custom Core Results Web Part implementing custom rendering format for the search results using the SPGridView, as this control provides the pagination, sorting & filtering feature

 Key Considerations/Challenges

  • The approach should reuse the OOTB Core Results web part to the max, thus reducing effort in re-inventing the wheel
  • Consistent UI should be provided for the user, between the search UI and the other list views in SharePoint
  • Most of all the features of OOTB Core Results web part can be reused for this approach, but the rendering of search result must be overridden for rendering the results in grid format with pagination, context menus and sorting/filtering
  • The pagination for the SPGridView control is based on the datasource being bound to that. Hence, the OOTB core results web part’s output should be set to the desired size, which is really not possible, as the PageSize property of the OOTB core results web part restricts the maximum size to 50
  • he context menus for the search results should be generated by using the same mechanism how SharePoint does it for the document library / list items

 Approach

  • To implement the custom rendering format in grid, the SPGridView control is used, as it implicitly provides the following features:
    • Pagination
    • Sorting
    • Filtering
  • For the SPGridView to have its own pagination, the results being returned from the OOTB Core Results web part should be overridden with the custom value. The number of records being returned by the OOTB core results web part can be overridden by manipulating the SRHO (Search Result Hidden Object) being used by the OOTB core results web part
  • The javascript function that is responsible for generating the context menu for list items, expects few set of parameters for each item to determine the items to be generated for the context menus. These set of parameters can be explicitly rendered along with the search results

 1) Manipulating the SRHO to return the desired no. of results (using Reflection)

//Get the SRHO from the context

object srhoInContext = System.Web.HttpContext.Current.Items["OSSSRHDC_0"]

//Create a Query instance

Microsoft.Office.Server.Search.Query.Query customQuery = new Microsoft.Office.Server.Search.Query.Query(Microsoft.Office.Server.ServerContext.Current)

//Invoke the methods “SetKeywordQueryProperties” & “SetQueryProperties” to populate the properties for the customQuery object

srhoInContext.GetType().GetMethod

(“SetKeywordQueryProperties”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).Invoke(srhoInContext, new object[] { customQuery });

srhoInContext.GetType().GetMethod

(“SetQueryProperties”,

System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic

).Invoke(srhoInContext, new object[] { customQuery });

 //Set the desired page size to the custom query object

customQuery.RowLimit = 1000

 //Execute the query and obtain results

Microsoft.Office.Server.Search.Query.ResultTableCollection customResults = customQuery.Execute();

 //Set the property variables of the SRHO using Reflection

srhoInContext.GetType().GetField(

“m_Result”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).SetValue(srhoInContext, customResults);

srhoInContext.GetType().GetField(

“m_totalRows”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).SetValue(srhoInContext, relevantResultTable.TotalRows);

srhoInContext.GetType().GetField(

“m_RowCount”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).SetValue(srhoInContext, relevantResultTable.RowCount);

srhoInContext.GetType().GetField(

“m_BestBetRowCount”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).SetValue(srhoInContext, bestBetResultTable.RowCount);

srhoInContext.GetType().GetField(

“m_HighConfidenceRowCount”,

System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance

).SetValue(srhoInContext, highConfidenceResultTable.RowCount);

 //Put the manipulated SRHO object back into context

System.Web.HttpContext.Current.Items["OSSSRHDC_0"] = srhoInContext

 //Get the results data table

DataTable tbl = new DataTable();

tbl.Load(customResults[Microsoft.Office.Server.Search.Query.ResultType.RelevantResults],LoadOption.OverwriteChanges);

 The above manipulation, just updates only the “m_Result” variable of ResultTableCollection type in SRHO object, which of ResultTableCollection.  But, the xmlResponseDoc, representing the results in xml document, is not updated.

 2) Using SPGridView for enabling Pagination, Sorting & Filtering

//INITIALIZE THE SPGRIDVIEW WITH THE PROPERTIES ENABLED FOR PAGINATION, SORTING AND FILTERING

SPGridView resultsView = new SPGridView();

// Initialize SPGridView

resultsView = new SPGridView();

// setting the basic properties of SPGridView

resultsView.ID = “<SPGridViewID>”";

resultsView.AutoGenerateColumns = false;

// setting the sorting properties

resultsView.AllowSorting = true;

// setting the paging properties

resultsView.PageSize = <desiredPageSize>;

resultsView.AllowPaging = true;

resultsView.PagerStyle.HorizontalAlign = HorizontalAlign.Right;

// setting the event handlers

resultsView.RowDataBound += new wRowEventHandler(resultsView_RowDataBound);

// setting the filter properties

resultsView.AllowFiltering = true;

resultsView.FilterDataFields = “<list of column names to be filtered>”;

resultsView.FilteredDataSourcePropertyName = “FilterExpression”;

resultsView.FilteredDataSourcePropertyFormat = “{1} LIKE ‘{0}’”;

// INITIALIZE THE DATASOURCE

ObjectDataSource ds = new ObjectDataSource();

ds.TypeName = “<TypeName>,”;

ds.TypeName += System.Reflection.Assembly.GetExecutingAssembly().FullName;

ds.SelectMethod = “FillDataTable”;

ds.ID = “<ID>”;

 

// setting the data source for the grid view

resultsView.DataSourceID = ds.ID

 

// add both the datasource and the grid to the control collection

this.Controls.Add(ds);

this.Controls.Add(resultsView);

 

// Set the PagerTemplate property to null for enabling the default pagination controls

// this line must be after adding the grid to the control collection

resultsView.PagerTemplate = null;

 

// Override the OnPreRender & Render method to set the filter expression and perform data //bind

 

protected override void OnPreRender(EventArgs e)

{

            ViewState["FilterExpression"] = ds.FilterExpression;

            base.OnPreRender(e);

}

protected override void Render(HtmlTextWriter writer)

{

            resultsView.DataBind();

            base.Render(writer);

}

 

// Setting the column headers with filter icon

// include the following code block in the RowDataBound Eventhandler method for

// SPGridView

 

if ((sender != null) && (e.Row.RowType == DataControlRowType.Header))

{

    string strFilteredColumn = ((SPGridView)sender).FilterFieldName;

    SetGridViewFilterIcon(resultsView, strFilteredColumn, e.Row);

}

 

public void SetGridViewFilter(SPGridView gridView, string strFilteredColumn, GridViewRow gridViewRow)

{

            if ((string.IsNullOrEmpty(strFilteredColumn) == false) && (gridViewRow != null))

             {

                // Show icon on filtered column

                for (int iIndex = 0; iIndex < gridView.Columns.Count; iIndex++)

                {

                    DataControlField currentField = gridView.Columns[iIndex];

                    if (currentField.HeaderText.Equals(strFilteredColumn))

                    {

                        Image filterIcon = new Image();

                        filterIcon.ImageUrl = “/_layouts/images/ewr093.gif”;

                        filterIcon.ImageAlign = ImageAlign.Left;

                        filterIcon.Style[System.Web.UI.HtmlTextWriterStyle.MarginTop] = “2px”;

                        filterIcon.ID = “FilterIcon”;

                        Panel panel = new Panel();

                        panel.Controls.Add(filterIcon);

                        gridViewRow.Cells[iIndex].Controls.Add(panel);

                        break;

                    }

                }

            }

}

3) Populating the context menus for search result items, using the OOTB javascript function which automatically picks up the required attributes and generates the context menus

Reference Table for the hidden attributes being used in this custom search results webpart, for enabling the javascript to populate the context mneus for search results.  The attributes needs to be passed in two places

            1.  ContextInfo object attributes

            2.  Input type attributes

Attribute Name

Description

Value Type

listBaseType                The base type id for the item list     Numeric value      
listTemplate                The list template id for the list      Numeric value      
listName                    The list name in GUID format           GUID               
view                        The list’s view ID                     GUID               
listUrlDir                  Relative URL for the list              string             
HttpPath                    Docsf_vti_binfowssvr.dll?65001      string             
HttpRoot                    Fully qualified URL for the site       string             
imagesPath                  Relative URL for the images            string             
PortalUrl                                                                                
SendToLocationName                                                                        
SendToLocationUrl                                                                        
RecycleBinEnabled                                                                        
OfficialFileName                                                                         
WriteSecurity                                                                            
SiteTitle                   Title of the site                      string              
ListTitle                   Title of the list                      string             
displayFormUrl              Server relative Url for the display form.    string             
editFormUrl                 Server relative Url for the edit form.    string             
ctxId                       The Id for the context object          string             
g_ViewIdToViewCounterMap[x] x – replace it with list view GUID     string             
CurrentUserId               Numeric notation for current user      Numeric            
isForceCheckout             Is force checkout enabled              true/false         
EnableMinorVersions         Is minor versions enabled              true/false         
verEnabled                   Is versioning enabled                  0 – true / 1 – false
WorkflowAssociated          Is workflow associated                 true/false         
ContentTypeEnabled          Is content type enabled                true/false         
ctx<id>=ctx                 Above properties are associated to the contextinfo object ctx & assign it to the ctx<id> – id replace with 1, if only one context object in place, else replace <id> with appropriate number                                        

 

INPUT TYPE ATTRIBUTES

Attribute Name

Description

Value Type

CTXName                     Name of the attribute specifies the context info object reference, with the above-mentioned properties set. ContextInfo object
id                          Id of the item in it’s list         Numeric          
url                         Relative Url of the item            Url as string    
dref                        Item’s file directory reference     Url as string    
perm                        Permission Mask of the list item    Octal as string  
type                        Type of the item                    string           
ext                         Item’s file extension               string           
icon                        <icon>|<client app. ProgID>|<action name>                        
     <icon> – icon name string
                            <client app. ProgID> – ProgID of the item’s associated application string
                            <action name> – Action to be done   string
otype Object Type                         string           
couid Checked out user’s numeric id       Numeric          
sred                        Server redirect Url Url as string    
cout                        Checked Out Status Numeric          
hcd                         Controls if a menu item to navigate to the “/_layouts/updatecopies.aspx” will be added.  I am guessing this   has to do with records management (update copies of the document when it was changed).                     
csrc                        Copy source link                    string           
ms                          Moderation Status                   Numeric          
ctype                       Content Type name                    string           
cid                         Content Type Id                     Octal as string  
uis                         UI version Id                       Numeric as string
surl                        Source Url                           Url as string    

// FORMAT FOR THE CONTEXT INFO ATTRIBUTES SCRIPT

<SCRIPT>

ctx = new ContextInfo();

ctx.listBaseType = <listBaseTypeID>;

ctx.listTemplate = <ListTempateID>;

ctx.listName = “{<GUID of List>}”;

ctx.view = “{GUID of List Default View}”;

ctx.listUrlDir = “<list url>”;

ctx.HttpPath = “u002f<site>u002f_vti_binu002fowssvr.dll?CS=65001″;

ctx.HttpRoot = “http:u002fu002f<SERVER>:<PORT>u002f<SITE>”;

ctx.imagesPath = “u002f_layoutsu002fimagesu002f”;

ctx.PortalUrl = “”;

ctx.SendToLocationName = “”;

ctx.SendToLocationUrl = “”;

ctx.RecycleBinEnabled = -1;

ctx.OfficialFileName = “”;

ctx.WriteSecurity = “1″;

ctx.SiteTitle = “<SITE TITLE>”;

ctx.ListTitle = “<LIST TITLE>”;

if (ctx.PortalUrl == “”) ctx.PortalUrl = null;

ctx.displayFormUrl = “<URL OF DISPLAY FORM>”;

ctx.editFormUrl = “<URL OF EDIT FORM>”;

ctx.isWebEditorPreview = 0;

ctx.ctxId = 1;

g_ViewIdToViewCounterMap[ "{<GUID of List's default view>}" ]= 1;

ctx.CurrentUserId = <CURRENT USER ID (numeric)>;

ctx.isForceCheckout = <true/false>;

ctx.EnableMinorVersions = <true/false>;

ctx.verEnabled = 1;

ctx.WorkflowsAssociated = <true/false>;

ctx.ContentTypesEnabled = <true/false>;

ctx1 = ctx;

</SCRIPT>

 

// FORMAT FOR THE INPUT TYPE ATTRIBUTES

<table height=”100%” cellspacing=0 class=”ms-unselectedtitle” onmouseover=”OnItem(this)”

CTXName=”ctx1″

Id=”<ID of the item in it’s list>”

Url=”<Relative Url of the item>”

DRef=”<File Directory Reference>”

Perm=”<Permission Mask>”

Type=”"

Ext=”<file extension>”

Icon=”<ImageIcon filename>|<ProgID of client application>|<action>”

OType=”<FileSystemObjectTypeID>”

COUId=”<CheckedOutUserID>”

SRed=”"

COut=”<IsCheckedOut><0/1>”

HCD=”"

CSrc=”"

MS=”<ModerationStatus>”

CType=”<ContentType>”

CId=”<ContentTypeID>”

UIS=”<UI String for version>”

SUrl=”">

<tr>

<td width=”100%” Class=”ms-vb”>

<A onfocus=”OnLink(this)”

HREF=”<itemURL>”

onclick=”return DispEx(this,event,’TRUE’,'FALSE’,'TRUE’,'SharePoint.OpenDocuments.3′,

’0′,’SharePoint.OpenDocuments’,”,”,”,’1073741823′,’1′,’0′,’0x7fffffffffffffff’)”>

The above code implemented still have some nitty bitty issues, which I’m still working on.  I thought I’d just give a headsup on the overall approach, so that it will help someone firefighting the same kind of issue.

Screenshots

searchresultswebpart4

1. Custom Core Results Web Part 

searchresultswebpart_filter3

2.  Custom Core Results Web Part – with Filter Menu 

searchresultswebpart_menu4

3.  Custom Core Results Web Part – with Context Menu