Archive

Archive for April, 2012

SharePoint 2010: Basic List search / filter WebPart

April 19, 2012 89 comments

I have created a very simple SharePoint list search / filter WebPart which was inspired by the following blog post. This WebPart allows you to search records in a list where a selected field contains a specified text. It is useful in scenarios where you dont have SharePoint Search setup and just need a simple way of performing some search operations in a SharePoint List.

Adding the web part to SharePoint List View

Simply drop this web part on top of a page that contains a SharePoint View and it will allow you to apply a very simple search criteria.

The screenshot below shows the WebPart in action:

Main view of the List Search WebPart

The field name DropDownList allows you to select from the fields that are present in the view. Once you select the field and add the text to search by, the relevant results are displayed:

Screenshot showing search results

You can also specify multiple text values by seperating the text with a semi-colon (;):

Screenshot showing search results, multiple text criteria

In the above example the specified criteria will display all the records where the manufacturer’s name contains ‘Honda’ OR ‘Audi’. The screenshot below shows the pagination working as expected:

Screenshot showing search results, multiple text criteria

Adding the web part to a page with an XsltListViewWebPart

You can also add this web part to a page that contains an XsltListViewWebPart. The web part will automatically detect that it has been added to a page (rather than a List View) and display a message asking you to select an XsltListViewWebPart that you would like to apply the filters to:

The screen shot below shows how to select the XsltListViewWebPart:

You can download the solution by clicking on the link below:

SharePoint WSP Download link

You can view the codeplex project site by clicking on the link below:

Codeplex Project Site

Please note that this is setup as a Farm Solution and not a Sandboxed Solution therefore it will not work if you deploy it to the SharePoint Solutions Gallery, you need to deploy the SharePoint Solution via Central Administration, via stsadm commands or via PowerShell commands.

How to it works

On a page that contains a ListViewWebPart you can apply filter by adding a couple of query strings:

  • FilterName
  • FilterMultiValue

In our example, when a user types ‘honda’ and then clicks on the search button we simply append ‘?FilterName=LinkTitle&FilterMultiValue=*honda*;’ to the query string and redirect the user to that page. Please note that ‘LinkTitle’ is the internal name of the ‘Manufacturer’ field.

The * in the *honda* is used to do a wildcard search (contains). If you would like to search for multiple texts you can seperate them by a semi-colon for example ‘FilterMultiValue=*honda*;*audi*;’ will search for records where the ‘Manufacturer’ name either contains ‘honda’ or ‘audi’. If you would like to search for an exact match rather than apply a contains filter then simply remove the *’s from the filter value text.

Although, this WebPart does not allow you to filter / search by more than one field this is very much possible. To apply filters on additional fields you simply need to append ‘FilterField1=Model&FilterValue1=Accord’ to the URL. You can apply further filters by incrementing the number i.e. FilterField2, FilterField3 …. and so on. I am not sure if there is a limit on this.

Please note that I haven’t found a way to get the wildcard search to work with this (multiple filters) approach.

Building the WebPart

In your Visual Studio solution (assuming you have created a Blank SharePoint Project) add a ‘Visual WebPart’. A Visual WebPart loads a UserControl that contains most of the code. Below is the code of the .ascx file:

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"
    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages"
    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ListSearchUserControl.ascx.cs"
    Inherits="Exaction.ListSearch.WebParts.ListSearch.ListSearchUserControl" %>
<script type="text/javascript" src="/_layouts/Exaction.ListSearch.Javascripts/jquery.min.js"></script>
<table>
    <tr>
        <td>
            <strong>Search Criteria:</strong>
        </td>
        <td>
            <asp:TextBox ID="TbSearchText" runat="server" Width="300px"></asp:TextBox>
        </td>
        <td>
            &nbsp;
        </td>
        <td>
            <strong>Field name:</strong>
        </td>
        <td>
            <asp:DropDownList ID="DdlListFields" runat="server">
            </asp:DropDownList>
        </td>
        <td>
            &nbsp;
        </td>
        <td>
            <div align="right">
                <asp:Button ID="BtnSearch" runat="server" OnClick="BtnSearch_Click" Text="Search" />
                <asp:Button ID="BtnClearFilter" runat="server" Visible="false" OnClick="BtnClearFilter_Click"
                    Text="Clear Criteria" />
            </div>
        </td>
    </tr>
</table>
<script type="text/javascript">
    $(document).ready(function () {
        var base_RefreshPageTo = RefreshPageTo;
        RefreshPageTo = function (event, url) {

            var filterName = getQuerystring('FilterName');
            var filterValue = getQuerystring('FilterMultiValue');
            var newUrl = url + '&FilterName=' + filterName + '&FilterMultiValue=' + filterValue;
            if (filterName != '' && filterValue != '') {
                base_RefreshPageTo(event, newUrl);
            }
            else {
                base_RefreshPageTo(event, url);
            }
            return;
        }
    });
    function getQuerystring(key, default_) {
        if (default_ == null) default_ = "";
        key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regex = new RegExp("[\\?&]" + key + "=([^&#]*)");
        var qs = regex.exec(window.location.href);
        if (qs == null)
            return default_;
        else
            return qs[1];
    }
