Merge pull request #958 from LauraKokkarinen/PR952

Manual merge of PR #952
This commit is contained in:
Laura Kokkarinen 2019-08-24 19:35:17 +03:00 committed by GitHub
commit bb42cdbb0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 18686 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-hooks/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.9.1",
"libraryName": "hellohooks",
"libraryId": "a31d666e-5429-4d34-8abe-2000c5627939",
"packageManager": "npm",
"isDomainIsolated": true,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,57 @@
# React Hooks Form WebPart
## Summary
The `React Hooks web part` is an example of how to implement Hooks in Spfx.
Hooks is a new feature included in React version 16.8, with the new version of **SharePoint Framework (SPFx) version 1.9.1** we can use them in our developments. In this example we are going to see the different types of hooks that are available and with the comparison of how this implementation can be done without the Hooks to be able to observe the benefits of using it.
![Brithdays Web Part](./assets/webpart.PNG)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Prerequisites
Existing list in tenant root site, with the Title "AvengersList" and columns:
Column Internal Name|Type|Required|Comments
--------------------|----|--------|----------
Title| Text| true
## Solution
Solution|Author(s)
--------|---------
react-hooks|Adrián Díaz [@AdrianDiaz81](https://www.twitter.com/adriandiaz81)
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|August 19, 2019|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
- `npm install`
- `gulp serve`
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
- Using React Hooks for building SharePoint Framework client-side web parts.
- Using @PnP/PnPjs to read items ...
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-hooks" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"hooks-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/hooks/HooksWebPart.js",
"manifest": "./src/webparts/hooks/HooksWebPart.manifest.json"
}
]
},
"classic-react-web-part-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/classicReactWebPart/ClassicReactWebPartWebPart.js",
"manifest": "./src/webparts/classicReactWebPart/ClassicReactWebPartWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"HooksWebPartStrings": "lib/webparts/hooks/loc/{locale}.js",
"ClassicReactWebPartWebPartStrings": "lib/webparts/classicReactWebPart/loc/{locale}.js"
}
}

View File

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

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "hellohooks",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "hellohooks-client-side-solution",
"id": "a31d666e-5429-4d34-8abe-2000c5627939",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": true
},
"paths": {
"zippedPackage": "solution/hellohooks.sppkg"
}
}

View File

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

View File

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

7
samples/react-hooks/gulpfile.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

17825
samples/react-hooks/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
{
"name": "hellohooks",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/common": "^1.3.4",
"@pnp/logging": "^1.3.4",
"@pnp/odata": "^1.3.4",
"@pnp/sp": "^1.3.4",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"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

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "e4866e17-54f0-46b3-a695-59e356580e06",
"alias": "ClassicReactWebPartWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "ClassicReactWebPart" },
"description": { "default": "ClassicReactWebPart description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "ClassicReactWebPart"
}
}]
}

View File

