Merge pull request #1372 from Harshagracy/master
This commit is contained in:
commit
9023f2335b
|
@ -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",
|
||||
|
|
|
@ -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.**
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -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
|
@ -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",
|
||||
|
|
|
@ -11,4 +11,6 @@ export interface IListFormState {
|
|||
notifications: string[];
|
||||
fieldErrors: { [fieldName: string]: string };
|
||||
showUnsupportedFields?: boolean;
|
||||
hasError: boolean;
|
||||
errorInfo: string;
|
||||
}
|
||||
|
|
|
@ -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] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue