React graph feedback form update (#1086)
Updated sample react-graph-feedback-form
This commit is contained in:
parent
11da7ad89d
commit
844ab1e4ce
|
@ -8,7 +8,7 @@ Sample SPFx React web part which allows sending emails using Microsoft Graph.
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## Used SharePoint Framework Version
|
||||||
|
|
||||||
![drop](https://img.shields.io/badge/version-1.8.2-green.svg)
|
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ react-graph-feedback-form|Sergei Zheleznov (CollabStack)
|
||||||
|
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0|August 12, 2019|Initial release
|
1.0.0|August 12, 2019|Initial release
|
||||||
|
1.0.3|Dec 15, 2019|Added Logger (@pnp/logging), Added max message length property (PropertyFieldNumber control from spfx-controls-react), Code refactoring, SPFx updated to 1.9.1
|
||||||
|
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
@ -54,5 +55,7 @@ This sample illustrates the following concepts:
|
||||||
* sending e-mails using Microsoft Graph
|
* sending e-mails using Microsoft Graph
|
||||||
* using MSGraphClient in a SharePoint Framework web part
|
* using MSGraphClient in a SharePoint Framework web part
|
||||||
* using @microsoft/microsoft-graph-types
|
* using @microsoft/microsoft-graph-types
|
||||||
|
* using @pnp/logging
|
||||||
|
* using @pnp/spfx-property-controls
|
||||||
|
|
||||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-feedback-form" />
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-feedback-form" />
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"externals": {},
|
"externals": {},
|
||||||
"localizedResources": {
|
"localizedResources": {
|
||||||
"FeedbackFormWebPartStrings": "lib/webparts/feedbackForm/loc/{locale}.js"
|
"FeedbackFormWebPartStrings": "lib/webparts/feedbackForm/loc/{locale}.js",
|
||||||
|
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "spfx-feedback-form-client-side-solution",
|
"name": "spfx-feedback-form-client-side-solution",
|
||||||
"id": "8cf91ad7-be8e-4f6c-a1eb-a790f3ef5a32",
|
"id": "8cf91ad7-be8e-4f6c-a1eb-a790f3ef5a32",
|
||||||
"version": "1.0.0.4",
|
"version": "1.0.0.5",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
"webApiPermissionRequests": [
|
"webApiPermissionRequests": [
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-graph-feedback-form",
|
"name": "react-graph-feedback-form",
|
||||||
"version": "1.0.0",
|
"version": "1.0.3",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Sergei Zheleznov",
|
"name": "Sergei Zheleznov",
|
||||||
"url": "https://collabstack.de"
|
"url": "https://collabstack.de"
|
||||||
|
@ -16,31 +16,33 @@
|
||||||
"test": "gulp test"
|
"test": "gulp test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "16.7.0",
|
"@microsoft/sp-core-library": "1.9.1",
|
||||||
"react-dom": "16.7.0",
|
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||||
|
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||||
|
"@microsoft/sp-property-pane": "1.9.1",
|
||||||
|
"@microsoft/sp-webpart-base": "1.9.1",
|
||||||
|
"@pnp/logging": "^1.3.6",
|
||||||
|
"@pnp/spfx-property-controls": "1.16.0",
|
||||||
|
"@types/es6-promise": "0.0.33",
|
||||||
"@types/react": "16.7.22",
|
"@types/react": "16.7.22",
|
||||||
"@types/react-dom": "16.8.0",
|
"@types/react-dom": "16.8.0",
|
||||||
"office-ui-fabric-react": "6.143.0",
|
|
||||||
"@microsoft/sp-core-library": "1.8.2",
|
|
||||||
"@microsoft/sp-property-pane": "1.8.2",
|
|
||||||
"@microsoft/sp-webpart-base": "1.8.2",
|
|
||||||
"@microsoft/sp-lodash-subset": "1.8.2",
|
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
|
|
||||||
"@types/webpack-env": "1.13.1",
|
"@types/webpack-env": "1.13.1",
|
||||||
"@types/es6-promise": "0.0.33"
|
"office-ui-fabric-react": "^6.143.0",
|
||||||
|
"react": "16.7.0",
|
||||||
|
"react-dom": "16.7.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "16.7.22"
|
"@types/react": "16.7.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/microsoft-graph-types": "^1.10.0",
|
"@microsoft/microsoft-graph-types": "^1.10.0",
|
||||||
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
|
"@microsoft/rush-stack-compiler-2.9": "0.9.10",
|
||||||
"@microsoft/sp-build-web": "1.8.2",
|
"@microsoft/sp-build-web": "1.9.1",
|
||||||
"@microsoft/sp-module-interfaces": "1.8.2",
|
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||||
"@microsoft/sp-tslint-rules": "1.8.2",
|
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||||
"@microsoft/sp-webpart-workbench": "1.8.2",
|
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||||
"@types/chai": "3.4.34",
|
"@types/chai": "4.2.7",
|
||||||
"@types/mocha": "2.2.38",
|
"@types/mocha": "5.2.7",
|
||||||
"ajv": "~5.2.2",
|
"ajv": "~5.2.2",
|
||||||
"gulp": "~3.9.1"
|
"gulp": "~3.9.1"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,38 +6,52 @@ import {
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField
|
PropertyPaneTextField
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
|
|
||||||
import * as strings from 'FeedbackFormWebPartStrings';
|
import * as strings from 'FeedbackFormWebPartStrings';
|
||||||
import FeedbackForm from './components/FeedbackForm';
|
import FeedbackForm from './components/FeedbackForm';
|
||||||
import { IFeedbackFormProps } from './components/IFeedbackFormProps';
|
import { IFeedbackFormProps } from './components/IFeedbackFormProps';
|
||||||
import { MSGraphClient } from '@microsoft/sp-http';
|
import { MSGraphClient } from '@microsoft/sp-http';
|
||||||
|
|
||||||
|
// https://sharepoint.github.io/sp-dev-fx-property-controls/
|
||||||
|
import { PropertyFieldNumber } from '@pnp/spfx-property-controls/lib/PropertyFieldNumber';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Logger,
|
||||||
|
ConsoleListener,
|
||||||
|
LogLevel
|
||||||
|
} from "@pnp/logging";
|
||||||
|
|
||||||
|
// https://pnp.github.io/pnpjs/logging/docs/
|
||||||
|
// https://blog.mastykarz.nl/logging-sharepoint-framework/
|
||||||
|
const LOG_SOURCE: string = 'FeedbackFormWebPart';
|
||||||
|
Logger.subscribe(new ConsoleListener());
|
||||||
|
Logger.activeLogLevel = LogLevel.Info;
|
||||||
|
|
||||||
export interface IFeedbackFormWebPartProps {
|
export interface IFeedbackFormWebPartProps {
|
||||||
targetEmail: string;
|
targetEmail: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
|
maxMessageLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FeedbackFormWebPart extends BaseClientSideWebPart<IFeedbackFormWebPartProps> {
|
export default class FeedbackFormWebPart extends BaseClientSideWebPart<IFeedbackFormWebPartProps> {
|
||||||
|
private graphClient: MSGraphClient;
|
||||||
private _graphClient: MSGraphClient;
|
public async onInit(): Promise<void> {
|
||||||
|
Logger.write(`[${LOG_SOURCE}] onInit()`);
|
||||||
public onInit(): Promise<void> {
|
try {
|
||||||
return new Promise<void>((resolve: () => void, reject: (error: any) => void ): void => {
|
Logger.write(`[${LOG_SOURCE}] trying to retrieve graphClient`);
|
||||||
this.context.msGraphClientFactory
|
this.graphClient = await this.context.msGraphClientFactory.getClient();
|
||||||
.getClient()
|
} catch (error) {
|
||||||
.then((cli: MSGraphClient): void => {
|
Logger.writeJSON(error, LogLevel.Error);
|
||||||
this._graphClient = cli;
|
}
|
||||||
resolve();
|
|
||||||
}, err => reject(err));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
|
Logger.write(`[${LOG_SOURCE}] render()`);
|
||||||
const element: React.ReactElement<IFeedbackFormProps> = React.createElement(
|
const element: React.ReactElement<IFeedbackFormProps> = React.createElement(
|
||||||
FeedbackForm,
|
FeedbackForm,
|
||||||
{
|
{
|
||||||
graphClient: this._graphClient,
|
graphClient: this.graphClient,
|
||||||
targetEmail: this.properties.targetEmail,
|
targetEmail: this.properties.targetEmail,
|
||||||
|
maxMessageLength: this.properties.maxMessageLength,
|
||||||
subject: this.properties.subject
|
subject: this.properties.subject
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -45,6 +59,7 @@ export default class FeedbackFormWebPart extends BaseClientSideWebPart<IFeedback
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDispose(): void {
|
protected onDispose(): void {
|
||||||
|
Logger.write(`[${LOG_SOURCE}] onDispose()`);
|
||||||
ReactDom.unmountComponentAtNode(this.domElement);
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +83,14 @@ export default class FeedbackFormWebPart extends BaseClientSideWebPart<IFeedback
|
||||||
}),
|
}),
|
||||||
PropertyPaneTextField('subject', {
|
PropertyPaneTextField('subject', {
|
||||||
label: strings.SubjectFieldLabel
|
label: strings.SubjectFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyFieldNumber("maxMessageLength", {
|
||||||
|
key: "maxMessageLength",
|
||||||
|
label: "Maximum length of a message",
|
||||||
|
value: this.properties.maxMessageLength,
|
||||||
|
maxValue: 250,
|
||||||
|
minValue: 3,
|
||||||
|
disabled: false
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
|
||||||
|
|
||||||
.feedbackForm {
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 0px auto;
|
|
||||||
.formActions {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,28 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styles from './FeedbackForm.module.scss';
|
|
||||||
import { IFeedbackFormProps } from './IFeedbackFormProps';
|
import { IFeedbackFormProps } from './IFeedbackFormProps';
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
import { escape } from '@microsoft/sp-lodash-subset';
|
||||||
|
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||||
|
|
||||||
|
// https://developer.microsoft.com/en-us/fabric#/controls/web
|
||||||
import {
|
import {
|
||||||
TextField,
|
TextField,
|
||||||
DefaultButton,
|
DefaultButton,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
MessageBarButton
|
MessageBarButton,
|
||||||
|
Stack
|
||||||
} from 'office-ui-fabric-react';
|
} from 'office-ui-fabric-react';
|
||||||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
|
||||||
|
import {
|
||||||
|
Logger,
|
||||||
|
LogLevel
|
||||||
|
} from "@pnp/logging";
|
||||||
|
const LOG_SOURCE: string = 'FeedbackForm';
|
||||||
|
|
||||||
export interface IFeedbackFormState {
|
export interface IFeedbackFormState {
|
||||||
isBusy: boolean;
|
isBusy: boolean;
|
||||||
message: string;
|
messageWasSended: boolean;
|
||||||
messageSended: boolean;
|
messageText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FeedbackForm extends React.Component<IFeedbackFormProps, IFeedbackFormState> {
|
export default class FeedbackForm extends React.Component<IFeedbackFormProps, IFeedbackFormState> {
|
||||||
|
@ -24,26 +32,55 @@ export default class FeedbackForm extends React.Component<IFeedbackFormProps, IF
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isBusy: false,
|
isBusy: false,
|
||||||
message: '',
|
messageWasSended: false,
|
||||||
messageSended: false
|
messageText: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactElement<IFeedbackFormProps> {
|
public render(): React.ReactElement<IFeedbackFormProps> {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.props.targetEmail ? '' : this.notConfiguredAlert}
|
||||||
|
{this.state.messageWasSended ? this.messageBar : this.feedbackForm}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get feedbackForm(): JSX.Element {
|
||||||
|
|
||||||
|
const { messageText, isBusy } = this.state;
|
||||||
|
const { targetEmail, maxMessageLength } = this.props;
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Stack gap={5} styles={{ root: { width: 650, margin: "0 auto" } }}>
|
||||||
|
<TextField
|
||||||
|
disabled={isBusy}
|
||||||
|
label="Feedback"
|
||||||
|
maxLength={maxMessageLength}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
value={messageText}
|
||||||
|
onChange={this.onTextFieldChangeHandler}
|
||||||
|
/>
|
||||||
|
<p>{messageText.length} of {maxMessageLength}</p>
|
||||||
|
<div>
|
||||||
|
<DefaultButton
|
||||||
|
disabled={isBusy || !targetEmail}
|
||||||
|
onClick={this.sendMessageHandler}
|
||||||
|
>Send Message</DefaultButton>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get messageBar(): JSX.Element {
|
||||||
|
Logger.write(`[${LOG_SOURCE}] renderMessageBar()`);
|
||||||
return(
|
return(
|
||||||
<div className={ styles.feedbackForm }>
|
|
||||||
{this.props.targetEmail ? '' : (
|
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>Target email is empty! Please configure this web part first.</MessageBar>
|
|
||||||
)}
|
|
||||||
{this.state.messageSended ? (
|
|
||||||
<MessageBar
|
<MessageBar
|
||||||
actions = {
|
actions = {
|
||||||
<div>
|
<div>
|
||||||
<MessageBarButton onClick={()=>{
|
<MessageBarButton onClick={this.messageBarButtonOnClickHandler}>I want to send more!</MessageBarButton>
|
||||||
this.setState({
|
|
||||||
messageSended:false
|
|
||||||
});
|
|
||||||
}}>I want to send more!</MessageBarButton>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
messageBarType={MessageBarType.success}
|
messageBarType={MessageBarType.success}
|
||||||
|
@ -51,53 +88,71 @@ export default class FeedbackForm extends React.Component<IFeedbackFormProps, IF
|
||||||
>
|
>
|
||||||
Message was sent!
|
Message was sent!
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
) :
|
|
||||||
(
|
|
||||||
<>
|
|
||||||
<TextField disabled={this.state.isBusy} label="Feedback" multiline rows={3} name="text" value={this.state.message} onChange={this._onChange} />
|
|
||||||
<div className={ styles.formActions }>
|
|
||||||
<DefaultButton disabled={this.state.isBusy || !this.props.targetEmail} onClick={this._sendMessage}>Send</DefaultButton>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onChange = (event: React.ChangeEvent<HTMLInputElement>) : void => {
|
private get notConfiguredAlert(): JSX.Element {
|
||||||
this.setState({message:event.target.value});
|
Logger.write(`[${LOG_SOURCE}] renderNotConfiguredAlert()`);
|
||||||
|
return(
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>Target email is empty! Please configure this web part first.</MessageBar>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sendMessage = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) : Promise<void> => {
|
private onTextFieldChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
||||||
|
const messageText = event.target.value;
|
||||||
|
this.setState({messageText});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendMessageHandler = async(): Promise<void> => {
|
||||||
|
Logger.write(`[${LOG_SOURCE}] sendMessageHandler()`);
|
||||||
|
|
||||||
|
const { graphClient, targetEmail, subject } = this.props;
|
||||||
|
const { messageText } = this.state;
|
||||||
this.setState({ isBusy:true });
|
this.setState({ isBusy:true });
|
||||||
|
|
||||||
const msg = {
|
Logger.write(`[${LOG_SOURCE}] composing message`);
|
||||||
subject: escape(this.props.subject),
|
const message: MicrosoftGraph.Message = {
|
||||||
|
subject: escape(subject),
|
||||||
importance:"low",
|
importance:"low",
|
||||||
body: {
|
body: {
|
||||||
contentType:"html",
|
contentType:"html",
|
||||||
content: escape(this.state.message)
|
content: escape(messageText)
|
||||||
},
|
},
|
||||||
toRecipients: [
|
toRecipients: [
|
||||||
{
|
{
|
||||||
emailAddress: {
|
emailAddress: {
|
||||||
address: this.props.targetEmail
|
address: targetEmail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
} as MicrosoftGraph.Message;
|
};
|
||||||
|
|
||||||
|
let messageWasSended = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Logger.write(`[${LOG_SOURCE}] trying send email to ${this.props.targetEmail}`);
|
||||||
|
|
||||||
|
await graphClient.api('/me/sendMail').post({message});
|
||||||
|
messageWasSended = true;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
Logger.writeJSON(error, LogLevel.Error);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
|
||||||
await this.props.graphClient.api('/me/sendMail')
|
|
||||||
.post({
|
|
||||||
message : msg
|
|
||||||
}).then(() => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isBusy:false,
|
isBusy:false,
|
||||||
message: '',
|
messageWasSended
|
||||||
messageSended: true
|
});
|
||||||
});
|
}
|
||||||
},(error: any) => {
|
}
|
||||||
console.log(error);
|
|
||||||
|
private messageBarButtonOnClickHandler = (): void => {
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
messageWasSended:false,
|
||||||
|
messageText: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,6 @@ import { MSGraphClient } from '@microsoft/sp-http';
|
||||||
export interface IFeedbackFormProps {
|
export interface IFeedbackFormProps {
|
||||||
graphClient: MSGraphClient;
|
graphClient: MSGraphClient;
|
||||||
targetEmail: string;
|
targetEmail: string;
|
||||||
|
maxMessageLength: number;
|
||||||
subject: string;
|
subject: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue