├── Demos ├── 02-spcrud │ ├── src │ │ ├── models │ │ │ ├── ButtonClickedCallback.ts │ │ │ ├── index.ts │ │ │ └── ICountryListItem.ts │ │ ├── index.ts │ │ └── webparts │ │ │ └── spFxHttpClientDemo │ │ │ ├── assets │ │ │ ├── welcome-dark.png │ │ │ └── welcome-light.png │ │ │ ├── components │ │ │ ├── ISpFxHttpClientDemoProps.ts │ │ │ ├── SpFxHttpClientDemo.module.scss │ │ │ └── SpFxHttpClientDemo.tsx │ │ │ ├── loc │ │ │ ├── mystrings.d.ts │ │ │ └── en-us.js │ │ │ ├── SpFxHttpClientDemoWebPart.manifest.json │ │ │ └── SpFxHttpClientDemoWebPart.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── teams │ │ ├── 02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png │ │ └── 02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png │ ├── gulpfile.js │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js ├── 01-spfxhttpclient │ ├── src │ │ ├── models │ │ │ ├── ButtonClickedCallback.ts │ │ │ ├── index.ts │ │ │ └── ICountryListItem.ts │ │ ├── index.ts │ │ └── webparts │ │ │ └── spFxHttpClientDemo │ │ │ ├── assets │ │ │ ├── welcome-dark.png │ │ │ └── welcome-light.png │ │ │ ├── components │ │ │ ├── ISpFxHttpClientDemoProps.ts │ │ │ ├── SpFxHttpClientDemo.module.scss │ │ │ └── SpFxHttpClientDemo.tsx │ │ │ ├── loc │ │ │ ├── mystrings.d.ts │ │ │ └── en-us.js │ │ │ ├── SpFxHttpClientDemoWebPart.manifest.json │ │ │ └── SpFxHttpClientDemoWebPart.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── teams │ │ ├── 02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png │ │ └── 02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png │ ├── gulpfile.js │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js ├── 03-uploadfile │ ├── src │ │ ├── index.ts │ │ └── webparts │ │ │ └── fileUpload │ │ │ ├── assets │ │ │ ├── welcome-dark.png │ │ │ └── welcome-light.png │ │ │ ├── loc │ │ │ ├── mystrings.d.ts │ │ │ └── en-us.js │ │ │ ├── FileUploadWebPart.module.scss │ │ │ ├── FileUploadWebPart.manifest.json │ │ │ └── FileUploadWebPart.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── teams │ │ ├── 47dc7aeb-db59-443d-8085-5e2d4cfe42df_color.png │ │ └── 47dc7aeb-db59-443d-8085-5e2d4cfe42df_outline.png │ ├── gulpfile.js │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js └── README.md ├── 03 upload file.pptx ├── Images ├── all-buttons.png ├── get-items-sp.png ├── add-items-sp-01.png ├── add-webpart-01.png ├── add-webpart-02.png ├── add-webpart-03.png ├── add-webpart-04.png ├── delete-items-sp-01.png ├── local-workbench-01.png ├── local-workbench-02.png ├── update-items-sp-01.png ├── update-items-sp-02.png ├── create-countries-list01.png └── create-countries-list02.png ├── 02 spfx sprest crud.pptx ├── 01 spfx + sp rest api.pptx ├── .vscode └── settings.json ├── .gitignore ├── LICENSE ├── SECURITY.md ├── README.md └── Lab.md /Demos/02-spcrud/src/models/ButtonClickedCallback.ts: -------------------------------------------------------------------------------- 1 | export type ButtonClickedCallback = () => void; 2 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/models/ButtonClickedCallback.ts: -------------------------------------------------------------------------------- 1 | export type ButtonClickedCallback = () => void; 2 | -------------------------------------------------------------------------------- /03 upload file.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/03 upload file.pptx -------------------------------------------------------------------------------- /Demos/02-spcrud/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ButtonClickedCallback'; 2 | export * from './ICountryListItem'; 3 | -------------------------------------------------------------------------------- /Images/all-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/all-buttons.png -------------------------------------------------------------------------------- /02 spfx sprest crud.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/02 spfx sprest crud.pptx -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ButtonClickedCallback'; 2 | export * from './ICountryListItem'; 3 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /Images/get-items-sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/get-items-sp.png -------------------------------------------------------------------------------- /01 spfx + sp rest api.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/01 spfx + sp rest api.pptx -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /Demos/02-spcrud/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/src/models/ICountryListItem.ts: -------------------------------------------------------------------------------- 1 | export interface ICountryListItem { 2 | Id: string; 3 | Title: string; 4 | } 5 | -------------------------------------------------------------------------------- /Images/add-items-sp-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/add-items-sp-01.png -------------------------------------------------------------------------------- /Images/add-webpart-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/add-webpart-01.png -------------------------------------------------------------------------------- /Images/add-webpart-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/add-webpart-02.png -------------------------------------------------------------------------------- /Images/add-webpart-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/add-webpart-03.png -------------------------------------------------------------------------------- /Images/add-webpart-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/add-webpart-04.png -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /Images/delete-items-sp-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/delete-items-sp-01.png -------------------------------------------------------------------------------- /Images/local-workbench-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/local-workbench-01.png -------------------------------------------------------------------------------- /Images/local-workbench-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/local-workbench-02.png -------------------------------------------------------------------------------- /Images/update-items-sp-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/update-items-sp-01.png -------------------------------------------------------------------------------- /Images/update-items-sp-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/update-items-sp-02.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/models/ICountryListItem.ts: -------------------------------------------------------------------------------- 1 | export interface ICountryListItem { 2 | Id: string; 3 | Title: string; 4 | } 5 | -------------------------------------------------------------------------------- /Images/create-countries-list01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/create-countries-list01.png -------------------------------------------------------------------------------- /Images/create-countries-list02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Images/create-countries-list02.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdownlint.config": { 3 | "MD028": false, 4 | "MD025": { 5 | "front_matter_title": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/assets/welcome-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/03-uploadfile/src/webparts/fileUpload/assets/welcome-dark.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /Demos/02-spcrud/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/02-spcrud/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/assets/welcome-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/03-uploadfile/src/webparts/fileUpload/assets/welcome-light.png -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/assets/welcome-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/02-spcrud/src/webparts/spFxHttpClientDemo/assets/welcome-dark.png -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/assets/welcome-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/02-spcrud/src/webparts/spFxHttpClientDemo/assets/welcome-light.png -------------------------------------------------------------------------------- /Demos/02-spcrud/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/02-spcrud/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png -------------------------------------------------------------------------------- /Demos/03-uploadfile/teams/47dc7aeb-db59-443d-8085-5e2d4cfe42df_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/03-uploadfile/teams/47dc7aeb-db59-443d-8085-5e2d4cfe42df_color.png -------------------------------------------------------------------------------- /Demos/03-uploadfile/teams/47dc7aeb-db59-443d-8085-5e2d4cfe42df_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/03-uploadfile/teams/47dc7aeb-db59-443d-8085-5e2d4cfe42df_outline.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/01-spfxhttpclient/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_color.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/assets/welcome-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/assets/welcome-dark.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/01-spfxhttpclient/teams/02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a_outline.png -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/assets/welcome-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-spcontent/master/Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/assets/welcome-light.png -------------------------------------------------------------------------------- /Demos/02-spcrud/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "file-upload", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "sp-fx-http-client-demo", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "sp-fx-http-client-demo", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/components/ISpFxHttpClientDemoProps.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ButtonClickedCallback, 3 | ICountryListItem 4 | } from '../../../models'; 5 | 6 | export interface ISpFxHttpClientDemoProps { 7 | spListItems: ICountryListItem[]; 8 | onGetListItems?: ButtonClickedCallback; 9 | isDarkTheme: boolean; 10 | environmentMessage: string; 11 | hasTeamsContext: boolean; 12 | userDisplayName: string; 13 | } 14 | -------------------------------------------------------------------------------- /Demos/02-spcrud/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /Demos/02-spcrud/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/jest-output": true, 10 | "**/lib-amd": true, 11 | "src/**/*.scss.ts": true 12 | }, 13 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 14 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/jest-output": true, 10 | "**/lib-amd": true, 11 | "src/**/*.scss.ts": true 12 | }, 13 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 14 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/jest-output": true, 10 | "**/lib-amd": true, 11 | "src/**/*.scss.ts": true 12 | }, 13 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 14 | } -------------------------------------------------------------------------------- /Demos/03-uploadfile/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/components/ISpFxHttpClientDemoProps.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ButtonClickedCallback, 3 | ICountryListItem 4 | } from '../../../models'; 5 | 6 | export interface ISpFxHttpClientDemoProps { 7 | spListItems: ICountryListItem[]; 8 | onGetListItems?: ButtonClickedCallback; 9 | onAddListItem?: ButtonClickedCallback; 10 | onUpdateListItem?: ButtonClickedCallback; 11 | onDeleteListItem?: ButtonClickedCallback; 12 | isDarkTheme: boolean; 13 | environmentMessage: string; 14 | hasTeamsContext: boolean; 15 | userDisplayName: string; 16 | } 17 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "file-upload-web-part": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/webparts/fileUpload/FileUploadWebPart.js", 9 | "manifest": "./src/webparts/fileUpload/FileUploadWebPart.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "FileUploadWebPartStrings": "lib/webparts/fileUpload/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demos/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft SharePoint Framework Training Module - Work with SharePoint Content using the SharePoint Framework 2 | 3 | This folder contains demos associated with the slide decks in this module. 4 | 5 | ## Demos 6 | 7 | - [Retrieve and display list data with the SharePoint REST API](./01-spfxhttpclient) 8 | - [Write operations using the SharePoint Framework APIs and SharePoint REST API](./02-spcrud) 9 | - [Upload files to SharePoint libraries with the SharePoint REST API](./03-uploadfile) 10 | 11 | ## Running demonstrations 12 | 13 | Each demonstration is included as source code for convenience. -------------------------------------------------------------------------------- /Demos/02-spcrud/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "sp-fx-http-client-demo-web-part": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.js", 9 | "manifest": "./src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "SpFxHttpClientDemoWebPartStrings": "lib/webparts/spFxHttpClientDemo/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "sp-fx-http-client-demo-web-part": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.js", 9 | "manifest": "./src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "SpFxHttpClientDemoWebPartStrings": "lib/webparts/spFxHttpClientDemo/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/loc/mystrings.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IFileUploadWebPartStrings { 2 | PropertyPaneDescription: string; 3 | BasicGroupName: string; 4 | DescriptionFieldLabel: string; 5 | AppLocalEnvironmentSharePoint: string; 6 | AppLocalEnvironmentTeams: string; 7 | AppLocalEnvironmentOffice: string; 8 | AppLocalEnvironmentOutlook: string; 9 | AppSharePointEnvironment: string; 10 | AppTeamsTabEnvironment: string; 11 | AppOfficeEnvironment: string; 12 | AppOutlookEnvironment: string; 13 | UnknownEnvironment: string; 14 | } 15 | 16 | declare module 'FileUploadWebPartStrings' { 17 | const strings: IFileUploadWebPartStrings; 18 | export = strings; 19 | } 20 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "18.20.4", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.24.0" 9 | }, 10 | "version": "1.20.0", 11 | "libraryName": "file-upload", 12 | "libraryId": "055f1422-eeb6-454a-9452-8d793ac3abe3", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "FileUpload", 16 | "solutionShortDescription": "FileUpload description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "webpart" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/loc/mystrings.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISpFxHttpClientDemoWebPartStrings { 2 | PropertyPaneDescription: string; 3 | BasicGroupName: string; 4 | DescriptionFieldLabel: string; 5 | AppLocalEnvironmentSharePoint: string; 6 | AppLocalEnvironmentTeams: string; 7 | AppLocalEnvironmentOffice: string; 8 | AppLocalEnvironmentOutlook: string; 9 | AppSharePointEnvironment: string; 10 | AppTeamsTabEnvironment: string; 11 | AppOfficeEnvironment: string; 12 | AppOutlookEnvironment: string; 13 | UnknownEnvironment: string; 14 | } 15 | 16 | declare module 'SpFxHttpClientDemoWebPartStrings' { 17 | const strings: ISpFxHttpClientDemoWebPartStrings; 18 | export = strings; 19 | } 20 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/loc/mystrings.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISpFxHttpClientDemoWebPartStrings { 2 | PropertyPaneDescription: string; 3 | BasicGroupName: string; 4 | DescriptionFieldLabel: string; 5 | AppLocalEnvironmentSharePoint: string; 6 | AppLocalEnvironmentTeams: string; 7 | AppLocalEnvironmentOffice: string; 8 | AppLocalEnvironmentOutlook: string; 9 | AppSharePointEnvironment: string; 10 | AppTeamsTabEnvironment: string; 11 | AppOfficeEnvironment: string; 12 | AppOutlookEnvironment: string; 13 | UnknownEnvironment: string; 14 | } 15 | 16 | declare module 'SpFxHttpClientDemoWebPartStrings' { 17 | const strings: ISpFxHttpClientDemoWebPartStrings; 18 | export = strings; 19 | } 20 | -------------------------------------------------------------------------------- /Demos/02-spcrud/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "18.20.4", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.24.0" 9 | }, 10 | "version": "1.20.0", 11 | "libraryName": "sp-fx-http-client-demo", 12 | "libraryId": "61367c61-8a8d-44fe-921f-e9321142a6c0", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "SPFxHttpClientDemo", 16 | "solutionShortDescription": "SPFxHttpClientDemo description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "webpart" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "18.20.4", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.24.0" 9 | }, 10 | "version": "1.20.0", 11 | "libraryName": "sp-fx-http-client-demo", 12 | "libraryId": "61367c61-8a8d-44fe-921f-e9321142a6c0", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "SPFxHttpClientDemo", 16 | "solutionShortDescription": "SPFxHttpClientDemo description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "webpart" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demos/02-spcrud/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "msedge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Demos/03-uploadfile/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "msedge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "msedge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /Demos/02-spcrud/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "noImplicitAny": true, 16 | 17 | "typeRoots": [ 18 | "./node_modules/@types", 19 | "./node_modules/@microsoft" 20 | ], 21 | "types": [ 22 | "webpack-env" 23 | ], 24 | "lib": [ 25 | "es5", 26 | "dom", 27 | "es2015.collection", 28 | "es2015.promise" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "noImplicitAny": true, 16 | 17 | "typeRoots": [ 18 | "./node_modules/@types", 19 | "./node_modules/@microsoft" 20 | ], 21 | "types": [ 22 | "webpack-env" 23 | ], 24 | "lib": [ 25 | "es5", 26 | "dom", 27 | "es2015.collection", 28 | "es2015.promise" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "noImplicitAny": true, 16 | 17 | "typeRoots": [ 18 | "./node_modules/@types", 19 | "./node_modules/@microsoft" 20 | ], 21 | "types": [ 22 | "webpack-env" 23 | ], 24 | "lib": [ 25 | "es5", 26 | "dom", 27 | "es2015.collection", 28 | "es2015.promise" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Description", 4 | "BasicGroupName": "Group Name", 5 | "DescriptionFieldLabel": "Description Field", 6 | "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part", 7 | "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app", 8 | "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com", 9 | "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook", 10 | "AppSharePointEnvironment": "The app is running on SharePoint page", 11 | "AppTeamsTabEnvironment": "The app is running in Microsoft Teams", 12 | "AppOfficeEnvironment": "The app is running in office.com", 13 | "AppOutlookEnvironment": "The app is running in Outlook", 14 | "UnknownEnvironment": "The app is running in an unknown environment" 15 | } 16 | }); -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Description", 4 | "BasicGroupName": "Group Name", 5 | "DescriptionFieldLabel": "Description Field", 6 | "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part", 7 | "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app", 8 | "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com", 9 | "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook", 10 | "AppSharePointEnvironment": "The app is running on SharePoint page", 11 | "AppTeamsTabEnvironment": "The app is running in Microsoft Teams", 12 | "AppOfficeEnvironment": "The app is running in office.com", 13 | "AppOutlookEnvironment": "The app is running in Outlook", 14 | "UnknownEnvironment": "The app is running in an unknown environment" 15 | } 16 | }); -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/FileUploadWebPart.module.scss: -------------------------------------------------------------------------------- 1 | @import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss'; 2 | 3 | .fileUpload { 4 | overflow: hidden; 5 | padding: 1em; 6 | color: "[theme:bodyText, default: #323130]"; 7 | color: var(--bodyText); 8 | &.teams { 9 | font-family: $ms-font-family-fallbacks; 10 | } 11 | } 12 | 13 | .welcome { 14 | text-align: center; 15 | } 16 | 17 | .welcomeImage { 18 | width: 100%; 19 | max-width: 420px; 20 | } 21 | 22 | .links { 23 | a { 24 | text-decoration: none; 25 | color: "[theme:link, default:#03787c]"; 26 | color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only 27 | 28 | &:hover { 29 | text-decoration: underline; 30 | color: "[theme:linkHovered, default: #014446]"; 31 | color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only 32 | } 33 | } 34 | } 35 | 36 | .inputs { 37 | padding-top: 20px; 38 | } 39 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Description", 4 | "BasicGroupName": "Group Name", 5 | "DescriptionFieldLabel": "Description Field", 6 | "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part", 7 | "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app", 8 | "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com", 9 | "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook", 10 | "AppSharePointEnvironment": "The app is running on SharePoint page", 11 | "AppTeamsTabEnvironment": "The app is running in Microsoft Teams", 12 | "AppOfficeEnvironment": "The app is running in office.com", 13 | "AppOutlookEnvironment": "The app is running in Outlook", 14 | "UnknownEnvironment": "The app is running in an unknown environment" 15 | } 16 | }); -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.module.scss: -------------------------------------------------------------------------------- 1 | @import '~@fluentui/react/dist/sass/References.scss'; 2 | 3 | .spFxHttpClientDemo { 4 | overflow: hidden; 5 | padding: 1em; 6 | color: "[theme:bodyText, default: #323130]"; 7 | color: var(--bodyText); 8 | &.teams { 9 | font-family: $ms-font-family-fallbacks; 10 | } 11 | } 12 | 13 | .welcome { 14 | text-align: center; 15 | } 16 | 17 | .welcomeImage { 18 | width: 100%; 19 | max-width: 420px; 20 | } 21 | 22 | .links { 23 | a { 24 | text-decoration: none; 25 | color: "[theme:link, default:#03787c]"; 26 | color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only 27 | 28 | &:hover { 29 | text-decoration: underline; 30 | color: "[theme:linkHovered, default: #014446]"; 31 | color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only 32 | } 33 | } 34 | } 35 | 36 | .buttons { 37 | padding-top: 20px; 38 | > button { 39 | margin-right: 10px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.module.scss: -------------------------------------------------------------------------------- 1 | @import '~@fluentui/react/dist/sass/References.scss'; 2 | 3 | .spFxHttpClientDemo { 4 | overflow: hidden; 5 | padding: 1em; 6 | color: "[theme:bodyText, default: #323130]"; 7 | color: var(--bodyText); 8 | &.teams { 9 | font-family: $ms-font-family-fallbacks; 10 | } 11 | } 12 | 13 | .welcome { 14 | text-align: center; 15 | } 16 | 17 | .welcomeImage { 18 | width: 100%; 19 | max-width: 420px; 20 | } 21 | 22 | .links { 23 | a { 24 | text-decoration: none; 25 | color: "[theme:link, default:#03787c]"; 26 | color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only 27 | 28 | &:hover { 29 | text-decoration: underline; 30 | color: "[theme:linkHovered, default: #014446]"; 31 | color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only 32 | } 33 | } 34 | } 35 | 36 | .buttons { 37 | padding-top: 20px; 38 | > button { 39 | margin-right: 10px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .DS_Store -------------------------------------------------------------------------------- /Demos/03-uploadfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-upload", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=18.17.1 <19.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "@microsoft/sp-core-library": "1.20.0", 17 | "@microsoft/sp-component-base": "1.20.0", 18 | "@microsoft/sp-property-pane": "1.20.0", 19 | "@microsoft/sp-webpart-base": "1.20.0", 20 | "@microsoft/sp-lodash-subset": "1.20.0", 21 | "@microsoft/sp-office-ui-fabric-core": "1.20.0" 22 | }, 23 | "devDependencies": { 24 | "@microsoft/rush-stack-compiler-4.7": "0.1.0", 25 | "@rushstack/eslint-config": "4.0.1", 26 | "@microsoft/eslint-plugin-spfx": "1.20.2", 27 | "@microsoft/eslint-config-spfx": "1.20.2", 28 | "@microsoft/sp-build-web": "1.20.2", 29 | "@types/webpack-env": "~1.15.2", 30 | "ajv": "^6.12.5", 31 | "eslint": "8.57.0", 32 | "gulp": "4.0.2", 33 | "typescript": "4.7.4", 34 | "@microsoft/sp-module-interfaces": "1.20.2", 35 | "@fluentui/react": "^8.106.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "file-upload-client-side-solution", 5 | "id": "055f1422-eeb6-454a-9452-8d793ac3abe3", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.20.0" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "FileUpload description" 20 | }, 21 | "longDescription": { 22 | "default": "FileUpload description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "file-upload Feature", 31 | "description": "The feature that activates elements of the file-upload solution.", 32 | "id": "4a88f553-1ace-49c3-ab5b-129fa9cd0cc3", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/file-upload.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/FileUploadWebPart.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", 3 | "id": "47dc7aeb-db59-443d-8085-5e2d4cfe42df", 4 | "alias": "FileUploadWebPart", 5 | "componentType": "WebPart", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"], 16 | "supportsThemeVariants": true, 17 | 18 | "preconfiguredEntries": [{ 19 | "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced 20 | "group": { "default": "Advanced" }, 21 | "title": { "default": "FileUpload" }, 22 | "description": { "default": "FileUpload description" }, 23 | "officeFabricIconFontName": "Page", 24 | "properties": { 25 | "description": "FileUpload" 26 | } 27 | }] 28 | } 29 | -------------------------------------------------------------------------------- /Demos/02-spcrud/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "sp-fx-http-client-demo-client-side-solution", 5 | "id": "61367c61-8a8d-44fe-921f-e9321142a6c0", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.20.0" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "SPFxHttpClientDemo description" 20 | }, 21 | "longDescription": { 22 | "default": "SPFxHttpClientDemo description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "sp-fx-http-client-demo Feature", 31 | "description": "The feature that activates elements of the sp-fx-http-client-demo solution.", 32 | "id": "72afc599-a83f-4157-a0cd-f72f0f12068d", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/sp-fx-http-client-demo.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "sp-fx-http-client-demo-client-side-solution", 5 | "id": "61367c61-8a8d-44fe-921f-e9321142a6c0", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.20.0" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "SPFxHttpClientDemo description" 20 | }, 21 | "longDescription": { 22 | "default": "SPFxHttpClientDemo description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "sp-fx-http-client-demo Feature", 31 | "description": "The feature that activates elements of the sp-fx-http-client-demo solution.", 32 | "id": "72afc599-a83f-4157-a0cd-f72f0f12068d", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/sp-fx-http-client-demo.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", 3 | "id": "02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a", 4 | "alias": "SpFxHttpClientDemoWebPart", 5 | "componentType": "WebPart", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"], 16 | "supportsThemeVariants": true, 17 | 18 | "preconfiguredEntries": [{ 19 | "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced 20 | "group": { "default": "Advanced" }, 21 | "title": { "default": "SPFxHttpClientDemo" }, 22 | "description": { "default": "SPFxHttpClientDemo description" }, 23 | "officeFabricIconFontName": "Page", 24 | "properties": { 25 | "description": "SPFxHttpClientDemo" 26 | } 27 | }] 28 | } 29 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", 3 | "id": "02b0efbe-62c2-4cc6-8c61-cc5a346e2a7a", 4 | "alias": "SpFxHttpClientDemoWebPart", 5 | "componentType": "WebPart", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"], 16 | "supportsThemeVariants": true, 17 | 18 | "preconfiguredEntries": [{ 19 | "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced 20 | "group": { "default": "Advanced" }, 21 | "title": { "default": "SPFxHttpClientDemo" }, 22 | "description": { "default": "SPFxHttpClientDemo description" }, 23 | "officeFabricIconFontName": "Page", 24 | "properties": { 25 | "description": "SPFxHttpClientDemo" 26 | } 27 | }] 28 | } 29 | -------------------------------------------------------------------------------- /Demos/02-spcrud/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sp-fx-http-client-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=18.17.1 <19.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1", 18 | "@fluentui/react": "^8.106.4", 19 | "@microsoft/sp-core-library": "1.20.0", 20 | "@microsoft/sp-component-base": "1.20.0", 21 | "@microsoft/sp-property-pane": "1.20.0", 22 | "@microsoft/sp-webpart-base": "1.20.0", 23 | "@microsoft/sp-lodash-subset": "1.20.0", 24 | "@microsoft/sp-office-ui-fabric-core": "1.20.0" 25 | }, 26 | "devDependencies": { 27 | "@microsoft/rush-stack-compiler-4.7": "0.1.0", 28 | "@rushstack/eslint-config": "4.0.1", 29 | "@microsoft/eslint-plugin-spfx": "1.20.2", 30 | "@microsoft/eslint-config-spfx": "1.20.2", 31 | "@microsoft/sp-build-web": "1.20.2", 32 | "@types/webpack-env": "~1.15.2", 33 | "ajv": "^6.12.5", 34 | "eslint": "8.57.0", 35 | "gulp": "4.0.2", 36 | "typescript": "4.7.4", 37 | "@types/react": "17.0.45", 38 | "@types/react-dom": "17.0.17", 39 | "eslint-plugin-react-hooks": "4.3.0", 40 | "@microsoft/sp-module-interfaces": "1.20.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sp-fx-http-client-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=18.17.1 <19.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1", 18 | "@fluentui/react": "^8.106.4", 19 | "@microsoft/sp-core-library": "1.20.0", 20 | "@microsoft/sp-component-base": "1.20.0", 21 | "@microsoft/sp-property-pane": "1.20.0", 22 | "@microsoft/sp-webpart-base": "1.20.0", 23 | "@microsoft/sp-lodash-subset": "1.20.0", 24 | "@microsoft/sp-office-ui-fabric-core": "1.20.0" 25 | }, 26 | "devDependencies": { 27 | "@microsoft/rush-stack-compiler-4.7": "0.1.0", 28 | "@rushstack/eslint-config": "4.0.1", 29 | "@microsoft/eslint-plugin-spfx": "1.20.2", 30 | "@microsoft/eslint-config-spfx": "1.20.2", 31 | "@microsoft/sp-build-web": "1.20.2", 32 | "@types/webpack-env": "~1.15.2", 33 | "ajv": "^6.12.5", 34 | "eslint": "8.57.0", 35 | "gulp": "4.0.2", 36 | "typescript": "4.7.4", 37 | "@types/react": "17.0.45", 38 | "@types/react-dom": "17.0.17", 39 | "eslint-plugin-react-hooks": "4.3.0", 40 | "@microsoft/sp-module-interfaces": "1.20.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './SpFxHttpClientDemo.module.scss'; 3 | import type { ISpFxHttpClientDemoProps } from './ISpFxHttpClientDemoProps'; 4 | import { escape } from '@microsoft/sp-lodash-subset'; 5 | 6 | export default class SpFxHttpClientDemo extends React.Component { 7 | 8 | private onGetListItemsClicked = (event: React.MouseEvent): void => { 9 | event.preventDefault(); 10 | 11 | if (this.props.onGetListItems) this.props.onGetListItems(); 12 | } 13 | 14 | public render(): React.ReactElement { 15 | const { 16 | spListItems, 17 | isDarkTheme, 18 | environmentMessage, 19 | hasTeamsContext, 20 | userDisplayName 21 | } = this.props; 22 | 23 | return ( 24 |
25 |
26 | 27 |

