Share via


SharePoint search using PnP in SharePoint Framework (SPFx) Client Web Part

How to perform PnP search using SharePoint Framework(SPFx)

It’s a simple example of how we can leverage SharePoint Pattern & Practice (SharePoint PnP) for Search in SharePoint with multiple options using SharePoint Framework.

As described by Microsoft "The Patterns and Practices JavaScript Core Library was created to help developers by simplifying common operations within SharePoint and the SharePoint Framework. Currently it contains a fluent API for working with the full SharePoint REST API as well as utility and helper functions. This takes the guess work out of creating REST requests, letting developers focus on the what and less on the how."

REST request are quite a hit and trial once we need to perform different operations in SharePoint, it’s always better if we convert these REST calls to Class structure. SharePoint PnP core library just do the same and allow us to concentrate on functionality rather than constructing REST calls.

SharePoint Framework is very well explained by Microsoft as “SharePoint Framework, a Page and Part model enables fully supported client-side development for building SharePoint experiences, easy integration with the SharePoint data and support for open source tooling development. We designed the SharePoint Framework to empower SharePoint developers both inside and outside Microsoft. Our engineers are building modern SharePoint experiences using the SharePoint Framework. You can use the same technology, tools and techniques we use to build more productive experiences and apps that are responsive and mobile-ready from day one.
more info..

Setup for Client Web Part

To start working with SharePoint Framework to build client-side web parts, follow the steps at the link below to set up the environment.

  1. Set up your development environment 

We will need following technologies:

  • Node.JS – Open Source cross-platform JavaScript-based runtime environment
  • NPM - Package Manager for JavaScript. This is equivalent to NuGet.
  • **Yeoman **– A scaffolding tool to generate project structure
  • **Gulp **– Build Process task runner
  • **TypeScript **– Superset of JavaScript that compiles into JavaScript. Will be used to build our application.
  • Visual Studio Code – Lightweight source code editor.
  • Office UI Fabric and React Components for Office UI Fabric

We have not used any JavaScript framework like Reach, Knockout etc to keep the example simple.

Steps for Search Client Web Part

Let's go through step by step creation on client-side web part using SharePoint framework using SharePoint PnP and display the search results with Office UI List.

First, we will create skeleton client-side web part as described in this link (Build First Client WebPart). We have given the folder name as ‘SPFx_CLSearch’ and web part name as ‘CLSearch’. 

Follow the steps described in above link to the command

Code .

This will open ‘Visual Studio Code’ IDE.

 

We will need to install SharePoint PnP and Office UI Fabric react to our project, go to View -> Output and click on Terminal. Now run following commands in sequence:

npm install sp-pnp-js --save
npm install office-ui-fabric-react --save

It will install and save required files in the project. We can see all dependencies in package.json

"dependencies": {
   "@microsoft/sp-client-base": "~1.0.0",
   "@microsoft/sp-core-library": "~1.0.0",
   "@microsoft/sp-webpart-base": "~1.0.0",
   "@types/webpack-env": ">=1.12.1 <1.14.0",
   "office-ui-fabric": "^2.6.3",
   "sp-pnp-js": "^2.0.2"
 },

Now we will start adding code for our client-side web part. First we will define ISearchResult interface to hold search results and define Interface for Search Service operations. Have added couple of Search Result properties but we can add many more.

'use strict';
import * as pnp from 'sp-pnp-js';
 
export interface ISearchResult {
    link : string;
    title : string;
    description : string;
    author:string;
}
 
export interface ISearchService{
  GetSearchResults(query:string) : Promise<ISearchResult[]>;
}

We have imported sp-pnp-js namespace in 'pnp' variable.

ISearchService returns a promise of ISearchResults array and take an input of search query string. Now we will define two classes MockSearchService and SearchService which will implement ISearchService interface. MockSearchService is for Local Workbench and SearchService for SharePoint environment.

export class MockSearchService implements ISearchService
{
    public GetSearchResults(query:string) : Promise<ISearchResult[]>{
        return new  Promise<ISearchResult[]>((resolve,reject) => {
          
      resolve([
                    {title:'Title 1',description:'Title 1 desc',link:'http://asdada',author:'Pal'},
                    {title:'Title 2',description:'Title 2 desc',link:'http://asdada',author:'Pal'},
                    ]);
        });
    }
}
  
export class SearchService implements ISearchService
{
    public GetSearchResults(query:string) : Promise<ISearchResult[]>{
        const _results:ISearchResult[] = [];
  
        return new  Promise<ISearchResult[]>((resolve,reject) => {
                pnp.sp.search({
                     Querytext:query,
                     RowLimit:5,
                     StartRow:0
                    })
                .then((results) => { 
                   results.PrimarySearchResults.forEach((result)=>{
                    _results.push({
                        title:result.Title,
                        description:result.HitHighlightedSummary,
                        link:result.Path,
                        author:result.Author
                    });
                   });
                })
                .then(
                   () => { resolve(_results);}
                )
                .catch(
                    () => {reject(new Error("Error")); }
                );
                  
        });
    }
}

Above Search service is using PNP namespace for search, pnp.sp.search take either a 'string' (search query) OR 'SearchQuery' object defined in PnP. Have used SearchQuery object which contains multiple options to fine-tune search results.

Our Search Service is now ready, lets modify CLSearchWebPart.ts file to use this Search Service and display results . We will need to import our SearchService class and @Microsoft/sp-loader. Also include 'Environment', 'EnvironementType' from 'sp-core-library' which will be used to differentiate local and SharePoint workbench.

import { Version,
Environment,
EnvironmentType } from '@microsoft/sp-core-library';
import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './ClSearch.module.scss';
import * as strings from 'clSearchStrings';
import { IClSearchWebPartProps } from './IClSearchWebPartProps';
import * as SearchService from './Services/SearchService';
 import {SPComponentLoader} from "@microsoft/sp-loader";

Add constructor to load Office UI Fabric CSS

export default  class ClSearchWebPart extends BaseClientSideWebPart<IClSearchWebPartProps> {
  
public constructor(){
  super();
  SPComponentLoader.loadCss("https://static2.sharepointonline.com/files/fabric/office-ui-fabric-js/1.2.0/css/fabric.min.css");
 SPComponentLoader.loadCss("https://static2.sharepointonline.com/files/fabric/office-ui-fabric-js/1.2.0/css/fabric.components.min.css");
}

Update the render method

public render(): void {
  
  this.domElement.innerHTML = `
    <div class="${styles.helloWorld}">
      <div class="${styles.container}">
        <div class="ms-Grid-row ms-bgColor-themeLight ms-fontColor-white ${styles.row}">
          <div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
            <div>
              <span class="ms-font-xl ms-fontColor-white">${escape(this.properties.description)}</span><br/>
             <div class="ms-Grid"> 
                  <div class="ms-Grid-row">
                    <div class="ms-Grid-col ms-u-sm10"><input class="ms-TextField-field"  id="textInput"  placeholder="Search..."  /></div>
                    <div class="ms-Grid-col ms-u-sm2"> <Button class="ms-Button ms-Button--primary" id="btnSearchSubmit" type="submit"  value="Submit">Search</Button></div>
                  </div>
                </div>
               <div class="ms-List ms-Grid-col ms-u-sm12" id="searchResults"></div>
              </div>
          </div>
        </div>         
      </div>
    </div>`;
   this.AttachEvents();
  
}

Render method has one Text field to enter search query, button for Search and searchResults div to display results. AttachEvents function is to attach click event to the Search Button.

private AttachEvents():void{
  const btnSearch = this.domElement.querySelector("#btnSearchSubmit");
  
  const queryText:HTMLElement = <HTMLInputElement>this.domElement.querySelector("#textInput");
  btnSearch.addEventListener('click',() => {
       (new ClSearchWebPart()).handleOnChange(queryText);
  });
 }
  
public handleOnChange(text:HTMLElement):void{
 (new ClSearchWebPart()).renderResults((<HTMLInputElement>text).value)
    .then((html) =>{
      const element  = document.getElementById("searchResults");
      element.innerHTML = html;
    });
}

'handleOnChange' function will take search query text as input and call 'renderResults' function which returns a promise of HTML string. This HTML string is been pushed and displayed in searchResults div.
In 'renderResults' function first statement is to decide the working environment, i.e. local workbench or SharePoint workbench.

private renderResults(query:string):Promise<string>{
  const _search:SearchService.ISearchService = Environment.type == EnvironmentType.SharePoint ?
                                                  new SearchService.SearchService() : new SearchService.MockSearchService();
  let resultsHtml:string = '';
  
  return new  Promise<string>((resolve) => {
    if(query){
       _search.GetSearchResults(query)
       .then((results) => {
           results.forEach((result) => {
               resultsHtml += `<div class=""ms-ListItem ms-Grid-col ms-u-sm8">
                                <a href="${result.link}"><span class="ms-ListItem-primaryText"  >${result.title}</span></a>
                                 <span class="ms-ListItem-secondaryText">${result.author}<span>
                                <span class="ms-ListItem-tertiaryText">${result.description}</span>
                                <span class="ms-ListItem-metaText">10:15a</span>
                                 <div class="ms-ListItem-actions">
                                     <div class="ms-ListItem-action" targerUrl="${result.link}"><i class="ms-Icon ms-Icon--OpenInNewWindow">
                                     </i></div>
                                   </div>
                              </div>`;
                 });
             })
      .then(
        () => {
         setTimeout(() => {
           const action:HTMLCollectionOf<Element> = document.getElementsByClassName("ms-ListItem-action");
           for(let i=0;i<action.length;i++){
             action[i].addEventListener('click',(e)=>{
               window.open((e.currentTarget as Element).getAttribute("targerUrl"));
             });
           }
         },300);
          resolve(resultsHtml);
         }
      );
    }
    else{
      resultsHtml += "Please provide Search query..";
      resolve(resultsHtml);
    }
  });
}

Depending upon the environment 'MockSearchService' or 'SearchService' class will be called to get the results. These results will be iterated through and displayed using Office UI List CSS classes. We have also added one action to each list item which will open the item in a new window.
        
                                              

                                                 

Conclusion

This article is to demonstrate an example of using SharePoint Pattern and Practice classes in SharePoint Framework. Developer can use and extend this search web part as per need. Similarly, SharePoint PnP provides multiple other classes such as web, site, user profile etc which are very easy way develop SharePoint Client Web part without getting worried about REST API syntax. 

Return to Top

References