Merge pull request #1372 from Harshagracy/master

This commit is contained in:
Hugo Bernier 2020-07-07 22:41:54 -04:00 committed by GitHub
commit 9023f2335b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 8590 additions and 7319 deletions

View File

@ -2,7 +2,7 @@
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"version": "1.10.0",
"libraryName": "react-form-webpart",
"libraryId": "b092661d-5730-49ea-be27-14ee4a84eb33",
"packageManager": "npm",

View File

@ -1,51 +1,57 @@
---
page_type: sample
products:
- office-sp
- office-sp
languages:
- javascript
- typescript
- javascript
- typescript
extensions:
contentType: samples
technologies:
- SharePoint Framework
- SharePoint Framework
platforms:
- react
- react
createdDate: 12/1/2017 12:00:00 AM
---
# React List Form WebPart
## Summary
The `React List Form web part` is a web part for adding a list form to any page. It provides a working example of implementing generic SharePoint list forms using the **SharePoint Framework (SPFx)** and the *React* and *Office UI Fabric* libraries.
The `React List Form web part` is a web part for adding a list form to any page. It provides a working example of implementing generic SharePoint list forms using the **SharePoint Framework (SPFx)** and the _React_ and _Office UI Fabric_ libraries.
The web part allows configuring which list to use and if a form for adding a new item, editing or displaying an existing item should be shown. When selecting display or edit form the ID can be defined either as a fixed number or as a query string parameter name. The form fields can be added, ordered using drag-and-drop or removed visually in the web part. A URL including placeholder for the ID can be provided to redirect to after successfully saving the form.
![Demo](./assets/React-ListForm-Overview.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Used SharePoint Framework Version
![SPFx 1.10.0](https://img.shields.io/badge/version-1.10.0-green.svg)
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
- [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
- [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
## Solution
Solution|Author(s)
--------|---------
react-list-form|Dany Wyss
| Solution | Author(s) |
| --------------- | ----------------------------------------------------------------- |
| react-list-form | Dany Wyss |
| react-list-form | Harsha Vardhini ([@harshagracy](https://twitter.com/harshagracy)) |
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|November 24, 2017|Initial release
1.0.1|February 22, 2019|Updated to SPFx 1.7.1 and dependencies, Added Turkish translation, Added RichText Mode and Tinymce Editor
1.0.2|October 14, 2019|Updated to SPFx 1.9.1 and dependencies
| Version | Date | Comments |
| ------- | ----------------- | --------------------------------------------------------------------------------------------------------- |
| 1.0.0 | November 24, 2017 | Initial release |
| 1.0.1 | February 22, 2019 | Updated to SPFx 1.7.1 and dependencies, Added Turkish translation, Added RichText Mode and Tinymce Editor |
| 1.0.2 | October 14, 2019 | Updated to SPFx 1.9.1 and dependencies |
| 1.0.3 | July 7, 2020 | Updated to SPFx 1.10.0 and dependencies. Fixed required field validation (Harsha Vardhini) |
## 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.**
**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.**
---

View File

@ -3,7 +3,7 @@
"solution": {
"name": "react-form-webpart-client-side-solution",
"id": "373a20ef-dfc6-456a-95ec-171de3c94581",
"version": "1.0.2.0",
"version": "1.0.3.0",
"title": "List form",
"supportedLocales": [
"en-US",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "react-form-webpart",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"engines": {
"node": ">=0.10.0"
@ -13,16 +13,18 @@
"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",
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@tinymce/tinymce-react": "^3.0.1",
"@types/es6-promise": "0.0.33",
"@types/react-dnd": "~2.0.34",
"@types/webpack-env": "1.13.1",
"@uifabric/icons": "^7.3.14",
"moment": "^2.24.0",
"office-ui-fabric-react": "^6.189.2",
"react-dnd": "~2.5.4",
"react-dnd-html5-backend": "~2.5.4",
"react-html-parser": "^2.0.2",
@ -31,10 +33,11 @@
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "^0.8.5",
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",

View File

@ -11,4 +11,6 @@ export interface IListFormState {
notifications: string[];
fieldErrors: { [fieldName: string]: string };
showUnsupportedFields?: boolean;
hasError: boolean;
errorInfo: string;
}

View File

@ -47,13 +47,19 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
originalData: {},
errors: [],
notifications: [],
fieldErrors: {}
fieldErrors: {},
hasError: false,
errorInfo: ''
};
this.listFormService = new ListFormService(props.spHttpClient);
}
public render() {
let menuProps;
if (this.state.hasError) {
// render any custom fallback UI
return <h1>{this.state.errorInfo}</h1>;
}
if (this.state.fieldsSchema) {
menuProps = {
shouldFocusOnMount: true,
@ -108,6 +114,12 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
);
}
public componentDidCatch(error, errorInfo) {
this.setState({
hasError: true,
errorInfo: error.toString()
});
}
private renderNotifications() {
if (this.state.notifications.length === 0) {
return null;
@ -272,62 +284,86 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
);
}
private async saveItem(): Promise<void> {
this.setState({ ...this.state, isSaving: true, errors: [] });
try {
let updatedValues;
if (this.props.id) {
updatedValues = await this.listFormService.updateItem(
this.props.webUrl,
this.props.listUrl,
this.props.id,
this.state.fieldsSchema,
this.state.data,
this.state.originalData);
} else {
updatedValues = await this.listFormService.createItem(
this.props.webUrl,
this.props.listUrl,
this.state.fieldsSchema,
this.state.data);
private validator = () => {
let fieldErrors = this.state.fieldErrors;
this.state.fieldsSchema.forEach(currentFieldSchema => {
if (currentFieldSchema.Required && !this.state.data[currentFieldSchema.InternalName]) {
fieldErrors = {
...fieldErrors,
[currentFieldSchema.InternalName]: strings.RequiredValueMessage
};
}
let dataReloadNeeded = false;
const newState: IListFormState = { ...this.state, fieldErrors: {} };
let hadErrors = false;
updatedValues.filter((fieldVal) => fieldVal.HasException).forEach((element) => {
newState.fieldErrors[element.FieldName] = element.ErrorMessage;
hadErrors = true;
});
if (hadErrors) {
if (this.props.onSubmitFailed) {
this.props.onSubmitFailed(newState.fieldErrors);
} else {
newState.errors = [...newState.errors, strings.FieldsErrorOnSaving];
}
} else {
updatedValues.reduce(
(val, merged) => {
merged[val.FieldName] = merged[val.FieldValue]; return merged;
},
newState.data,
);
// we shallow clone here, so that changing values on state.data won't be changing in state.originalData too
newState.originalData = { ...newState.data };
let id = (this.props.id) ? this.props.id : 0;
if (id === 0) {
id = updatedValues.filter((val) => val.FieldName === 'Id')[0].FieldValue;
}
if (this.props.onSubmitSucceeded) { this.props.onSubmitSucceeded(id); }
newState.notifications = [...newState.notifications, strings.ItemSavedSuccessfully];
dataReloadNeeded = true;
});
this.setState({
fieldErrors: fieldErrors
});
for (let key in fieldErrors) {
if (fieldErrors[key]) {
return false;
}
newState.isSaving = false;
this.setState(newState);
}
return true;
}
if (dataReloadNeeded) { this.readData(this.props.listUrl, this.props.formType, this.props.id); }
} catch (error) {
const errorText = strings.ErrorOnSavingListItem + error;
this.setState({ ...this.state, errors: [...this.state.errors, errorText] });
private async saveItem(): Promise<void> {
let shouldSave = this.validator();
if (shouldSave) {
this.setState({ ...this.state, isSaving: true, errors: [] });
try {
let updatedValues;
if (this.props.id) {
updatedValues = await this.listFormService.updateItem(
this.props.webUrl,
this.props.listUrl,
this.props.id,
this.state.fieldsSchema,
this.state.data,
this.state.originalData);
} else {
updatedValues = await this.listFormService.createItem(
this.props.webUrl,
this.props.listUrl,
this.state.fieldsSchema,
this.state.data);
}
let dataReloadNeeded = false;
const newState: IListFormState = { ...this.state, fieldErrors: {} };
let hadErrors = false;
updatedValues.filter((fieldVal) => fieldVal.HasException).forEach((element) => {
newState.fieldErrors[element.FieldName] = element.ErrorMessage;
hadErrors = true;
});
if (hadErrors) {
if (this.props.onSubmitFailed) {
this.props.onSubmitFailed(newState.fieldErrors);
} else {
newState.errors = [...newState.errors, strings.FieldsErrorOnSaving];
}
} else {
updatedValues.reduce(
(val, merged) => {
merged[val.FieldName] = merged[val.FieldValue]; return merged;
},
newState.data,
);
// we shallow clone here, so that changing values on state.data won't be changing in state.originalData too
newState.originalData = { ...newState.data };
let id = (this.props.id) ? this.props.id : 0;
if (id === 0) {
id = updatedValues.filter((val) => val.FieldName === 'Id')[0].FieldValue;
}
if (this.props.onSubmitSucceeded) { this.props.onSubmitSucceeded(id); }
newState.notifications = [...newState.notifications, strings.ItemSavedSuccessfully];
dataReloadNeeded = true;
}
newState.isSaving = false;
this.setState(newState);
if (dataReloadNeeded) { this.readData(this.props.listUrl, this.props.formType, this.props.id); }
} catch (error) {
const errorText = strings.ErrorOnSavingListItem + error;
this.setState({ ...this.state, errors: [...this.state.errors, errorText] });
}
}
}

View File

@ -11,7 +11,6 @@ import { ControlMode } from '../../../../common/datatypes/ControlMode';
import { IFieldSchema } from '../../../../common/services/datatypes/RenderListData';
import * as stylesImport from 'office-ui-fabric-react/lib/components/TextField/TextField.types';
const styles: any = stylesImport;
import ardStyles from './FormField.module.scss';
@ -29,7 +28,7 @@ export interface IFormFieldProps {
}
const FormField: React.SFC<IFormFieldProps> = (props) => {
const styles: any = stylesImport;
const {
children,
className,
@ -60,13 +59,15 @@ const FormField: React.SFC<IFormFieldProps> = (props) => {
<span>
{description && <span className={css('ard-FormField-description', styles.description)}>{description}</span>}
{errorMessage &&
<div aria-live='assertive'>
<DelayedRender>
<p className={css('ard-FormField-errorMessage', AnimationClassNames.slideDownIn20, styles.errorMessage)}>
{Icon({ iconName: 'Error', className: styles.errorIcon })}
<span className={styles.errorText} data-automation-id='error-message'>{errorMessage}</span>
</p>
</DelayedRender>
<div aria-live='assertive' style={{ color: '#a80000' }}>
{errorMessage}
{/* <DelayedRender>
<p className={css('ard-FormField-errorMessage', AnimationClassNames.slideDownIn20, styles.errorMessage ? styles.errorMessage : '')}>
{Icon({ iconName: 'Error', className: styles.errorIcon ? styles.errorIcon : '' })}
<span className={styles.errorText ? styles.errorText : ''} data-automation-id='error-message'>{errorMessage}</span>
</p>
</DelayedRender> */}
</div>
}
</span>

View File

@ -1,5 +1,5 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
@ -35,4 +35,4 @@
"node_modules",
"lib"
]
}
}