SharePoint 2013: How to show all documents of current user with preview using Search REST services

https://msdnshared.blob.core.windows.net/media/2016/08/7827.NinjaAwardTinyBronze.pngBronze Award Winner


Introduction

Why SharePoint 2013 is far better than its previous versions? And the answer is it’s powerful and yet simpler Search engine. Things which were difficult to configure in SharePoint 2010 are now very much simpler and straight forward.  Things like: managing search properties, result sources and query rules are much easier in SharePoint 2013. Even customizing and configuring the display templates are easier. 

One more thing we have in SharePoint 2013 is REST services. These services make the SharePoint 2013 Search very powerful and different. Anyone can now create powerful Search applications using REST services plus the easier configuration. In this article we are going to see how to display a logged in users document; not only document lists but their preview using the Office Web Apps. 

To develop this article I have used BootStarp and KnockOut JS frameworks. To use this application you need to refer them in code. Before diving into the application code lets visit the SharePoint 2013 REST services. 

REST Services overview

It is very simple to start; following is the REST URL to get the results from SharePoint services for search-keyword. 

[Site-Url]/_api/search/query?querytext='text'

Just try this URL in browser itself and you become a developer of SharePoint Search which you feel difficult earlier. In result the service return the ‘ReleventResults’, ‘RefineResults’ and ‘Refiners’. This is what you are looking for; now the rest of things like displaying this result in proper manner can be done through JavaScript and HTML. 

Let’s see how the result sections look like:

PrimaryQueryResult :

      PrimaryQueryResult is the main element of search REST response. It contains following important elements: RefinementResults, ReleventResults and other elements are QueryId, QueryRuleId.

**ReleventResults: **

      It contains search result in the form of array. To get the every single result iterate through object: data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results

**RefinementResults:
**
This element contains the list of refiners found for the search text. To get the refiners you need to construct the REST url like : /_api/search/query?querytext='yammer'&refiners='Write,DisplayAuthor,ContentType,FileExtension,FileType'.
 

**Refiners: **

This contains the list of refiners returned in the result with their refiner tokens. You can use them to refine the results to get the best match for search.

You can use refiners to get more relevant results. With refiners the REST URL looks like below:

[Site-Url]/_api/search/query?querytext='text'&refinementfilters='title:equals("text")'

REST services their self are a big subject to discuss, there are plenty of articles on them you can get over internet.

Problem Statement

One of my friend has a requirement to show the logged in users document (we say it ‘My documents’) from all sites within the farm or tenant.
He also wanted to show the preview of file in result itself so that end user can preview document on search screen itself before navigating to file. 

Solution

 
This is very simple requirement if you know which things in SharePoint can help you. Two things you can use to achieve this – SharePoint 2013 Search REST services and Office Web Apps (this must configured to work this application). I have SharePoint Online so don’t need to install Office Web Apps separately. 
How this application works:

  1. Get current user name.
  2. Generate a Search REST URL with query to get the documents for current logged in user.
  3. Provide the ‘sourceid’ of ‘Document’ result source as this application going to display only documents.
  4. Bind the results to the HTML using Knock Out JS.

HTML Code

<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.js"></script>
 
<!-- Add your CSS styles to the following file -->
<link rel="Stylesheet" type="text/css" href="/Site Assets/css/App.css" />
<link rel="Stylesheet" type="text/css" href="/Site Assets/css/bootstrap.min.css" />
 
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="/Site Assets/js/knockout.3.2.0.js"></script>
<script type="text/javascript" src="/Site Assets/js/App.js"></script>
 
