Creating a custom web part for SharePoint Online pages

Creating a custom web part for SharePoint Online pages

Reinder Wit

Reinder Wit

You can create some pretty awesome webpages with SharePoint Online, right out-of-the-box, using the SharePoint web parts that are provided by default. Web parts like Highlighted Content, Hero, Image gallery, Quick chart, Call to Action, News and Quicklinks provide enough flexibility to create a layout that is both functional and visually appealing. But what if the default web parts just don’t quite fit your needs? What if you need to fetch additional info using an (SharePoint) API or what if you want to show data using a customized layout? In comes SPFx, the SharePoint framework that lets you create custom web parts, to be used in your own SharePoint pages. And, as it turns out, it’s relatively easy if you know your way around TypeScript/JavaScript, HTML and CSS…

In this post, I will show you how to create a SharePoint web part project, customize it to fetch and then show SharePoint documents and build/deploy it to your SharePoint site.

Contents of this blog post:

Requirements
To create a web part on your machine, the following software should be installed:
– Node.js
– gulp, globally installed
– a code editor (I use Visual Studio Code)
Yeoman (to install, run this command: npm install -g yo)
– a working SharePoint site, preferably with admin rights otherwise you won’t be able to deploy the web part in the end

1. Create local web part project

Within a folder on your machine, run this PowerShell command:

yo @microsoft/sharepoint

This will start Yeoman, and Yeoman will start to ask you some questions so it can scaffold your TypeScript project for you. This is what I used:

Solution name: CustomWebPart
Baseline packages: SharePoint online only (latest)
Files location: Use the current folder
Allow tenant admin to deploy to all sites immediately: Y
Permissions to access web APIs: N
Type of client-side component: WebPart
Web part name: RecentDocuments
Web part description: RecentDocuments description
Framework: No Javascript Framework

Verify that the project functions correctly by running this PowerShell command from the root of the project folder:

gulp serve

Your project will get built and ultimately a browser window is opened running a SharePoint tool called ‘Workbench’. It offers a blank page where you can add your new web part to a section on the page. If you click the web part, you can choose to edit its properties by clicking the little pencil at the top left:

SharePoint Workbench - localhost

2. Customize web part

Open up your favourite code editor (Visual Studio Code in my case, for projects like this) and load src/webparts/recentDocuments/RecentDocumentsWebPart. This is a TypeScript file, and it contains some import statements at the top, an interface that defines all the properties of our class and the class itself with a method to render the HTML for our web part and a method to configure the property pane on the right.

Import SharePoint HTTP client
At the top of our script (where all the import statements are), add the following import statement to include the SharePoint HTTP client library. We will be needing this if we want to call SharePoint APIs:

import { SPHttpClient } from '@microsoft/sp-http';

Add new properties
For this web part, I will be showing a list of SharePoint documents so I want to have a numeric value to hold the number of documents that I want to show and a column value that I want the documents to be sorted on. Within the interface definition, remove the ‘description’ property and add these 2 new properties:

export interface IRecentDocumentsWebPartProps {
  nrOfItems: number;
  orderBy: string;
}

It might be a good idea to define default values for your properties, depending on your code. This can be done in the manifest file (RecentDocumentsWebPart.manifest.json in my case):

default values for properties

The manifest file also allows you to change the icon for your web part (officeFabricIconFontName, see possible options) and you can configure the web part to be used in Teams as well by adding TeamsTab or TeamsPersonalApp to supportedHosts!

Modify PropertyPane
The property pane on the right of a SharePoint page (when editing a web part) consists of one or more pages, which each contains one or more groups. Each page has a header and each group has a groupName and a groupFields array with a configuration object for each of your properties. For our numeric nrOfItems value I will be using a slider and for our sort column value I will use a dropdown. Out of the box, the SharePoint framework supports the most common type of fields, like a textbox, slider, toggle, checkbox or dropdown and you can even create your own fields if needed.
At the bottom of our TypeScript page, modify the getPropertyPaneConfiguration method to include the newly created properties:

 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: 'Settings'
          },
          groups: [
            {
              groupName: 'Group name',
              groupFields: [
                PropertyPaneSlider('nrOfItems', {
                  label: 'How many items do you want to show?',
                  min: 5,
                  max: 15
                }),
                PropertyPaneDropdown('orderBy', {
                  label: 'Order by which column?',
                  options: [
                    { key: '1', text: 'Name' },
                    { key: '2', text: 'Author' },
                    { key: '3', text: 'Date/Time' }
                  ]                 
                })                
              ]
            }
          ]
        }
      ]
    };
  }

Modify Render method
Now for the most important part: rendering and customizing our web part.
The idea is to have a <table> element ready to be filled with HTML and then call a SharePoint API to fetch all the shared documents from our SharePoint site, order the documents by our orderBy property, take only nrOfItems items and place a <tr> for each document inside the table.
First, let’s simplify the render() method:

  public render(): void {
    this.domElement.innerHTML = `
      <div class="${ styles.recentDocuments }">
        <div class="${ styles.container }">
          <div class="${ styles.row }">
            <span class="${ styles.title }">Recent documents</span>
            <table class="${ styles.documenttable }" id="documents">
            </table>
          </div>
        </div>
      </div>`;

      this._renderList();
  }

