SharePoint Framework with CKeditor5, PnP JS, OfficeUIFabric PeoplePicker and much more

Important : SharePoint Framework React Controls are now available and can be used to include people picker control in SharePoint Framework
Please refer this article for more information on SharePoint Framework React Controls

Prerequisites

Before we jump into building the web part, please have a look at how to set-up-your-development-environment & how to set-up-your-developer-tenant

In short, the following setups are required to get started with SPFx web part development:

  • Node JS along with Node Package Manager -  https://nodejs.org/en  (Make sure you install the supported version)
  • Visual Studio Code from https://code.visualstudio.com/   (Not Mandatory, any editor can be used)
  • Install Yeoman and  Gulp by running the command npm install -g yo gulp
  • Install   SharePoint Framework Yeoman generator   by running the command **npm install -g @microsoft/generator-sharepoint**

Purpose

This article can be referred as a base to develop a SPFx web part, which can be used for developing Global Announcement web part, Send Email Utility, Customizing List Form or referring other external JavaScript libraries.

Getting Started

Create SharePoint Framework Solution

Step 1) Open Windows Powershell.

Step 2) Create a folder inside a directory by entering the below command, here for example

**md GlobalAnnouncement **

Step 3) Go Inside the folder by entering the command

**cd  GlobalAnnouncement **

Step 4) Create a webpart by entering below command

**yo @microsoft/sharepoint**

Specify the following :

  • Solution Name - GlobalAnnouncement
  • Target - SharePoint Online only
  • File Location - Use the current folder
  • Allow tenant admin to deploy the solution to all Site Immediately without running any feature deployment or adding apps in sites -** Yes**
  • Which client -side component- Webpart
  • WebpartName - GlobalAnnouncement
  • A description - GlobalAnnouncement is a utility to send email
  • Framework- No Javascript framework

Referring CKEditor5 Framework in SPFx Solution

Step 5) Now, import CKeditor package from Node package manager using the following command.

**npm install --save @ckeditor/ckeditor5-build-classic**

Step 6) Open the solution in Visual Studio Code by running the below code.

Code .

Step 7) Once CKeditor Solution is imported successfully, the respective files can be located under

**Solution >  node_modules >  @ckeditor **

Also, it would be marked under one of the dependencies in Package.json file

Solution >  Package.json

Step 8) To utilize the solution and to change the default webpart render logic, add the below code to GlobalAnnouncementWebpart.ts  file located under

Solution >  src >  webparts >  globalannouncements >  GlobalAnnouncementWebpart.ts

Include the below import section along with the default ones.

import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

The below code is executed when the webpart is rendered which will incorporate the CKeditor with this webpart.

 
public render(): void {
 this.domElement.innerHTML = `<div id="editor"></div>`;
 this.getCKeditor();
}
 
private getCKeditor(){
 ClassicEditor.create( document.querySelector( '#editor' ) )
 .then( editor => {
 console.log( editor );
 } )
 .catch( error => {
 console.error( error );
 } );
}

Step 9) At this point, if we switch to PowerShell  and run gulp serve.

CKEditor is launched in LocalWorkbench

Add OfficeUIFabric PeoplePicker in SPFx

Step 10) Since we opted for no javascript framework  and the PeoplePicker we are referring is build in React, following dependent files(React Components) need to be imported\downloaded from here

 

Step 11) Once this solution is downloaded and unzipped, Copy componentmodels  folder and IOfficeUiFabricPeoplePickerWebPartProps.ts  file from the downloaded component( (Solution)SPFx-OfficeUIFabric-PeoplePicker-master > src > webparts > officeUiFabricPeoplePicker ) to current solution  (GlobalAnnouncement > src >webparts >globalAnnouncement ). Rename** IOfficeUiFabricPeoplePickerWebPartProps.ts ** which was copied** ** to IGlobalAnnouncementWebPartProps.ts

Step 12)  Add the below code to GlobalAnnouncementWebpart.ts . Arrange the import section along with the existing code and replace the Render  method.