<div style="padding-top:30px">
    <div class="row" style="padding-bottom:20px">
        <div class="col-sm-12 col-md-12">
        <ul class="nav nav-pills">
          <li id="allLI" style="width:150px;" role="presentation" class="active"><a href="#" data-bind="click: DefaultSearch">All <span id="allCnt">(<span data-bind="text:MatchCount"></span>)</span></a></li>
          <li id="wordLI" style="width:150px;" role="presentation"><a href="#" data-bind="click: WordSearch">MS Word <span id="wordCnt" style="display:none">(<span data-bind="text:MatchCount"></span>)</span></a></li>
          <li id="pptLI" style="width:150px;" role="presentation"><a href="#" data-bind="click: PptSearch">MS Presentation <span id="presCnt" style="display:none">(<span data-bind="text:MatchCount"></span>)</span></a></li>
          <li id="excelLI" style="width:150px;" role="presentation"><a href="#" data-bind="click: ExcelSearch">MS Excel <span id="excelCnt" style="display:none">(<span data-bind="text:MatchCount"></span>)</span></a></li>
          <li id="pdfLI" style="width:150px;" role="presentation"><a href="#" data-bind="click: PdfSearch">Pdf <span id="pdfCnt" style="display:none">(<span data-bind="text:MatchCount"></span>)</span></a></li>
        </ul>
        </div>
    </div>
    <div class="row" data-bind="foreach: SearchResults">
         
      <div class="col-sm-6 col-md-3 mydoc">
        <div class="thumbnail" style="margin-top:10px;">
         <!-- <img src="..." alt="...">-->
         <iframe height="300px" width="100%" data-bind="attr: {src: FileEmbedUrl}" ></iframe>
          
          <div class="caption">
            <h5 style="font-weight: bold"><a data-bind="attr:{href: FileUrl}"><span data-bind="text: FileName"></span></a></h5>
            <p style="margin-bottom:0px !important;font-size:10px;">
                <a data-bind="attr:{href: DocumentLibUrl}">Click for Document library</a>
            </p>
            <p style="margin-bottom:5px !important;font-size:10px;">
                <a data-bind="attr:{href: SiteUrl}">Click for Site</a>
            </p>
            <p style="margin-bottom:5px !important;font-size:10px;">
                Last Modified date: <span data-bind="text: ModifiedDate"></span>
            </p>
             
          </div>
        </div>
      </div>
       
    </div>
     
    <div class="row" style="padding-top:20px">
        <div class="col-sm-12 col-md-12" style="text-align:center;">
            <button type="button" data-bind="click: LoadMore" class="btn btn-default" align="center" style="width:80%" style="text-align:center;">Load More..</button>
        </div>
    </div>
</div>
<script type="text/javascript" src="/Site Assets/js/bootstrap.min.js"></script>

JS Code

001.var context = SP.ClientContext.get_current();
002.var user = context.get_web().get_currentUser();
003.var userName = "";
004.var hostweburl;
005.var appweburl;
006.var defRESTURL = "/_api/search/query?&sourceid='e7ec8cee-ded8-43c9-beb5-436b54b31e84'";
007.var completeRESTUrl = defRESTURL;
008. 
009.    // This code runs when the DOM is ready and creates a context object which is 
010.    // needed to use the SharePoint object model
011.    $(document).ready(function () {
012.        getUserName();
013.    });
014. 
015.    // This function prepares, loads, and then executes a SharePoint query to get 
016.    // the current users information
017.    function getUserName() {
018.        context.load(user);
019.        context.executeQueryAsync(onGetUserNameSuccess, onGetUserNameFail);
020.    }
021. 
022.    // This function is executed if the above call is successful
023.    // It replaces the contents of the 'message' element with the user name
024.    function onGetUserNameSuccess() {
025.        userName = user.get_title();
026.    completeRESTUrl = completeRESTUrl + "&refinementfilters='displayauthor:equals(\"" + userName + "\")'";
027.        KOViewModel();
028.    }
029. 
030.    // This function is executed if the above call fails
031.    function onGetUserNameFail(sender, args) {
032.        alert('Failed to get user name. Error:' + args.get_message());
033.    }
034.     
035.    function KOViewModel()
036.    {
037.         var self = this;
038.         self.SearchResults = ko.observableArray([]);
039.         self.MatchCount = ko.observable(0);
040.         self.pageIndex = ko.observable(0);
041.          
042.         self.SearchResult = function(fileEmbedUrl, fileName, fileUrl, siteUrl, documentLibUrl, mDate)
043.         {
044.             var self = this;            
045.             self.FileEmbedUrl = fileEmbedUrl;
046.             self.FileName = fileName;
047.             self.FileUrl = fileUrl;             
048.             self.SiteUrl = siteUrl;             
049.             self.DocumentLibUrl = documentLibUrl;
050.             self.ModifiedDate = mDate;
051.         }
052.          
053.         self.LoadMore = function()
054.         {
055.             self.Search(completeRESTUrl, false);
056.         }
057.          
058.         self.DefaultSearch = function()
059.         {
060.         
061.            $("#allLI").addClass("active");
062.            $("#wordLI").removeClass("active");
063.            $("#presLI").removeClass("active");
064.            $("#excelLI").removeClass("active");
065.            $("#pdfLI").removeClass("active");
066.             
067.            completeRESTUrl = defRESTURL + "&refinementfilters='displayauthor:equals(\"" + userName + "\")'";
068.            self.Search(completeRESTUrl, true);
069.            $("#allCnt").show();
070.            $("#wordCnt").hide();
071.            $("#presCnt").hide();
072.            $("#excelCnt").hide();
073.            $("#pdfCnt").hide(); 
074.         }
075.         self.WordSearch = function()
076.         {      
077.            $("#allLI").removeClass("active");
078.            $("#wordLI").addClass("active");
079.            $("#presLI").removeClass("active");
080.            $("#excelLI").removeClass("active");
081.            $("#pdfLI").removeClass("active");
082.             
083.             completeRESTUrl = defRESTURL + "&refinementfilters='and(displayauthor:equals(\"" + userName + "\"), OR(fileextension:equals(\"docx\"), fileextension:equals(\"doc\")))'";
084.             self.Search(completeRESTUrl, true);
085.            $("#allCnt").hide();
086.            $("#wordCnt").show();
087.            $("#presCnt").hide();
088.            $("#excelCnt").hide();
089.            $("#pdfCnt").hide(); 
090.         }
091.         self.PptSearch = function()
092.         {
093.            $("#allLI").removeClass("active");
094.            $("#wordLI").removeClass("active");
095.            $("#presLI").addClass("active");
096.            $("#excelLI").removeClass("active");
097.            $("#pdfLI").removeClass("active");
098.             
099.             completeRESTUrl = defRESTURL + "&refinementfilters='and(displayauthor:equals(\"" + userName + "\"), OR(fileextension:equals(\"pptx\"), fileextension:equals(\"ppt\")))'";
100.             self.Search(completeRESTUrl, true);
101.            $("#allCnt").hide();
102.            $("#wordCnt").hide();
103.            $("#presCnt").show();
104.            $("#excelCnt").hide();
105.            $("#pdfCnt").hide();
106.         }
107.         self.ExcelSearch = function()
108.         {
109.            $("#allLI").removeClass("active");
110.            $("#wordLI").removeClass("active");
111.            $("#presLI").removeClass("active");
112.            $("#excelLI").addClass("active");
113.            $("#pdfLI").removeClass("active");
114.             
115.             completeRESTUrl = defRESTURL + "&refinementfilters='and(displayauthor:equals(\"" + userName + "\"), OR(fileextension:equals(\"xlsx\"), fileextension:equals(\"xls\")))'";
116.             self.Search(completeRESTUrl, true);
117.            $("#allCnt").hide();
118.            $("#wordCnt").hide();
119.            $("#presCnt").hide();
120.            $("#excelCnt").show();
121.            $("#pdfCnt").hide();
122.         }
123.         self.PdfSearch = function()
124.         {
125.            $("#allLI").removeClass("active");
126.            $("#wordLI").removeClass("active");
127.            $("#presLI").removeClass("active");
128.            $("#excelLI").removeClass("active");
129.            $("#pdfLI").addClass("active");
130.             
131.             completeRESTUrl = defRESTURL + "&refinementfilters='and(displayauthor:equals(\"" + userName + "\"), fileextension:equals(\"pdf\"))'";
132.             self.Search(completeRESTUrl, true);
133.            $("#allCnt").hide();
134.            $("#wordCnt").hide();
135.            $("#presCnt").hide();
136.            $("#excelCnt").hide();
137.            $("#pdfCnt").show();
138.         }
139.          
140.         self.Search = function(restUrl, needClear)
141.         {  
142.         if(needClear)
143.         {
144.             self.SearchResults.removeAll();
145.             self.pageIndex(0);
146.         }
147.          
148.         $.ajax({
149.             url: restUrl + "&startrow=" + self.pageIndex(),
150.             type: "GET",
151.             headers: {
152.                 "accept": "application/json;odata=verbose",
153.             },
154.             success: function (data) {
155.                 self.MatchCount(data.d.query.PrimaryQueryResult.RelevantResults.TotalRows);
156.                 $.each(data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results, function () {
157.                      
158.                  var arrTitle = jQuery.grep(this.Cells.results, function( a ) {
159.                                                  return a.Key == "Title";
160.                                                });
161.                  var title =  arrTitle[0].Value;
162.                   
163.                   
164.                    if(title.length >= 30)
165.                    {
166.                        title = title.substring(0, 30);
167.                        title = title + "...";
168.                    }
169.                     
170.                  var arrServerRedirectedEmbedURL = jQuery.grep(this.Cells.results, function( a ) {
171.                                                      return a.Key == "ServerRedirectedEmbedURL";
172.                                                    }); 
173.                  var serverRedirectedEmbedURL =  arrServerRedirectedEmbedURL[0].Value;
174.                   
175.                  var arrPath = jQuery.grep(this.Cells.results, function( a ) {
176.                                                  return a.Key == "Path";
177.                                                });
178.                  var Path =  arrPath[0].Value;
179.                   
180.                  var arrSiteName = jQuery.grep(this.Cells.results, function( a ) {
181.                                                  return a.Key == "SiteName";
182.                                                });
183.                  var siteName =  arrSiteName[0].Value;
184.                   
185.                  var arrDate = jQuery.grep(this.Cells.results, function( a ) {
186.                                                  return a.Key == "LastModifiedTime";
187.                                                });
188.                  var date =  self.GetDate(arrDate[0].Value);
189.                   
190.                  var arrParentLink = jQuery.grep(this.Cells.results, function( a ) {
191.                                                  return a.Key == "ParentLink";
192.                                                });
193.                  var parentLink =  arrParentLink[0].Value;           
194.                   
195.                   
196.                  self.SearchResults.push(new self.SearchResult(serverRedirectedEmbedURL, title, Path, siteName, parentLink, date));                        
197.                   
198.                 });
199.                 self.pageIndex(self.pageIndex() + 10);
200.             },
201.             error: function (error) {       
202.                 alert(JSON.stringify(error));
203.             }
204.         });
205.         
206.        }
207.         
208.        self.GetDate = function(dateStr) {
209.            var monthNames = new Array("JAN", "FEB", "MAR",
210.                "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
211.                "OCT", "NOV", "DEC");
212.            var dtStr = dateStr; //.replace(".","-").replace(".","-");
213.            var d = new Date(dtStr);
214.            var date = d.getDate();
215.            var month = d.getMonth();
216.            var year = d.getFullYear();
217.         
218.            var hrs = d.getHours();
219.            var mins = d.getMinutes();
220.            var amOrPm;
221.            amOrPm = 'AM';
222.            if (hrs >= 12) {
223.                amOrPm = 'PM';
224.                if (hrs != 12) {
225.                    hrs = hrs - 12;
226.                }
227.            }
228.         
229.            // return date + "-" + monthNames[month] + "-" +year;
230.            month = month + 1;
231.            return date + "-" + monthNames[month] + "-" + year; // + " " + hrs + ":" + mins + " " + amOrPm;
232.        }
233.         
234.        self.Search(completeRESTUrl, false);
235.        ko.applyBindings(self);
236.    }

