Tuesday, January 8, 2013

SharePoint 2013 Search with REST, App Model and JQuery (Part One)

Technorati Tags: ,,,

During the holiday break I became restless and decided to do a mash up with the SharePoint 2013 app model. I put together a simple on premises application using SharePoint 2013 search called Basic Search. I was intrigued by the simplicity of the SharePoint 2013 REST API and also am a fan of the JQuery plug-in called DataTables.  What I came up with was a practical search that can display search results in a JQuery DataTable grid using very little code. In this post I will show you the simplicity of the REST search API and the incredible amount of flexibility using the DataTable JQuery plug-in. I will also show you a simple way of getting a default list of managed properties so the user can select which managed properties to display in the results. You can also download the sample project here. Code

 

 

The Beginning

My first step was to create a SharePoint 2013 on premises application project. I am not going into the details about this since there has already been a lot written about it. Some great links I used for this are below:

Introducing SharePoint Apps

Using the Search REST API from the SharePoint App Model

Planning the Infrastructure for the new SharePoint 2013 App Model

Please note you must add the the “Search QueryAsUserIgnorAppPrincipal” permission to the application manifest in order to retrieve search results.

 

Using the SharePoint Search REST API

I started my mashing by stepping. My first step was to create a text box to type in a search and a button to submit the search using the REST API. Of course there is a lot of documentation on the URL syntax to use when submitting the search.  I decided to use JQuery for all the code so I made sure the default.aspx included a link to the JQuery libraries. When creating the application through VS 2012 the JQuery libraries for version 1.6 are included by default. I recommend you link to the latest and include them in the scripts folder of your project. Below is the code which uses JQuery to request a search using the $ajax command.

function doSearch() {
var searchRestSource = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?querytext='"
+ $get('searchText').value + "'"
+ "&rowlimit=500";

$.ajax(
{
url: searchRestSource,
method: "GET",
headers: {
"accept": "application/json; odata=verbose",
},
success: function (data) {
if (data.d.query.PrimaryQueryResult.RelevantResults.RowCount > 0)
{

}
else
{

}

},
error: function (err) {
alert(JSON.stringify(err));
},
}
);

}



The best way to test this initial code was to use the developer tools that are furnished with Internet Explorer. Using IE9 just press the F12 button and navigate to the Scripts tab then select the app.js file from the drop down. You can now set a break point and click the “Start Debugging” button. I set a break point in the success function and examined the results. When examining the data variable the developer tools give the same ability to view live objects as if you are debugging in VS 20102. I was able to determine where the relevant results were located so I could display them. When using the REST API you code needs to be able to concatenate URL requests. Your best friend in this case is to use the always available _spPageContextInfo object and the webAbsoluteUrl property. The code concatenates the search term to the querytext variable and sets the returned rowlimit variable to 500. The search REST API has many variables that you can set and I recommend you use the POST method rather the GET to overcome the 4K query string limitation. You can read more about the api from these SharePoint 2013 Search REST API Programming the SharePoint 2013 REST API




Displaying Results




Displaying search results and making them useful to users has always been difficult. It was difficult in SharePoint 2010 using XSLT and basic things like sorting on any return value just did not exist. In SharePoint 2013 result customization has greatly improved with the use of the new Query Rules and Display Templates. Unfortunately these are limited to the out of the box SharePoint search web parts. I needed something I could deploy to the app model that would run on premise or anywhere. The JQuery DataTable grid plug-in proved to be ideal for this. It is free and has many useful features such as sorting, styling, scrolling, paging and supports themes.







The grid comes with built in features like selecting how many rows you wish to display, column sorting, paging and paging information. The API for this plug-in is versatile and supports JQueryUI themes, multi-column sorting, filtering, grouping, custom headers and footers. For more information see this link DataTables JQuery Plug-in




Below is the doSearch method that incorporates the loading of the DataTable grid with search results.



