SharePoint 2013 Modern WebPart (2 of 4)
- Modern WebPart Overview
- Demo 1 - TypeScript
- Demo 2 - React (This Post)
- Demo 3 - VueJS
- Demo 4 - AngularJS
React WebPart Example
This is the second of four demos giving an overview of creating modern webpart solutions for SharePoint 2013+ environments. The demo code can be found in github. The goal of this post is to give an overview of gd-sprest-react library and expand on the previous post to give better code examples using the React framework. This example will be written in TypeScript using the Office Fabric-UI React framework.
Requirements
- NodeJS - A superset of JavaScript functions. NodeJS allows us to develop code similar to C#, which is compiled into JavaScript.
- TypeScript - Link to TypeScript for reference.
Global Libraries
After installing NodeJS, we will install TypeScript globally. This will allow us to link it to our project, so we don’t need this library stored w/in each project.
npm i -g typescript
SharePoint React Components
The webpart logic originated from the gd-sprest-react library, which is why this post will have many more examples then the rest. The main reason for selecting the React framework, is Microsoft’s decision on using React for the Fabric-UI library. The gd-sprest-react library was designed to extend the Fabric-UI components for SharePoint 2013+ environments. For the latest information, refer to the wiki
Available Components
There is just too much to go over, so I created a blog post giving a high level overview of the available components.
Project
Create the NodeJS Project
Using the command-line, create the project folder and initialize the project. This will create the package.json file for the NodeJS project.
mkdir demo-wp-react
cd demo-wp-react
npm init --y
Running the ‘npm init –y’ command will select the default values and create the package.json file.
Install the Libraries
- Babel - The babel-core, babel-loader, babel-preset-es2015 & ts-loader are referenced by WebPack to compile the TypeScript code to JavaScript ES2015 standards
- gd-sprest - The library used to create the webpart
- gd-sp-webpart - The webpart library
- React - The react library is split up into react and react-dom. For intellisense we will need to download the @types/react & @types/react-dom libraries.
- SASS - The node-sass, sass-loader, css-loader & style-loader libraries are used to compile SASS to JS.
- WebPack - Used to compile and bundle the code into a single output file
npm i --save gd-sprest gd-sprest-react office-ui-fabric-react react react-dom
npm i --save-dev @types/react @types/react-dom babel-core babel-loader babel-preset-es2015 ts-loader webpack node-sass sass-loader css-loader style-loader
This will download the libraries to the node_modules folder.
Source Code
Create a “src” folder and add the following files
SharePoint Configuration (src/cfg.ts)
To stay consistent with the out of the box (OTB) SharePoint experience, we will be using the gd-sprest library’s Helper class to automate the creation of webparts. This configuration file will help us create the list and webpart in SharePoint. I’ve added a sample method for adding test data.
import { Helper, List, SPTypes, Types } from "gd-sprest";
/**
* Configuration
*/
export const Configuration = {
// List
List: Helper.SPConfig({
ListCfg: [
{
// Custom fields for this list
CustomFields: [
{
choices: ["Business", "Family", "Personal"],
name: "MCCategory",
title: "Category",
type: Helper.SPCfgFieldType.Choice
} as Types.Helper.IFieldInfoChoice,
{
name: "MCPhoneNumber",
title: "Phone Number",
type: Helper.SPCfgFieldType.Text
}
],
// The list creation information
ListInformation: {
BaseTemplate: SPTypes.ListTemplateType.GenericList,
Title: "My Contacts"
},
// Update the 'Title' field's display name
TitleFieldDisplayName: "Full Name",
// Update the default 'All Items' view
ViewInformation: [
{
ViewFields: ["MCCategory", "LinkTitle", "MCPhoneNumber"],
ViewName: "All Items",
ViewQuery: "<OrderBy><FieldRef Name='MCCategory' /><FieldRef Name='Title' /></OrderBy>"
}
]
}
]
}),
// WebPart
WebPart: Helper.SPConfig({
WebPartCfg: [
{
FileName: "wpContacts.webpart",
Group: "Demo",
XML: `<?xml version="1.0" encoding="utf-8"?>
<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="Microsoft.SharePoint.WebPartPages.ScriptEditorWebPart, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
<importErrorMessage>$Resources:core,ImportantErrorMessage;</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">My Contacts</property>
<property name="Description" type="string">Demo displaying my contacts.</property>
<property name="ChromeType" type="chrometype">TitleOnly</property>
<property name="Content" type="string">
<script type="text/javascript" src="/sites/dev/siteassets/demo-react.js"></script>
<div id="wp-contacts"></div>
<div id="wp-contactsCfg" style="display:none;"></div>
<script type="text/javascript">SP.SOD.executeOrDelayUntilScriptLoaded(function() { new Contacts(); }, 'demo-react.js');</script>
</property>
</properties>
</data>
</webPart>
</webParts>`
}
]
})
};
// Method to add list test data
Configuration.List["addTestData"] = () => {
// Get the list
let list = new List("My Contacts");
// Define the list of names
let names = [
"John A. Doe",
"Jane B. Doe",
"John C. Doe",
"Jane D. Doe",
"John E. Doe",
"Jane F. Doe",
"John G. Doe",
"Jane H. Doe",
"John I. Doe",
"Jane J. Doe"
];
// Loop 10 item
for (let i = 0; i < 10; i++) {
// Set the category
let category = "";
switch (i % 3) {
case 0:
category = "Business";
break;
case 1:
category = "Family";
break;
case 2:
category = "Personal";
break;
}
// Add the item
list.Items().add({
MCCategory: category,
MCPhoneNumber: "nnn-nnn-nnnn".replace(/n/g, i.toString()),
Title: names[i]
})
// Execute the request, but wait for the previous request to complete
.execute((item) => {
// Log
console.log("[WP Demo] Test item '" + item["Title"] + "' was created successfully.");
}, true);
}
// Wait for the requests to complete
list.done(() => {
// Log
console.log("[WP Demo] The test data has been added.");
});
};
Main (src/index.ts)
The index.ts file will be treated as the “Main” function or “Entry Point” of the component we created. We will import the demo webpart and make it globally available. Next we will use the SharePoint Script-On-Demand (SP SOD) library to notify other scripts that the demo class is loaded. If you refer to the configuration file, and look at the JavaScript, it utilizes the SP SOD library to control when its called.
import { WebParts } from "gd-sprest-react";
import { Configuration } from "./cfg";
import { ContactsWebPart } from "./wp";
declare var SP;
/**
* Contacts Demo
*/
export class Contacts {
// The configuration for the demo
static Configuration = Configuration;
/**
* Constructor
*/
constructor() {
// Create an instance of the contacts webpart
new WebParts.FabricWebPart({
cfgElementId: "wp-contactsCfg",
displayElement: ContactsWebPart,
editElement: WebParts.WebPartListCfg,
targetElementId: "wp-contacts",
});
}
}
// Set the global variable
window["Contacts"] = Contacts;
// Let SharePoint know the script has been loaded
SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs("demo-react.js");
Main (src/wp.ts)
The webpart logic will inherit the list webpart type. This will simplify and allow us to focus on the item data to render.
import * as React from "react";
import { Types } from "gd-sprest";
import { WebParts } from "gd-sprest-react";
/**
* Contact Item
*/
export interface IContactItem extends Types.SP.IListItemQueryResult {
MCCategory: string;
MCPhoneNumber: string;
Title: string;
}
/**
* Contacts WebPart
*/
export class ContactsWebPart extends WebParts.WebPartList {
// Render item event
onRenderItem = (item: IContactItem) => {
// Return the item template
return (
<div key={item.Id} className="ms-Grid">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-md4">{item.MCCategory}</div>
<div className="ms-Grid-col ms-md4">{item.Title}</div>
<div className="ms-Grid-col ms-md4">{item.MCPhoneNumber}</div>
</div>
</div>
);
}
}
Compiler Configuration Files
Before we are able to compile the files, we need to update the configuration files.
Package (package.json)
Update the “scripts” and the “build” option. This will allow us to type in “npm run build” to compile the code.
"scripts": {
"build": "webpack"
}
TypeScript (tsconfig.json)
The compiler options for TypeScript are straight forward. We are targeting the code in the source (src) folder, and outputing ES5 JavaScript.
{
"compilerOptions": {
"target": "es5"
},
"include": [
"src/**/*"
]
}
WebPack (webpack.config.js)
var path = require('path');
var webpack = require("webpack");
module.exports = {
// Target the output of the typescript compiler
context: path.join(__dirname, "src"),
// File(s) to target
entry: './index.ts',
// Output
output: {
filename: 'demo-react.js',
path: path.resolve(__dirname, 'dist')
},
// Resolve the file extensions
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"]
},
// Module to define what libraries with the compiler
module: {
// Loaders
loaders: [
{
// Target the sass files
test: /\.s?css?$/,
// Define the compiler to use
use: [
// Create style nodes from the CommonJS code
{ loader: "style-loader" },
// Translate css to CommonJS
{ loader: "css-loader" },
// Compile sass to css
{ loader: "sass-loader" }
]
},
{
// Target the typescript files
test: /\.tsx?$/,
// Exclude the npm libraries
exclude: /node_modules/,
// Define the compiler to use
use: [
{
// Compile the JSX code to javascript
loader: "babel-loader",
// Options
options: {
// Ensure the javascript will work in legacy browsers
presets: ["es2015"]
}
},
{
// Compile the typescript code to JSX
loader: "ts-loader"
}
]
}
]
}
};
Compile the Code
Run ‘npm run build’ or ‘webpack’ to compile the code and create the ‘demo-react.js’ file located in the dist folder.
Error Could not load TypeScript
Since we installed TypeScript globally, we can link to it.
npm link typescript
Demo
Step 1 - Create Demo Page
1) Upload the file to SharePoint 2) Create a webpart page of your choice 3) Access the page
Step 2 - Install the WebPart
1) Press F-12 to access the developer tools 2) Click on the “Console” tab 3) Reference the script - We will use simple javascript to insert a script link into the page. Afterwards, our library will be available.
var s = document.createElement("script");
s.src = "/sites/dev/siteassets/demo-react.js";
document.head.appendChild(s);
4) Install the list
Contacts.Configuration.List.install()
This part may take a little time to initialize, depending on the size of the web. 5) Install the webpart
Contacts.Configuration.WebPart.install()
This part may take a little time to initialize, depending on the size of the web.
Step 3 - Test
1) Edit the page 2) Click on a webpart zone to add a new webpart 3) From the webpart gallery, select the “Demo - Contacts” group “My Contacts” webpart 4) After the page reloads, click on the “Edit Configuration” 5) Select the “My Contacts” test list we created 6) Save the webpart 7) Save the page At this point, you should see the webpart title only. This is because we don’t have any data in the list, so nothing is being rendered.
Step 4 - Add Test Data
1) Press F-12 to access the developer tools 2) Click on the “Console” tab 3) Create the test data
Contacts.Configuration.List.addTestData()
4) Refresh the page and view the data
Step 4 - Clean Up
1) Press F-12 to access the developer tools 2) Click on the “Console” tab 3) Uninstall the list and webpart
DemoWebPart.Configuration.List.uninstall()
DemoWebPart.Configuration.WebPart.uninstall()
This part may take a little time to initialize, depending on the size of the web.
Output Size
One thing to note, is the output file size (uncompressed) is about 3MB. This is due to the output file bundling the react and office fabric-ui libraries. You’ll want to pay attention to this as we look at all of the frameworks.
Conclusion
I hope this post was useful for using the React framework. This gd-sprest-react library was designed to reduce the redundancies and complexities for developing react solutions. In the next post we will explore VueJS framework.