Below is the code for the new _renderList() function. In here, we get the URL of our SharePoint site and then use the SharePoint HTTPClient to call the API. The API call fetches all the files in a certain folder, namely the default ‘Shared Documents’ folder. You can use OData queries in a SharePoint API request to further filter or modify the response. In our case, I need some more info on the author of the documents so I’ve added ‘?$expand=Author’ to the API URL. Then, when the JSON response is returned, I sort the results and loop through only the nrOfItems that I want to show and call a function that will create the HTML for that row:

private _renderList() {
  let site = this.context.pageContext.site.absoluteUrl;
  this.context.spHttpClient
    .get(site + "/_api/web/GetFolderByServerRelativeUrl('/Shared Documents')/Files?$expand=Author", SPHttpClient.configurations.v1)
    .then(
      (response) => {
        response.json()
          .then((responseJSON) => {
            document.getElementById('documents').innerHTML = '';
            let html = `<tbody><tr><th>Name</th><th>Author</th><th>Date</th><th>Time</th></tr>`;
            let sorted = this._sortDocuments(responseJSON.value);
            sorted.slice(0, this.properties.nrOfItems).forEach(item => {
              html += this._buildRow(item);
            })
            document.getElementById('documents').innerHTML = html + '</tbody>';
          })
          .catch((error) => {
            console.warn(error);
          });
      }
  )
}  

And here are the functions that sort the documents and build the HTML for each row. They’re pretty basic and can probably be refactored into something more intelligent, but that’s not what this post is about ;)

  private _sortDocuments(documents) {
    if(this.properties.orderBy == '2'){
      documents.sort(function(a, b) {
        return ('' + a.Author.Title).localeCompare(b.Author.Title);
      });      
    }
    else if(this.properties.orderBy == '3'){
      documents.sort(function(a, b) {
        return new Date(b.TimeCreated).getTime() - new Date(a.TimeCreated).getTime();
      });            
    }
    else{
      documents.sort(function(a, b) {
        return ('' + a.Name).localeCompare(b.Name);
      });             
    }
    return documents;
  }

  private _buildRow(item){
    let dt = new Date(item.TimeCreated);
    let ds = dt.getDate() + '-' + (dt.getMonth()+1) + '-' + dt.getFullYear().toString().substring(2, 4);
    let min = dt.getMinutes().toString();
    if(min.length == 1) min = '0' + min;
    let hours = dt.getHours().toString();
    if(hours.length == 1) hours = '0' + hours;
    let url = this.context.pageContext.site.absoluteUrl + item.ServerRelativeUrl;
    return `<tr><td><a href="${url}" target="_blank">${item.Name}</a></td><td>${item.Author.Title}</td><td>${ds}</td><td>${hours}:${min}</td></tr>`;
  }

Add custom CSS
You might have noticed that I added a new CSS class documenttable to our project (Visual Studio Code will put a squiggly line underneath to nofity you of missing CSS definitions). The definitions for this class can be placed in the corresponding SASS file (RecentDocumentsWebPart.module.scss). Here is the CSS I’m using:

.documenttable {
    margin:20px 0;
    width:100%;

    tbody {
      width:100%;
    }
    th {
      text-align:left
    }
    td:nth-child(1) {
      width:50%;
    }
    td:nth-child(2) {
      width:25%;
    }
    td:nth-child(3) {
      width:15%;
    }
    td:nth-child(4) {
      width:10%;
    }
    th,td {
      padding: 0 20px 10px 0;
      vertical-align:top;
    }
    a {
      color:#fff;
    }
}

3. Verify

If your local server is still up and running, you can see some changes have taken place but you won’t see any documents. This is because your local website doesn’t have a valid SharePoint session. Luckily, each SharePoint site has the workbench tool running as well, so you can see your web part in action before it has been deployed.
Navigate to https://[your site].sharepoint.com/_layouts/15/workbench.aspx; you will see a page similar to your local workbench, but when choosing a web part you now see that all SharePoint web parts are available and if you choose the new RecentDocuments web part, you should now see that it actually loads some documents (provided that you have uploaded some documents to your SharePoint site, of course). You can now also try out the slider and the dropdown in the propertypane:

workbench remote final animated

So all is looking good, let’s now build and deploy the web part so we can actually start using it in our pages.

4. Build & Deploy

Building and deploying a web part to your SharePoint environment is actually pretty easy. The project comes with a couple of Gulp commands to build & package the files.
First, run this command:

gulp bundle --ship

Then, create the final package using this command:

gulp package-solution --ship

When the command has completed, you should have a new folder called ‘sharepoint’ in the root of your project and in that folder there is a folder called ‘solution’, with a sppkg file in it. This is the file we need to deploy.

package location

In order for you to deploy the package you will need an app catalog in your SharePoint site.
If you don’t have this yet, navigate to https://[your site]-admin.sharepoint.com/_layouts/15/online/TenantAdminApps.aspx and click the ‘App Catalog’ link to create one. Follow the steps to create your own catalog:

create apps catalog create app catalog page 2

Once you are done, browse to your catalog page (in my case: https://reinderw.sharepoint.com/sites/apps/) and click ‘Apps for SharePoint’ in the left menu. You should see an overview of all installed apps, and an ‘Upload’ button at the top. Click this button and upload the sppkg file we created earlier (you can also just drag the file into the app overview section on the page). Once uploaded, a dialog will appear asking if you trust the custom web part and if you want to deploy it to all sites within the organization immediately (remember the Yeoman question?); tick the ‘Make this solution available to all sites in the organization’ box and click ‘Deploy’.

Once deployed, you’re all done!
You should now be able to select your new web part when editing a SharePoint online page:

Use the web part

References: