Thursday, September 11, 2014

Sending in arbitrary extra query parameters to your SharePoint Search Center

I touched on this subject previously in my post Appending query terms in SharePoint 2013/O365 without adding them to the search box, but I did not have a solution at that time. Now I have.
Topics covered
  • Extend Search.ClientControls.js to support arbitrary FQL statements
  • Add appended search queries to your page without them showing in the search box
As mentioned in Limiting search results by exact time in SharePoint 2013–and how to do FQL you can pass extra query information beside the search text itself by using the refinement filter parameter of a query. This parameter takes FQL by default which is very handy at times. So, the refinement filter property is not just for adding refiners, but can be used to add any FQL statement of your choice, that being an extra query term or XRANK statement.

Let that sink in and think about the possibilities. The good guys at FAST actually handed over the master key to search, probably without knowing it!

image
In my current project we are adding a Social refiner to the search page. This is not a real refiner coming from a refinable managed property, but a filter we want to apply on the page.

As seen from the image above there is an option named Someone near me. The logic behind this is to show only items which have been created or modified by yourself or one of your colleagues. Colleagues are defined as someone working in the same organizational unit as yourself.

If my fellow colleagues are Petter, Pam and Mia the KQL query would look like:

(EditorOWSUSER:petter EditorOWSUSER:pam EditorOWSUSER:mia EditorOWSUSER:mikael) OR (AuthorOWSUSER:petter AuthorOWSUSER:pam AuthorOWSUSER:mia AuthorOWSUSER:mikael)

Certainly something you do not want displayed in your search box.

If I only wanted to check for the last modified I could take advantage of a function from Search.ClientControls.js named updateRefiners. This is one of several functions used by the out of the box refinement display templates.

Check out  Part 5: The Search Refiner Control Methods Explained by Elio Struyf for an excellent explanation of the functions available.

Here’s the sample code for limiting on the last modified user.

// logic executed when clicking a refiner value
var refinementControl = $getClientControl(scope);
var refiners = { 'EditorOWSUSER': ['petter', 'pam', 'mia', 'mikael'] };
refinementControl.updateRefiners(refiners, 'or', false, null);

If I added another updateRefiners line similar to this for AuthorOWSUSER, then the result would be an AND between them, and not an OR which is what I want.
And here comes the beauty of JavaScript prototype. By extending the logic of Srch.RefinementCategory.prototype.toString you can add logic to handle custom FQL. toString() is the function transforming the refinement filter clicked to a valid search statement for the KeywordQuery object.

Srch.RefinementCategory.prototype.toString = function () {
    var refinementFilter = '';
    if (Srch.U.n(this.t) || !this.t.length) {
        return refinementFilter;
    }
    if (this.t.length > 0) {
        if (this.t.length === 1) {
            // Check for special case where we send in FQL as the key, and the length of the query is 1
            if (this.n === "FQL") {
                refinementFilter = this.t[0];
            } else {
                refinementFilter = this.n + ':' + this.t[0];
            }
        } else {
            var operator = (Srch.U.e(this.o)) ? 'and' : this.o;
            var useKQL = !Srch.U.n(this.k) && this.k;
            if (useKQL) {
                if (this.t.length > 1) {
                    refinementFilter += '(';
                }
                for (var i = 0; i < this.t.length; i++) {
                    refinementFilter += this.n + ':' + this.t[i];
                    if (i < this.t.length - 1) {
                        refinementFilter += ' ' + operator + ' ';
                    }
                }
                if (this.t.length > 1) {
                    refinementFilter += ')';
                }
            } else {
                refinementFilter = this.n + ':' + operator + '(' + this.t.join(',') + ')';
            }
        }
    }
    return refinementFilter;
}

The highlighted lines show my added lines. If the key to the filter is equal to FQL, and you have one and only one value, then it will pass the value off as the complete statement and skip prepending it with the key done by the default behavior in the else clause.

With this in place the original code for adding the refiner can be replaced with

var refinementControl = $getClientControl(scope);
var refiners = { 'FQL': ['or(EditorOWSUSER:or(petter,pam,mia,mikael),AuthorOWSUSER:or(petter,pam,mia,mikael))'] };
refinementControl.updateRefiners(refiners, 'or', false, null);

The long KQL statement has now been turned into a much more condensed FQL statement, with the exact logic I was after.

I’ll leave it as an exercise to the reader where to put this code and how to add code to retrieve your colleagues, but at least it shows the extension point. Hopefully I’ll wrap it all up and put it over at github.com/SPCSR.

Summary

By extending the built-in search JavaScript functions and taking advantage of how refinement filters work in SharePoint, you can with little work pass in arbitrary FQL statements to be appended to the users query in the search center.