Extending Sitefinity search functionality - Autocomplete

Extending Sitefinity search functionality - Autocomplete

February 27, 2012 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

NOTE: The following blog post has been updated for compatibility with the latest Sitefinity versions. 

Autocomplete functionality has been one of the most useful things implemented on a search box. Not only the displayed suggestions can speed up your search by simply clicking on one of them instead of typing the full term, but also they can give you a really handy "on the fly search for search terms" while typing, and give you a better search term hint.

There are several factors that need to be taken into account when making your own autocomplete functionality:

  • Speed - an autocomplete that returns the suggestions after you've finished typing the whole word is practically useless, so the call for retrieving the suggestions should return the results quickly
  • Relevance - the returned results should be among the most frequent matches in the search index
  • Styling and extensibility - the returned results should look like a natural part of the search box, and not interfere with the page overall design;  the autocomplete functionality should be easily configurable by the end user

Most autocomplete samples that you'll find rely on a predefined array of data to pull suggestions from, which is not the best option for our use case scenario, that's why we've taken a slightly different approach. Since Sitefinity is using the Lucene engine for it search indexing functionality we can take advantage of the generated search index  and search for keyword suggestions on the fly making an AJAX call to a WebService that will pull the data for us. The service consists of a single method that does all the work for us:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class SearchAutoComplete : ISearchCompleteService
    {
        public string[] GetKeywords(string catalogueName, string term)
        {
            //Adding * allows wildcard search. For more info check http://www.lucenetutorial.com/lucene-query-syntax.html
            var searchTerm = term + '*';
            //these are the Fields we'll search in
            var searchFields = new string[] { "Title" };
            //get the Sitefinity Search service
            var searchService = Telerik.Sitefinity.Services.ServiceBus.ResolveService<ISearchService>();
            //Build the query
            var compiledQuery = searchService.BuildQuery(searchTerm, searchFields, SystemManager.CurrentContext.AppSettings.Multilingual);
            //Get the search results
            var result = searchService.Search(catalogueName, compiledQuery, null, 0, 10, null);
            var strResult = result.Select(res => res.GetValue("Title"));
            return strResult.ToArray();
        }

 

We're passing as parameters the user typed string (the text currently being entered in the search box) and the name of the search index specified for this search box. 
We then resolve the Sitefinity Search service,and build the search query.
As we're interested in pulling just the Title of the items, the specified search field include only "Title". What's interesting is the way Lucene allows us to do wildcard searches, which is very useful in the current case as we can simply search for "Sitef*" which will return all results in the search index whose title starts with "Sitef".
In the call to searchService.Search the returned results are sorted by default by relevance, and we're taking the top 10 results.

For the frontend representation of the autocomplete suggestions we'll be using jQuery UI autocomplete plugin which offers a convenient solution to several problems you might have to deal with if implementing your own autocomplete suggestions visualization, namely:

  • How many symbols do we allow the user to type before calling the service for the first time - an option  minLength can be specified
  • How to call your service upon text changed and then display the returned results
  • Clicking on a result sets its value in the search box automatically

If you want to implement this from scratch maybe the easiest approach would be to define a <div> element which you'll populate with the returned keywords and then add them to a list, each item having an onClick implementation that would set its value as a value in the search box.

Here's how you can very easily accomplish this with jQueryUI autocomplete on the default Sitefinity SearchBox template:

<%@ Control Language="C#" %>
<%@ Register TagPrefix="sitefinity" Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI" %>
<sitefinity:ResourceLinks ID="resourcesLinks" runat="server">
    <sitefinity:ResourceFile JavaScriptLibrary="JQuery" />
    <sitefinity:ResourceFile JavaScriptLibrary="JQueryUI" />
</sitefinity:ResourceLinks>
<%--remove the below style if you want to have the loading icon in the searchbox--%>
<style type="text/css">
    .ui-autocomplete-loading {
        background: white right center no-repeat !important;
    }
</style>
<fieldset id="main" class="sfsearchBox" runat="server">
    <asp:TextBox ID="searchTextBox" runat="server" CssClass="sfsearchTxt" />
    <asp:Button ID="searchButton" runat="server" Text="<%$Resources:SearchResources, Search %>" OnClientClick="return false;" CssClass="sfsearchSubmit" />
    <asp:HiddenField runat="server" ID="searchIndexName" />
</fieldset>
<script type="text/javascript">
    $(document).ready(function () {
        var id = "#" + "<%= searchTextBox.ClientID %>";
        var indexId = "#" + "<%= searchIndexName.ClientID %>";
        var indexName = $(indexId).val();
        var url = "/Sitefinity/Public/Services/SearchCompletion/AutoCompleteService.svc/GetKeywords?catalogueName=" + indexName;
        $(id).autocomplete({
            minLength: 2,
            source: url
        });
    });
</script>

 

If you prefer to implement your own styling for the autocomplete suggestions you don't need to load the default ones, or simply override some of them like shown in the above sample. Please make sure to specify the correct path to where your service is hosted, in our case this is under /Sitefinity/Public/Services/SearchCompletion/ folder. For your convenience I'll be attaching the service hosting to the sample as well.

Since we need to pass the search index name to the service, we have added a HiddenField control above, which we'll be setting in a custom implementation of the SearchBox widget, inheriting from the default one like this:

public class SearchBoxCustom:SearchBox
    {
        public override string LayoutTemplatePath
        {
            get
            {
                return layoutTemplatePath;
            }
            set
            {
                base.LayoutTemplatePath = value;
            }
        }
        protected virtual HiddenField SearchIndexName
        {
            get
            {
                return this.Container.GetControl<HiddenField>("searchIndexName", true);
            }
        }
        protected override void InitializeControls(Telerik.Sitefinity.Web.UI.GenericContainer container)
        {
            base.InitializeControls(container);
            this.SearchIndexName.Value = IndexCatalogue;
        }
        public static readonly string layoutTemplatePath = "~/Controls/Lucene_AutoComplete/SearchBoxTemplate.ascx";
    }

The only other thing worth noting is the specified layoutTemplatePath which you can either change to reflect your folder structure where you've placed the modified template or use Virtual path Provider if you'll be using it as an embedded resource.

Please find attached an archive of the sample folder - you can simply extract it in your SitefinityWebApp solution, add the service hosting under the /Sitefinity/Public/Services folder and adjust any paths wherever necessary (if you'll be placing the template or service in a different folder). Then simply build the project and register the SearchResultsCustom custom control in your PageControls toolbox - you'll be able to use it as any other widget on any Sitefinity page. Here's a short demonstrative video on setting this functionality up, and using the modified SearchBox widget with the autocomplete functionality.

Attachments:  SF_Search_Autocomplete

progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments
Comments are disabled in preview mode.
Topics
 
 
Latest Stories in
Your Inbox
Subscribe
More From Progress
New_Mobile_Dev_Ebook_Progress_Website_Thumbail
The New Mobile Development Landscape
Download Whitepaper
 
IDC Spotlight Sitefinity Thumbnail
Choosing the Right Digital Experience Platform to Improve Business Outcomes
Download Whitepaper
 
TheFastestWayToBuildMobileAppsArtboard-2
The Fastest Way to Build Mobile Apps With Cloud Data
Watch Webinar