JS code explanation

Line No. 6: the default REST URL is defined in which ‘Document’ result source’s id is passed to get the only documents in result.

Line No. 17 to 28: method ‘getUserName’ gets called on page load. This page loads the information of current user which is defined at Line 2. Then on success it calls method -  onGetUserNameSuccess.
Method ‘onGetUserNameSuccess’ generates a query to get only results or documents of which the current user is a logged in user. Then this method calls another method “KOViewModel” which is nothing but view-model of Knockout. 

Line No. 35 to 236:  In this section KnockOut required objects and methods are defined and declared. 
When “KOViewModel” is called, then KO objects and methods are defined and declared and search method “Search” gets called from Line No. 234. 

Inside the Search method:

Line No. 142 to 146: this code clears the search result collection ‘self.SearchResults’ and sets pageindex to 0. This section gets executed whenever user clicks on buttons to see specific type (word, excel, pdf) of files/documents. 

Line No. 149: at this point we are adding the page index to REST URL so that we can get the search results of specific page. On page load it has value 0, but whenever user clicks on ‘Load more’ buttons it gets increased and returns result of that specific page. This is also useful if someone wants to use pagination.

Line No. 155: we are setting the total results to count to KO object.

Line No. 156: at this point application iterates the search result collection “data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results”. Remember the search result comes in this way. Here in the each loop we are getting search properties value and pushing them to the KO collection object. So we are capturing following properties – Title, ServerRedirectedEmbedURL, Path, SiteName, LastModifiedTime and Parentlink. 
Title : this represents the title  of document.
ServerRedirectedEmbedURL: this is nothing but the URL which helps to generate the document preview. This is nothing but: _layouts/15/WopiFrame.aspx?sourcedoc={2a3b97b8-3b43-4dda-ab02-a50a175771aa}&action=interactivepreview. And this generates the preview of document on screen. This page is important to create the preview of document; _layouts/15/WopiFrame.aspx. You just pass the ‘socurcedoc’ to it and rest of things it does. 
SiteName: this represents the site name in which the document is uploaded.
ParentLink: this represents the document library path. 

Line No. 42 to 51: this is a method which creates and returns KO object which gets pushed to KO search collection.

Line No. 53 to 56: the ‘LoadMore’ method gets called on click of Load More button. It just calls the Search method again to get more results.

Line No. 58 to 74: the ‘self.DefaultSearch’ method gets called on click of tab - All. It just sets the Search REST URL to original state and calls the search method. 

Line No. 75 to 90: the ‘self.WordSearch’ method gets called on click of tab – MS Word. This method modifies the REST URL to get only word documents in result. It does this by doing following: "&refinementfilters='and(displayauthor:equals(\" + userName + "\), OR(fileextension:equals(\docx\), fileextension:equals(\doc\)))'";
The same things applies to other document types tabs. To filter the result on document type we can use “fileextension” search properties.

Remember to bind the result to the HTML KnockOut JS framework is used. So to use this application you need to use both html and js files as well as refer KnockOut JS library and bootstrap library.

In this way we can show the preview of documents in SharePoint 2013. I have developed and tested this code on SharePoint Online.
It should work as is on On-Premise also. 

Snapshots

Enjoy the SharePoint Search :) .