import * as React from 'react';
import * as ReactDom from 'react-dom';
 
import { IGlobalAnnouncementWebPartProps } from './IGlobalAnnouncementWebPartProps';
import OfficeUiFabricPeoplePicker from './components/OfficeUiFabricPeoplePicker';
import { IOfficeUiFabricPeoplePickerProps } from './components/IOfficeUiFabricPeoplePickerProps';
 
 
 public render(): void {
 this.domElement.innerHTML = `
 <div class="${ styles.globalAnnouncement }">
 <div class="${ styles.container }">
 <div id="editor"></div> 
 <div class="pplPicker"></div>
 <div class="${ styles.row }"> 
 </div>
 </div>
 </div>`;
 this.getCKeditor();
 this.initializeOfficeUIPPLPicker();
}
 
private initializeOfficeUIPPLPicker():void
 {
 const element: React.ReactElement<IOfficeUiFabricPeoplePickerProps> = React.createElement(
 OfficeUiFabricPeoplePicker,
 {
 description: this.properties.description,
 spHttpClient: this.context.spHttpClient,
 siteUrl: this.context.pageContext.web.absoluteUrl,
 typePicker: this.properties.typePicker,
 principalTypeUser: this.properties.principalTypeUser,
 principalTypeSharePointGroup: this.properties.principalTypeSharePointGroup,
 principalTypeSecurityGroup: this.properties.principalTypeSecurityGroup,
 principalTypeDistributionList: this.properties.principalTypeDistributionList,
 numberOfItems: this.properties.numberOfItems
 }
 );
 ReactDom.render(element, this.domElement.querySelector('.pplPicker'));
  
 }

Step 13) Open **IGlobalAnnouncementWebPartProps.ts file ** Interface Properties and replace the below content. Remove the default Export Method of IGlobalAnnouncementWebPartProp  from GlobalAnnouncementWebpart.ts  file.

export interface IGlobalAnnouncementWebPartProps {
  description: string;
  typePicker: string;
  principalTypeUser: boolean;
  principalTypeSharePointGroup: boolean;
  principalTypeSecurityGroup: boolean;
  principalTypeDistributionList: boolean;
  numberOfItems: number;
}



Step 14) Open **GlobalAnnouncementWebPart.manifest.json ** file and replace the following property (in Bold) with the existing one.

{
 "$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
 "id": "b933da35-67af-4b0a-ac3b-f3a76639e64b",
 "alias": "GlobalAnnouncementWebPart",
 "componentType": "WebPart",
  
 // The "*" signifies that the version should be taken from the package.json
 "version": "*",
 "manifestVersion": 2,
  
 // If true, the component can only be installed on sites where Custom Script is allowed.
 // Components that allow authors to embed arbitrary script code should set this to true.
 // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
 "requiresCustomScript": false,
  
 "preconfiguredEntries": [{
 "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
 "group": { "default": "Other" },
 "title": { "default": "GlobalAnnouncement" },
 "description": { "default": "Global Announcement is a Send Email Utility" },
 "officeFabricIconFontName": "Family",
  "properties": {
 "description": "check",
 "typePicker": "Normal",
 "principalTypeUser": true,
 "principalTypeSharePointGroup": true,
 "principalTypeSecurityGroup": false,
 "principalTypeDistributionList": false,
 "numberOfItems": 10
 }
 }]
}

Step 14) Save all files present inside solution, again, if we switch to PowerShell  and run gulp serve

**CKEditor5 **along with  OfficeUIFabric PeoplePicker is launched in local workbench.

Add PnP JS to SPFx Solution

Step 15) Run the following command in Windows PowerShell. This will import all the necessary PnP JS  package.

Step 16) To terminate the current session of gulp serve, press ctrl+c .

npm install sp-pnp-js --save

