Merge pull request #3050 from bcameron1231/pnp-js-hooks
New Sample - PnPJs V3 with Hooks
This commit is contained in:
commit
df0d103402
|
@ -0,0 +1,5 @@
|
|||
require("@rushstack/eslint-config/patch/modern-module-resolution");
|
||||
module.exports = {
|
||||
extends: ["@microsoft/eslint-config-spfx/lib/profiles/react"],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
release
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
.heft
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,16 @@
|
|||
!dist
|
||||
config
|
||||
|
||||
gulpfile.js
|
||||
|
||||
release
|
||||
src
|
||||
temp
|
||||
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
|
||||
*.log
|
||||
|
||||
.yo-rc.json
|
||||
.vscode
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"plusBeta": true,
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.15.2",
|
||||
"libraryName": "spfx-pnp-js-example",
|
||||
"libraryId": "d20ceaf6-094b-4086-b7a0-85761bc8be23",
|
||||
"packageManager": "npm",
|
||||
"solutionShortDescription": "spfx-pnp-js-example description",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
page_type: sample
|
||||
products:
|
||||
- office-sp
|
||||
languages:
|
||||
- javascript
|
||||
- typescript
|
||||
extensions:
|
||||
contentType: samples
|
||||
technologies:
|
||||
- SharePoint Framework
|
||||
platforms:
|
||||
- react
|
||||
createdDate: 10/10/2022 12:00:00 AM
|
||||
---
|
||||
# @pnp/js and ReactJS Functional Components
|
||||
|
||||
## Summary
|
||||
|
||||
This solution builds off of the solution [react-async-await-sp-pnp-js](./react-async-await-sp-pnp-js) submitted by Jose Quinto ([@jquintozamora](https://twitter.com/jquintozamora) , [blog.josequinto.com](https://blog.josequinto.com)) and re-work of the existing class based [react-pnp-js-sample](./react-pnp-js-sample) by [Julie Turner](https://twitter.com/jfj1997)
|
||||
|
||||
This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks.
|
||||
|
||||
![React-pnp-js-sample](./assets/react-pnp-js-sample.png)
|
||||
|
||||
## Compatibility
|
||||
|
||||
This sample is optimally compatible with the following environment configuration:
|
||||
|
||||
![SPFx 1.15.2](https://img.shields.io/badge/SPFx-1.15.2-green.svg)
|
||||
![Node.js v16 | v14 | v12](https://img.shields.io/badge/Node.js-v16%20%7C%20v14%20%7C%20v12-green.svg)
|
||||
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
|
||||
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
|
||||
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
|
||||
![Local Workbench Unsupported](https://img.shields.io/badge/Local%20Workbench-Unsupported-red.svg "Local workbench is no longer available as of SPFx 1.13 and above")
|
||||
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
|
||||
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
|
||||
|
||||
For more information about SPFx compatibility, please refer to <https://aka.ms/spfx-matrix>
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
* [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
|
||||
|
||||
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/m365devprogram)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-pnp-js-hooks | [Beau Cameron](https://github.com/bcameron1231) ([@beau__cameron](https://twitter.com/Beau__Cameron))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|Oct 09, 2022|Initial release
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
* Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks) then unzip it)
|
||||
* From your command line, change your current directory to the directory containing this sample (`react-pnp-js-hooks`, located under `samples`)
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* Update online workbench URL in the `initialPage` property of the `config/serve.json` file.
|
||||
* `gulp serve`
|
||||
|
||||
## Features
|
||||
|
||||
* Establishing context for the SharePoint Factory Interface
|
||||
* Creating a project config file to centralize defining the PnPjs imports and SharePoint Queryable object for reuse.
|
||||
* Demo using PnPjs with React Functional Components & Hooks
|
||||
* Demo extending the SharePoint Queryables instance with the PnPLogging behavior.
|
||||
* Demo extending the SharePoint Queryable instance with the Caching behavior
|
||||
* Demo loading list items from a SharePoint library
|
||||
* Demo creating a batched instance of the SharePoint Queryable object.
|
||||
* Demo updating list items by modifying the Title property.
|
||||
* Demo executing a batch and working with the results.
|
||||
|
||||
## Help
|
||||
|
||||
We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
|
||||
|
||||
If you're having issues building the solution, please run [spfx doctor](https://pnp.github.io/cli-microsoft365/cmd/spfx/spfx-doctor/) from within the solution folder to diagnose incompatibility issues with your environment.
|
||||
|
||||
You can try looking at [issues related to this sample](https://github.com/pnp/sp-dev-fx-webparts/issues?q=label%3A%22sample%3A%20react-pnp-js-hooks%22) to see if anybody else is having the same issues.
|
||||
|
||||
You can also try looking at [discussions related to this sample](https://github.com/pnp/sp-dev-fx-webparts/discussions?discussions_q=react-pnp-js-hooks) and see what the community is saying.
|
||||
|
||||
If you encounter any issues using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected%2Csample%3A%20react-pnp-js-hooks&template=bug-report.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
|
||||
|
||||
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aquestion%2Csample%3A%20react-pnp-js-hooks&template=question.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
|
||||
|
||||
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aenhancement%2Csample%3A%20react-pnp-js-hooks&template=suggestion.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
<img src="https://pnptelemetry.azurewebsites.net/sp-dev-fx-webparts/samples/react-pnp-js-hooks" />
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,50 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-pnp-js-hooks",
|
||||
"source": "pnp",
|
||||
"title": "pnp/js and ReactJS Functional Components",
|
||||
"shortDescription": "This solution builds off of the solution react-async-await-sp-pnp-js submitted by Jose Quinto, and re-work of the existing class based react-pnp-js-sample by Julie Turner. This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks.",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks",
|
||||
"downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks",
|
||||
"longDescription": [
|
||||
"This solution builds off of the solution react-async-await-sp-pnp-js submitted by Jose Quinto, and re-work of the existing class based react-pnp-js-sample by Julie Turner. This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks."
|
||||
],
|
||||
"creationDateTime": "2022-10-17",
|
||||
"updateDateTime": "2022-10-17",
|
||||
"products": [
|
||||
"SharePoint"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.15.2"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-pnp-js-hooks/assets/react-pnp-js-sample.png",
|
||||
"alt": "Web Part Preview"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "bcameron1231",
|
||||
"pictureUrl": "https://github.com/bcameron1231.png",
|
||||
"name": "Beau Cameron"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"name": "Build your first SharePoint client-side web part",
|
||||
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"pn-pjs-example-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/pnPjsExample/PnPjsExampleWebPart.js",
|
||||
"manifest": "./src/webparts/pnPjsExample/PnPjsExampleWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"PnPjsExampleWebPartStrings": "lib/webparts/pnPjsExample/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-pnp-js-example",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "spfx-pnp-js-example-client-side-solution",
|
||||
"id": "d20ceaf6-094b-4086-b7a0-85761bc8be23",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": "Undefined-1.14.0-beta.4"
|
||||
},
|
||||
"metadata": {
|
||||
"shortDescription": {
|
||||
"default": "spfx-pnp-js-example description"
|
||||
},
|
||||
"longDescription": {
|
||||
"default": "spfx-pnp-js-example description"
|
||||
},
|
||||
"screenshotPaths": [],
|
||||
"videoUrl": "",
|
||||
"categories": []
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"title": "spfx-pnp-js-example Feature",
|
||||
"description": "The feature that activates elements of the spfx-pnp-js-example solution.",
|
||||
"id": "97ead3d0-6478-4aa5-abba-151b0e9b76c9",
|
||||
"version": "1.0.0.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/spfx-pnp-js-example.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
var getTasks = build.rig.getTasks;
|
||||
build.rig.getTasks = function () {
|
||||
var result = getTasks.call(build.rig);
|
||||
|
||||
result.set('serve', result.get('serve-deprecated'));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
build.initialize(require('gulp'));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "spfx-pnp-js-example",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.15.2",
|
||||
"@microsoft/sp-lodash-subset": "1.15.2",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.15.2",
|
||||
"@microsoft/sp-property-pane": "1.15.2",
|
||||
"@microsoft/sp-webpart-base": "1.15.2",
|
||||
"@pnp/logging": "^3.6.0",
|
||||
"@pnp/sp": "^3.6.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-4.5": "^0.2.2",
|
||||
"@microsoft/sp-build-web": "1.15.2",
|
||||
"@microsoft/sp-module-interfaces": "1.15.2",
|
||||
"@types/react": "16.9.51",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@types/webpack-env": "1.15.2",
|
||||
"ajv": "6.12.5",
|
||||
"gulp": "~4.0.2",
|
||||
"@rushstack/eslint-config": "2.5.1",
|
||||
"@microsoft/eslint-plugin-spfx": "1.15.2",
|
||||
"@microsoft/eslint-config-spfx": "1.15.2",
|
||||
"eslint": "8.7.0",
|
||||
"eslint-plugin-react-hooks": "4.3.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "72f602dd-5b1b-4a6e-ae8f-83c9544cb711",
|
||||
"alias": "PnPjsExampleWebPart",
|
||||
"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,
|
||||
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
||||
"supportsThemeVariants": true,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "PnPJSExample" },
|
||||
"description": { "default": "PnPJSExample description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "PnPJSExample"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { IReadonlyTheme } from "@microsoft/sp-component-base";
|
||||
|
||||
import * as strings from "PnPjsExampleWebPartStrings";
|
||||
import { PnPjsExample } from "./components/PnPjsExample";
|
||||
import { IPnPjsExampleProps } from "./components/IPnPjsExampleProps";
|
||||
|
||||
import { getSP } from "./pnpjsConfig";
|
||||
|
||||
export interface IPnPjsExampleWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class PnPjsExampleWebPart extends BaseClientSideWebPart<IPnPjsExampleWebPartProps> {
|
||||
private _isDarkTheme = false;
|
||||
private _environmentMessage = "";
|
||||
|
||||
protected async onInit(): Promise<void> {
|
||||
this._environmentMessage = this._getEnvironmentMessage();
|
||||
|
||||
super.onInit();
|
||||
|
||||
//Initialize our _sp object that we can then use in other packages without having to pass around the context.
|
||||
// Check out pnpjsConfig.ts for an example of a project setup file.
|
||||
getSP(this.context);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IPnPjsExampleProps> = React.createElement(
|
||||
PnPjsExample,
|
||||
{
|
||||
description: this.properties.description,
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
environmentMessage: this._environmentMessage,
|
||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
||||
userDisplayName: this.context.pageContext.user.displayName,
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
private _getEnvironmentMessage(): string {
|
||||
if (!this.context.sdks.microsoftTeams) {
|
||||
// running in Teams
|
||||
return this.context.isServedFromLocalhost
|
||||
? strings.AppLocalEnvironmentTeams
|
||||
: strings.AppTeamsTabEnvironment;
|
||||
}
|
||||
|
||||
return this.context.isServedFromLocalhost
|
||||
? strings.AppLocalEnvironmentSharePoint
|
||||
: strings.AppSharePointEnvironment;
|
||||
}
|
||||
|
||||
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
|
||||
if (!currentTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDarkTheme = !!currentTheme.isInverted;
|
||||
const { semanticColors } = currentTheme;
|
||||
this.domElement.style.setProperty("--bodyText", semanticColors.bodyText);
|
||||
this.domElement.style.setProperty("--link", semanticColors.link);
|
||||
this.domElement.style.setProperty(
|
||||
"--linkHovered",
|
||||
semanticColors.linkHovered
|
||||
);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,7 @@
|
|||
export interface IPnPjsExampleProps {
|
||||
description: string;
|
||||
isDarkTheme: boolean;
|
||||
environmentMessage: string;
|
||||
hasTeamsContext: boolean;
|
||||
userDisplayName: string;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.pnPjsExample {
|
||||
overflow: hidden;
|
||||
padding: 1em;
|
||||
color: "[theme:bodyText, default: #323130]";
|
||||
color: var(--bodyText);
|
||||
&.teams {
|
||||
font-family: $ms-font-family-fallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcomeImage {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.links {
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: "[theme:link, default:#03787c]";
|
||||
color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: "[theme:linkHovered, default: #014446]";
|
||||
color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import * as React from "react";
|
||||
import styles from "./PnPjsExample.module.scss";
|
||||
import { IPnPjsExampleProps } from "./IPnPjsExampleProps";
|
||||
|
||||
// import interfaces & hooks
|
||||
import { Label, PrimaryButton } from "@microsoft/office-ui-fabric-react-bundle";
|
||||
import useDocuments from "../hooks/useDocuments";
|
||||
|
||||
export interface IAsyncAwaitPnPJsProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const PnPjsExample: React.FC<IPnPjsExampleProps> = (props) => {
|
||||
//use custom hook for CRUD operations with documents
|
||||
const [documents, updateDocuments, totalSize, isError] = useDocuments();
|
||||
|
||||
if (!isError) {
|
||||
return (
|
||||
<div className={styles.pnPjsExample}>
|
||||
<Label>
|
||||
Welcome to PnP JS Version 3 Demo with Functional Components + Hooks!
|
||||
</Label>
|
||||
<PrimaryButton onClick={updateDocuments}>
|
||||
Update Item Titles
|
||||
</PrimaryButton>
|
||||
<Label>List of documents:</Label>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Title</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>Name</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>Size (KB)</strong>
|
||||
</td>
|
||||
</tr>
|
||||
{documents.map((document, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td>{document.Title}</td>
|
||||
<td>{document.Name}</td>
|
||||
<td>{(document.Size / 1024).toFixed(2)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<td>&nbps;</td>
|
||||
<td>
|
||||
<strong>Total:</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{(totalSize / 1024).toFixed(2)}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <p>Error</p>;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
// create File item to work with it internally
|
||||
export interface IFile {
|
||||
Id: number;
|
||||
Title: string;
|
||||
Name: string;
|
||||
Size: number;
|
||||
}
|
||||
|
||||
// create PnP JS response interface for File
|
||||
export interface IResponseFile {
|
||||
Length: number;
|
||||
}
|
||||
|
||||
// create PnP JS response interface for Item
|
||||
export interface IResponseItem {
|
||||
Id: number;
|
||||
File: IResponseFile;
|
||||
FileLeafRef: string;
|
||||
Title: string;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import { Logger, LogLevel } from "@pnp/logging";
|
||||
import { spfi, SPFI } from "@pnp/sp";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IFile, IResponseItem } from "../components/interfaces";
|
||||
import { getSP } from "../pnpjsConfig";
|
||||
import { Caching } from "@pnp/queryable";
|
||||
import { IItemUpdateResult } from "@pnp/sp/items";
|
||||
|
||||
const useDocuments = () => {
|
||||
const LOG_SOURCE = "🅿PnPjsExample";
|
||||
const LIBRARY_NAME = "Documents";
|
||||
|
||||
const [documents, setDocuments] = useState<IFile[]>([]);
|
||||
const [totalSize, setTotalSize] = useState<number>(0);
|
||||
const [isError, setError] = useState<Boolean>(false);
|
||||
|
||||
const _sp: SPFI = getSP();
|
||||
|
||||
//side effect with empty depdency array, means to run once
|
||||
useEffect(() => {
|
||||
//mount logic to prevent updates to state when unmounted. Typically we'd use AbortController with Fetch, just can't with PnPjs
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
// do PnP JS query, some notes:
|
||||
// - .expand() method will retrive Item.File item but only Length property
|
||||
// - .get() always returns a promise
|
||||
// - await resolves proimises making your code act syncronous, ergo Promise<IResponseItem[]> becomes IResponse[]
|
||||
|
||||
//Extending our sp object to include caching behavior, this modification will add caching to the sp object itself
|
||||
//this._sp.using(Caching("session"));
|
||||
|
||||
//Creating a new sp object to include caching behavior. This way our original object is unchanged.
|
||||
const spCache = spfi(_sp).using(Caching({ store: "session" }));
|
||||
|
||||
const response: IResponseItem[] = await spCache.web.lists
|
||||
.getByTitle(LIBRARY_NAME)
|
||||
.items.select("Id", "Title", "FileLeafRef", "File/Length")
|
||||
.expand("File/Length")();
|
||||
|
||||
// use map to convert IResponseItem[] into our internal object IFile[]
|
||||
const documents: IFile[] = response.map((item: IResponseItem) => {
|
||||
return {
|
||||
Id: item.Id,
|
||||
Title: item.Title || "Unknown",
|
||||
Size: item.File?.Length || 0,
|
||||
Name: item.FileLeafRef,
|
||||
};
|
||||
});
|
||||
|
||||
//only update state if we are still mounted
|
||||
if (mounted) {
|
||||
// Add the items and totalsize to the state of the hook
|
||||
setDocuments(documents);
|
||||
setTotalSize(
|
||||
documents.length > 0
|
||||
? documents.reduce<number>((acc: number, item: IFile) => {
|
||||
return acc + Number(item.Size);
|
||||
}, 0)
|
||||
: 0
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (mounted) {
|
||||
setError(true);
|
||||
}
|
||||
Logger.write(
|
||||
`${LOG_SOURCE} (getting files useEffect) - ${JSON.stringify(err)} - `,
|
||||
LogLevel.Error
|
||||
);
|
||||
}
|
||||
|
||||
//best practice to return a cleanup method in scenarios where component unmounts before completion
|
||||
return () => (mounted = false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const updateDocuments = async () => {
|
||||
//mount logic to prevent updates to state when unmounted. Typically we'd use AbortController with Fetch, just can't with PnPjs
|
||||
let mounted = true;
|
||||
|
||||
try {
|
||||
const [batchedSP, execute] = _sp.batched();
|
||||
|
||||
//clone documents
|
||||
const items = JSON.parse(JSON.stringify(documents));
|
||||
|
||||
const res: IItemUpdateResult[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
// you need to use .then syntax here as otherwise the application will stop and await the result
|
||||
batchedSP.web.lists
|
||||
.getByTitle(LIBRARY_NAME)
|
||||
.items.getById(items[i].Id)
|
||||
.update({ Title: `${items[i].Name}-Updated` })
|
||||
.then((r) => res.push(r));
|
||||
}
|
||||
// Executes the batched calls
|
||||
await execute();
|
||||
|
||||
// Results for all batched calls are available
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
//If the result is successful update the item
|
||||
//NOTE: This code is over simplified, you need to make sure the Id's match
|
||||
const item = await res[i].item.select("Id, Title")<{
|
||||
Id: number;
|
||||
Title: string;
|
||||
}>();
|
||||
items[i].Name = item.Title;
|
||||
}
|
||||
|
||||
//only update state if we are still mounted
|
||||
if (mounted) {
|
||||
//Update the state
|
||||
setDocuments(items);
|
||||
setError(false);
|
||||
}
|
||||
} catch (err) {
|
||||
if (mounted) {
|
||||
setError(true);
|
||||
}
|
||||
Logger.write(
|
||||
`${LOG_SOURCE} (updating titles) - ${JSON.stringify(err)} - `,
|
||||
LogLevel.Error
|
||||
);
|
||||
}
|
||||
|
||||
//best practice to return a cleanup method in scenarios where component unmounts before completion
|
||||
return () => (mounted = false);
|
||||
};
|
||||
|
||||
return [documents, updateDocuments, totalSize, isError] as const;
|
||||
};
|
||||
|
||||
export default useDocuments;
|
|
@ -0,0 +1,11 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
|
||||
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
|
||||
"AppSharePointEnvironment": "The app is running on SharePoint page",
|
||||
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
declare interface IPnPjsExampleWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
AppLocalEnvironmentSharePoint: string;
|
||||
AppLocalEnvironmentTeams: string;
|
||||
AppSharePointEnvironment: string;
|
||||
AppTeamsTabEnvironment: string;
|
||||
}
|
||||
|
||||
declare module 'PnPjsExampleWebPartStrings' {
|
||||
const strings: IPnPjsExampleWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
// import pnp and pnp logging system
|
||||
import { spfi, SPFI, SPFx } from "@pnp/sp";
|
||||
import { LogLevel, PnPLogging } from "@pnp/logging";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/lists";
|
||||
import "@pnp/sp/items";
|
||||
import "@pnp/sp/batching";
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var _sp: SPFI = null;
|
||||
|
||||
export const getSP = (context?: WebPartContext): SPFI => {
|
||||
if (context != null) {
|
||||
//You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
|
||||
// The LogLevel set's at what level a message will be written to the console
|
||||
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
|
||||
}
|
||||
return _sp;
|
||||
};
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 542 B |
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue