Tuesday, August 26, 2014

SharePoint 2010 Enterprise Search Grouping

In the previous post, we discussed sorting the search results of the core search results web part by our custom search properties. This can be achieved by extending the core search results web part and overwriting the ConfigureDataSourceProperties() method.
It is also possible to group the results by a property or even a combination of properties.

For example, if I have 2 managed properties called 'MyDepartment' and 'MyReportType' and I would like to see the results grouped by all possible combinations of these two properties, such as:


Asset Management - Account Activity  -

  • Account Activity for Account A and Quarter 1
  • Account Activity for Account B Q4

Asset Management - Asset Acquisitions -

  • Asset Acquisitions 2014
  • Asset Acquisitions Feb 2014

Asset Management - Quarterly Income Reports +
Compliance - Quarterly Compliance Report  +
Investment Management - Risk Analysis +
Investment Management - Risk Decomposition +


, each one with a suite of reports within each group, where inside each grouping the reports would be sorted by other managed properties, such as a specific date property for example.
The group by criteria is bolded out and on expand (+) it would show all results for the group.

As a first step, the group-by properties need to be added to the sort-by collection, in their order of relevance. Note the MyDepartment is the first sort criteria, alphabetically ascending, followed by 'MyReportType':

                   // get the datasource and change the sort order
                    CoreResultsDatasource dataSource = this.DataSource as CoreResultsDatasource;
                    if (sortBy != string.Empty)
                    {
                        dataSource.SortOrder.Clear();
                        dataSource.SortOrder.Add("mydepartment", sd);
                        dataSource.SortOrder.Add("myreporttype", sd);


                    }

In the XSLT we will generate the presentation of the group, by applying the Muenchian method, found on Jeni Tennison's site:

  1. First we declare a key in the declaration area of the XSLT:
    <xsl:key name="gg" match="/All_Results/Result" use="concat(mydepartment, myreporttype)"/>
  2. We modify the regular body template to perform two for-eaches, one for the group-by criteria and another one for the results within each group-by value:

<xsl:template name="dvt_1.body">
    <xsl:for-each select="/All_Results/Result[count(. | key('gg', concat(mydepartment,myreporttype))[1]) = 1]">
      <tr>
        <td colspan="8">
           <xsl:value-of select="
mydepartment"/>
          <xsl:text> - </xsl:text>
          <xsl:value-of select="
myreporttype"/>
          <xsl:text> + </xsl:text> <!-- additional html and javascript to achieve expand-collapse-->
        </td>
      </tr>
      <xsl:for-each select="key('gg', concat(
mydepartment,myreporttype))">
        <xsl:sort select="myeffectivedate" order="descending"/>
        <xsl:call-template name="dvt_1.rowview"/>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>


3. Inside the inner for-each, specify the sort criteria to be the date column. The results are rendered by calling the dvt_1.rowview template.


Tuesday, July 29, 2014

SharePoint 2010 Enterprise Search Custom Sort

When you want to sort your search by something more than just Modified date or Relevance, such as your own custom managed properties, a way to achieve this in SharePoint 2010 would be by extending the Core Search Results Web Part like below.
The Core Search Results Web Part can give a page size for up to 50 items, but you may also be able to overwrite that via the extension.

The webpart now reads the sort criteria and direction from the query-string, as well as an extra parameter that allows the user to specify whether the results should be paged at all or displayed in a single page. If they are paged, rather than using the limit of 50, a custom web part property is being used, which is in this example set to a default of 100.


    [ToolboxItemAttribute(false)]
    public class ExtendedSearchResultsWebPart : CoreResultsWebPart
    {
        int customResultsPerPage = 100;
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [WebDescription("Results per page")]
        [WebDisplayName("Results per page")]
        [Category("Custom")]
        public int CustomResultsPerPage { get{ return customResultsPerPage;} set { value = customResultsPerPage;} }

        protected override void ConfigureDataSourceProperties()
        {
            if (this.ShowSearchResults)
            {
                base.ConfigureDataSourceProperties();
                try
                {
                    bool viewAll = false;
                    string sortBy = string.Empty;
                    Microsoft.Office.Server.Search.Query.SortDirection sd = Microsoft.Office.Server.Search.Query.SortDirection.Descending;
                    if (this.Page.Request.QueryString["pall"] != null)
                    {
                        if (this.Page.Request.QueryString["pall"] == "1")
                            viewAll = true;
                    }
                    if (this.Page.Request.QueryString["sort"] != null)
                    {
                        sortBy = this.Page.Request.QueryString["sort"];
                        if(this.Page.Request.QueryString["sd"] != null)
                        {
                            sd = this.Page.Request.QueryString["sd"] == "ascending" ? Microsoft.Office.Server.Search.Query.SortDirection.Ascending : Microsoft.Office.Server.Search.Query.SortDirection.Descending;
                        }
                    }
                    else sortBy = "MYCUSTOMDEFAULTPROPERY";

                        // get the datasource and change the sortorder
                        CoreResultsDatasource dataSource = this.DataSource as CoreResultsDatasource;
                        dataSource.ResultsPerPage = (viewAll == false ? CustomResultsPerPage : 5000);
                        if (sortBy != string.Empty)
                        {
                            dataSource.SortOrder.Clear();
                            dataSource.SortOrder.Add(sortBy, sd);
                        }

                 }
                catch (Exception ex)
                {
                     ULSLogging.LogError("MYLOGGING", "Search: " + ex.Message, ex.StackTrace); }
       
                }
            }

        }

Tuesday, February 18, 2014

Custom Actions for list items assigned programmatically


Custom actions are usually deployed in a declarative manner, such as an Elements.xml file. They can be deployed to a specific content type in case your document library inherits from the specific content type, such as below:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  </CustomAction>
  <CustomAction
  Id="{ACE9ADB4-A9CE-4503-9819-AC9C6FC09E01}"
  RegistrationType="ContentType"
  RegistrationId="0x01010059dee241fa9e47e8aded6c595cb2b406"
  Location="EditControlBlock"
  Sequence="101"
  Title="Approve/Reject Monthly Report">
    <UrlAction Url="javascript:MyNamespace.retrieveListItems({ItemId});"/>
  </CustomAction>
  
</Elements>
       
However, when your document library gets deployed via code, such as  feature receiver, and especially if the document library is not associated with a content type, there is the option to attach a custom action programmatically such as  in the example below.           


                    Guid formlibID = web.Lists.Add("My Library", string.Empty, SPListTemplateType.XMLForm);
                    SPList formLib = web.Lists[formlibID];

                    SPUserCustomAction printForm = formLib.UserCustomActions.Add();
                    printForm.Title = "Print Form";
                    printForm.Url = "javascript:MyNamespace.printForm({ItemId},'{ItemUrl}')";
                    printForm.Location = "EditControlBlock";
                    printForm.Update();

In both examples, the custom action performs some javascript logic (in this case the two functions both open up a modal popup), that takes in the current items' out-of-the-box ID and URL.