@ -0,0 +1,63 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { sp } from "@pnp/sp";
import * as strings from 'ClassicReactWebPartWebPartStrings';
import ClassicReactWebPart from './components/ClassicReactWebPart';
import { IClassicReactWebPartProps } from './components/IClassicReactWebPartProps';
export interface IClassicReactWebPartWebPartProps {
description: string;
}
export default class ClassicReactWebPartWebPart extends BaseClientSideWebPart<IClassicReactWebPartWebPartProps> {
public render(): void {
sp.setup({
spfxContext: this.context
});
const element: React.ReactElement<IClassicReactWebPartProps > = React.createElement(
ClassicReactWebPart,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.classicReactWebPart {
.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;
}
}
}

View File

@ -0,0 +1,12 @@
import * as React from 'react';
import { IClassicReactWebPartProps } from './IClassicReactWebPartProps';
import { LoadListComponent } from '../../hooks/components/LoadList';
export default class ClassicReactWebPart extends React.Component<IClassicReactWebPartProps, {}> {
public render(): React.ReactElement<IClassicReactWebPartProps> {
return (
<LoadListComponent />
);
}
}

View File

@ -0,0 +1,3 @@
export interface IClassicReactWebPartProps {
description: string;
}

View File

@ -0,0 +1,90 @@
import * as React from 'react';
import { sp } from "@pnp/sp";
import { Environment,EnvironmentType} from '@microsoft/sp-core-library';
export interface ILoadListComponentProps{
}
export interface ILoadListComponentState {
filter:string;
avengerCollection:any[];
}
export default class LoadListComponent extends React.Component<ILoadListComponentProps, ILoadListComponentState> {
public constructor(props: any) {
super(props);
this.setFilter=this.setFilter.bind(this);
this.state={
filter:'',
avengerCollection:[]
};
}
public loadAvengers (filter:string){
if (Environment.type==EnvironmentType.Local)
{
return fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`)
.then(response => response.json())
.then(json => this.setAvengerCollection(json));
}
else
{
if (filter=="")
{
sp.web.lists.getByTitle("AvengersList").items.select('Title', 'Id').get().then((items) => {
let result:any[]=[];
items.forEach(element => {result.push({"id":element["Id"], "name":element["Title"]
});
});
return result;
}).then(json => this.setAvengerCollection(json));;
}
else{
sp.web.lists.getByTitle("AvengersList").items.select('Title', 'Id').filter("substringof('" + filter + "',Title)")
.get().then((items) => {
let result:any[]=[];
items.forEach(element => {
result.push({
"id":element["Id"],
"name":element["Title"]
});
});
return result;
}).then(json => this.setAvengerCollection(json));
}
}
}
public componentWillMount(){
this.loadAvengers(this.state.filter);
}
public componentDidUpdate(prevProps, prevState) {
if (prevState.filter!==this.state.filter){
this.loadAvengers(prevState.filter);
}
}
private setAvengerCollection(json:any)
{
this.setState({
filter: json
});
}
private setFilter(event: any) {
this.setState({
filter: event.target.value
});
}
public render() {
return <div>
<input value={this.state.filter} onChange={this.setFilter} />
<ul>
{this.state.avengerCollection.map((user, index) => (
<li key={index}>{user.name}</li>
))}
</ul>
</div>;
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IClassicReactWebPartWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'ClassicReactWebPartWebPartStrings' {
const strings: IClassicReactWebPartWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "beedcf95-f6ec-4f3e-80e8-13f62ce792e6",
"alias": "HooksWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "hooks" },
"description": { "default": "hooks description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "hooks"
}
}]
}

View File

@ -0,0 +1,63 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { sp } from "@pnp/sp";
import * as strings from 'HooksWebPartStrings';
import Hooks from './components/Hooks';
import { IHooksProps } from './components/IHooksProps';
export interface IHooksWebPartProps {
description: string;
}
export default class HooksWebPart extends BaseClientSideWebPart<IHooksWebPartProps> {
public render(): void {
sp.setup({
spfxContext: this.context
});
const element: React.ReactElement<IHooksProps > = React.createElement(
Hooks,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import { EnvironmentType} from '@microsoft/sp-core-library';
import { sp } from "@pnp/sp";
export const useAvengerCollection = (type:EnvironmentType) => {
const [filter, setFilter] = React.useState("");
const [avengerCollection, setAvengerCollection] = React.useState([]);
const loadAvengers = () => {
if (type==EnvironmentType.Local)
{
fetch(`https://jsonplaceholder.typicode.com/users?name_like=${filter}`)
.then(response => response.json())
.then(json => setAvengerCollection(json));
}
else
{
if (filter=="")
{
sp.web.lists.getByTitle("AvengersList").items.select('Title', 'Id').get().then((items) => {
let result:any[]=[];
items.forEach(element => {result.push({"id":element["Id"], "name":element["Title"]
});
});
return result;
})
.then(json => setAvengerCollection(json));
}
else{
sp.web.lists.getByTitle("AvengersList").items.select('Title', 'Id').filter("substringof('" + filter + "',Title)")
.get().then((items) => {
let result:any[]=[];
items.forEach(element => {
result.push({
"id":element["Id"],
"name":element["Title"]
});
});
return result;
})
.then(json => setAvengerCollection(json));
}
}
};
return {avengerCollection, loadAvengers, filter,setFilter, setAvengerCollection};
};

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.hooks {
.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;
}
}
}

View File

@ -0,0 +1,10 @@
import * as React from 'react';
import { IHooksProps } from './IHooksProps';
import {LoadListComponent} from './LoadList';
export default class Hooks extends React.Component<IHooksProps, {}> {
public render(): React.ReactElement<IHooksProps> {
return (
<LoadListComponent />
);
}
}

View File

@ -0,0 +1,3 @@
export interface IHooksProps {
description: string;
}

View File

@ -0,0 +1,21 @@
import * as React from 'react';
import {useAvengerCollection} from './AvengersCollection';
import { Environment} from '@microsoft/sp-core-library';
export const LoadListComponent = () => {
const {avengerCollection, loadAvengers, filter, setFilter} = useAvengerCollection(Environment.type);
React.useEffect(() => {
loadAvengers();
}, [filter]);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>
{avengerCollection.map((user, index) => (
<li key={index}>{user.name}</li>
))}
</ul>
</div>
);
};

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IHooksWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'HooksWebPartStrings' {
const strings: IHooksWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"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
}
}