Sunday, November 17, 2013

Help out #Haiyan victims in the Philippines and get free SharePoint consultancy

Dux Raymond Sy has started an initiative where if you donate USD99, you will get 1h of expert SharePoint consultancy from one of many SharePoint MVP’s (me included).

Head over to http://meetdux.com/2013/11/12/operation-sharelove-help-typhoon-haiyan-victims-sharepoint-experts-will-help-you-rescueph/ for more details and the complete list of people you may select from.

Thursday, November 14, 2013

Issue with Promoted Results in SharePoint 2013 and using REST for search

I was creating some promoted results using query rules this morning, and noticed that they worked fine in the search UI, but I got a 500 internal error when using REST.

Turns out there is a bug in both on-premises 2013 and SharePoint Online which will make your REST query fail if you omit to enter a URL for your promoted result.

image

It’s no big issue as I was planning to add a URL later anyways, but good to know until the issue is fixed.

Tuesday, November 12, 2013

Issue with Display Templates not updating once saved

I’m doing Display Template work at the moment and suddenly my changes to the templates were not reflected once I refreshed the page in the test environment.

Turns out blob cache had been turned on on the servers which also caches .js files, but clearing the cache folder did not seem to work. What did work was going to the Object cache settings page (_layouts/15/objectcachesettings.aspx) of the site collection of the Search Center and then flushing the cache.

clip_image002

Monday, November 11, 2013

Install Display Templates using PowerShell (or copy files using WebDAV in SharePoint)

I’m in the process of writing a Search Center configuration script for SharePoint in PowerShell, and in the process I wanted to copy my display templates as well.

I could have used the SharePoint API to upload the files into the _catalogs/masterpage/Display Templates folder, but this felt like too much work.

My next thought was to copy the files using WebDAV. I started my search on the interwebs and found some posts around PowerShell and WebDAV, but nothing really clear and simple. Then I had an epiphany, maybe I can just copy the files? And you can!!

$dest = '\\intranet.contoso.com@80\_catalogs\masterpage\Display Templates\'
Copy-Item LocalPath -Destination $dest -Recursive

It’s a matter of formatting the URL in a server UNC notation and it just works.

What this doesn’t do is making sure the item copied is approved and published as a major version in SharePoint. To do this you either have to use the SharePoint API from PowerShell, or for example navigate to https://intranet.contoso.com/search/_layouts/DesignDisplayTemplates.aspx and filter on Approval Status = Draft, and publish each one.

Personally I change the settings on the _catalogs/masterpage library to not require approval or check-in/check-out, and only have major versions. If doing this you have to make sure you have control over the library as any saved change will go live right away – this means using a test environment or test site collection for the search page first is highly recommended.

Friday, November 8, 2013

Returning more than 50 results in a search result (ResultScriptWebPart) is a bit tricky

I’ve thought about breaking the 50 item limitation at times, and have received questions if it’s possible to do. And, yes it’s doable….but I won’t vouch for the robustness of my current solution :-)

First off, the technical reason why it blocks at 50 is this piece of code found in Microsoft.Office.Server.Search.WebControls.ResultScriptWebPart

public void set_ResultsPerPage(int value)
{
    try
    {
        DataVerification.ThrowIfOutOfRange(value, "ResultsPerPage", 1, 50, true);
        this.resultsPerPage = value;
    }
    catch (Exception exception)
    {
        ULS.SendTraceTag(0x15a2e3, ULSCat.msoulscat_SEARCH_Query, ULSTraceLevel.Unexpected, "Setting webpart property failed: {0}", new object[] { exception });
        base.Messages.Add(new ControlMessage(null, MessageLevel.Error, -1, exception.GetType().ToString(), exception.Message, "", exception.StackTrace, ULS.CorrelationGetVisibleID().ToString(), false, true, true));
        throw exception;
    }
}

It’s a hardcoded limit. The logical reason behind this limit is could be either one of user experience, or one of performance, as it takes more resources to load up more items.

If you go the custom web part approach, inheriting from the ResultScriptWebPart and using for example reflection to set the private property for ResultsPerPage does not work, as the property itself is being set at page load time, and will then bomb at the limit verification. You could probably disassemble the whole web part and create a new one without the limit, and you would be golden.

In this post I’ll opt for a different approach, I’ll do it client side… which has one drawback, it won’t kick in on the first loading of the result page, as the results are then embedded in the page itself, and not loaded via ajax. This is however solved by kicking off an ajax call automatically after the page has loaded with the effect that the result list will expand fairly quickly after the initial rendering of the page.

On your result pages add a script editor web part and add the JavaScript at the end of this post and you should be able to get more than 50 results. The default max limit for search is 500, which may be changed on the web application for an on-premises farm:

$ssa = Get-SPEnterpriseSearchServiceApplication
$ssa.MaxRowLimit = 1000
$ssa.Update($true)


image


<script>

String.prototype.endsWith = function(suffix) {
    return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

Sys.Application.add_load( function() {
    // Get a collection of all divs
    var collection = document.getElementsByTagName("div");
    var length = collection.length;
    var resultsPerPage = 200;
    // Iterate all divs to find the control element for search
    for( var i=0; i<length; i++ )
    {        
        if(collection[i].id.endsWith("_csr") // element name ends with _csr
            && collection[i].control != null // element has a control object
            && collection[i].control.set_resultsPerPage != null) // control object supports resultsPerPage property
        {
            // override the web part setting
            collection[i].control.set_resultsPerPage(resultsPerPage);
            break;
        }
    }    
    
    // wait until search box is in the DOM
    var checkExist = setInterval(function() {
       if ($get("searchImg") != null ) {          
          clearInterval(checkExist);
          // Reload the results using ajax with the new limit
          $get("searchImg").parentElement.click();
       }
    }, 100); // check every 100ms
});
</script>