SharePoint CRUD sample updated to SPFx 1.6.0 (#666)

This commit is contained in:
Gautam Sheth 2018-11-09 13:14:42 +05:30 committed by Vesa Juvonen
parent 5723134c29
commit 277af705b0
32 changed files with 18514 additions and 113 deletions

View File

@ -1,8 +1,11 @@
{ {
"@microsoft/generator-sharepoint": { "@microsoft/generator-sharepoint": {
"version": "1.4.1", "isCreatingSolution": false,
"environment": "spo",
"version": "1.6.0",
"libraryName": "sharepoint-crud", "libraryName": "sharepoint-crud",
"libraryId": "037cc555-b7cd-4bdc-b661-0b38a09b5844", "libraryId": "1409e8a2-1ce4-4888-87f8-1dc377b6b62e",
"environment": "spo" "packageManager": "npm",
"componentType": "webpart"
} }
} }

View File

@ -2,12 +2,12 @@
## Summary ## Summary
Sample Web Parts illustrating performing SharePoint CRUD operations in React, Angular, JavaScript without any framework and using the [SP PnP JS library](https://github.com/OfficeDev/PnP-JS-Core). Sample Web Parts illustrating performing SharePoint CRUD operations in React, Angular, JavaScript without any framework and using the [@pnp/sp library](https://github.com/pnp/pnpjs).
![Sample To do SharePoint Framework Client-Side Web Part built using Angular and ngOfficeUIFabric](./assets/preview.png) ![Sample To do SharePoint Framework Client-Side Web Part built using Angular and ngOfficeUIFabric](./assets/preview.png)
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.4.1-green.svg) ![drop](https://img.shields.io/badge/drop-1.6.0-green.svg)
## Applies to ## Applies to
@ -24,6 +24,7 @@ sharepoint-crud|Waldek Mastykarz (MVP, Rencore, @waldekm), Gautam Sheth (SharePo
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.3|November 1, 2018|Updated to SPFx 1.6.0
1.2|March 30, 2018|Updated to SPFx 1.4.1 1.2|March 30, 2018|Updated to SPFx 1.4.1
1.1|March 9, 2017|Updated to SPFx GA 1.1|March 9, 2017|Updated to SPFx GA
1.0|September 16, 2016|Initial release 1.0|September 16, 2016|Initial release
@ -58,7 +59,7 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
- in React - in React
- in Angular v1.x - in Angular v1.x
- without a particular JavaScript framework - without a particular JavaScript framework
- using the [SP PnP JS library](https://github.com/OfficeDev/PnP-JS-Core) - using the [@pnp/sp library](https://github.com/PnP/PnPJS)
- using ETag to ensure data integrity when updating and deleting items - using ETag to ensure data integrity when updating and deleting items
- chaining promises for performing multiple asynchronous operations as part of one use case - chaining promises for performing multiple asynchronous operations as part of one use case
- breaking a chain of promises in case of an error and handling it gracefully - breaking a chain of promises in case of an error and handling it gracefully
@ -72,8 +73,8 @@ This sample illustrates the following concepts on top of the SharePoint Framewor
- loading Angular and [ngOfficeUIFabric](http://ngofficeuifabric.com) from CDN - loading Angular and [ngOfficeUIFabric](http://ngofficeuifabric.com) from CDN
- using conditional rendering for one-time Web Part setup - using conditional rendering for one-time Web Part setup
- passing Web Part configuration to Angular and reacting to configuration changes in the Angular application - passing Web Part configuration to Angular and reacting to configuration changes in the Angular application
- SP PnP JS library - @pnp/sp library
- using the SP PnP JS library with SharePoint Framework Client-Side Web Parts - using the @pnp/sp JS library with SharePoint Framework Client-Side Web Parts
- configuring global request headers and overriding them for specific requests - configuring global request headers and overriding them for specific requests
- sorting and selecting top n items from a list using the fluent API - sorting and selecting top n items from a list using the fluent API

View File

@ -1,5 +1,5 @@
{ {
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0", "version": "2.0",
"bundles": { "bundles": {
"no-framework-crud-web-part": { "no-framework-crud-web-part": {
@ -10,11 +10,11 @@
} }
] ]
}, },
"sp-pn-p-js-crud-web-part": { "angular-crud-web-part": {
"components": [ "components": [
{ {
"entrypoint": "./lib/webparts/spPnPJsCrud/SpPnPJsCrudWebPart.js", "entrypoint": "./lib/webparts/angularCrud/AngularCrudWebPart.js",
"manifest": "./src/webparts/spPnPJsCrud/SpPnPJsCrudWebPart.manifest.json" "manifest": "./src/webparts/angularCrud/AngularCrudWebPart.manifest.json"
} }
] ]
}, },
@ -26,26 +26,26 @@
} }
] ]
}, },
"angular-crud-web-part": { "pnpjs-crud-web-part": {
"components": [ "components": [
{ {
"entrypoint": "./lib/webparts/angularCrud/AngularCrudWebPart.js", "entrypoint": "./lib/webparts/pnpjsCrud/PnpjsCrudWebPart.js",
"manifest": "./src/webparts/angularCrud/AngularCrudWebPart.manifest.json" "manifest": "./src/webparts/pnpjsCrud/PnpjsCrudWebPart.manifest.json"
} }
] ]
} }
}, },
"externals": { "externals": {
"angular": { "angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js", "path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js",
"globalName": "angular" "globalName": "angular"
}, },
"ng-office-ui-fabric": "https://cdnjs.cloudflare.com/ajax/libs/ngOfficeUiFabric/0.16.0/ngOfficeUiFabric.min.js" "ng-office-ui-fabric": "https://cdnjs.cloudflare.com/ajax/libs/ngOfficeUiFabric/0.16.1/ngOfficeUiFabric.min.js"
}, },
"localizedResources": { "localizedResources": {
"NoFrameworkCrudWebPartStrings": "lib/webparts/noFrameworkCrud/loc/{locale}.js", "NoFrameworkCrudWebPartStrings": "lib/webparts/noFrameworkCrud/loc/{locale}.js",
"SpPnPJsCrudWebPartStrings": "lib/webparts/spPnPJsCrud/loc/{locale}.js", "AngularCrudWebPartStrings": "lib/webparts/angularCrud/loc/{locale}.js",
"ReactCrudWebPartStrings": "lib/webparts/reactCrud/loc/{locale}.js", "ReactCrudWebPartStrings": "lib/webparts/reactCrud/loc/{locale}.js",
"AngularCrudWebPartStrings": "lib/webparts/angularCrud/loc/{locale}.js" "PnpjsCrudWebPartStrings": "lib/webparts/pnpjsCrud/loc/{locale}.js"
} }
} }

