SharePoint Framework (SPFx) (Part 2) Create a Simple Client-side Web Part

Blog written by:
Dhaval Shah
SharePoint & .Net Consultant

Intro

In our previous article we showed how to create a simple client-side web part using SharePoint Framework (SPFx). In this article, we add some more features to set up announcements from the SharePoint lists and display them in the web part. When completed successfully, the web part will look something like this.

Announcement Webpart SharePoint Framework SPFX.

You can download the entire source code for this article from here

Announcement List

For loading the announcement list in the web part, use the data from the SharePoint OOB announcement list. Make sure the site where you’re deploying the web part has an announcement list, and it has some data added to it.

The announcement list looks like this:Announcement List

Development of Web Part

Add the announcement class, which you’ll use to store the data available from the SharePoint List. Create an interface for the announcement (IAnnouncement) and an array of announcements (IAnnouncements), as shown below. Create a separate typescript file “Announcement.ts” as also shown below.

You can view the entire code on github repository here.

 export interface IAnnouncements{  
   value: IAnnouncement[];  
 }  
 export interface IAnnouncement{  
   Id:string;  
   Title:string;  
   Body:string;  
 }  

Service Layer

You also create your service layer, which fetches the data from the announcement list if you currently have the web part launched in the SharePoint; otherwise, it launches the data from the mock service.

Create an interface for the service ISharedServiceProvider.ts.

 import { IAnnouncements } from "../announcement/Announcements";  
 export interface ISharedServiceProvider {  
   getAnnouncements(): Promise;  
   }  

Now, create two classes MockServiceProvider and SharedServiceProvider.

MockServiceProvider—This fetches the mock data for the local workbench.

SharedServiceProvider—This fetches the data from the SharePoint List for the SharePoint workbench.

MockServiceProvider.ts

 import { ISharedServiceProvider } from './ISharedServiceProvider';  
 import { IAnnouncement, IAnnouncements } from "../announcement/Announcements";  
 export class MockServiceProvider implements ISharedServiceProvider {  
   constructor() {  
   }  
   private static mockAnnouncements: IAnnouncement[] = [  
     { Title: 'Announcment #1', Body: 'Some Announcment description 1', Id: '1' },  
     { Title: 'Announcment #2', Body: 'Some Announcment description 2', Id: '2' },  
     { Title: 'Announcment #3', Body: 'Some Announcment description 3', Id: '3' }  
   ];  
   public getAnnouncements(): Promise {  
     var announcements: IAnnouncements = { value: MockServiceProvider.mockAnnouncements };  
     return new Promise((resolve) => {  
       resolve(announcements);  
     });  
   }  
 }  

SharedServiceProvider.ts

 import { ISharedServiceProvider } from './ISharedServiceProvider';  
 import { IAnnouncements } from "../announcement/Announcements";  
 import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';  
 import { ServiceKey, ServiceScope } from '@microsoft/sp-core-library';  
 import { PageContext } from '@microsoft/sp-page-context';  
 export class SharedServiceProvider implements ISharedServiceProvider {  
   public static readonly serviceKey: ServiceKey = ServiceKey.create('dps:ISharedServiceProvider', SharedServiceProvider);  
   private _spHttpClient: SPHttpClient;  
   private _pageContext: PageContext;  
   private _currentWebUrl: string;  
   constructor(serviceScope: ServiceScope) {  
     serviceScope.whenFinished(() => {  
       this._spHttpClient = serviceScope.consume(SPHttpClient.serviceKey);  
       this._pageContext = serviceScope.consume(PageContext.serviceKey);  
       this._currentWebUrl = this._pageContext.web.absoluteUrl;  
     });  
   }  
   public getAnnouncements(): Promise {      
     return new Promise((resolve) => {  
       resolve(this._spHttpClient.get(`${this._currentWebUrl}/_api/lists/GetByTitle('Announcements')/items`, SPHttpClient.configurations.v1)  
         .then((response: SPHttpClientResponse) => {  
           return response.json();  
         }));  
     });  
   }   
 }  

Now, go back and design the AnnouncementWebpart.ts,

Initialize the web part with the isDebug property that keeps track of the current environment of the web part. If the current environment type is test or local, fetch the data from the mock service, and if the current environment is SharePoint, fetch the data from the SharePoint.

Initialize the constructor of the web part as follows:

 private isDebug: boolean;  
  public constructor(context: IWebPartContext) {  
   super();  
   this.isDebug =  
    DEBUG && (Environment.type === EnvironmentType.Test || Environment.type === EnvironmentType.Local);  
  }  

In the render() method of the web part, create the web part outer structure of the webpart, and based on the isDebug variable, initialize the service.

 public render(): void {  
   this.domElement.innerHTML = `  
    <div class="${styles.announcements}">  
     <header class="${styles.titleblock}">  
        <h2>${escape(this.properties.description)}</h2>         
       </header>  
      <div id="announcementListContainer">  
       </div>  
     </div>  
    </div>`;  
 // Based on the debug variable initialize the service   
   this._sharedService = this.isDebug  
    ? new MockServiceProvider()  
    : this.context.serviceScope.consume(SharedServiceProvider.serviceKey);  
   this.renderWebpartData();  
  }  

Create a renderWebpartData() method that makes a call to the service initialized earlier and get the data.

 private renderWebpartData() {  
   this._sharedService.getAnnouncements().then((response: IAnnouncements) => {  
    this.renderHtmlFromData(response.value);  
   }).catch((err) => {  
    console.log('Error getting announcements : ' + err);  
   });  
  }  

renderHtmlFromData() method will iterate through the data obtained and display it as HTML.

 private renderHtmlFromData(announcements: IAnnouncement[]): void {  
   let html: string = '';  
   //const announcementLogo: any = require('../images/announcement.png');  
   let announcementLogo: string = String(require('./images/Announcement.png'));  
   announcements.forEach((item: IAnnouncement) => {  
    html += `     
     <ul class="${styles.announcementsList}">  
       <li>    
        <div class="${styles.announcementIcon}">  
        <img src="${announcementLogo}" />  
        </div>      
         <div class="${styles.txt}">  
          <h2>${item.Title}</h2>  
          <p>${item.Body}</p>  
         </div>        
       </li>  
     </ul>`;  
   });  
   const listContainer: Element = this.domElement.querySelector('#announcementListContainer');  
   listContainer.innerHTML = html;  
  }  

Styling the Web Part

Now, add some css classes in the Announcement.module.scss to use in the web part.

 .announcements {  
   .titleblock {  
     padding: 15px 17px 18px 1px;  
   }  
   .titleblock:before {  
     width: 338px;  
     height: 10px;  
   }  
   .titleblock h2 {  
     font-size: 22px;  
     margin-top: 12px;  
     color: #435563;  
   }  
   .titleblock .btn {  
     margin: 4px 0 0;  
   }  
   .titleblock h2 {  
     font-size: 22px;  
     margin-top: 12px;  
   }  
   .titleblock h2 {  
     float: left;  
     font-size: 20px;  
     font-weight: 500;  
     margin-bottom: 0;  
     line-height: 1.2;  
     margin: 7px 0 0;  
     text-transform: uppercase;  
     width: calc(100% - 100px);  
   }  
   .titleblock:before {  
     top: 0;  
     left: 0;  
     content: "";  
     width: 300px;  
     height: 10px;  
     position: absolute;  
     background-color: #435563;  
   }  
   .announcementsList {  
     display: -webkit-box;  
     display: -ms-flexbox;  
     display: flex;  
     -ms-flex-wrap: wrap;  
     flex-wrap: wrap;  
     background: #fff;  
     margin-bottom: 33px;  
     padding: 26px 15px 1px;  
     border-bottom: 1px solid #a2a2a2;  
     list-style: none;  
   }  
   .announcementsList li {  
     display: flex;  
     margin-bottom: 20px;  
     padding-bottom: 10px;  
   }  
   .announcementIcon {  
     display: block;  
     width: 54px;  
     height: 68px;  
     padding: 30px 15px;  
     border-radius: 4px;  
     text-align: center;  
     color: #7a8995;  
     line-height: 1.2;  
   }  
   .txt {  
     padding: 7px 30px 0 23px;  
   }  
 }  

Once you complete all the steps, you can build your web part using the command below.

Gulp Build

gulp build

Launch Web Part in Local Workbench

You can test the web part in the local workbench with the mock data using the gulp serve command. It launches the browser, and you can add the announcement web part on the page.

Add webpart to Local Workbench

After selecting the web part, it will add the web part, and you can see the mock data on the web part.

Announcement MockData SPFX Webpart

Launch a Web Part in SharePoint Workbench

Note: Make sure the gulp serve command is running in the background.

Navigate to the SharePoint Website created in the first article of the series.

https://yoursharepointsite/sites/SPFX/_layouts/15/workbench.aspx

As you did with a local workbench, you have to add the web part on the workbench.

Announcement SharePoint SPFX Webpart

In the next article, you’ll learn how to package the web part and deploy it to the SharePoint. Stay tuned!

Do you use SharePoint? Try our toolkit
Download SharePoint Essentials Toolkit Now
Download the SharePoint Essentials Toolkit
Dhaval Shah - MCD, MCSD
Follow me
Latest posts by Dhaval Shah - MCD, MCSD (see all)

One thought on “SharePoint Framework (SPFx) (Part 2) Create a Simple Client-side Web Part

  1. Hi, thanks for the article. I successfully used it as a reference.
    Now i would like to abstract the list url from the current web in the constructor of the SharedServiceProvider, but since the service provider is created from the ..serviceScope.consume(SharedServiceProvider.serviceKey) i can’t directly do it (i think).
    Is it possible? If yes can you tell how to it?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.