Step 17) Include the below code inside OfficeUiFabricPeoplePicker.tsx  located under src > webparts > globalAnnouncement > component. (replace the _onChange  method) which collects and export the Email IDs of the person resolved inside the people picker an array.  

  private _onChange(items:any[]) {
    this.setState({
      selectedItems: items
    });
    
    if (this.props.onChange)
    {
      this.props.onChange(items);      
    }
     
    pplList.length = 0;
    for (let item in items)
    {     
      pplList.push(items[item]._user.Email);
    }
  } 

Include the below code as a export constant in OfficeUiFabricPeoplePicker.tsx file.

export const pplList:any[] = [];

Step 18) Include the below code in **GlobalAnnouncementWebpart.ts ** file which imports the Email ID array that is further utilized to send email using PnP JS. The code also collects the CKEditor content and pass it as the mail body.

var mailContent: any = null;
 
import pnp, { EmailProperties } from "sp-pnp-js";
import * as Vars from './components/OfficeUiFabricPeoplePicker';
 
public onInit(): Promise<void> {  
 return super.onInit().then(_ => {  
 pnp.setup({
 spfxContext: this.context
 });
 });
 }
 
private getCKeditor(){
 ClassicEditor.create( document.querySelector( '#editor' ) )
 .then( editor => {
 mailContent = editor;
 } )
 .catch( error => {
 console.error( error );
 } );
 } 
 
public render(): void {
 this.domElement.innerHTML = `
 <div class="${ styles.globalAnnouncement }">
 <div class="${ styles.container }">
 <div id="editor"></div>  
 <div class="pplPicker"></div>
 <div class="status"></div>
 <button class="${styles.button} create-Button">
 <span class="${styles.label}">Save</span>
 </button>
 </div>
 </div>`;
 this.getCKeditor(); 
 this.setButtonsEventHandlers();
 this.initializeOfficeUIPPLPicker();
 }
 
 private sendEmail(): void
 {
 const emailProps: EmailProperties = {
 To: Vars.pplList,
 CC: [],
 Subject: "Test Email from SPFx",
 Body: mailContent.getData(),
 };
  
 pnp.sp.utility.sendEmail(emailProps).then(_ => { 
 this.updateStatus("Email Sent!"); 
 });
 }
 
private setButtonsEventHandlers(): void {
 const webPart: GlobalAnnouncementWebPart = this;
 this.domElement.querySelector('button.create-Button').addEventListener('click', () => { webPart.createItem(); });
 }
 
 private createItem(): void {
 this.sendEmail();
 }
  
 
 private updateStatus(status: string): void {
 this.domElement.querySelector('.status').innerHTML = status; 
 }        

Overall code inside GlobalAnnouncementWebpart.ts ** file ** will look as below