function doSearch() {
var searchRestSource = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?querytext='"
+ $get('searchText').value + "'"
+ "&rowlimit=500";

$.ajax(
{
url: searchRestSource,
method: "GET",
headers: {
"accept": "application/json; odata=verbose",
},
success: function (data) {
if (data.d.query.PrimaryQueryResult.RelevantResults.RowCount > 0)
{
convertResults(data);

var oTable = $('#searchResults').dataTable({
"bDestroy": true,
"bStateSave":false,
"bPaginate": true,
"bLengthChange": true,
"bFilter": false,
"bSort": true,
"bInfo": true,
"bAutoWidth": false,
"aaData": results
});

}
else
{
$('#searchResults').dataTable().fnClearTable();
}

},
error: function (err) {
alert(JSON.stringify(err));
},
}
);

}



You should have a table element already defined with an id of “searchResults”. The code uses the DataTable JQuery plug-in attaching a datatable to the table and passing in arguments. Many of the arguments are enabling features. You can look these up in the DataTables site. The aaData argument contains an array of string arrays containing values which represent the values from  each row of the search results. This array is built from the results in the convertResults method.



function convertResults(data) {

var queryResults = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
var tempResults = [];
var cellValues = [];
var tempColumns = [];

for (var i = 0; i < queryResults.length; i++)
{
cellValues = [];

for (var h = 0; h < queryResults[i].Cells.results.length; h++)
{
cellValues.push(queryResults[i].Cells.results[h].Value);
}

tempResults.push(cellValues);
}

results = tempResults;

}



This is very simple looping of the result rows and cells that populates arrays. The DataTable plug-in supports custom formatting of result values such as dates and hyperlinks.  So now I have an SharePoint app model application that allows me to type in a search term and return all the results. The results can be sorted and paged.




Dynamic Results




The next step was to allow users to select which managed properties to display the results. Usually this is a simple task using the SharePoint server side object model. The SharePoint 2013 client side libraries for Search are supposed to be equivalent with the server side object model. Unfortunately, this is not true. The list of Managed Properties is not available in the CSOM, JSOM or REST. They are still available in the deprecated Search.asmx web service through the GetSearchMetadata method. However, you cannot call this web service from the app model. I decided to construct an initial search when the application loads and set the row limit to one. The search term I use is “Microsoft” which you can almost guarantee will find something. The returned search results always returns approximately 41 default managed property values. I used these to populate a list box and an array that the user can choose from to display. This was better than nothing.







In order to customize which managed properties to display in the DataTable you must pass in an array of objects in the aoColumnDefs argument, and an array of the available managed properties in the aoColumns argument. The code basically loops through the available managed properties and checks to see if the managed property chosen exists. If it exists then it is added to the column definitions setting the bVisible property to true, else it is set to false and the bSearchable property is set to false preventing the column from being used in filtering.



function doSearch() {
var searchRestSource = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?querytext='"
+ $get('searchText').value + "'"
+ "&rowlimit=500";

$.ajax(
{
url: searchRestSource,
method: "GET",
headers: {
"accept": "application/json; odata=verbose",
},
success: function (data) {
if (data.d.query.PrimaryQueryResult.RelevantResults.RowCount > 0)
{
convertResults(data);
var displayColumns = getDisplayManagedProperties();
var columnDefs = [];

for (var h = 0; h < resultColumns.length; h++) {
var columns = $.grep(displayColumns, function (e) {
return e == resultColumns[h].sTitle;
});

if (columns.length > 0)
columnDefs.push({ "bVisible": true, "aTargets": [h] });
else
columnDefs.push({ "bSearchable": false, "bVisible": false, "aTargets": [h] });

}

var oTable = $('#searchResults').dataTable({
"bDestroy": true,
"aoColumnDefs": columnDefs,
"bStateSave":false,
"bPaginate": true,
"bLengthChange": true,
"bFilter": false,
"bSort": true,
"bInfo": true,
"bAutoWidth": false,
"aaData": results,
"aoColumns": resultColumns
});

}
else
{
$('#searchResults').dataTable().fnClearTable();
}

},
error: function (err) {
alert(JSON.stringify(err));
},
}
);

}



More Mashing to Come




In this post I showed how easy it is to put together a useful SharePoint 2013 app model application that only uses client side code only. SharePoint Search REST is a URL based API that is easy to integrate into your JQuery code. The DataTable plug-in is free and offers a rich and capable tool for offering users actionable search results. In the next post I hope to make this application an “AppPart” web part. Moving the option to display managed properties, scrolling, display items and formatting of paging to the tool pane. This may well contain options to theme the AppPart using JQuery UI ThemeRoller.