View File

@ -1,3 +1,4 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy" "deployCdnPath": "temp/deploy"
} }

View File

@ -1,4 +1,5 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/", "workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->", "account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "sharepoint-crud", "container": "sharepoint-crud",

View File

@ -1,8 +1,10 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": { "solution": {
"name": "sharepoint-crud-client-side-solution", "name": "sharepoint-crud-client-side-solution",
"id": "c51ba9f2-6f5c-412a-a5fc-76610f39be8c", "id": "1409e8a2-1ce4-4888-87f8-1dc377b6b62e",
"version": "1.1.0.0" "version": "1.0.0.0",
"includeClientSideAssets": true
}, },
"paths": { "paths": {
"zippedPackage": "solution/sharepoint-crud.sppkg" "zippedPackage": "solution/sharepoint-crud.sppkg"

View File

@ -1,7 +1,8 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321, "port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true, "https": true,
"initialPage": "https://localhost:5432/workbench",
"api": { "api": {
"port": 5432, "port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/" "entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"

View File

@ -1,3 +1,4 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->" "cdnBasePath": "<!-- PATH TO CDN -->"
} }

17848
samples/sharepoint-crud/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "sharepoint-crud", "name": "sharepoint-crud",
"version": "1.4.1", "version": "1.6.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -15,27 +15,30 @@
"test": "gulp test" "test": "gulp test"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "~1.4.1", "@microsoft/sp-core-library": "1.6.0",
"@microsoft/sp-lodash-subset": "~1.4.1", "@microsoft/sp-lodash-subset": "1.6.0",
"@microsoft/sp-office-ui-fabric-core": "~1.4.1", "@microsoft/sp-office-ui-fabric-core": "1.6.0",
"@microsoft/sp-webpart-base": "~1.4.1", "@microsoft/sp-webpart-base": "1.6.0",
"@pnp/common": "^1.0.3", "@pnp/common": "^1.2.3",
"@pnp/logging": "^1.0.3", "@pnp/logging": "^1.2.3",
"@pnp/odata": "^1.0.3", "@pnp/odata": "^1.2.3",
"@pnp/sp": "^1.0.3", "@pnp/sp": "^1.2.3",
"@types/angular": "^1.6.51",
"@types/es6-promise": "0.0.33",
"@types/react": "15.6.6", "@types/react": "15.6.6",
"@types/react-dom": "15.5.6", "@types/react-dom": "15.5.6",
"@types/webpack-env": ">=1.12.1 <1.14.0", "@types/webpack-env": "1.13.1",
"react": "15.6.2", "react": "15.6.2",
"react-dom": "15.6.2" "react-dom": "15.6.2"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/sp-build-web": "~1.4.1", "@microsoft/sp-build-web": "1.6.0",
"@microsoft/sp-module-interfaces": "~1.4.1", "@microsoft/sp-module-interfaces": "1.6.0",
"@microsoft/sp-webpart-workbench": "~1.4.1", "@microsoft/sp-webpart-workbench": "1.6.0",
"tslint-microsoft-contrib": "~5.0.0",
"gulp": "~3.9.1", "gulp": "~3.9.1",
"@types/chai": ">=3.4.34 <3.6.0", "@types/chai": "3.4.34",
"@types/mocha": ">=2.2.33 <2.6.0", "@types/mocha": "2.2.38",
"ajv": "~5.2.2" "ajv": "~5.2.2"
} }
} }

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "cceebf19-128e-4ffd-bb97-9989bf7c04d4", "id": "74208436-1c27-40df-8cb4-1625cd764c2e",
"alias": "AngularCrudWebPart", "alias": "AngularCrudWebPart",
"componentType": "WebPart", "componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json // The "*" signifies that the version should be taken from the package.json
@ -20,7 +20,7 @@
"default": "Angular CRUD" "default": "Angular CRUD"
}, },
"description": { "description": {
"default": "Sample implementation of SharePoint CRUD operations in Angular" "default": "Sample implementation of SharePoint CRUD operations in AngularJS (1.x)"
}, },
"officeFabricIconFontName": "Page", "officeFabricIconFontName": "Page",
"properties": { "properties": {

View File

@ -8,6 +8,9 @@
} }
.row { .row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px; padding: 20px;
} }
@ -17,38 +20,61 @@
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
} }
:global .ms-Button { .column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button // Our button
text-decoration: none; text-decoration: none;
height: 32px; height: 32px;
// Primary Button // Primary Button
min-width: 80px; min-width: 80px;
background-color: #0078d7; background-color: $ms-color-themePrimary;
border-color: #0078d7; border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button // Basic Button
outline: transparent; outline: transparent;
position: relative; position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif; font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-size: 14px; font-size: $ms-font-size-m;
font-weight: 400; font-weight: $ms-font-weight-regular;
border-width: 0; border-width: 0;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
padding: 0 16px; padding: 0 16px;
:global .ms-Button-label { .label {
font-weight: 600; font-weight: $ms-font-weight-semibold;
font-size: 14px; font-size: $ms-font-size-m;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
margin: 0 4px; margin: 0 4px;
vertical-align: top; vertical-align: top;
display: inline-block; display: inline-block;
color: #ffffff;
} }
&.disabled, &:disabled { &.disabled, &:disabled {

View File

@ -11,11 +11,13 @@ import * as strings from 'AngularCrudWebPartStrings';
import * as angular from 'angular'; import * as angular from 'angular';
import './app/app-module'; import './app/app-module';
export interface IAngularCrudWebPartProps { export interface IAngularCrudWebPartProps {
listName: string; listName: string;
} }
export default class AngularCrudWebPart extends BaseClientSideWebPart<IAngularCrudWebPartProps> { export default class AngularCrudWebPart extends BaseClientSideWebPart<IAngularCrudWebPartProps> {
private $injector: angular.auto.IInjectorService; private $injector: angular.auto.IInjectorService;
public render(): void { public render(): void {

View File

@ -1,4 +1,4 @@
define([], function () { define([], function() {
return { return {
"PropertyPaneDescription": "Configure settings", "PropertyPaneDescription": "Configure settings",
"DataGroupName": "Data", "DataGroupName": "Data",

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "dd6cd5e1-46e0-46cc-a0c9-be9e9adedcad", "id": "b724fd00-ee58-451d-95e1-8d496e6121f6",
"alias": "NoFrameworkCrudWebPart", "alias": "NoFrameworkCrudWebPart",
"componentType": "WebPart", "componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json // The "*" signifies that the version should be taken from the package.json

View File

@ -8,13 +8,33 @@
} }
.row { .row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px; padding: 20px;
} }
.listItem { .column {
max-width: 715px; @include ms-Grid-col;
margin: 5px auto 5px auto; @include ms-lg10;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); @include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
} }
.button { .button {
@ -24,16 +44,17 @@
// Primary Button // Primary Button
min-width: 80px; min-width: 80px;
background-color: #0078d7; background-color: $ms-color-themePrimary;
border-color: #0078d7; border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button // Basic Button
outline: transparent; outline: transparent;
position: relative; position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif; font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-size: 14px; font-size: $ms-font-size-m;
font-weight: 400; font-weight: $ms-font-weight-regular;
border-width: 0; border-width: 0;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@ -41,14 +62,13 @@
padding: 0 16px; padding: 0 16px;
.label { .label {
font-weight: 600; font-weight: $ms-font-weight-semibold;
font-size: 14px; font-size: $ms-font-size-m;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
margin: 0 4px; margin: 0 4px;
vertical-align: top; vertical-align: top;
display: inline-block; display: inline-block;
color: #ffffff;
} }
&.disabled, &:disabled { &.disabled, &:disabled {
@ -61,5 +81,6 @@
color: #a6a6a6; color: #a6a6a6;
} }
} }
} }
} }

View File

@ -4,17 +4,22 @@ import {
IPropertyPaneConfiguration, IPropertyPaneConfiguration,
PropertyPaneTextField PropertyPaneTextField
} from '@microsoft/sp-webpart-base'; } from '@microsoft/sp-webpart-base';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import { IListItem } from './IListItem';
import styles from './NoFrameworkCrudWebPart.module.scss'; import styles from './NoFrameworkCrudWebPart.module.scss';
import * as strings from 'NoFrameworkCrudWebPartStrings'; import * as strings from 'NoFrameworkCrudWebPartStrings';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export interface INoFrameworkCrudWebPartProps { export interface INoFrameworkCrudWebPartProps {
listName: string; listName: string;
} }
interface IListItem {
Title?: string;
Id: number;
}
export default class NoFrameworkCrudWebPart extends BaseClientSideWebPart<INoFrameworkCrudWebPartProps> { export default class NoFrameworkCrudWebPart extends BaseClientSideWebPart<INoFrameworkCrudWebPartProps> {
private listItemEntityTypeName: string = undefined; private listItemEntityTypeName: string = undefined;
public render(): void { public render(): void {
@ -95,32 +100,6 @@ export default class NoFrameworkCrudWebPart extends BaseClientSideWebPart<INoFra
this.domElement.querySelector('button.delete-Button').addEventListener('click', () => { webPart.deleteItem(); }); this.domElement.querySelector('button.delete-Button').addEventListener('click', () => { webPart.deleteItem(); });
} }
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.DataGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
private listNotConfigured(): boolean { private listNotConfigured(): boolean {
return this.properties.listName === undefined || return this.properties.listName === undefined ||
this.properties.listName === null || this.properties.listName === null ||
@ -375,4 +354,30 @@ export default class NoFrameworkCrudWebPart extends BaseClientSideWebPart<INoFra
private updateItemsHtml(items: IListItem[]): void { private updateItemsHtml(items: IListItem[]): void {
this.domElement.querySelector('.items').innerHTML = items.map(item => `<li>${item.Title} (${item.Id})</li>`).join(""); this.domElement.querySelector('.items').innerHTML = items.map(item => `<li>${item.Title} (${item.Id})</li>`).join("");
} }
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.DataGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
} }

View File

@ -1,4 +1,4 @@
define([], function () { define([], function() {
return { return {
"PropertyPaneDescription": "Configure settings", "PropertyPaneDescription": "Configure settings",
"DataGroupName": "Data", "DataGroupName": "Data",

View File

@ -0,0 +1,31 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "8019d2e5-c480-4ecd-a8ac-f59ac457af96",
"alias": "PnpjsCrudWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Under Development"
},
"title": {
"default": "PnP JS CRUD"
},
"description": {
"default": "Sample implementation of SharePoint CRUD operations using @pnp/sp library"
},
"officeFabricIconFontName": "Page",
"properties": {
"listName": ""
}
}
]
}

View File

@ -0,0 +1,85 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.pnpjsCrud {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
&.disabled, &:disabled {
background-color: #f4f4f4;
border-color: #f4f4f4;
cursor: default;
pointer-events: none;
.label {
color: #a6a6a6;
}
}
}
}

View File

@ -0,0 +1,288 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import styles from './PnpjsCrudWebPart.module.scss';
import * as strings from 'PnpjsCrudWebPartStrings';
import { sp, Item, ItemAddResult, ItemUpdateResult } from '@pnp/sp';
export interface IPnpjsCrudWebPartProps {
listName: string;
}
interface IListItem {
Title?: string;
Id: number;
}
export default class PnpjsCrudWebPart extends BaseClientSideWebPart<IPnpjsCrudWebPartProps> {
protected onInit(): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error?: any) => void): void => {
sp.setup({
sp: {
headers: {
"Accept": "application/json; odata=nometadata"
}
}
});
resolve();
});
}
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.pnpjsCrud}">
<div class="${styles.container}">
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span class="ms-font-xl ms-fontColor-white">
Sample SharePoint CRUD operations using the SP PnP JS library
</span>
</div>
</div>
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<button class="${styles.button} create-Button">
<span class="${styles.label}">Create item</span>
</button>
<button class="${styles.button} read-Button">
<span class="${styles.label}">Read item</span>
</button>
</div>
</div>
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<button class="${styles.button} readall-Button">
<span class="${styles.label}">Read all items</span>
</button>
</div>
</div>
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<button class="${styles.button} update-Button">
<span class="${styles.label}">Update item</span>
</button>
<button class="${styles.button} delete-Button">
<span class="${styles.label}">Delete item</span>
</button>
</div>
</div>
<div class="ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}">
<div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<div class="status"></div>
<ul class="items"><ul>
</div>
</div>
</div>
</div>
`;
this.updateStatus(this.listNotConfigured() ? 'Please configure list in Web Part properties' : 'Ready');
this.setButtonsState();
this.setButtonsEventHandlers();
}
private setButtonsState(): void {
const buttons: NodeListOf<Element> = this.domElement.querySelectorAll(`button.${styles.button}`);
const listNotConfigured: boolean = this.listNotConfigured();
for (let i: number = 0; i < buttons.length; i++) {
const button: Element = buttons.item(i);
if (listNotConfigured) {
button.setAttribute('disabled', 'disabled');
}
else {
button.removeAttribute('disabled');
}
}
}
private setButtonsEventHandlers(): void {
const webPart: PnpjsCrudWebPart = this;
this.domElement.querySelector('button.create-Button').addEventListener('click', () => { webPart.createItem(); });
this.domElement.querySelector('button.read-Button').addEventListener('click', () => { webPart.readItem(); });
this.domElement.querySelector('button.readall-Button').addEventListener('click', () => { webPart.readItems(); });
this.domElement.querySelector('button.update-Button').addEventListener('click', () => { webPart.updateItem(); });
this.domElement.querySelector('button.delete-Button').addEventListener('click', () => { webPart.deleteItem(); });
}
private listNotConfigured(): boolean {
return this.properties.listName === undefined ||
this.properties.listName === null ||
this.properties.listName.length === 0;
}
private createItem(): void {
this.updateStatus('Creating item...');
sp.web.lists.getByTitle(this.properties.listName).items.add({
'Title': `Item ${new Date()}`
}).then((result: ItemAddResult): void => {
const item: IListItem = result.data as IListItem;
this.updateStatus(`Item '${item.Title}' (ID: ${item.Id}) successfully created`);
}, (error: any): void => {
this.updateStatus('Error while creating the item: ' + error);
});
}
private readItem(): void {
this.updateStatus('Loading latest items...');
this.getLatestItemId()
.then((itemId: number): Promise<IListItem> => {
if (itemId === -1) {
throw new Error('No items found in the list');
}
this.updateStatus(`Loading information about item ID: ${itemId}...`);
return sp.web.lists.getByTitle(this.properties.listName)
.items.getById(itemId).select('Title', 'Id').get();
})
.then((item: IListItem): void => {
this.updateStatus(`Item ID: ${item.Id}, Title: ${item.Title}`);
}, (error: any): void => {
this.updateStatus('Loading latest item failed with error: ' + error);
});
}
private getLatestItemId(): Promise<number> {
return new Promise<number>((resolve: (itemId: number) => void, reject: (error: any) => void): void => {
sp.web.lists.getByTitle(this.properties.listName)
.items.orderBy('Id', false).top(1).select('Id').get()
.then((items: { Id: number }[]): void => {
if (items.length === 0) {
resolve(-1);
}
else {
resolve(items[0].Id);
}
}, (error: any): void => {
reject(error);
});
});
}
private readItems(): void {
this.updateStatus('Loading all items...');
sp.web.lists.getByTitle(this.properties.listName)
.items.select('Title', 'Id').get()
.then((items: IListItem[]): void => {
this.updateStatus(`Successfully loaded ${items.length} items`, items);
}, (error: any): void => {
this.updateStatus('Loading all items failed with error: ' + error);
});
}
private updateItem(): void {
this.updateStatus('Loading latest items...');
let latestItemId: number = undefined;
let etag: string = undefined;
this.getLatestItemId()
.then((itemId: number): Promise<Item> => {
if (itemId === -1) {
throw new Error('No items found in the list');
}
latestItemId = itemId;
this.updateStatus(`Loading information about item ID: ${itemId}...`);
return sp.web.lists.getByTitle(this.properties.listName)
.items.getById(itemId).get(undefined, {
headers: {
'Accept': 'application/json;odata=minimalmetadata'
}
});
})
.then((item: Item): Promise<IListItem> => {
etag = item["odata.etag"];
return Promise.resolve((item as any) as IListItem);
})
.then((item: IListItem): Promise<ItemUpdateResult> => {
return sp.web.lists.getByTitle(this.properties.listName)
.items.getById(item.Id).update({
'Title': `Item ${new Date()}`
}, etag);
})
.then((result: ItemUpdateResult): void => {
this.updateStatus(`Item with ID: ${latestItemId} successfully updated`);
}, (error: any): void => {
this.updateStatus('Loading latest item failed with error: ' + error);
});
}
private deleteItem(): void {
if (!window.confirm('Are you sure you want to delete the latest item?')) {
return;
}
this.updateStatus('Loading latest items...');
let latestItemId: number = undefined;
let etag: string = undefined;
this.getLatestItemId()
.then((itemId: number): Promise<Item> => {
if (itemId === -1) {
throw new Error('No items found in the list');
}
latestItemId = itemId;
this.updateStatus(`Loading information about item ID: ${latestItemId}...`);
return sp.web.lists.getByTitle(this.properties.listName)
.items.getById(latestItemId).select('Id').get(undefined, {
headers: {
'Accept': 'application/json;odata=minimalmetadata'
}
});
})
.then((item: Item): Promise<IListItem> => {
etag = item["odata.etag"];
return Promise.resolve((item as any) as IListItem);
})
.then((item: IListItem): Promise<void> => {
this.updateStatus(`Deleting item with ID: ${latestItemId}...`);
return sp.web.lists.getByTitle(this.properties.listName)
.items.getById(item.Id).delete(etag);
})
.then((): void => {
this.updateStatus(`Item with ID: ${latestItemId} successfully deleted`);
}, (error: any): void => {
this.updateStatus(`Error deleting item: ${error}`);
});
}
private updateStatus(status: string, items: IListItem[] = []): void {
this.domElement.querySelector('.status').innerHTML = status;
this.updateItemsHtml(items);
}
private updateItemsHtml(items: IListItem[]): void {
this.domElement.querySelector('.items').innerHTML = items.map(item => `<li>${item.Title} (${item.Id})</li>`).join("");
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.DataGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Configure settings",
"DataGroupName": "Data",
"ListNameFieldLabel": "List Name"
}
});

View File

@ -0,0 +1,10 @@
declare interface IPnpjsCrudWebPartStrings {
PropertyPaneDescription: string;
DataGroupName: string;
ListNameFieldLabel: string;
}
declare module 'PnpjsCrudWebPartStrings' {
const strings: IPnpjsCrudWebPartStrings;
export = strings;
}

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "b1900cc3-964d-40be-95bc-633843494258", "id": "3d06d75b-c339-4c4c-9fd6-ea6807f6dd1b",
"alias": "ReactCrudWebPart", "alias": "ReactCrudWebPart",
"componentType": "WebPart", "componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json // The "*" signifies that the version should be taken from the package.json

View File

@ -30,6 +30,10 @@ export default class ReactCrudWebPart extends BaseClientSideWebPart<IReactCrudWe
ReactDom.render(element, this.domElement); ReactDom.render(element, this.domElement);
} }
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version { protected get dataVersion(): Version {
return Version.parse('1.0'); return Version.parse('1.0');
} }

View File

@ -1,4 +1,4 @@
export interface IListItem { export interface IListItem {
Title?: string; Title?: string;
Id: number; Id: number;
} }

View File

@ -8,13 +8,33 @@
} }
.row { .row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px; padding: 20px;
} }
.listItem { .column {
max-width: 715px; @include ms-Grid-col;
margin: 5px auto 5px auto; @include ms-lg10;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); @include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
} }
.button { .button {
@ -24,16 +44,17 @@
// Primary Button // Primary Button
min-width: 80px; min-width: 80px;
background-color: #0078d7; background-color: $ms-color-themePrimary;
border-color: #0078d7; border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button // Basic Button
outline: transparent; outline: transparent;
position: relative; position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif; font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-size: 14px; font-size: $ms-font-size-m;
font-weight: 400; font-weight: $ms-font-weight-regular;
border-width: 0; border-width: 0;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@ -41,14 +62,13 @@
padding: 0 16px; padding: 0 16px;
.label { .label {
font-weight: 600; font-weight: $ms-font-weight-semibold;
font-size: 14px; font-size: $ms-font-size-m;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
margin: 0 4px; margin: 0 4px;
vertical-align: top; vertical-align: top;
display: inline-block; display: inline-block;
color: #ffffff;
} }
&.disabled, &:disabled { &.disabled, &:disabled {
@ -61,5 +81,6 @@
color: #a6a6a6; color: #a6a6a6;
} }
} }
} }
} }

View File

@ -6,6 +6,7 @@ import { IListItem } from './IListItem';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http'; import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export default class ReactCrud extends React.Component<IReactCrudProps, IReactCrudState> { export default class ReactCrud extends React.Component<IReactCrudProps, IReactCrudState> {
private listItemEntityTypeName: string = undefined; private listItemEntityTypeName: string = undefined;
constructor(props: IReactCrudProps, state: IReactCrudState) { constructor(props: IReactCrudProps, state: IReactCrudState) {

View File

@ -2,12 +2,14 @@
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs", "module": "esnext",
"moduleResolution": "node",
"jsx": "react", "jsx": "react",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "lib",
"typeRoots": [ "typeRoots": [
"./node_modules/@types", "./node_modules/@types",
"./node_modules/@microsoft" "./node_modules/@microsoft"
@ -21,5 +23,12 @@
"dom", "dom",
"es2015.collection" "es2015.collection"
] ]
} },
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
} }

View File

@ -1,3 +1,32 @@
{ {
"rulesDirectory": "./config" "rulesDirectory": [
"tslint-microsoft-contrib"
],
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
} }