import { Version } from '@microsoft/sp-core-library';
import {
 BaseClientSideWebPart,
 IPropertyPaneConfiguration,
 PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
 
import styles from './GlobalAnnouncementWebPart.module.scss';
import * as strings from 'GlobalAnnouncementWebPartStrings';
import * as React from 'react';
import * as ReactDom from 'react-dom';
 
import { IGlobalAnnouncementWebPartProps } from './IGlobalAnnouncementWebPartProps';
import OfficeUiFabricPeoplePicker from './components/OfficeUiFabricPeoplePicker';
import { IOfficeUiFabricPeoplePickerProps } from './components/IOfficeUiFabricPeoplePickerProps';
var mailContent: any = null;
 
import pnp, { EmailProperties } from "sp-pnp-js";
import * as Vars from './components/OfficeUiFabricPeoplePicker';
const ClassicEditor = require( '@ckeditor/ckeditor5-build-classic' );
export default class GlobalAnnouncementWebPart extends BaseClientSideWebPart<IGlobalAnnouncementWebPartProps> {
  
public onInit(): Promise<void> {  
 return super.onInit().then(_ => {  
 pnp.setup({
 spfxContext: this.context
 });
 });
}
 
public render(): void {
 this.domElement.innerHTML = `
 <div class="${ styles.globalAnnouncement }">
 <div class="${ styles.container }">
 <div id="editor"></div> 
 <div class="pplPicker"></div>
 <div class="status"></div>
 <button class="${styles.button} create-Button">
 <span class="${styles.label}">Save</span>
 </button>
 </div>
 </div>`;
 this.getCKeditor(); 
 this.setButtonsEventHandlers();
 this.initializeOfficeUIPPLPicker();
}
 
private setButtonsEventHandlers(): void {
 const webPart: GlobalAnnouncementWebPart = this;
 this.domElement.querySelector('button.create-Button').addEventListener('click', () => { webPart.createItem(); });
 }
 
 private createItem(): void {
 this.sendEmail();
}
 
 
private updateStatus(status: string): void {
 this.domElement.querySelector('.status').innerHTML = status; 
}
 
private sendEmail(): void
{
 const emailProps: EmailProperties = {
 To: Vars.pplList,
 CC: [],
 Subject: "Test Email from SPFx",
 Body: mailContent.getData(),
 };
  
 pnp.sp.utility.sendEmail(emailProps).then(_ => { 
 this.updateStatus("Email Sent!"); 
 });
}
 
private initializeOfficeUIPPLPicker():void
 {
 const element: React.ReactElement<IOfficeUiFabricPeoplePickerProps> = React.createElement(
 OfficeUiFabricPeoplePicker,
 {
 description:  this.properties.description,
 spHttpClient: this.context.spHttpClient,
 siteUrl: this.context.pageContext.web.absoluteUrl,
 typePicker: this.properties.typePicker,
 principalTypeUser:  this.properties.principalTypeUser,
 principalTypeSharePointGroup: this.properties.principalTypeSharePointGroup,
 principalTypeSecurityGroup: this.properties.principalTypeSecurityGroup,
 principalTypeDistributionList: this.properties.principalTypeDistributionList,
 numberOfItems:  this.properties.numberOfItems
 }
 );
 ReactDom.render(element, this.domElement.querySelector('.pplPicker'));
  
 }
 
 
 private getCKeditor(){
 ClassicEditor.create( document.querySelector( '#editor' ) )
 .then( editor => {
 mailContent = editor;
 } )
 .catch( error => {
 console.error( error );
 } );
 } 
 
 protected get dataVersion(): Version {
 return Version.parse('1.0');
 }
 
 protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
 return {
 pages: [
 {
 header: {
 description: strings.PropertyPaneDescription
 },
 groups: [
 {
 groupName: strings.BasicGroupName,
 groupFields: [
 PropertyPaneTextField('description', {
 label: strings.DescriptionFieldLabel
 })
 ]
 }
 ]
 }
 ]
 };
 }
}

Step 19) All Set. Now bundle\Build the solution using the below command

gulp bundle

Step 20) Package\Publish the solution using the below command. This command creates sppkg  file inside a new sharepoint  folder.

gulp package-solution

Step 21) Open AppCatalog site of your tenant or site collection and upload the sppkg file generated in our previous step to app for sharepoint list.

Step 22) Trust the solution by clicking on Deploy  button.

Step 23) Switch to PowerShell and Run gulp serve. Option the Hosted Workbench by navigating to the URL "https://<<yourdomain>>.sharepoint.com/_layouts/workbench.aspx"

Step 24) Add the web part to hosted workbench page.

Step 25) Add some content inside the CKeditor 5 and specify members whom this email need to be sent inside the people picker. Click on Save\Send.

 

Email with the specified image\content is received. Hence, a small mission to send email using PnP JS, utilizing OfficeUIFabric People picker, customizing a mail content with a rich text editor have been achieved. 

Hence this article explains how external javascript plugins like CKeditor, PnP JS with OfficeUIReact Component can be referred in SPFx.

Over All Flow in a GIF

https://i1.gallery.technet.s-msft.com/sharepoint-framework-aka-a2878107/image/file/200783/1/outcome.gif

 Solution can be downloaded from https://gallery.technet.microsoft.com/SharePoint-framework-aka-a2878107

References

Below are some of the useful links referred in this regard.