</script>

The code above is pretty self-explanatory but very briefly it contains the UI elements (TextBox, Labels, DropDownList and Buttons) and some jQuery. The jQuery code overrides the ‘RefreshPageTo’ SharePoint javascript function. This is basically to get our filtering to work with pagination. If you have a SharePoint List View that is displaying paginated date to you then clicking on the next or previous page calls the ‘RefreshPageTo’ JavaScript function. The problem is that when this function is called it clears the querystrings we use to filter the data. To ensure that the filtering is maintained we override this function, modify the URL ensuring the filtering querystrings are present and then pass it in as the second parameter to the function.

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Collections.Specialized;
using Microsoft.SharePoint;
using System.Collections.Generic;
using Exaction.ListSearch.UI.Entities;
using System.Text;

namespace Exaction.ListSearch.WebParts.ListSearch
{

    /// <summary>
    /// User control that deals with the registration process
    /// </summary>
    public partial class ListSearchUserControl : UserControl
    {

        /// <summary>
        /// Gets the share point list field items.
        /// </summary>
        /// <param name="filterCriteria">The filter criteria.</param>
        private List<OptionEntity> GetSharePointListFieldItems()
        {
            List<OptionEntity> fieldItems = new List<OptionEntity>();
            fieldItems = new List<OptionEntity>();
            OptionEntity item;
            SPField field;
            StringCollection viewFieldCollection = SPContext.Current.ViewContext.View.ViewFields.ToStringCollection();
            foreach (string viewField in viewFieldCollection)
            {
                field = SPContext.Current.List.Fields.GetFieldByInternalName(viewField);
                item = new OptionEntity();
                item.Id = field.InternalName;
                item.Title = field.Title;
                fieldItems.Add(item);
            }
            return fieldItems;
        }
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            List<OptionEntity> items = GetSharePointListFieldItems();
            DdlListFields.DataSource = items;
            DdlListFields.DataTextField = "Title";
            DdlListFields.DataValueField = "Id";
            DdlListFields.DataBind();
        }
        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void  OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (!IsPostBack)
            {
                if (Request.QueryString["FilterName"] != null)
                {
                    DdlListFields.SelectedValue = Request.QueryString["FilterName"].ToString();
                }

                if (Request.QueryString["FilterMultiValue"] != null)
                {
                    TbSearchText.Text = Request.QueryString["FilterMultiValue"].ToString().Replace("*", "");
                    BtnClearFilter.Visible = true;
                }
            }
        }
        /// <summary>
        /// Handles the Click event of the BtnSearch control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void BtnSearch_Click(object sender, EventArgs e)
        {
            string redirectUrlFormat = "{0}?FilterName={1}&FilterMultiValue={2}";
            string[] selectionCollection = TbSearchText.Text.ToString().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sbValues = new StringBuilder();
            foreach (string selection in selectionCollection)
            {
                sbValues.Append("*" + selection.Trim() + "*;");
            }

            string urlToRedirectTo = string.Format(redirectUrlFormat, Request.Url.GetLeftPart(UriPartial.Path), DdlListFields.SelectedValue, sbValues.ToString());
            Response.Redirect(urlToRedirectTo);
        }
        /// <summary>
        /// Handles the Click event of the BtnClearFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void BtnClearFilter_Click(object sender, EventArgs e)
        {
            Response.Redirect(Request.Url.GetLeftPart(UriPartial.Path));
        }
    }
}

The code behind above initialises the controls and handles the Search and Clear Search Criteria Button click events.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Exaction.ListSearch.UI.Entities
{
    public class OptionEntity
    {
        #region "Fields"
        public string Id { get; set; }
        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>The title.</value>
        public string Title { get; set; }
        #endregion

        #region "Constructor"
        public OptionEntity()
        {
        }
        #endregion
    }
}

We set a collection of the OptionEntity items as the DataSource of the Field name DropDownList.

That is basically it. In this simple manner you have a WebPart that you can drop on top of any List View and apply some basic free text Filterting.

Known issues

There are two minor known issues which I haven’t found a solution for yet:

  • Adding the WebPart on top of the page of a List View takes the focus away from the ListViewWebPart which in turn hides the ribbon. Once you click on the ListViewWebPart and focus on it then the ribbon becomes visible.
  • This WebPart does not work properly with Views that use groupings that are collapsed by default, it works if the groupings are expanded by default
  • As pointed out by Goran (see comments) it might not work with External SharePoint Lists

I hope you find this WebPart useful. Please post your comments and feedback and it would be helpful if you can rate this post.