Improving contextual Search in SharePoint 2013

Contextual search in SharePoint should be easy and intuitive. Apparently it is not, and I’ve investigated this a bit in order to come up with a configurable solution using out of the box functionality.

The challenge is simple: I want to search in the context of the site I’m browsing. At the same time I want to use a specific search center I have branded and configured. Easier said than done.

My research have found that I cannot get the contextual search to work using the standard search settings without being sent to the out of the box / _layouts/15/osssearchresults.aspx.  This is achieved by selecting the “Turn on the drop-down menu inside the search box and use the first Search Navigation node at the destination results page”. The dropdown, however, doesn’t show up anyway.

In this post I’m going to explain how I solved this.

The u-parameter and the {contexturl}-token

The wanted result is acheived by setting the search result page to the search results page of your search center. The u-parameter lets the search know that you want to search only beneath the url which is the value of u. The {contexturl} is actually translated to the current site’s url when you perform a search, yielding the result you are after.

ContextualSearch

Searching within the site’s context, now what?

Performing a search from the search field in the masterpage will now search only in content of the site you were standing at. But the only way of seeing that is by investigating the URL, where you’ll see the the u-parameter with the value of your site’s url.url

Not very user friendly, huh? What if the user doesn’t know he’s just searching within the context of the site he searched from? What if the user wants to search everything again?

Notification and a way out

The way I’ve solved this is through javascript on the search results page which checks for the u-parameter and adds a message to the page if it is present. This could be added in a script snippet web part or added to the masterpage directly. In my case I had several search result pages I wanted this to happen on, and I found it easier to just add the javascript to the masterpage instead of adding it to each results page. The result looks like the following.

searchcontextresults

From the masterpage I’m calling Pzl.Search.AddContextualMessage() which does the work.

Pzl.Search.AddContextualMessage = function () {
    //Check if the current page is the results page and the u-parameter is present
    var url = decodeURIComponent(window.location.href.toLowerCase());
    if (url.indexOf('results.aspx') > -1 && (url.indexOf('?u=') > -1 || url.indexOf('&u=') > -1)) {
        //Get the site the search initiated from
        var contextualUrl = getParameterByName('u').toLowerCase();

        //Create the 'search in everything' URL but removing unnecessary parameters
        var searchAllUrl = url.replace('&u=' + contextualUrl, '').replace('?u=' + contextualUrl + '&', '?').replace('?u=' + contextualUrl, '?');
        //Create the contextual search html message
        var contextMessage = '<div class="contextualMsg ms-textLarge ms-srch-result-noResultsTitle">Du søker nå innenfor området <a href="' + contextualUrl + '">' + contextualUrl + '</a>. Du kan <a href="' + searchAllUrl + '">utvide søket</a> for å søke i alt.</div>';
        
        //Wait until the search results are returned as they are loaded async (this could be done in a smoother way)
        setTimeout(function () {
            if (jQuery('.ms-srch-result .contextualMsg').length <= 0) {
                jQuery('.ms-srch-result').prepend(contextMessage);
            }
        }, 500);
    }
};

The javascript should explain itself (especially with the inline comments, lol), but basically it checks if it’s a contextual search, grabs the contextual URL, creates the “search all”-URL, creates the contextual message-HTML and finally adds it to the top of the search results.

I thought I was ready to wrap up this functionality, but then I discovered an interesting feature/bug. On successive searches, the message is not displayed even though we’re still searching with the u-parameter. What’s up with that?

Adding the message after successive searches

It was apparent that my javascript did not run on successive searches. The reason is simple: SharePoint loads new search results asynchronously, meaning that my javascript method would not be called when the user searches for something else. I investigated the search input field and found that onkeypress the following javascript is called.

if (Srch.U.isEnterKey(String.fromCharCode(event.keyCode))) { $getClientControl(this).search(this.value);return Srch.U.cancelEvent(event); }

As you see, the enter keypressevent is canceled because SharePoint wants to load the results its own way – fair enough and we all like fewer page loads, right?

To work around this issue I hooked into the Srch.U.cancelEvent function and called my Pzl.Search.AddContextualMessage function from there after the event is canceled. I do this by overwriting the cancelEvent-function in the method Pzl.Search.QueueAddContextualMessageOnNewSearches(). This method looks like the following

Pzl.Search.QueueAddContextualMessageOnNewSearches = function() {
    var orginalCancelSearchEvent = Srch.U.cancelEvent;

    Srch.U.cancelEvent = function (event) {
        orginalCancelSearchEvent(event);
        Pzl.Search.AddContextualMessage();
    };
};

That sealed the deal. Contextual search in SharePoint is now a little bit better in my opinion.

PS: My colleague Trond Øivind Eriksen suggested that the logic for adding the contextual message could also be added as part of the Control display template of the search results webparts, which I think is a good idea. Then you shouldn’t have to worry about how SharePoint load new searches, and can kill the QueueAddContextualMessageOnNewSearches function.

13 thoughts on “Improving contextual Search in SharePoint 2013”

  1. I’d suggest two changes.
    1. The search url I’d use would look like…
    {SearchCenterURL}/results.aspx?u={contexturl}

    2. Instead of doing javascript, change the Search Navigation for the search center site to set results.aspx to a Title of Contextual, or something similar. Change Everything to a new results page, allresults.aspx.

    1. Sorry, nevermind on #2 for now.

      The problem is the contextual part of the URL doesn’t go away when you click on Everything. Essentially, you’re doing the same search a second time. I haven’t found a way to ignore the contextual query string parameter on the Everything page…yet.

      1. I’ve found the route I’m going to take. No javascript!

        1. In the search service application, set the Global Search Center URL.

        2. For each site collection, Site Collection Administration -> Search Settings -> Send queries to a custom results page URL.
        {SearchCenterURL}/contextresults.aspx?cntxt={contexturl}

        3. In the search center, create a new page contextresults.aspx. Edit the Search Results web part and click ‘Change query’. Set the ‘Query text’ to {searchboxquery} Path:{QueryString.cntxt}. This will pull out the cntxt query string parameter and match it against file paths.

        Side note, it shouldn’t be this hard.

        1. This does not appear to work with Search Navigation. The url stays at cntxt={contexturl} rather than replacing contexturl with the url of the site you want to search. Any ideas why ?

  2. On the search page I want to link back to actual site, {contexturl} in the url will give url of that site, Can we get “Title”?

  3. u={contexturl} is useful in getting current site context, is there any value that gives you current site collection context ? Reason I am asking this because “Everyone” takes you to common results.aspx page of tenant and once user is directed to that page they lose context. Now passing u={contexturl} to results.aspx page makes context of current site not site collection. Currently I created search page at site collection level and using u={contexturl} for “This site” scope and u=hardcoded url of site collection for “Every one” scope as we need to only search within site collection. Is there a way I can use a parameter that gives me current site collection value rather than hardcoding it ?

    1. Hmm, the best would be if there was a token to do this, but I don’t think such a token exists. What you can do is pick up the URL in the search site, figure out the site collection URL from the weburl (this is easy for O365 where you can only create site collections at /teams or /sites), and create a link to another search which searches the scope of the site collection with u as a query param or path in the query.

Leave a Reply

Your email address will not be published. Required fields are marked *