Well done, {escape(userDisplayName)}!

28 |
{environmentMessage}
29 |
30 |
31 | 32 |
33 |
34 |
    35 | {spListItems && spListItems.map((list) => 36 |
  • 37 | Id: {list.Id}, Title: {list.Title} 38 |
  • 39 | ) 40 | } 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './SpFxHttpClientDemo.module.scss'; 3 | import type { ISpFxHttpClientDemoProps } from './ISpFxHttpClientDemoProps'; 4 | import { escape } from '@microsoft/sp-lodash-subset'; 5 | 6 | export default class SpFxHttpClientDemo extends React.Component { 7 | 8 | private onGetListItemsClicked = (event: React.MouseEvent): void => { 9 | event.preventDefault(); 10 | 11 | if (this.props.onGetListItems) this.props.onGetListItems(); 12 | } 13 | 14 | public render(): React.ReactElement { 15 | const { 16 | spListItems, 17 | isDarkTheme, 18 | environmentMessage, 19 | hasTeamsContext, 20 | userDisplayName 21 | } = this.props; 22 | 23 | return ( 24 |
25 |
26 | 27 |

Well done, {escape(userDisplayName)}!

28 |
{environmentMessage}
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
37 |
    38 | {spListItems && spListItems.map((list) => 39 |
  • 40 | Id: {list.Id}, Title: {list.Title} 41 |
  • 42 | ) 43 | } 44 |
45 |
46 |
47 | ); 48 | } 49 | 50 | private onAddListItemClicked = (event: React.MouseEvent): void => { 51 | event.preventDefault(); 52 | 53 | if (this.props.onAddListItem) this.props.onAddListItem(); 54 | } 55 | 56 | private onUpdateListItemClicked = (event: React.MouseEvent): void => { 57 | event.preventDefault(); 58 | 59 | if (this.props.onUpdateListItem) this.props.onUpdateListItem(); 60 | } 61 | 62 | private onDeleteListItemClicked = (event: React.MouseEvent): void => { 63 | event.preventDefault(); 64 | 65 | if (this.props.onDeleteListItem) this.props.onDeleteListItem(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/README.md: -------------------------------------------------------------------------------- 1 | # file-upload 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.20.0-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /Demos/02-spcrud/README.md: -------------------------------------------------------------------------------- 1 | # sp-fx-http-client-demo 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.20.0-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/README.md: -------------------------------------------------------------------------------- 1 | # sp-fx-http-client-demo 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.20.0-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDom from 'react-dom'; 3 | import { Version } from '@microsoft/sp-core-library'; 4 | import { 5 | type IPropertyPaneConfiguration, 6 | PropertyPaneTextField 7 | } from '@microsoft/sp-property-pane'; 8 | import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; 9 | import { IReadonlyTheme } from '@microsoft/sp-component-base'; 10 | 11 | import * as strings from 'SpFxHttpClientDemoWebPartStrings'; 12 | import SpFxHttpClientDemo from './components/SpFxHttpClientDemo'; 13 | import { ISpFxHttpClientDemoProps } from './components/ISpFxHttpClientDemoProps'; 14 | 15 | import { SPHttpClient } from '@microsoft/sp-http'; 16 | import { ICountryListItem } from '../../models'; 17 | 18 | export interface ISpFxHttpClientDemoWebPartProps { 19 | description: string; 20 | } 21 | 22 | export default class SpFxHttpClientDemoWebPart extends BaseClientSideWebPart { 23 | 24 | private _isDarkTheme: boolean = false; 25 | private _environmentMessage: string = ''; 26 | private _countries: ICountryListItem[] = []; 27 | 28 | public render(): void { 29 | const element: React.ReactElement = React.createElement( 30 | SpFxHttpClientDemo, 31 | { 32 | spListItems: this._countries, 33 | onGetListItems: this._onGetListItems, 34 | isDarkTheme: this._isDarkTheme, 35 | environmentMessage: this._environmentMessage, 36 | hasTeamsContext: !!this.context.sdks.microsoftTeams, 37 | userDisplayName: this.context.pageContext.user.displayName 38 | } 39 | ); 40 | 41 | ReactDom.render(element, this.domElement); 42 | } 43 | 44 | protected onInit(): Promise { 45 | return this._getEnvironmentMessage().then(message => { 46 | this._environmentMessage = message; 47 | }); 48 | } 49 | 50 | private _onGetListItems = async (): Promise => { 51 | const response: ICountryListItem[] = await this._getListItems(); 52 | this._countries = response; 53 | this.render(); 54 | } 55 | 56 | private async _getListItems(): Promise { 57 | const response = await this.context.spHttpClient.get( 58 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title`, 59 | SPHttpClient.configurations.v1); 60 | 61 | if (!response.ok) { 62 | const responseText = await response.text(); 63 | throw new Error(responseText); 64 | } 65 | 66 | const responseJson = await response.json(); 67 | 68 | return responseJson.value as ICountryListItem[]; 69 | } 70 | 71 | private _getEnvironmentMessage(): Promise { 72 | if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook 73 | return this.context.sdks.microsoftTeams.teamsJs.app.getContext() 74 | .then(context => { 75 | let environmentMessage: string = ''; 76 | switch (context.app.host.name) { 77 | case 'Office': // running in Office 78 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; 79 | break; 80 | case 'Outlook': // running in Outlook 81 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; 82 | break; 83 | case 'Teams': // running in Teams 84 | case 'TeamsModern': 85 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; 86 | break; 87 | default: 88 | environmentMessage = strings.UnknownEnvironment; 89 | } 90 | 91 | return environmentMessage; 92 | }); 93 | } 94 | 95 | return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment); 96 | } 97 | 98 | protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { 99 | if (!currentTheme) { 100 | return; 101 | } 102 | 103 | this._isDarkTheme = !!currentTheme.isInverted; 104 | const { 105 | semanticColors 106 | } = currentTheme; 107 | 108 | if (semanticColors) { 109 | this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null); 110 | this.domElement.style.setProperty('--link', semanticColors.link || null); 111 | this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null); 112 | } 113 | 114 | } 115 | 116 | protected onDispose(): void { 117 | ReactDom.unmountComponentAtNode(this.domElement); 118 | } 119 | 120 | protected get dataVersion(): Version { 121 | return Version.parse('1.0'); 122 | } 123 | 124 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 125 | return { 126 | pages: [ 127 | { 128 | header: { 129 | description: strings.PropertyPaneDescription 130 | }, 131 | groups: [ 132 | { 133 | groupName: strings.BasicGroupName, 134 | groupFields: [ 135 | PropertyPaneTextField('description', { 136 | label: strings.DescriptionFieldLabel 137 | }) 138 | ] 139 | } 140 | ] 141 | } 142 | ] 143 | }; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft SharePoint Framework Training Module - Work with SharePoint Content using the SharePoint Framework 2 | 3 | This module will introduce you to working with SharePoint content in SharePoint Framework projects. You'll learn about the SharePoint REST API, do CRUD operations with SharePoint data, the local workbench and creating mock data in SharePoint Framework. 4 | 5 | > This module is also published as a Microsoft Learn module: [Work with SharePoint Content using the SharePoint Framework](https://learn.microsoft.com/training/modules/sharepoint-spfx-spcontent) 6 | 7 | ## Lab - Work with SharePoint Content using the SharePoint Framework 8 | 9 | The lab for this module is available in multiple units within the associated Microsoft Learn module. Use the following links to jump to the specific unit. Each Microsoft Learn unit represents a different lab exercise & demo in the presentation. 10 | 11 | - [Exercise - Retrieve and display list data with the SharePoint REST API](https://learn.microsoft.com/training/modules/sharepoint-spfx-spcontent/3-exercise-get-data) 12 | 13 | In this exercise, you'll create a SharePoint Framework (SPFx) web part that will get and display data from a SharePoint list using the SharePoint REST API. 14 | 15 | - [Exercise - Write operations using the SharePoint Framework APIs and SharePoint REST API](https://learn.microsoft.com/training/modules/sharepoint-spfx-spcontent/5-exercise-crud-operations) 16 | 17 | In this exercise, you'll extend the SharePoint Framework project from the previous exercise to add write capabilities using the SharePoint REST API. 18 | 19 | - [Exercise - Upload files to SharePoint libraries with the SharePoint REST API](https://learn.microsoft.com/training/modules/sharepoint-spfx-spcontent/7-exercise-upload-file) 20 | 21 | In this exercise, you'll create a new web part that someone can use to select a file to upload to the current site's Documents library. 22 | 23 | ## Demos 24 | 25 | - [Retrieve and display list data with the SharePoint REST API](./Demos/01-spfxhttpclient) 26 | - [Write operations using the SharePoint Framework APIs and SharePoint REST API](./Demos/02-spcrud) 27 | - [Upload files to SharePoint libraries with the SharePoint REST API](./Demos/03-uploadfile) 28 | 29 | ## Watch the Module - Video 30 | 31 | This module has been recorded and is available in the SharePoint Development YouTube channel: [SharePoint Framework Training - Developing with the SharePoint Framework: Working with SharePoint Content](https://www.youtube.com/watch?v=0OiC7AzoCVI&list=PLR9nK3mnD-OV-RPXQ3Lco845qoEy7VJoc) 32 | 33 | ## Contributors 34 | 35 | | Roles | Author(s) | 36 | | -------------------- | -------------------------------------------------------------------------------------------------------------- | 37 | | Lab Manuals / Slides | Andrew Connell (Microsoft MVP, [Voitanos](//github.com/voitanos)) [@andrewconnell](//github.com/andrewconnell) | 38 | | QA | Rob Windsor (Microsoft MVP) [@rob-windsor](//github.com/rob-windsor) | 39 | | Sponsor / Support | Vesa Juvonen (Microsoft) [@VesaJuvonen](//github.com/VesaJuvonen) | 40 | 41 | ## Version history 42 | 43 | | Version | Date | Comments | 44 | | ------- | ------------------ | ----------------------------------------------------- | 45 | | 1.21 | December 13, 2024 | FY2025Q2 content refresh; update to SPFx v1.20.0 | 46 | | 1.20 | June 24, 2024 | FY2024Q4 content refresh; update to SPFx v1.19.0 | 47 | | 1.19 | May 5, 2023 | FY2023Q4 content refresh; update to SPFx v1.17.1 | 48 | | 1.18 | February 28, 2023 | FY2023Q3 content refresh; update to SPFx v1.16.1 | 49 | | 1.17 | December 5, 2022 | FY2023Q1 content refresh | 50 | | 1.16 | September 5, 2022 | FY2023Q1 content refresh, update to SPFx v1.15.2 | 51 | | 1.15 | May 2, 2022 | FY2022Q3 content refresh, update to SPFx v1.14 | 52 | | 1.14 | March 7, 2022 | FY2022Q3 content refresh | 53 | | 1.13 | December 8, 2021 | FY2022Q2 content refresh, replace last section & demo | 54 | | 1.12 | September 13, 2021 | FY2022Q1 content refresh | 55 | | 1.11 | June 17, 2021 | FY2021Q4 content refresh | 56 | | 1.10 | March 6, 2021 | FY2021Q3 content refresh | 57 | | 1.9 | November 30, 2020 | FY2021Q2 content refresh | 58 | | 1.8 | August 31, 2020 | FY2021Q1 content refresh | 59 | | 1.7 | March 10, 2020 | FY2020Q3 content refresh & port module to MS Learn | 60 | | 1.6 | December 6, 2019 | FY2020Q2 content refresh | 61 | | 1.5 | September 2, 2019 | FY2020Q1 content refresh | 62 | | 1.4 | June 10, 2019 | FY2019Q4 content refresh | 63 | | 1.3 | March 11, 2019 | FY2019Q3 content refresh | 64 | | 1.2 | January 17, 2019 | Slide updates, added screencast link to readme | 65 | | 1.1 | December 14, 2018 | Rewritten to use latest guidance | 66 | | 1.0 | ~February 2017 | Initial release | 67 | 68 | ## Disclaimer 69 | 70 | **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.** 71 | 72 | ## Contributing 73 | 74 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 75 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 76 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 77 | 78 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 79 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 80 | provided by the bot. You will only need to do this once across all repos using our CLA. 81 | 82 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 83 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 84 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 85 | 86 | 87 | -------------------------------------------------------------------------------- /Demos/03-uploadfile/src/webparts/fileUpload/FileUploadWebPart.ts: -------------------------------------------------------------------------------- 1 | import { Version } from '@microsoft/sp-core-library'; 2 | import { 3 | type IPropertyPaneConfiguration, 4 | PropertyPaneTextField 5 | } from '@microsoft/sp-property-pane'; 6 | import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; 7 | import type { IReadonlyTheme } from '@microsoft/sp-component-base'; 8 | import { escape } from '@microsoft/sp-lodash-subset'; 9 | 10 | import styles from './FileUploadWebPart.module.scss'; 11 | import * as strings from 'FileUploadWebPartStrings'; 12 | 13 | import { 14 | ISPHttpClientOptions, 15 | SPHttpClient 16 | } from '@microsoft/sp-http'; 17 | 18 | export interface IFileUploadWebPartProps { 19 | description: string; 20 | } 21 | 22 | export default class FileUploadWebPart extends BaseClientSideWebPart { 23 | 24 | private _isDarkTheme: boolean = false; 25 | private _environmentMessage: string = ''; 26 | 27 | public render(): void { 28 | this.domElement.innerHTML = ` 29 |
30 |
31 | 32 |

Well done, ${escape(this.context.pageContext.user.displayName)}!

33 |
${this._environmentMessage}
34 |
Web part property value: ${escape(this.properties.description)}
35 |
36 |
37 |
38 | 39 |
40 |
`; 41 | 42 | // get reference to file control 43 | const inputFileElement = document.getElementsByClassName(`${styles.fileUpload}-fileUpload`)[0] as HTMLInputElement; 44 | 45 | // wire up button control 46 | const uploadButton = document.getElementsByClassName(`${styles.fileUpload}-uploadButton`)[0] as HTMLButtonElement; 47 | 48 | uploadButton.addEventListener('click', async () => { 49 | // get filename 50 | const filePathParts = inputFileElement.value.split('\\'); 51 | const fileName = filePathParts[filePathParts.length - 1]; 52 | 53 | // get file data 54 | if (inputFileElement.files) { 55 | const fileData = await this._getFileBuffer(inputFileElement.files[0]); 56 | 57 | // upload file 58 | await this._uploadFile(fileData, fileName); 59 | } 60 | }); 61 | } 62 | 63 | protected onInit(): Promise { 64 | return this._getEnvironmentMessage().then(message => { 65 | this._environmentMessage = message; 66 | }); 67 | } 68 | 69 | private _getFileBuffer(file: File): Promise { 70 | return new Promise((resolve, reject) => { 71 | const fileReader = new FileReader(); 72 | 73 | // write up error handler 74 | fileReader.onerror = (event: ProgressEvent) => { 75 | reject(event.target?.error); 76 | }; 77 | 78 | // wire up when finished reading file 79 | fileReader.onloadend = (event: ProgressEvent) => { 80 | resolve(event.target?.result as ArrayBuffer); 81 | }; 82 | 83 | // read file 84 | fileReader.readAsArrayBuffer(file); 85 | 86 | }); 87 | } 88 | 89 | private async _uploadFile(fileData: ArrayBuffer, fileName: string): Promise { 90 | 91 | // create target endpoint for REST API HTTP POST 92 | const endpoint = `${this.context.pageContext.web.absoluteUrl}/_api/web/lists/GetByTitle('Documents')/RootFolder/Files/add(overwrite=true,url='${fileName}')`; 93 | 94 | const options: ISPHttpClientOptions = { 95 | headers: { 'CONTENT-LENGTH': fileData.byteLength.toString() }, 96 | body: fileData 97 | }; 98 | 99 | // upload file 100 | const response = await this.context.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, options); 101 | 102 | if (response.status === 200) { 103 | alert('File uploaded successfully'); 104 | } else { 105 | throw new Error(`Error uploading file: ${response.statusText}`); 106 | } 107 | } 108 | 109 | private _getEnvironmentMessage(): Promise { 110 | if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook 111 | return this.context.sdks.microsoftTeams.teamsJs.app.getContext() 112 | .then(context => { 113 | let environmentMessage: string = ''; 114 | switch (context.app.host.name) { 115 | case 'Office': // running in Office 116 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; 117 | break; 118 | case 'Outlook': // running in Outlook 119 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; 120 | break; 121 | case 'Teams': // running in Teams 122 | case 'TeamsModern': 123 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; 124 | break; 125 | default: 126 | environmentMessage = strings.UnknownEnvironment; 127 | } 128 | 129 | return environmentMessage; 130 | }); 131 | } 132 | 133 | return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment); 134 | } 135 | 136 | protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { 137 | if (!currentTheme) { 138 | return; 139 | } 140 | 141 | this._isDarkTheme = !!currentTheme.isInverted; 142 | const { 143 | semanticColors 144 | } = currentTheme; 145 | 146 | if (semanticColors) { 147 | this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null); 148 | this.domElement.style.setProperty('--link', semanticColors.link || null); 149 | this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null); 150 | } 151 | 152 | } 153 | 154 | protected get dataVersion(): Version { 155 | return Version.parse('1.0'); 156 | } 157 | 158 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 159 | return { 160 | pages: [ 161 | { 162 | header: { 163 | description: strings.PropertyPaneDescription 164 | }, 165 | groups: [ 166 | { 167 | groupName: strings.BasicGroupName, 168 | groupFields: [ 169 | PropertyPaneTextField('description', { 170 | label: strings.DescriptionFieldLabel 171 | }) 172 | ] 173 | } 174 | ] 175 | } 176 | ] 177 | }; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Demos/02-spcrud/src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDom from 'react-dom'; 3 | import { Version } from '@microsoft/sp-core-library'; 4 | import { 5 | type IPropertyPaneConfiguration, 6 | PropertyPaneTextField 7 | } from '@microsoft/sp-property-pane'; 8 | import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; 9 | import { IReadonlyTheme } from '@microsoft/sp-component-base'; 10 | 11 | import * as strings from 'SpFxHttpClientDemoWebPartStrings'; 12 | import SpFxHttpClientDemo from './components/SpFxHttpClientDemo'; 13 | import { ISpFxHttpClientDemoProps } from './components/ISpFxHttpClientDemoProps'; 14 | 15 | import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; 16 | import { ICountryListItem } from '../../models'; 17 | 18 | export interface ISpFxHttpClientDemoWebPartProps { 19 | description: string; 20 | } 21 | 22 | export default class SpFxHttpClientDemoWebPart extends BaseClientSideWebPart { 23 | 24 | private _isDarkTheme: boolean = false; 25 | private _environmentMessage: string = ''; 26 | private _countries: ICountryListItem[] = []; 27 | 28 | public render(): void { 29 | const element: React.ReactElement = React.createElement( 30 | SpFxHttpClientDemo, 31 | { 32 | spListItems: this._countries, 33 | onGetListItems: this._onGetListItems, 34 | onAddListItem: this._onAddListItem, 35 | onUpdateListItem: this._onUpdateListItem, 36 | onDeleteListItem: this._onDeleteListItem, 37 | isDarkTheme: this._isDarkTheme, 38 | environmentMessage: this._environmentMessage, 39 | hasTeamsContext: !!this.context.sdks.microsoftTeams, 40 | userDisplayName: this.context.pageContext.user.displayName 41 | } 42 | ); 43 | 44 | ReactDom.render(element, this.domElement); 45 | } 46 | 47 | protected onInit(): Promise { 48 | return this._getEnvironmentMessage().then(message => { 49 | this._environmentMessage = message; 50 | }); 51 | } 52 | 53 | private _onGetListItems = async (): Promise => { 54 | const response: ICountryListItem[] = await this._getListItems(); 55 | this._countries = response; 56 | this.render(); 57 | } 58 | 59 | private async _getListItems(): Promise { 60 | const response = await this.context.spHttpClient.get( 61 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title`, 62 | SPHttpClient.configurations.v1); 63 | 64 | if (!response.ok) { 65 | const responseText = await response.text(); 66 | throw new Error(responseText); 67 | } 68 | 69 | const responseJson = await response.json(); 70 | 71 | return responseJson.value as ICountryListItem[]; 72 | } 73 | 74 | private _onAddListItem = async (): Promise => { 75 | const addResponse: SPHttpClientResponse = await this._addListItem(); 76 | 77 | if (!addResponse.ok) { 78 | const responseText = await addResponse.text(); 79 | throw new Error(responseText); 80 | } 81 | 82 | const getResponse: ICountryListItem[] = await this._getListItems(); 83 | this._countries = getResponse; 84 | this.render(); 85 | } 86 | 87 | private _onUpdateListItem = async (): Promise => { 88 | const updateResponse: SPHttpClientResponse = await this._updateListItem(); 89 | 90 | if (!updateResponse.ok) { 91 | const responseText = await updateResponse.text(); 92 | throw new Error(responseText); 93 | } 94 | 95 | const getResponse: ICountryListItem[] = await this._getListItems(); 96 | this._countries = getResponse; 97 | this.render(); 98 | } 99 | 100 | private _onDeleteListItem = async (): Promise => { 101 | const deleteResponse: SPHttpClientResponse = await this._deleteListItem(); 102 | 103 | if (!deleteResponse.ok) { 104 | const responseText = await deleteResponse.text(); 105 | throw new Error(responseText); 106 | } 107 | 108 | const getResponse: ICountryListItem[] = await this._getListItems(); 109 | this._countries = getResponse; 110 | this.render(); 111 | } 112 | 113 | private async _getItemEntityType(): Promise { 114 | const endpoint: string = this.context.pageContext.web.absoluteUrl + 115 | `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title`; 116 | 117 | const response = await this.context.spHttpClient.get( 118 | endpoint, 119 | SPHttpClient.configurations.v1); 120 | 121 | if (!response.ok) { 122 | const responseText = await response.text(); 123 | throw new Error(responseText); 124 | } 125 | 126 | const responseJson = await response.json(); 127 | 128 | return responseJson.ListItemEntityTypeFullName; 129 | } 130 | 131 | private async _addListItem(): Promise { 132 | const itemEntityType = await this._getItemEntityType(); 133 | 134 | /* eslint-disable @typescript-eslint/no-explicit-any */ 135 | const request: any = {}; 136 | request.body = JSON.stringify({ 137 | Title: new Date().toUTCString(), 138 | '@odata.type': itemEntityType 139 | }); 140 | /* eslint-enable @typescript-eslint/no-explicit-any */ 141 | 142 | const endpoint = this.context.pageContext.web.absoluteUrl + 143 | `/_api/web/lists/getbytitle('Countries')/items`; 144 | 145 | return this.context.spHttpClient.post( 146 | endpoint, 147 | SPHttpClient.configurations.v1, 148 | request); 149 | } 150 | 151 | private async _updateListItem(): Promise { 152 | const getEndpoint: string = this.context.pageContext.web.absoluteUrl + 153 | `/_api/web/lists/getbytitle('Countries')/items?` + 154 | `$select=Id,Title&$filter=Title eq 'United States'`; 155 | 156 | const getResponse = await this.context.spHttpClient.get( 157 | getEndpoint, 158 | SPHttpClient.configurations.v1); 159 | 160 | if (!getResponse.ok) { 161 | const responseText = await getResponse.text(); 162 | throw new Error(responseText); 163 | } 164 | 165 | const responseJson = await getResponse.json(); 166 | const listItem: ICountryListItem = responseJson.value[0]; 167 | 168 | listItem.Title = 'USA'; 169 | /* eslint-disable @typescript-eslint/no-explicit-any */ 170 | const request: any = {}; 171 | request.headers = { 172 | 'X-HTTP-Method': 'MERGE', 173 | 'IF-MATCH': (listItem as any)['@odata.etag'] 174 | }; 175 | /* eslint-enable @typescript-eslint/no-explicit-any */ 176 | request.body = JSON.stringify(listItem); 177 | 178 | const postEndpoint: string = this.context.pageContext.web.absoluteUrl + 179 | `/_api/web/lists/getbytitle('Countries')/items(${listItem.Id})`; 180 | 181 | return this.context.spHttpClient.post( 182 | postEndpoint, 183 | SPHttpClient.configurations.v1, 184 | request); 185 | } 186 | 187 | private async _deleteListItem(): Promise { 188 | const getEndpoint = this.context.pageContext.web.absoluteUrl + 189 | `/_api/web/lists/getbytitle('Countries')/items?` + 190 | `$select=Id,Title&$orderby=ID desc&$top=1`; 191 | 192 | const getResponse = await this.context.spHttpClient.get( 193 | getEndpoint, 194 | SPHttpClient.configurations.v1); 195 | 196 | if (!getResponse.ok) { 197 | const responseText = await getResponse.text(); 198 | throw new Error(responseText); 199 | } 200 | 201 | const responseJson = await getResponse.json(); 202 | const listItem: ICountryListItem = responseJson.value[0]; 203 | 204 | /* eslint-disable @typescript-eslint/no-explicit-any */ 205 | const request: any = {}; 206 | request.headers = { 207 | 'X-HTTP-Method': 'DELETE', 208 | 'IF-MATCH': '*' 209 | }; 210 | /* eslint-enable @typescript-eslint/no-explicit-any */ 211 | request.body = JSON.stringify(listItem); 212 | 213 | const postEndpoint = this.context.pageContext.web.absoluteUrl + 214 | `/_api/web/lists/getbytitle('Countries')/items(${listItem.Id})`; 215 | 216 | return this.context.spHttpClient.post( 217 | postEndpoint, 218 | SPHttpClient.configurations.v1, 219 | request); 220 | } 221 | 222 | private _getEnvironmentMessage(): Promise { 223 | if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook 224 | return this.context.sdks.microsoftTeams.teamsJs.app.getContext() 225 | .then(context => { 226 | let environmentMessage: string = ''; 227 | switch (context.app.host.name) { 228 | case 'Office': // running in Office 229 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; 230 | break; 231 | case 'Outlook': // running in Outlook 232 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; 233 | break; 234 | case 'Teams': // running in Teams 235 | case 'TeamsModern': 236 | environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; 237 | break; 238 | default: 239 | environmentMessage = strings.UnknownEnvironment; 240 | } 241 | 242 | return environmentMessage; 243 | }); 244 | } 245 | 246 | return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment); 247 | } 248 | 249 | protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { 250 | if (!currentTheme) { 251 | return; 252 | } 253 | 254 | this._isDarkTheme = !!currentTheme.isInverted; 255 | const { 256 | semanticColors 257 | } = currentTheme; 258 | 259 | if (semanticColors) { 260 | this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null); 261 | this.domElement.style.setProperty('--link', semanticColors.link || null); 262 | this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null); 263 | } 264 | 265 | } 266 | 267 | protected onDispose(): void { 268 | ReactDom.unmountComponentAtNode(this.domElement); 269 | } 270 | 271 | protected get dataVersion(): Version { 272 | return Version.parse('1.0'); 273 | } 274 | 275 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 276 | return { 277 | pages: [ 278 | { 279 | header: { 280 | description: strings.PropertyPaneDescription 281 | }, 282 | groups: [ 283 | { 284 | groupName: strings.BasicGroupName, 285 | groupFields: [ 286 | PropertyPaneTextField('description', { 287 | label: strings.DescriptionFieldLabel 288 | }) 289 | ] 290 | } 291 | ] 292 | } 293 | ] 294 | }; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /Demos/02-spcrud/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 24 | // Even if the compiler may be able to infer a type, this inference will be unavailable 25 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 26 | // but writing code is a much less important activity than reading it. 27 | // 28 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 29 | '@typescript-eslint/explicit-function-return-type': [ 30 | 1, 31 | { 32 | 'allowExpressions': true, 33 | 'allowTypedFunctionExpressions': true, 34 | 'allowHigherOrderFunctions': false 35 | } 36 | ], 37 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 38 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 39 | // Set to 1 (warning) or 2 (error) to enable. 40 | '@typescript-eslint/explicit-member-accessibility': 0, 41 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 42 | '@typescript-eslint/no-array-constructor': 1, 43 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 44 | // 45 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 46 | // This rule should be suppressed only in very special cases such as JSON.stringify() 47 | // where the type really can be anything. Even if the type is flexible, another type 48 | // may be more appropriate such as "unknown", "{}", or "Record". 49 | '@typescript-eslint/no-explicit-any': 1, 50 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 51 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 52 | // or else return the object to a caller (who assumes this responsibility). Unterminated 53 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 54 | // they can also cause a NodeJS process to terminate unexpectedly. 55 | '@typescript-eslint/no-floating-promises': 2, 56 | // RATIONALE: Catches a common coding mistake. 57 | '@typescript-eslint/no-for-in-array': 2, 58 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 59 | '@typescript-eslint/no-misused-new': 2, 60 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 61 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 62 | // optimizations. If you are declaring loose functions/variables, it's better to make them 63 | // static members of a class, since classes support property getters and their private 64 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 65 | // class name tends to produce more discoverable APIs: for example, search+replacing 66 | // the function "reverse()" is likely to return many false matches, whereas if we always 67 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 68 | // to decompose your code into separate NPM packages, which ensures that component 69 | // dependencies are tracked more conscientiously. 70 | // 71 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 72 | '@typescript-eslint/no-namespace': [ 73 | 1, 74 | { 75 | 'allowDeclarations': false, 76 | 'allowDefinitionFiles': false 77 | } 78 | ], 79 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 80 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 81 | // code easier to write, but arguably sacrifices readability: In the notes for 82 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 83 | // a class's design, so we wouldn't want to bury them in a constructor signature 84 | // just to save some typing. 85 | // 86 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 87 | // Set to 1 (warning) or 2 (error) to enable the rule 88 | '@typescript-eslint/parameter-properties': 0, 89 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 90 | // may impact performance. 91 | // 92 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 93 | '@typescript-eslint/no-unused-vars': [ 94 | 1, 95 | { 96 | 'vars': 'all', 97 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 98 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 99 | // that are overriding a base class method or implementing an interface. 100 | 'args': 'none' 101 | } 102 | ], 103 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 104 | '@typescript-eslint/no-use-before-define': [ 105 | 2, 106 | { 107 | 'functions': false, 108 | 'classes': true, 109 | 'variables': true, 110 | 'enums': true, 111 | 'typedefs': true 112 | } 113 | ], 114 | // Disallows require statements except in import statements. 115 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 116 | '@typescript-eslint/no-var-requires': 'error', 117 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | '@typescript-eslint/prefer-namespace-keyword': 1, 121 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 122 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 123 | // Set to 1 (warning) or 2 (error) to enable the rule 124 | '@typescript-eslint/no-inferrable-types': 0, 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 127 | '@typescript-eslint/no-empty-interface': 0, 128 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 129 | 'accessor-pairs': 1, 130 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 131 | 'dot-notation': [ 132 | 1, 133 | { 134 | 'allowPattern': '^_' 135 | } 136 | ], 137 | // RATIONALE: Catches code that is likely to be incorrect 138 | 'eqeqeq': 1, 139 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 140 | 'for-direction': 1, 141 | // RATIONALE: Catches a common coding mistake. 142 | 'guard-for-in': 2, 143 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 144 | // to split up your code. 145 | 'max-lines': ['warn', { max: 2000 }], 146 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 147 | 'no-async-promise-executor': 2, 148 | // RATIONALE: Deprecated language feature. 149 | 'no-caller': 2, 150 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 151 | 'no-compare-neg-zero': 2, 152 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 153 | 'no-cond-assign': 2, 154 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 155 | 'no-constant-condition': 1, 156 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 157 | 'no-control-regex': 2, 158 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 159 | 'no-debugger': 1, 160 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 161 | 'no-delete-var': 2, 162 | // RATIONALE: Catches code that is likely to be incorrect 163 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 164 | 'no-duplicate-case': 2, 165 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 166 | 'no-empty': 1, 167 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 168 | 'no-empty-character-class': 2, 169 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 170 | 'no-empty-pattern': 1, 171 | // RATIONALE: Eval is a security concern and a performance concern. 172 | 'no-eval': 1, 173 | // RATIONALE: Catches code that is likely to be incorrect 174 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 175 | 'no-ex-assign': 2, 176 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 177 | // If two different libraries (or two versions of the same library) both try to modify 178 | // a type, only one of them can win. Polyfills are acceptable because they implement 179 | // a standardized interoperable contract, but polyfills are generally coded in plain 180 | // JavaScript. 181 | 'no-extend-native': 1, 182 | // Disallow unnecessary labels 183 | 'no-extra-label': 1, 184 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 185 | 'no-fallthrough': 2, 186 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 187 | 'no-func-assign': 1, 188 | // RATIONALE: Catches a common coding mistake. 189 | 'no-implied-eval': 2, 190 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 191 | 'no-invalid-regexp': 2, 192 | // RATIONALE: Catches a common coding mistake. 193 | 'no-label-var': 2, 194 | // RATIONALE: Eliminates redundant code. 195 | 'no-lone-blocks': 1, 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-misleading-character-class': 2, 198 | // RATIONALE: Catches a common coding mistake. 199 | 'no-multi-str': 2, 200 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 201 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 202 | // or else implies that the constructor is doing nontrivial computations, which is often 203 | // a poor class design. 204 | 'no-new': 1, 205 | // RATIONALE: Obsolete language feature that is deprecated. 206 | 'no-new-func': 2, 207 | // RATIONALE: Obsolete language feature that is deprecated. 208 | 'no-new-object': 2, 209 | // RATIONALE: Obsolete notation. 210 | 'no-new-wrappers': 1, 211 | // RATIONALE: Catches code that is likely to be incorrect 212 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 213 | 'no-octal': 2, 214 | // RATIONALE: Catches code that is likely to be incorrect 215 | 'no-octal-escape': 2, 216 | // RATIONALE: Catches code that is likely to be incorrect 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-regex-spaces': 2, 219 | // RATIONALE: Catches a common coding mistake. 220 | 'no-return-assign': 2, 221 | // RATIONALE: Security risk. 222 | 'no-script-url': 1, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-self-assign': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-self-compare': 2, 227 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 228 | // commas to create compound expressions. In general code is more readable if each 229 | // step is split onto a separate line. This also makes it easier to set breakpoints 230 | // in the debugger. 231 | 'no-sequences': 1, 232 | // RATIONALE: Catches code that is likely to be incorrect 233 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 234 | 'no-shadow-restricted-names': 2, 235 | // RATIONALE: Obsolete language feature that is deprecated. 236 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 237 | 'no-sparse-arrays': 2, 238 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 239 | // such flexibility adds pointless complexity, by requiring every catch block to test 240 | // the type of the object that it receives. Whereas if catch blocks can always assume 241 | // that their object implements the "Error" contract, then the code is simpler, and 242 | // we generally get useful additional information like a call stack. 243 | 'no-throw-literal': 2, 244 | // RATIONALE: Catches a common coding mistake. 245 | 'no-unmodified-loop-condition': 1, 246 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 247 | 'no-unsafe-finally': 2, 248 | // RATIONALE: Catches a common coding mistake. 249 | 'no-unused-expressions': 1, 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-unused-labels': 1, 252 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 253 | 'no-useless-catch': 1, 254 | // RATIONALE: Avoids a potential performance problem. 255 | 'no-useless-concat': 1, 256 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 257 | // Always use "let" or "const" instead. 258 | // 259 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 260 | 'no-var': 2, 261 | // RATIONALE: Generally not needed in modern code. 262 | 'no-void': 1, 263 | // RATIONALE: Obsolete language feature that is deprecated. 264 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 265 | 'no-with': 2, 266 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 267 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 268 | 'prefer-const': 1, 269 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 270 | 'promise/param-names': 2, 271 | // RATIONALE: Catches code that is likely to be incorrect 272 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 273 | 'require-atomic-updates': 2, 274 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 275 | 'require-yield': 1, 276 | // "Use strict" is redundant when using the TypeScript compiler. 277 | 'strict': [ 278 | 2, 279 | 'never' 280 | ], 281 | // RATIONALE: Catches code that is likely to be incorrect 282 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 283 | 'use-isnan': 2, 284 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 285 | // Set to 1 (warning) or 2 (error) to enable. 286 | // Rationale to disable: !!{} 287 | 'no-extra-boolean-cast': 0, 288 | // ==================================================================== 289 | // @microsoft/eslint-plugin-spfx 290 | // ==================================================================== 291 | '@microsoft/spfx/import-requires-chunk-name': 1, 292 | '@microsoft/spfx/no-require-ensure': 2, 293 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 294 | } 295 | }, 296 | { 297 | // For unit tests, we can be a little bit less strict. The settings below revise the 298 | // defaults specified in the extended configurations, as well as above. 299 | files: [ 300 | // Test files 301 | '*.test.ts', 302 | '*.test.tsx', 303 | '*.spec.ts', 304 | '*.spec.tsx', 305 | 306 | // Facebook convention 307 | '**/__mocks__/*.ts', 308 | '**/__mocks__/*.tsx', 309 | '**/__tests__/*.ts', 310 | '**/__tests__/*.tsx', 311 | 312 | // Microsoft convention 313 | '**/test/*.ts', 314 | '**/test/*.tsx' 315 | ], 316 | rules: {} 317 | } 318 | ] 319 | }; -------------------------------------------------------------------------------- /Demos/03-uploadfile/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 24 | // Even if the compiler may be able to infer a type, this inference will be unavailable 25 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 26 | // but writing code is a much less important activity than reading it. 27 | // 28 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 29 | '@typescript-eslint/explicit-function-return-type': [ 30 | 1, 31 | { 32 | 'allowExpressions': true, 33 | 'allowTypedFunctionExpressions': true, 34 | 'allowHigherOrderFunctions': false 35 | } 36 | ], 37 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 38 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 39 | // Set to 1 (warning) or 2 (error) to enable. 40 | '@typescript-eslint/explicit-member-accessibility': 0, 41 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 42 | '@typescript-eslint/no-array-constructor': 1, 43 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 44 | // 45 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 46 | // This rule should be suppressed only in very special cases such as JSON.stringify() 47 | // where the type really can be anything. Even if the type is flexible, another type 48 | // may be more appropriate such as "unknown", "{}", or "Record". 49 | '@typescript-eslint/no-explicit-any': 1, 50 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 51 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 52 | // or else return the object to a caller (who assumes this responsibility). Unterminated 53 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 54 | // they can also cause a NodeJS process to terminate unexpectedly. 55 | '@typescript-eslint/no-floating-promises': 2, 56 | // RATIONALE: Catches a common coding mistake. 57 | '@typescript-eslint/no-for-in-array': 2, 58 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 59 | '@typescript-eslint/no-misused-new': 2, 60 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 61 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 62 | // optimizations. If you are declaring loose functions/variables, it's better to make them 63 | // static members of a class, since classes support property getters and their private 64 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 65 | // class name tends to produce more discoverable APIs: for example, search+replacing 66 | // the function "reverse()" is likely to return many false matches, whereas if we always 67 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 68 | // to decompose your code into separate NPM packages, which ensures that component 69 | // dependencies are tracked more conscientiously. 70 | // 71 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 72 | '@typescript-eslint/no-namespace': [ 73 | 1, 74 | { 75 | 'allowDeclarations': false, 76 | 'allowDefinitionFiles': false 77 | } 78 | ], 79 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 80 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 81 | // code easier to write, but arguably sacrifices readability: In the notes for 82 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 83 | // a class's design, so we wouldn't want to bury them in a constructor signature 84 | // just to save some typing. 85 | // 86 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 87 | // Set to 1 (warning) or 2 (error) to enable the rule 88 | '@typescript-eslint/parameter-properties': 0, 89 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 90 | // may impact performance. 91 | // 92 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 93 | '@typescript-eslint/no-unused-vars': [ 94 | 1, 95 | { 96 | 'vars': 'all', 97 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 98 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 99 | // that are overriding a base class method or implementing an interface. 100 | 'args': 'none' 101 | } 102 | ], 103 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 104 | '@typescript-eslint/no-use-before-define': [ 105 | 2, 106 | { 107 | 'functions': false, 108 | 'classes': true, 109 | 'variables': true, 110 | 'enums': true, 111 | 'typedefs': true 112 | } 113 | ], 114 | // Disallows require statements except in import statements. 115 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 116 | '@typescript-eslint/no-var-requires': 'error', 117 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | '@typescript-eslint/prefer-namespace-keyword': 1, 121 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 122 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 123 | // Set to 1 (warning) or 2 (error) to enable the rule 124 | '@typescript-eslint/no-inferrable-types': 0, 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 127 | '@typescript-eslint/no-empty-interface': 0, 128 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 129 | 'accessor-pairs': 1, 130 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 131 | 'dot-notation': [ 132 | 1, 133 | { 134 | 'allowPattern': '^_' 135 | } 136 | ], 137 | // RATIONALE: Catches code that is likely to be incorrect 138 | 'eqeqeq': 1, 139 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 140 | 'for-direction': 1, 141 | // RATIONALE: Catches a common coding mistake. 142 | 'guard-for-in': 2, 143 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 144 | // to split up your code. 145 | 'max-lines': ['warn', { max: 2000 }], 146 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 147 | 'no-async-promise-executor': 2, 148 | // RATIONALE: Deprecated language feature. 149 | 'no-caller': 2, 150 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 151 | 'no-compare-neg-zero': 2, 152 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 153 | 'no-cond-assign': 2, 154 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 155 | 'no-constant-condition': 1, 156 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 157 | 'no-control-regex': 2, 158 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 159 | 'no-debugger': 1, 160 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 161 | 'no-delete-var': 2, 162 | // RATIONALE: Catches code that is likely to be incorrect 163 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 164 | 'no-duplicate-case': 2, 165 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 166 | 'no-empty': 1, 167 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 168 | 'no-empty-character-class': 2, 169 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 170 | 'no-empty-pattern': 1, 171 | // RATIONALE: Eval is a security concern and a performance concern. 172 | 'no-eval': 1, 173 | // RATIONALE: Catches code that is likely to be incorrect 174 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 175 | 'no-ex-assign': 2, 176 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 177 | // If two different libraries (or two versions of the same library) both try to modify 178 | // a type, only one of them can win. Polyfills are acceptable because they implement 179 | // a standardized interoperable contract, but polyfills are generally coded in plain 180 | // JavaScript. 181 | 'no-extend-native': 1, 182 | // Disallow unnecessary labels 183 | 'no-extra-label': 1, 184 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 185 | 'no-fallthrough': 2, 186 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 187 | 'no-func-assign': 1, 188 | // RATIONALE: Catches a common coding mistake. 189 | 'no-implied-eval': 2, 190 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 191 | 'no-invalid-regexp': 2, 192 | // RATIONALE: Catches a common coding mistake. 193 | 'no-label-var': 2, 194 | // RATIONALE: Eliminates redundant code. 195 | 'no-lone-blocks': 1, 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-misleading-character-class': 2, 198 | // RATIONALE: Catches a common coding mistake. 199 | 'no-multi-str': 2, 200 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 201 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 202 | // or else implies that the constructor is doing nontrivial computations, which is often 203 | // a poor class design. 204 | 'no-new': 1, 205 | // RATIONALE: Obsolete language feature that is deprecated. 206 | 'no-new-func': 2, 207 | // RATIONALE: Obsolete language feature that is deprecated. 208 | 'no-new-object': 2, 209 | // RATIONALE: Obsolete notation. 210 | 'no-new-wrappers': 1, 211 | // RATIONALE: Catches code that is likely to be incorrect 212 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 213 | 'no-octal': 2, 214 | // RATIONALE: Catches code that is likely to be incorrect 215 | 'no-octal-escape': 2, 216 | // RATIONALE: Catches code that is likely to be incorrect 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-regex-spaces': 2, 219 | // RATIONALE: Catches a common coding mistake. 220 | 'no-return-assign': 2, 221 | // RATIONALE: Security risk. 222 | 'no-script-url': 1, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-self-assign': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-self-compare': 2, 227 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 228 | // commas to create compound expressions. In general code is more readable if each 229 | // step is split onto a separate line. This also makes it easier to set breakpoints 230 | // in the debugger. 231 | 'no-sequences': 1, 232 | // RATIONALE: Catches code that is likely to be incorrect 233 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 234 | 'no-shadow-restricted-names': 2, 235 | // RATIONALE: Obsolete language feature that is deprecated. 236 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 237 | 'no-sparse-arrays': 2, 238 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 239 | // such flexibility adds pointless complexity, by requiring every catch block to test 240 | // the type of the object that it receives. Whereas if catch blocks can always assume 241 | // that their object implements the "Error" contract, then the code is simpler, and 242 | // we generally get useful additional information like a call stack. 243 | 'no-throw-literal': 2, 244 | // RATIONALE: Catches a common coding mistake. 245 | 'no-unmodified-loop-condition': 1, 246 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 247 | 'no-unsafe-finally': 2, 248 | // RATIONALE: Catches a common coding mistake. 249 | 'no-unused-expressions': 1, 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-unused-labels': 1, 252 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 253 | 'no-useless-catch': 1, 254 | // RATIONALE: Avoids a potential performance problem. 255 | 'no-useless-concat': 1, 256 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 257 | // Always use "let" or "const" instead. 258 | // 259 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 260 | 'no-var': 2, 261 | // RATIONALE: Generally not needed in modern code. 262 | 'no-void': 1, 263 | // RATIONALE: Obsolete language feature that is deprecated. 264 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 265 | 'no-with': 2, 266 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 267 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 268 | 'prefer-const': 1, 269 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 270 | 'promise/param-names': 2, 271 | // RATIONALE: Catches code that is likely to be incorrect 272 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 273 | 'require-atomic-updates': 2, 274 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 275 | 'require-yield': 1, 276 | // "Use strict" is redundant when using the TypeScript compiler. 277 | 'strict': [ 278 | 2, 279 | 'never' 280 | ], 281 | // RATIONALE: Catches code that is likely to be incorrect 282 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 283 | 'use-isnan': 2, 284 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 285 | // Set to 1 (warning) or 2 (error) to enable. 286 | // Rationale to disable: !!{} 287 | 'no-extra-boolean-cast': 0, 288 | // ==================================================================== 289 | // @microsoft/eslint-plugin-spfx 290 | // ==================================================================== 291 | '@microsoft/spfx/import-requires-chunk-name': 1, 292 | '@microsoft/spfx/no-require-ensure': 2, 293 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 294 | } 295 | }, 296 | { 297 | // For unit tests, we can be a little bit less strict. The settings below revise the 298 | // defaults specified in the extended configurations, as well as above. 299 | files: [ 300 | // Test files 301 | '*.test.ts', 302 | '*.test.tsx', 303 | '*.spec.ts', 304 | '*.spec.tsx', 305 | 306 | // Facebook convention 307 | '**/__mocks__/*.ts', 308 | '**/__mocks__/*.tsx', 309 | '**/__tests__/*.ts', 310 | '**/__tests__/*.tsx', 311 | 312 | // Microsoft convention 313 | '**/test/*.ts', 314 | '**/test/*.tsx' 315 | ], 316 | rules: {} 317 | } 318 | ] 319 | }; -------------------------------------------------------------------------------- /Demos/01-spfxhttpclient/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 24 | // Even if the compiler may be able to infer a type, this inference will be unavailable 25 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 26 | // but writing code is a much less important activity than reading it. 27 | // 28 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 29 | '@typescript-eslint/explicit-function-return-type': [ 30 | 1, 31 | { 32 | 'allowExpressions': true, 33 | 'allowTypedFunctionExpressions': true, 34 | 'allowHigherOrderFunctions': false 35 | } 36 | ], 37 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 38 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 39 | // Set to 1 (warning) or 2 (error) to enable. 40 | '@typescript-eslint/explicit-member-accessibility': 0, 41 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 42 | '@typescript-eslint/no-array-constructor': 1, 43 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 44 | // 45 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 46 | // This rule should be suppressed only in very special cases such as JSON.stringify() 47 | // where the type really can be anything. Even if the type is flexible, another type 48 | // may be more appropriate such as "unknown", "{}", or "Record". 49 | '@typescript-eslint/no-explicit-any': 1, 50 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 51 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 52 | // or else return the object to a caller (who assumes this responsibility). Unterminated 53 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 54 | // they can also cause a NodeJS process to terminate unexpectedly. 55 | '@typescript-eslint/no-floating-promises': 2, 56 | // RATIONALE: Catches a common coding mistake. 57 | '@typescript-eslint/no-for-in-array': 2, 58 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 59 | '@typescript-eslint/no-misused-new': 2, 60 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 61 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 62 | // optimizations. If you are declaring loose functions/variables, it's better to make them 63 | // static members of a class, since classes support property getters and their private 64 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 65 | // class name tends to produce more discoverable APIs: for example, search+replacing 66 | // the function "reverse()" is likely to return many false matches, whereas if we always 67 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 68 | // to decompose your code into separate NPM packages, which ensures that component 69 | // dependencies are tracked more conscientiously. 70 | // 71 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 72 | '@typescript-eslint/no-namespace': [ 73 | 1, 74 | { 75 | 'allowDeclarations': false, 76 | 'allowDefinitionFiles': false 77 | } 78 | ], 79 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 80 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 81 | // code easier to write, but arguably sacrifices readability: In the notes for 82 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 83 | // a class's design, so we wouldn't want to bury them in a constructor signature 84 | // just to save some typing. 85 | // 86 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 87 | // Set to 1 (warning) or 2 (error) to enable the rule 88 | '@typescript-eslint/parameter-properties': 0, 89 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 90 | // may impact performance. 91 | // 92 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 93 | '@typescript-eslint/no-unused-vars': [ 94 | 1, 95 | { 96 | 'vars': 'all', 97 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 98 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 99 | // that are overriding a base class method or implementing an interface. 100 | 'args': 'none' 101 | } 102 | ], 103 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 104 | '@typescript-eslint/no-use-before-define': [ 105 | 2, 106 | { 107 | 'functions': false, 108 | 'classes': true, 109 | 'variables': true, 110 | 'enums': true, 111 | 'typedefs': true 112 | } 113 | ], 114 | // Disallows require statements except in import statements. 115 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 116 | '@typescript-eslint/no-var-requires': 'error', 117 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | '@typescript-eslint/prefer-namespace-keyword': 1, 121 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 122 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 123 | // Set to 1 (warning) or 2 (error) to enable the rule 124 | '@typescript-eslint/no-inferrable-types': 0, 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 127 | '@typescript-eslint/no-empty-interface': 0, 128 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 129 | 'accessor-pairs': 1, 130 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 131 | 'dot-notation': [ 132 | 1, 133 | { 134 | 'allowPattern': '^_' 135 | } 136 | ], 137 | // RATIONALE: Catches code that is likely to be incorrect 138 | 'eqeqeq': 1, 139 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 140 | 'for-direction': 1, 141 | // RATIONALE: Catches a common coding mistake. 142 | 'guard-for-in': 2, 143 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 144 | // to split up your code. 145 | 'max-lines': ['warn', { max: 2000 }], 146 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 147 | 'no-async-promise-executor': 2, 148 | // RATIONALE: Deprecated language feature. 149 | 'no-caller': 2, 150 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 151 | 'no-compare-neg-zero': 2, 152 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 153 | 'no-cond-assign': 2, 154 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 155 | 'no-constant-condition': 1, 156 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 157 | 'no-control-regex': 2, 158 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 159 | 'no-debugger': 1, 160 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 161 | 'no-delete-var': 2, 162 | // RATIONALE: Catches code that is likely to be incorrect 163 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 164 | 'no-duplicate-case': 2, 165 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 166 | 'no-empty': 1, 167 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 168 | 'no-empty-character-class': 2, 169 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 170 | 'no-empty-pattern': 1, 171 | // RATIONALE: Eval is a security concern and a performance concern. 172 | 'no-eval': 1, 173 | // RATIONALE: Catches code that is likely to be incorrect 174 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 175 | 'no-ex-assign': 2, 176 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 177 | // If two different libraries (or two versions of the same library) both try to modify 178 | // a type, only one of them can win. Polyfills are acceptable because they implement 179 | // a standardized interoperable contract, but polyfills are generally coded in plain 180 | // JavaScript. 181 | 'no-extend-native': 1, 182 | // Disallow unnecessary labels 183 | 'no-extra-label': 1, 184 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 185 | 'no-fallthrough': 2, 186 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 187 | 'no-func-assign': 1, 188 | // RATIONALE: Catches a common coding mistake. 189 | 'no-implied-eval': 2, 190 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 191 | 'no-invalid-regexp': 2, 192 | // RATIONALE: Catches a common coding mistake. 193 | 'no-label-var': 2, 194 | // RATIONALE: Eliminates redundant code. 195 | 'no-lone-blocks': 1, 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-misleading-character-class': 2, 198 | // RATIONALE: Catches a common coding mistake. 199 | 'no-multi-str': 2, 200 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 201 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 202 | // or else implies that the constructor is doing nontrivial computations, which is often 203 | // a poor class design. 204 | 'no-new': 1, 205 | // RATIONALE: Obsolete language feature that is deprecated. 206 | 'no-new-func': 2, 207 | // RATIONALE: Obsolete language feature that is deprecated. 208 | 'no-new-object': 2, 209 | // RATIONALE: Obsolete notation. 210 | 'no-new-wrappers': 1, 211 | // RATIONALE: Catches code that is likely to be incorrect 212 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 213 | 'no-octal': 2, 214 | // RATIONALE: Catches code that is likely to be incorrect 215 | 'no-octal-escape': 2, 216 | // RATIONALE: Catches code that is likely to be incorrect 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-regex-spaces': 2, 219 | // RATIONALE: Catches a common coding mistake. 220 | 'no-return-assign': 2, 221 | // RATIONALE: Security risk. 222 | 'no-script-url': 1, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-self-assign': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-self-compare': 2, 227 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 228 | // commas to create compound expressions. In general code is more readable if each 229 | // step is split onto a separate line. This also makes it easier to set breakpoints 230 | // in the debugger. 231 | 'no-sequences': 1, 232 | // RATIONALE: Catches code that is likely to be incorrect 233 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 234 | 'no-shadow-restricted-names': 2, 235 | // RATIONALE: Obsolete language feature that is deprecated. 236 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 237 | 'no-sparse-arrays': 2, 238 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 239 | // such flexibility adds pointless complexity, by requiring every catch block to test 240 | // the type of the object that it receives. Whereas if catch blocks can always assume 241 | // that their object implements the "Error" contract, then the code is simpler, and 242 | // we generally get useful additional information like a call stack. 243 | 'no-throw-literal': 2, 244 | // RATIONALE: Catches a common coding mistake. 245 | 'no-unmodified-loop-condition': 1, 246 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 247 | 'no-unsafe-finally': 2, 248 | // RATIONALE: Catches a common coding mistake. 249 | 'no-unused-expressions': 1, 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-unused-labels': 1, 252 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 253 | 'no-useless-catch': 1, 254 | // RATIONALE: Avoids a potential performance problem. 255 | 'no-useless-concat': 1, 256 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 257 | // Always use "let" or "const" instead. 258 | // 259 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 260 | 'no-var': 2, 261 | // RATIONALE: Generally not needed in modern code. 262 | 'no-void': 1, 263 | // RATIONALE: Obsolete language feature that is deprecated. 264 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 265 | 'no-with': 2, 266 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 267 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 268 | 'prefer-const': 1, 269 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 270 | 'promise/param-names': 2, 271 | // RATIONALE: Catches code that is likely to be incorrect 272 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 273 | 'require-atomic-updates': 2, 274 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 275 | 'require-yield': 1, 276 | // "Use strict" is redundant when using the TypeScript compiler. 277 | 'strict': [ 278 | 2, 279 | 'never' 280 | ], 281 | // RATIONALE: Catches code that is likely to be incorrect 282 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 283 | 'use-isnan': 2, 284 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 285 | // Set to 1 (warning) or 2 (error) to enable. 286 | // Rationale to disable: !!{} 287 | 'no-extra-boolean-cast': 0, 288 | // ==================================================================== 289 | // @microsoft/eslint-plugin-spfx 290 | // ==================================================================== 291 | '@microsoft/spfx/import-requires-chunk-name': 1, 292 | '@microsoft/spfx/no-require-ensure': 2, 293 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 294 | } 295 | }, 296 | { 297 | // For unit tests, we can be a little bit less strict. The settings below revise the 298 | // defaults specified in the extended configurations, as well as above. 299 | files: [ 300 | // Test files 301 | '*.test.ts', 302 | '*.test.tsx', 303 | '*.spec.ts', 304 | '*.spec.tsx', 305 | 306 | // Facebook convention 307 | '**/__mocks__/*.ts', 308 | '**/__mocks__/*.tsx', 309 | '**/__tests__/*.ts', 310 | '**/__tests__/*.tsx', 311 | 312 | // Microsoft convention 313 | '**/test/*.ts', 314 | '**/test/*.tsx' 315 | ], 316 | rules: {} 317 | } 318 | ] 319 | }; -------------------------------------------------------------------------------- /Lab.md: -------------------------------------------------------------------------------- 1 | # Working with SharePoint Content 2 | 3 | In this lab you work with the SharePoint Framework to perform CRUD operations on SharePoint lists and libraries. 4 | 5 | ## In this lab 6 | 7 | - [Using SPHttpClient to talk to SharePoint](#exercise1) 8 | - [CRUD with SharePoint Data](#exercise2) 9 | - [Using Mocks to Simulate SharePoint Data](#exercise3) 10 | 11 | ## Prerequisites 12 | 13 | To complete this lab, you need the following: 14 | 15 | - Office 365 tenancy 16 | > If you do not have one, you obtain one (for free) by signing up to the [Office 365 Developer Program](https://developer.microsoft.com/office/dev-program). 17 | - Local SharePoint Framework development environment installed and configured 18 | - Refer to the SharePoint Framework documentation, specifically the **[Getting Started > Set up development environment](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)** for the most current steps 19 | 20 | 21 | 22 | ## Exercise 1: Using SPHttpClient to talk to SharePoint 23 | 24 | In this exercise you will create a SharePoint Framework (SPFx) web part that will get and display data from a SharePoint list. 25 | 26 | ### Create a List of Countries for Sample Data 27 | 28 | 1. In a browser, navigate to a site collection in SharePoint Online. 29 | 1. Select **Site Contents** in the left-hand navigation. 30 | 1. Select **New > List** in the toolbar. 31 | 1. Set the list name to **Countries** and select **Create**. 32 | 33 | ![Screenshot showing form to create a list](./Images/create-countries-list01.png) 34 | 35 | 1. Add items to the list by entering the names of different countries as shown in the following image. 36 | 37 | ![Screenshot showing sample countries in the list](./Images/create-countries-list02.png) 38 | 39 | ### Create the web part to display data using the SharePoint REST API 40 | 41 | > NOTE: The instructions below assume you are using v1.9.1 of the SharePoint Framework Yeoman generator. 42 | 43 | 1. Open a command prompt and change to the folder where you want to create the project. 44 | 1. Run the SharePoint Yeoman generator by executing the following command: 45 | 46 | ```shell 47 | yo @microsoft/sharepoint 48 | ``` 49 | 50 | Use the following to complete the prompt that is displayed: 51 | 52 | - **What is your solution name?**: SpFxHttpClientDemo 53 | - **Which baseline packages do you want to target for your component(s)?**: SharePoint Online only (latest) 54 | - **Where do you want to place the files?**: Use the current folder 55 | - **Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately without running any feature deployment or adding apps in sites?**: No 56 | - **Will the components in the solution require permissions to access web APIs that are unique and not shared with other components in the tenant?**: No 57 | - **Which type of client-side component to create?**: Web Part 58 | - **What is your Web Part name?**: SPFxHttpClientDemo 59 | - **What is your Web Part description?**: SPFxHttpClientDemo description 60 | - **Which framework would you like to use?** React 61 | 62 | After provisioning the folders required for the project, the generator will install all the dependency packages using NPM. 63 | 64 | 1. Open the project in an editor such as Visual Studio Code (*aka: VSCode*). 65 | 1. Create an interface for the SharePoint list items: 66 | 1. Locate the **./src** folder in the project. Create a new subfolder **models** in the **src** folder. 67 | 1. Create a new file **ICountryListItem.ts** in the **models** folder and add the following code to it: 68 | 69 | ```ts 70 | export interface ICountryListItem { 71 | Id: string; 72 | Title: string; 73 | } 74 | ``` 75 | 76 | 1. Create a new type to represent when someone clicks a button on the React component: 77 | 1. Create a new file **ButtonClickedCallback.ts** in the **models** folder and add the following code to it: 78 | 79 | ```ts 80 | export type ButtonClickedCallback = () => void; 81 | ``` 82 | 83 | 1. Create a new file **index.ts** in the **models** folder and add the following code to it: 84 | 85 | ```ts 86 | export * from './ButtonClickedCallback'; 87 | export * from './ICountryListItem'; 88 | ``` 89 | 90 | 1. Update the public interface for the React component: 91 | 1. Locate and open the file **./src/webparts/spFxHttpClientDemo/components/ISpFxHttpClientDemoProps.ts**. This is the interface for the public properties of the React component. It will need to display a list of items, so update the signature to accept a list of items. 92 | 1. Add the following code to the top of the file: 93 | 94 | ```ts 95 | import { 96 | ButtonClickedCallback, 97 | ICountryListItem 98 | } from '../../../models'; 99 | 100 | ``` 101 | 102 | 1. Update the interface to replace the existing `description` property to be a collection of items that can be passed in and add an event when a button is clicked: 103 | 104 | ```ts 105 | export interface ISpFxHttpClientDemoProps { 106 | spListItems: ICountryListItem[]; 107 | onGetListItems?: ButtonClickedCallback; 108 | } 109 | ``` 110 | 111 | 1. Implement the user interface for the web part to display a list of items. 112 | 1. Locate and open the file **./src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.module.scss**. 113 | 1. Add the following classes to the bottom of the file, immediately before the closing `}`: 114 | 115 | ```css 116 | .list { 117 | color: $ms-color-themeDark; 118 | background-color: $ms-color-themeLight; 119 | font-family: 'Segoe UI Regular WestEuropean', 'Segoe UI', Tahoma, Arial, sans-serif; 120 | font-size: 14px; 121 | font-weight: normal; 122 | box-sizing: border-box; 123 | margin: 0 0; 124 | padding: 10px 0 100px 0; 125 | line-height: 50px; 126 | list-style-type: none; 127 | } 128 | 129 | .item { 130 | color: $ms-color-themeDark; 131 | background-color: $ms-color-themeLighterAlt; 132 | vertical-align: center; 133 | font-family: 'Segoe UI Regular WestEuropean', 'Segoe UI', Tahoma, Arial, sans-serif; 134 | font-size: 14px; 135 | font-weight: normal; 136 | box-sizing: border-box; 137 | margin: 0; 138 | padding: 0; 139 | box-shadow: none; 140 | *zoom: 1; 141 | padding: 0 15px; 142 | position: relative; 143 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); 144 | } 145 | ``` 146 | 147 | 1. Locate and open the file **./src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.tsx**. 148 | 1. Update the markup returned by the `render()` method to the following code. This will create a list displaying the data contained in the `spListItems` property rendered using the CSS classes added in the previous step. Also notice that there is an anchor tag `` that acts as a button and has an `onClick` handler wired up to it: 149 | 150 | ```html 151 |
152 |
153 | 161 | 162 |
163 |
    164 | { this.props.spListItems && 165 | this.props.spListItems.map((list) => 166 |
  • 167 | Id: {list.Id}, Title: {list.Title} 168 |
  • 169 | ) 170 | } 171 |
172 |
173 | 174 |
175 |
176 | ``` 177 | 178 | 1. Add the following event handler to the `SpFxHttpClientDemo` class to handle the click event on the button. This code will prevent the default action of the `` element from navigating away from (or refreshing) the page and call the callback set on the public property, notifying the consumer of the component an event occurred: 179 | 180 | ```ts 181 | private onGetListItemsClicked = (event: React.MouseEvent): void => { 182 | event.preventDefault(); 183 | 184 | this.props.onGetListItems(); 185 | } 186 | ``` 187 | 188 | 1. Update the web part to retrieve data from the SharePoint REST API when the button in the React component is clicked: 189 | 1. Locate and open the **./src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebPart.ts** file. 190 | 1. After the existing `import` statements at the top of the file, add the following import statements: 191 | 192 | ```ts 193 | import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; 194 | import { ICountryListItem } from '../../models'; 195 | ``` 196 | 197 | 1. Locate the class `SpFxHttpClientDemoWebPart` and add the following private member to it: 198 | 199 | ```ts 200 | private _countries: ICountryListItem[] = []; 201 | ``` 202 | 203 | 1. Locate the `render()` method. Within this method notice that it is creating an instance of the component `SpFxHttpClientDemo` and then setting it's public properties. The default code sets the `description` property, however this was removed from the interface `ISpFxHttpClientDemoProps` in the previous steps. Update the properties to set the list of countries to the private member added above as well as attach to the event handler: 204 | 205 | ```ts 206 | spListItems: this._countries, 207 | onGetListItems: this._onGetListItems 208 | ``` 209 | 210 | 1. Add the following method as an event handler to the `SpFxHttpClientDemoWebPart` class. This method calls another method (*that you will add in the next step*) that returns a collection of list items. Once those items are returned via a JavaScript Promise, the method updates the internal `_countries` member and re-renders the web part. This will bind the collection of countries returned by the `_getListItems()` method to the public property on the React component which will render the items. 211 | 212 | ```ts 213 | private _onGetListItems = (): void => { 214 | this._getListItems() 215 | .then(response => { 216 | this._countries = response; 217 | this.render(); 218 | }); 219 | } 220 | ``` 221 | 222 | 1. Add the following method `SpFxHttpClientDemoWebPart` class that retrieves list items from the **Countries** list using the SharePoint REST API. Notice it will use the `spHttpClient` object to query the SharePoint REST API. When it receives the response, it calls `response.json()` that will process the response as a JSON object and then returns the `value` property in the response to the caller. The `value` property is a collection of list items that match the interface created previously: 223 | 224 | ```ts 225 | private _getListItems(): Promise { 226 | return this.context.spHttpClient.get( 227 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title`, 228 | SPHttpClient.configurations.v1) 229 | .then(response => { 230 | return response.json(); 231 | }) 232 | .then(jsonResponse => { 233 | return jsonResponse.value; 234 | }) as Promise; 235 | } 236 | ``` 237 | 238 | 1. Test the webpart: 239 | 1. Start the local web server and test the web part in the hosted workbench: 240 | 241 | ```shell 242 | gulp serve 243 | ``` 244 | 245 | 1. The browser will load the local workbench, but you can not use this for testing because there is no SharePoint context in the local workbench. Instead, navigate to the SharePoint Online site where you created the **Countries** list, and load the hosted workbench at **https://[sharepoint-online-site]/_layouts/workbench.aspx**. 246 | 247 | 1. Add the web part to the page: Select the **Add a new web part** control... 248 | 249 | ![Screenshot of the SharePoint workbench](./Images/add-webpart-01.png) 250 | 251 | ...then select the expand toolbox icon in the top-right... 252 | 253 | ![Screenshot of the SharePoint workbench](./Images/add-webpart-02.png) 254 | 255 | ...and select the **SPFxHttpClientDemo** web part to add the web part to the page: 256 | 257 | ![Screenshot of the SharePoint workbench toolbox](./Images/add-webpart-03.png) 258 | 259 | 1. The web part will appear on the page with a single button and no data in the list: 260 | 261 | ![Screenshot of the web part with no data](./Images/add-webpart-04.png) 262 | 263 | 1. Select the **Get Countries** button and notice the list will display the data from the SharePoint REST API: 264 | 265 | ![Screenshot of the web part with data](./Images/get-items-sp.png) 266 | 267 | 1. Stop the local web server by pressing CTRL+C in the console/terminal window. 268 | 269 | 270 | 271 | ## Exercise 2: CRUD with SharePoint Data 272 | 273 | In this exercise, you will extend the SPFx project from the previous exercise to add write capabilities using the SharePoint REST and SPFx APIs. 274 | 275 | > *NOTE: If you did not complete the previous exercise, you can start from the final working version found in the **[./Demos/01-spfxhttpclient](./Demos/01-spfxhttpclient)** folder.* 276 | 277 | 1. Add public properties for the write operations on the React component: 278 | 1. Locate and open the **./src/webparts/spFxHttpClientDemo/components/ISpFxHttpClientDemoProps.ts** file. 279 | 1. Add the following properties to the `ISpFxHttpClientDemoProps` interface. 280 | 281 | ```ts 282 | onAddListItem?: ButtonClickedCallback; 283 | onUpdateListItem?: ButtonClickedCallback; 284 | onDeleteListItem?: ButtonClickedCallback; 285 | ``` 286 | 1. Add buttons and event handlers to the React component to trigger the write operations added later in this exercise: 287 | 1. Locate and open the **./src/webparts/spFxHttpClientDemo/components/SpFxHttpClientDemo.tsx** file. 288 | 1. Within the `render()` method in the `SpFxHttpClientDemo` class, locate the button **Get Countries**. Add the following markup to add three more buttons to the user interface: 289 | 290 | ```html 291 | 292 | Add List Item 293 | 294 | 295 | Update List Item 296 | 297 | 298 | Delete List Item 299 | 300 | ``` 301 | 302 | 1. Add the following event handlers to the `SpFxHttpClientDemo` class: 303 | 304 | ```ts 305 | private onAddListItemClicked = (event: React.MouseEvent): void => { 306 | event.preventDefault(); 307 | 308 | this.props.onAddListItem(); 309 | } 310 | 311 | private onUpdateListItemClicked = (event: React.MouseEvent): void => { 312 | event.preventDefault(); 313 | 314 | this.props.onUpdateListItem(); 315 | } 316 | 317 | private onDeleteListItemClicked = (event: React.MouseEvent): void => { 318 | event.preventDefault(); 319 | 320 | this.props.onDeleteListItem(); 321 | } 322 | ``` 323 | 324 | 1. Update the web part to trigger write operations when the buttons are pressed in the React component: 325 | 1. Locate and open the **./src/webparts/spFxHttpClientDemo/SpFxHttpClientDemoWebpart.ts** file. 326 | 1. Within the `render()` method in the `SpFxHttpClientDemoWebPart` class, locate the code where the public properties are set on the React component `SpFxHttpClientDemo`. It will look like this: 327 | 328 | ```ts 329 | { 330 | spListItems: this._countries, 331 | onGetListItems: this._onGetListItems 332 | } 333 | ``` 334 | 335 | 1. Update the public properties to add handlers for the events when buttons are pressed in the component: 336 | 337 | ```ts 338 | { 339 | spListItems: this._countries, 340 | onGetListItems: this._onGetListItems, 341 | onAddListItem: this._onAddListItem, 342 | onUpdateListItem: this._onUpdateListItem, 343 | onDeleteListItem: this._onDeleteListItem 344 | } 345 | ``` 346 | 347 | 1. Implement the three event handlers you just added. 348 | 349 | ```ts 350 | private _onAddListItem = (): void => { 351 | this._addListItem() 352 | .then(() => { 353 | this._getListItems() 354 | .then(response => { 355 | this._countries = response; 356 | this.render(); 357 | }); 358 | }); 359 | } 360 | 361 | private _onUpdateListItem = (): void => { 362 | this._updateListItem() 363 | .then(() => { 364 | this._getListItems() 365 | .then(response => { 366 | this._countries = response; 367 | this.render(); 368 | }); 369 | }); 370 | } 371 | 372 | private _onDeleteListItem = (): void => { 373 | this._deleteListItem() 374 | .then(() => { 375 | this._getListItems() 376 | .then(response => { 377 | this._countries = response; 378 | this.render(); 379 | }); 380 | }); 381 | } 382 | ``` 383 | 384 | These event handlers will call different methods which you will add in the remainder of this exercise. Each one will add, update, or delete an item in the SharePoint list, call the existing `_getListItems()` method you created in the previous exercise, and refresh the web part by calling `render()`. 385 | 386 | 1. Add the following methods to the `SpFxHttpClientDemoWebPart` class to add a new item to the list: 387 | 388 | ```ts 389 | private _getItemEntityType(): Promise { 390 | return this.context.spHttpClient.get( 391 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')?$select=ListItemEntityTypeFullName`, 392 | SPHttpClient.configurations.v1) 393 | .then(response => { 394 | return response.json(); 395 | }) 396 | .then(jsonResponse => { 397 | return jsonResponse.ListItemEntityTypeFullName; 398 | }) as Promise; 399 | } 400 | 401 | private _addListItem(): Promise { 402 | return this._getItemEntityType() 403 | .then(spEntityType => { 404 | const request: any = {}; 405 | request.body = JSON.stringify({ 406 | Title: new Date().toUTCString(), 407 | '@odata.type': spEntityType 408 | }); 409 | 410 | return this.context.spHttpClient.post( 411 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items`, 412 | SPHttpClient.configurations.v1, 413 | request); 414 | } 415 | ) ; 416 | } 417 | ``` 418 | 419 | The method `_getItemEntityType()` will get the type of data that the **Countries** list expects. This is done by: 420 | 421 | - Using the `spHttpClient` API's `get()` method to issue an HTTP GET request to the SharePoint REST API. This method requires two parameters: (1) the endpoint to query and (2) the configuration to use. 422 | - After processing the response body as JSON... 423 | - It returns the `ListItemEntityTypeFullName` as a single string value to the caller. 424 | 425 | The method `_addListItem()` first obtains the data type supported by the list needed when creating a new item. Then it creates a new item by: 426 | 427 | - Creating a new object with a new `Title`, set to the current UTC timestamp, and the `@odata.type` property that is set to the value obtained in the `_getItemEntityType()` method. 428 | - This new object is set to the `body` property as a string on a `request` object that will be sent in the HTTP POST. 429 | - Then, using the `spHttpClient` API's `post()` method, set the endpoint to the list's `items` collection, with the specified configuration and then set the `request` object as the third parameter for the `post()` method. This will tell the `spHttpClient` API to send the new object as part of the body in the HTTP request. 430 | 431 | 1. Add the following method to the `SpFxHttpClientDemoWebPart` class to update an item to the list: 432 | 433 | ```ts 434 | private _updateListItem(): Promise { 435 | // get the first item 436 | return this.context.spHttpClient.get( 437 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title&$filter=Title eq 'United States'`, 438 | SPHttpClient.configurations.v1) 439 | .then(response => { 440 | return response.json(); 441 | }) 442 | .then(jsonResponse => { 443 | return jsonResponse.value[0]; 444 | }) 445 | .then((listItem: ICountryListItem) => { 446 | // update item 447 | listItem.Title = 'USA'; 448 | // save it 449 | const request: any = {}; 450 | request.headers = { 451 | 'X-HTTP-Method': 'MERGE', 452 | 'IF-MATCH': (listItem as any)['@odata.etag'] 453 | }; 454 | request.body = JSON.stringify(listItem); 455 | 456 | return this.context.spHttpClient.post( 457 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items(${listItem.Id})`, 458 | SPHttpClient.configurations.v1, 459 | request); 460 | }); 461 | } 462 | ``` 463 | 464 | This method will update an existing item in the list by doing the following steps: 465 | 466 | - It first gets a reference to a single item in the list with the `Title` = **United States**. This is done using the OData `$filter` operator on the URL parameters of the HTTP GET request endpoint. 467 | - Upon receiving the response, after processing the response as JSON, it gets the first item in the `value` collection which is an array. This will contain the single item in our query results. 468 | - Once the item is retrieved, the `Title` property is changed to **USA**. 469 | - A new request object is created to submit to the SharePoint REST API: 470 | - The headers are set to instruct the REST API you wish to perform a **MERGE** operation and... 471 | - The item that will be updated on the server should have the same version, as indicated by the `@odata.etag` property, as the item that is submitted in the HTTP request. 472 | - Similar to the add operation, using the `spHttpClient` API's `post()` method, the specific item in the SharePoint list is updated by using the endpoint of the specific item, the desired configuration and the request object this method constructed. 473 | 474 | 1. Add the following method to the `SpFxHttpClientDemoWebPart` class to delete the last item in the list: 475 | 476 | ```ts 477 | private _deleteListItem(): Promise { 478 | // get the last item 479 | return this.context.spHttpClient.get( 480 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items?$select=Id,Title&$orderby=ID desc&$top=1`, 481 | SPHttpClient.configurations.v1) 482 | .then(response => { 483 | return response.json(); 484 | }) 485 | .then(jsonResponse => { 486 | return jsonResponse.value[0]; 487 | }) 488 | .then((listItem: ICountryListItem) => { 489 | const request: any = {}; 490 | request.headers = { 491 | 'X-HTTP-Method': 'DELETE', 492 | 'IF-MATCH': '*' 493 | }; 494 | request.body = JSON.stringify(listItem); 495 | 496 | return this.context.spHttpClient.post( 497 | this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('Countries')/items(${listItem.Id})`, 498 | SPHttpClient.configurations.v1, 499 | request); 500 | }); 501 | } 502 | ``` 503 | 504 | This method will delete the last item in the list by doing the following steps: 505 | 506 | - It first gets a reference to the last item in the list by sorting the list in descending order by the `ID` property and taking just the first result. This is done using the OData `$orderby` & `$top` operators on the URL parameters of the HTTP GET request endpoint. 507 | - A new request object is created to submit to the SharePoint REST API: 508 | - The headers are set to instruct the REST API you wish to perform a **DELETE** operation and... 509 | - The item that will be deleted can match any version. 510 | - Using the `spHttpClient` API's `post()` method, the specific item in the SharePoint list is deleted by using the endpoint of the specific item, the desired configuration and the request object this method constructed. 511 | 512 | 1. Test the webpart: 513 | 1. Start the local web server and test the web part in the hosted workbench: 514 | 515 | ```shell 516 | gulp serve 517 | ``` 518 | 519 | 1. The browser will loads the local workbench, but you can not use this for testing because there is no SharePoint context in the local workbench. Instead, navigate to the SharePoint Online site where you created the **Countries** list, and load the hosted workbench at **https://[sharepoint-online-site]/_layouts/workbench.aspx**. 520 | 521 | 1. Add the web part to the page: Select the **Add a new web part** control... 522 | 523 | ![Screenshot of the SharePoint workbench](./Images/add-webpart-01.png) 524 | 525 | ...then select the expand toolbox icon in the top-right... 526 | 527 | ![Screenshot of the SharePoint workbench](./Images/add-webpart-02.png) 528 | 529 | ...and select the **SPFxHttpClientContent** web part to add the web part to the page: 530 | 531 | ![Screenshot of the SharePoint workbench toolbox](./Images/add-webpart-03.png) 532 | 533 | 1. The web part will appear on the page with a single button and no data in the list: 534 | 535 | ![Screenshot of the web part with all buttons](./Images/all-buttons.png) 536 | 537 | 1. Select the **Get Countries** button and examine the results returned... scroll to the bottom of the list and notice there is no entry with a timestamp for the **Title**. 538 | 1. Select the **Add List item** button and scroll to the end of the results returned. Notice the new item that appears with a timestamp as the **Title**. 539 | 540 | ![Screenshot of the web part with the new item](./Images/add-items-sp-01.png) 541 | 542 | 1. Test the update process by selecting the **Update List Item** button. Notice before selecting it the title of the **United States** item: 543 | 544 | ![Screenshot of the web part before updating an item](./Images/update-items-sp-01.png) 545 | 546 | Notice after selecting the button, the title has changed: 547 | 548 | ![Screenshot of the web part after updating an item](./Images/update-items-sp-02.png) 549 | 550 | 1. Test the delete process by selecting the **Delete List Item** button. Notice before selecting it the last item in the list... in this case, the item with the timestamp for the **Title**: 551 | 552 | ![Screenshot of the web part delete button](./Images/delete-items-sp-01.png) 553 | 554 | Notice after selecting the button, the last item in the list has been removed. 555 | 556 | 1. Stop the local web server by pressing CTRL+C in the console/terminal window. 557 | 558 | 559 | 560 | ## Exercise 3: Using Mocks to Simulate SharePoint Data 561 | 562 | In this exercise, you will extend the SPFx project from the previous exercise to add logic so mock data is used when the web part is run from the local workbench. 563 | 564 | > *NOTE: If you did not complete the previous exercise, you can start from the final working version found in the **[./Demos/02-spcrud](./Demos/02-spcrud)** folder.* 565 | 566 | 1. Before adding code to deal with mock data, test the existing web part in the local workbench: 567 | 1. Start the local web server and test the web part in the hosted workbench: 568 | 569 | ```shell 570 | gulp serve 571 | ``` 572 | 573 | 1. Add the web part to the workbench using the same process as the previous exercises. 574 | 1. When the web part loads, try selecting any of the buttons... notice nothing happens: 575 | 576 | ![Screenshot of empty web part in local workbench](./Images/local-workbench-01.png) 577 | 578 | 1. If you open the JavaScript console in your browser's developer tools, you will notice a few errors related to the workbench. If you inspect the network traffic int he same developer tools, when selecting the buttons, notice requests to the `*/_api/` endpoint will first show as **pending** and then ultimately timeout. 579 | 580 | This is because the local workbench has no SharePoint context nor does it have a working SharePoint REST API. That is only available in a real SharePoint environment. 581 | 582 | 1. Update the web part to determine if it is running in the local workbench or a real SharePoint environment: 583 | 1. Locate and open the **./src/webparts/spFxHttpClientDemo/spFxHttpClientDemoWebPart.ts** file. 584 | 1. Add the following `import` statement to the existing `import` statements: 585 | 586 | ```ts 587 | import { Environment, EnvironmentType } from '@microsoft/sp-core-library'; 588 | ``` 589 | 590 | 1. Add the following property to the `SpFxHttpClientDemoWebPart` class. This will return a boolean value if the current SharePoint environment is real (`true`) or the local workbench (`false`) used in testing: 591 | 592 | ```ts 593 | private get _isSharePoint(): boolean { 594 | return (Environment.type === EnvironmentType.SharePoint || Environment.type === EnvironmentType.ClassicSharePoint); 595 | } 596 | ``` 597 | 598 | 1. Update the web part to use mock data when the **Get Countries** button is pressed while running in the local workbench. Locate the `_onGetListItems` handler and update it's contents to the following code. This will first check if the web part is in the local workbench (`!this._isSharePoint`). If it is, it will return mock data. Otherwise it will run the previous code that will call the SharePoint REST API: 599 | 600 | ```ts 601 | if (!this._isSharePoint) { 602 | this._countries = [ 603 | { Id: '1', Title: 'Country 1' }, 604 | { Id: '2', Title: 'Country 2' }, 605 | { Id: '3', Title: 'Country 3' }, 606 | { Id: '4', Title: 'Country 4' } 607 | ]; 608 | this.render(); 609 | } else { 610 | this._getListItems() 611 | .then(response => { 612 | this._countries = response; 613 | this.render(); 614 | }); 615 | } 616 | ``` 617 | 618 | 1. Apply similar logic to the methods that implement write operations. However, in these cases we simply want the web part to do nothing as there is nothing to simulate in a write operation. Locate the methods `_onAddListItem`, `_onUpdateListItem` and `onDeleteListItem` and add the following line to the top of each method. This will exit the function if the web part is running in the local workbench: 619 | 620 | ```ts 621 | if (!this._isSharePoint) { return; } 622 | ``` 623 | 624 | 1. Go back to the browser with the local workbench loaded. Notice that when you select the **Get Countries** button, you see the mock data returned: 625 | 626 | ![Screenshot of mock data in the web part](./Images/local-workbench-02.png) 627 | 628 | 1. Also notice that if you go back to the hosted workbench in a SharePoint Online site, the web part works as it did prior to adding mock data as it uses the live SharePoint REST API in a real SharePoint environment. 629 | 630 | 1. Stop the local web server by pressing CTRL+C in the console/terminal window. --------------------------------------------------------------------------------