🎉 - [react-jsonForm]: Submit form
This commit is contained in:
parent
dd8d73f31f
commit
af84afa787
|
@ -4,6 +4,6 @@ export default function useObject<T>(InitialValue?: T) {
|
|||
const [value, setValue] = useState<T>(InitialValue ?? {} as T);
|
||||
const updateValue: (Updates: Partial<T>) => void = (Updates: Partial<T>) => setValue((prev) => ({ ...prev, ...Updates }))
|
||||
return {
|
||||
value, updateValue
|
||||
value, updateValue, overwriteData: setValue
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { IChoiceField, IConditionalField, IField, IGroupField } from "./FormField";
|
||||
|
||||
export interface IForm {
|
||||
Title: string;
|
||||
Fields: (IField | IChoiceField | IGroupField | IConditionalField)[];
|
||||
}
|
|
@ -37,8 +37,3 @@ export interface IGroupField extends IField {
|
|||
Fields: (IField | IChoiceField | IGroupField | IConditionalField)[]
|
||||
Direction: GroupDirection;
|
||||
}
|
||||
|
||||
export interface IForm {
|
||||
Title: string;
|
||||
Fields: (IField | IChoiceField | IGroupField | IConditionalField)[];
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { IForm } from "./Form";
|
||||
|
||||
export interface SaveObject {
|
||||
form: IForm;
|
||||
response: any;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { BaseComponentContext } from "@microsoft/sp-component-base"
|
||||
import { SaveObject } from "../Models/SaveObject";
|
||||
import { SPFI, SPFx, spfi } from '@pnp/sp/presets/all'
|
||||
|
||||
export interface IDataProvider {
|
||||
SaveSubmission: (Submission: SaveObject) => Promise<string>;
|
||||
GetSubmission: (ServerRelativeUrl: string) => Promise<SaveObject>;
|
||||
}
|
||||
|
||||
export class SharePointProvider implements IDataProvider {
|
||||
private SP: SPFI;
|
||||
private LIST_ID: string;
|
||||
|
||||
constructor(context: BaseComponentContext, ListID: string) {
|
||||
this.SP = spfi().using(SPFx(context));
|
||||
this.LIST_ID = ListID;
|
||||
}
|
||||
|
||||
public async SaveSubmission(Submission: SaveObject): Promise<string> {
|
||||
const item = await this.SP.web.lists.getById(this.LIST_ID).rootFolder.files.addUsingPath(`${new Date().getTime()}.json`, JSON.stringify(Submission, null, 2))
|
||||
return item.data.ServerRelativeUrl;
|
||||
}
|
||||
|
||||
public async GetSubmission(ServerRelativeUrl: string): Promise<SaveObject> {
|
||||
const form = await this.SP.web.getFileByServerRelativePath(ServerRelativeUrl).getText();
|
||||
return JSON.parse(form);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { FieldType, IConditionalField, IField, IGroupField } from "../webparts/jsonForm/model/FormField";
|
||||
import { FieldType, IConditionalField, IField, IGroupField } from "../Models/FormField";
|
||||
|
||||
export const generateGuid: () => string = () => {
|
||||
return Math.random().toString(36).substring(2, 15) +
|
||||
|
@ -8,7 +8,7 @@ export const generateGuid: () => string = () => {
|
|||
export const NewField: () => IField = () => ({ Id: generateGuid(), Type: FieldType.PlaceHolder } as IField)
|
||||
|
||||
|
||||
const UNSUPPORTED_FIELDTYPES: FieldType[] = [FieldType.PlaceHolder, FieldType.MultiChoice, FieldType.PlaceHolder, FieldType.Label];
|
||||
const UNSUPPORTED_LOOKUP_FIELDTYPES: FieldType[] = [FieldType.PlaceHolder, FieldType.MultiChoice, FieldType.PlaceHolder, FieldType.Label];
|
||||
|
||||
export const GetLookupFields: (Fields: IField[]) => IField[] = (Fields: IField[]) => {
|
||||
const arr: IField[] = [];
|
||||
|
@ -27,5 +27,5 @@ export const GetLookupFields: (Fields: IField[]) => IField[] = (Fields: IField[]
|
|||
}
|
||||
}
|
||||
|
||||
return arr.filter(x => x.DisplayName != null && !UNSUPPORTED_FIELDTYPES.some(bannedType => bannedType == x.Type));;
|
||||
return arr.filter(x => x.DisplayName != null && !UNSUPPORTED_LOOKUP_FIELDTYPES.some(bannedType => bannedType == x.Type));;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
|||
import { PropertyFieldCodeEditor, PropertyFieldCodeEditorLanguages } from '@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor';
|
||||
import { PropertyFieldListPicker, PropertyFieldListPickerOrderBy } from '@pnp/spfx-property-controls/lib/PropertyFieldListPicker';
|
||||
import { IJsonFormProps, JsonForm } from './components/JsonForm';
|
||||
import { IForm } from './model/FormField';
|
||||
import { SPFI, SPFx, spfi } from '@pnp/sp/presets/all'
|
||||
import { IForm } from '../../Models/Form';
|
||||
import { IDataProvider, SharePointProvider } from '../../Providers/SharePointProvider';
|
||||
|
||||
export interface IJsonFormWebPartProps {
|
||||
formJson: string;
|
||||
|
@ -19,12 +19,12 @@ export interface IJsonFormWebPartProps {
|
|||
|
||||
export interface AppContext {
|
||||
context: BaseComponentContext;
|
||||
SP: SPFI;
|
||||
ListId: string;
|
||||
ItemId?: number
|
||||
provider: IDataProvider;
|
||||
}
|
||||
|
||||
export const SPFxContext = React.createContext<AppContext>(null);
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
export const FILLED_FORM_QUERY_KEY = "FormServerRelativeUrl";
|
||||
|
||||
export default class JsonFormWebPart extends BaseClientSideWebPart<IJsonFormWebPartProps> {
|
||||
|
||||
|
@ -34,9 +34,7 @@ export default class JsonFormWebPart extends BaseClientSideWebPart<IJsonFormWebP
|
|||
{
|
||||
value: {
|
||||
context: this.context,
|
||||
SP: spfi().using(SPFx(this.context)),
|
||||
ListId: urlSearchParams.get("ListId") ?? this.properties.listId,
|
||||
ItemId: urlSearchParams.has("ItemId") ? parseInt(urlSearchParams.get("ItemId")) : null
|
||||
provider: new SharePointProvider(this.context, this.properties.listId)
|
||||
} as AppContext
|
||||
},
|
||||
React.createElement<IJsonFormProps>(
|
||||
|
@ -44,7 +42,9 @@ export default class JsonFormWebPart extends BaseClientSideWebPart<IJsonFormWebP
|
|||
{
|
||||
Form: JSON.parse(this.properties.formJson),
|
||||
SaveForm: (updated: IForm) => this.properties.formJson = JSON.stringify({ ...JSON.parse(this.properties.formJson), ...updated }, null, 2),
|
||||
Mode: this.displayMode
|
||||
Mode: this.displayMode,
|
||||
ListId: this.properties.listId,
|
||||
ServerRelativeUrl: urlSearchParams.has(FILLED_FORM_QUERY_KEY) ? urlSearchParams.get(FILLED_FORM_QUERY_KEY) : null,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../model/FormField';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../../../Models/FormField';
|
||||
import { Position, SpinButton, TextField, Checkbox, Dropdown, MessageBar, MessageBarType, Text } from '@fluentui/react';
|
||||
import { Label } from 'office-ui-fabric-react';
|
||||
|
||||
|
@ -8,49 +8,52 @@ export interface IFieldProps {
|
|||
onChange: (updates: object) => void;
|
||||
form: any;
|
||||
field: IField;
|
||||
readonly: boolean;
|
||||
}
|
||||
|
||||
export const Field: React.FunctionComponent<IFieldProps> = (props: React.PropsWithChildren<IFieldProps>) => {
|
||||
const { field, onChange, form } = props;
|
||||
const { field, onChange, form, readonly } = props;
|
||||
switch (field.Type) {
|
||||
case FieldType.Label: return <Label>{field.DisplayName}</Label>;
|
||||
|
||||
case FieldType.Header: return <Text variant='xLarge'>{field.DisplayName}</Text>;
|
||||
|
||||
case FieldType.Text: return <TextField
|
||||
value={form[field.Id]}
|
||||
value={form[field.Id] ?? ""}
|
||||
label={field.DisplayName}
|
||||
readOnly={readonly}
|
||||
onChange={(_, val) => onChange({ [field.Id]: val })}
|
||||
/>;
|
||||
|
||||
case FieldType.MultilineText: return <TextField
|
||||
value={form[field.Id]}
|
||||
value={form[field.Id] ?? ""}
|
||||
label={field.DisplayName}
|
||||
rows={5}
|
||||
multiline
|
||||
readOnly={readonly}
|
||||
onChange={(_, val) => onChange({ [field.Id]: val })}
|
||||
/>;
|
||||
|
||||
case FieldType.Number: return <SpinButton
|
||||
inputMode='numeric'
|
||||
labelPosition={Position.top}
|
||||
value={form[field.Id]}
|
||||
value={form[field.Id] ?? ""}
|
||||
label={field.DisplayName}
|
||||
onChange={(_, val) => onChange({ [field.Id]: Number(val) })}
|
||||
onChange={readonly ? null : (_, val) => onChange({ [field.Id]: Number(val) })}
|
||||
/>;
|
||||
|
||||
case FieldType.Boolean: return <Checkbox
|
||||
label={field.DisplayName}
|
||||
checked={form[field.Id]}
|
||||
onChange={(_, val) => onChange({ [field.Id]: val })}
|
||||
checked={form[field.Id] ?? false}
|
||||
onChange={readonly ? () => null : (_, val) => onChange({ [field.Id]: val })}
|
||||
styles={{ root: { alignItems: 'center', marginTop: "1.75em" } }}
|
||||
/>;
|
||||
|
||||
case FieldType.Choice: return <Dropdown
|
||||
options={(field as any as IChoiceField).Options.map(x => ({ key: x, text: x }))}
|
||||
selectedKey={form[field.Id]}
|
||||
selectedKey={form[field.Id] ?? ""}
|
||||
label={field.DisplayName}
|
||||
onChange={(_, option) => onChange({ [field.Id]: option.key })}
|
||||
onChange={readonly ? null : (_, option) => onChange({ [field.Id]: option.key })}
|
||||
/>
|
||||
|
||||
case FieldType.MultiChoice: return <Dropdown
|
||||
|
@ -58,7 +61,7 @@ export const Field: React.FunctionComponent<IFieldProps> = (props: React.PropsWi
|
|||
selectedKeys={form[field.Id] ?? []}
|
||||
label={field.DisplayName}
|
||||
multiSelect
|
||||
onChange={(_, val) => {
|
||||
onChange={readonly ? null : (_, val) => {
|
||||
let selected: string[] = form[field.Id] ?? [];
|
||||
selected = val.selected ? [...selected, val.key as string] : selected.filter(x => x != val.key);
|
||||
onChange({ [field.Id]: selected })
|
||||
|
@ -81,7 +84,7 @@ export const Field: React.FunctionComponent<IFieldProps> = (props: React.PropsWi
|
|||
const f = (field as IConditionalField);
|
||||
const visible = form[f.LookupFieldId] == f.MatchValue
|
||||
if (!visible) return <></>
|
||||
return <Field field={f.Field} form={form} onChange={onChange} />
|
||||
return <Field readonly={readonly} field={f.Field} form={form} onChange={onChange} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../model/FormField';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../../../Models/FormField';
|
||||
import { ActionButton, ChoiceGroup, DefaultButton, Dialog, DialogFooter, Dropdown, Label, Position, PrimaryButton, SpinButton, Stack, TextField } from '@fluentui/react';
|
||||
import useObject from '../../../../Hooks/UseObject';
|
||||
import { NewField } from '../../../../Util/Util';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../model/FormField';
|
||||
import { FieldType, GroupDirection, IChoiceField, IConditionalField, IField, IGroupField } from '../../../../Models/FormField';
|
||||
import { ActionButton, Checkbox, Dropdown, Label, MessageBar, MessageBarType, Position, PrimaryButton, SpinButton, Text, TextField, getTheme } from '@fluentui/react';
|
||||
import { cloneDeep } from '@microsoft/sp-lodash-subset';
|
||||
import { FieldEditorDialog } from './FieldEditorDialog';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from 'react';
|
||||
import { IForm } from '../model/FormField';
|
||||
import useObject from '../../../Hooks/UseObject';
|
||||
import { PrimaryButton, Stack, DialogFooter } from '@fluentui/react'
|
||||
import { Field } from './Fields/Field';
|
||||
|
@ -8,22 +7,42 @@ import { Placeholder, WebPartTitle } from '@pnp/spfx-controls-react';
|
|||
import { FormFieldCustomizer } from './Fields/FormFieldCustomizer';
|
||||
import { GetLookupFields, NewField } from '../../../Util/Util';
|
||||
import { cloneDeep } from '@microsoft/sp-lodash-subset';
|
||||
import { SPFxContext } from '../JsonFormWebPart';
|
||||
import { FILLED_FORM_QUERY_KEY, SPFxContext } from '../JsonFormWebPart';
|
||||
import { IForm } from '../../../Models/Form';
|
||||
|
||||
export interface IJsonFormProps {
|
||||
Form: IForm;
|
||||
SaveForm: (updated: IForm) => void;
|
||||
Mode: DisplayMode;
|
||||
ServerRelativeUrl?: string;
|
||||
ListId: string;
|
||||
}
|
||||
|
||||
export const JsonForm: React.FunctionComponent<IJsonFormProps> = (props: React.PropsWithChildren<IJsonFormProps>) => {
|
||||
const { Mode } = props;
|
||||
const { value: Form, updateValue: UpdateForm } = useObject<IForm>(props.Form);
|
||||
const { value: filledForm, updateValue } = useObject<any>();
|
||||
const { ListId } = React.useContext(SPFxContext);
|
||||
const { Mode, ServerRelativeUrl } = props;
|
||||
const { value: Form, updateValue: UpdateForm, overwriteData: __SETFORM } = useObject<IForm>(props.ServerRelativeUrl ? { Fields: [], Title: "" } : props.Form);
|
||||
const { value: filledForm, updateValue, overwriteData: __SETFILLEDFORM } = useObject<any>();
|
||||
const { provider } = React.useContext(SPFxContext);
|
||||
|
||||
console.log(ListId);
|
||||
if (ListId == null || ListId == "")
|
||||
React.useEffect(() => {
|
||||
if (ServerRelativeUrl != null) {
|
||||
const fetch = async () => {
|
||||
const result = await provider.GetSubmission(ServerRelativeUrl);
|
||||
__SETFILLEDFORM(result.response);
|
||||
__SETFORM(result.form);
|
||||
}
|
||||
fetch();
|
||||
}
|
||||
}, [])
|
||||
|
||||
const saveForm = async () => {
|
||||
const serverRelativeUrl = await provider.SaveSubmission({ form: Form, response: filledForm });
|
||||
var searchParams = new URLSearchParams(window.location.search);
|
||||
searchParams.set(FILLED_FORM_QUERY_KEY, serverRelativeUrl);
|
||||
window.location.search = searchParams.toString();
|
||||
}
|
||||
|
||||
if (props.ListId == null || props.ListId == "")
|
||||
return <Placeholder description={'Open the property pane and select a list to store responses'} iconName={'Edit'} iconText={'Please configure web part'} />
|
||||
|
||||
return (
|
||||
|
@ -34,12 +53,14 @@ export const JsonForm: React.FunctionComponent<IJsonFormProps> = (props: React.P
|
|||
<>
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
{Form.Fields.map(field => {
|
||||
return <Field field={field} onChange={updateValue} form={filledForm} />
|
||||
return <Field readonly={props.ServerRelativeUrl != null} field={field} onChange={updateValue} form={filledForm} />
|
||||
})}
|
||||
</Stack>
|
||||
{props.ServerRelativeUrl == null &&
|
||||
<DialogFooter>
|
||||
<PrimaryButton text='Submit' iconProps={{ iconName: "Accept" }} onClick={() => alert(JSON.stringify(filledForm, null, 2))} />
|
||||
<PrimaryButton text='Submit' iconProps={{ iconName: "Accept" }} onClick={() => saveForm()} />
|
||||
</DialogFooter>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue