For those of us still supporting Sites with the standard view we've had for the last decade or so, or what Microsoft now calls "classic experience", Modern experiences have been rolling out to Office 365 Tenant's since late 2016 and now in SharePoint 2016 with Feature Pack 2. These modern team site pages are quick with an easy authoring experience and support rich multimedia content in a simple interface. Additionally, these new types of pages are automatically responsive on any device, in a browser, or from within the SharePoint mobile app. Now with the foundation of modern web part hosting showing up since the new Feature Pack, we can build web parts that are cross-page and cross-platform compatible.
Note: Microsoft is not deprecating the "classic" experience, with both "classic" and "modern" coexisting side-by-side until further notice.
Since we want to pull in Search Results to our Modern web part we need to map out our 'inputs' and 'outputs' to make our tool a reality. By inputs I mean how do our users normally search for content, what do they look for, and what features do we give them to find that content quickly. This won't be a replacement for the Enterprise Search Center, but instead a way to find things quickly on the current page or navigate to the Search Center for more capabilities.
To help with the outputs, in the web part Property Pane I implemented three controls to manage the web part so it can be dropped on any page in any Tenant and be customized as desired.
- Web Part Header - Allows for a custom header
- Enterprise Search URL - To send users to the real Search Center for more results, refining the query, or targeting a specific Result Source Page URL
- Number of Results - Dropdown to tune how many results to show on the page in case space is limited
On the display portion of the web part itself, I added a standard search box / button that we're all used to, but also added in a checkbox so users could add the wildcard character (*) to the end of their keywords in case they wanted more results than just matching the word. SharePoint out-of-box supports this capability like most enterprise search engines, you just had to know about it and add it yourself in the search box.
For the outputs portion, what we're talking about is what way will look best for showing our users the SharePoint items returned from their query. Since we're building them out on the page ourselves, we can style them anyway we want. In this case, I wanted the results to look exactly as standard Display Templates, so I imported SharePoint's 'searchv15.css' Style Sheet and modeled the output HTML to use the same classes and hierarchy.
What it Required:
Since there are plenty of articles on getting started with SPFx development, we won’t cover that here but the framework to build our web part is easy to setup. If you need a quick tutorial on getting your first SPFx web parts up and running, follow the guides in the References section below.
The other big part of this web part, the elephant in the room if you will, is the Search REST API. While I've built hundreds of solutions using all of SharePoint's different API's, when it comes to Search I always need to pull up the reference documents since the syntax is so different from standard List / Library REST calls for doing the same things. To help with visualizing this data I always turn to the SharePoint Search Query Tool (see reference below). This allows me to try multiple different queries and refine how I want to pull the data in before I even start coding.
After determining exactly how I wanted to make the Search queries, and what Search Managed Properties I wanted to display, I went through the Yeoman steps of building a new project and then launched it in Visual Studio Code. The following section details the code I implemented with TypeScript and SASS to make it all work.
For lines 1 – 39 above, most of this top section is standard boilerplate code that is generated from Yeoman, referencing SharePoint library's. To get started, I added in lines 8 - 12 to bring in imports for making secure SharePoint REST calls. Then on line 22 we added this import for setting up the special OData Version needed to support a bug in the Search API (more on this later). Line 23 was added so that I could later load custom SharePoint objects / URL’s into the code. And finally, lines 25 - 37 were built as interface classes for the Search Results that were returned to us.
Line 39 begins our processing web part code that standard SPFx web parts start with. Notice that on lines 40 - 43 I added an on-initialization section that tells the SPComponentLoader we imported above to call its load CSS method, passing in the relative reference to SharePoint's core searchv15.css file. This ensures that the styling always matches the default search experience users see. Lines 46 - 66 show the HTML first loaded by the web part as it's added to a page, in this case without Angular or React, with a results Div element on line 61 that I'll use later to write the results into. On Line 67, and the subsequent lines 70 - 75, is the _setButtonEventHandlers() function that adds a click event listener on the search button. Inside the listener is a reference to the asynchronous _renderListAsync() function that will make the search queries for us.
Starting with line 77 through 93, our _renderListAsync() function checks that a keyword was entered before the button click and if so, calls the _getSearchResults() function on line 82. This is our first function that doesn't return void, instead sending back a promise of type ISearchResults so we can process the data on the return. In this function we do the core lookup logic, running from lines 95 - 129. Lines 98 - 102 format our search keyword with or without the ending wildcard. From lines 115 - 121 the code sets up a SharePoint API call (spHttpClient) by building our target URL on line 116, implements the OData Version override on lines 117 - 120 needed to compensate for the Search API bug, and finally returns our response object as JSON back up to line 83 where it was called from. Back up in our _renderListAsync() function, lines 83 - 88 reformat the JSON into ISearchResult objects so we can then pass the results to our final _renderList(items: ISearchResult) function.
In our _renderList() function, running from line 131 - 199, we take the search results by looping through each ISearchResult on line 136 to process them. On line 132 we setup a reference to the Div element to push our search results into. Then for lines 137 - 153, we gather information from each looped result, calling our reusable _getValueByKey(itmKey: string,srchRow: any) & _buildNiceDate(strIsoDate: string) functions on lines 201 - 219. Starting on line 155 we begin putting together the raw HTML of a standard search result that we'll inject into our target Div element mentioned above, ending on line 196 for each item returned. Finally, on line 198 after looping through all our results, we take the collated HTML and inject it into the Div's HTML property for showing. This asynchronous and delegated approach allows us to query as often as we like without having to do a standard postback to the page like normal SharePoint Search does, and we reuse the same results area over and over.
This last section running from line 225 - 261 is our boilerplate Property Pane section where we define controls and values to help control the output of the SPFx web part. In this case on lines 239 - 242 I added another PropertyPaneTextField for the Enterprise Search URL, and on lines 243 - 253 I inserted a PropertyPaneDropdown for admins to decide how many results to show on a page.
So at the end of all this, we now have a fully-functioning Modern web part that can be added into any Classic or Modern page, in almost any modern version of SharePoint, to give our users the capability to do quick searches without leaving the page they are on. This web part could even be extended in the Property Pane with more controls to allow for selecting Resulting Sources to target or even default keywords to fire on page load. The possibilities are almost endless, thanks to Enterprise Search and the SharePoint Framework.