From 61c8dd85a623521f269d231acb0c703397a3c410 Mon Sep 17 00:00:00 2001 From: Ravichandran Krishnasamy Date: Sun, 16 May 2021 18:50:02 +0530 Subject: [PATCH] Dynamic Form --- .../docs/controls/DynamicForm.md | 39 +++ src/DynamicForm.ts | 1 + .../dynamicForm/DynamicForm.module.scss | 71 ++++ src/controls/dynamicForm/DynamicForm.tsx | 319 ++++++++++++++++++ src/controls/dynamicForm/IDynamicFormProps.ts | 13 + src/controls/dynamicForm/IDynamicFormState.ts | 8 + .../dynamicForm/dynamicField/DynamicField.tsx | 153 +++++++++ .../dynamicField/IDynamicFieldProps.ts | 29 ++ .../dynamicField/IDynamicFieldState.ts | 11 + .../dynamicForm/dynamicField/index.ts | 3 + src/controls/dynamicForm/index.ts | 3 + src/services/SPService.ts | 172 ++++++++++ .../controlsTest/components/ControlsTest.tsx | 5 + 13 files changed, 827 insertions(+) create mode 100644 docs/documentation/docs/controls/DynamicForm.md create mode 100644 src/DynamicForm.ts create mode 100644 src/controls/dynamicForm/DynamicForm.module.scss create mode 100644 src/controls/dynamicForm/DynamicForm.tsx create mode 100644 src/controls/dynamicForm/IDynamicFormProps.ts create mode 100644 src/controls/dynamicForm/IDynamicFormState.ts create mode 100644 src/controls/dynamicForm/dynamicField/DynamicField.tsx create mode 100644 src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts create mode 100644 src/controls/dynamicForm/dynamicField/IDynamicFieldState.ts create mode 100644 src/controls/dynamicForm/dynamicField/index.ts create mode 100644 src/controls/dynamicForm/index.ts diff --git a/docs/documentation/docs/controls/DynamicForm.md b/docs/documentation/docs/controls/DynamicForm.md new file mode 100644 index 000000000..088c79602 --- /dev/null +++ b/docs/documentation/docs/controls/DynamicForm.md @@ -0,0 +1,39 @@ +# Dynamic Form + +This control can dynamically generate SharePoint list or SharePoint document library form and everything controlled through list setting. + +## How to use this control in your solutions + +- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency. +- Import the following modules to your component: + +```TypeScript +import { DynamicForm } from "fx-controls-react/lib/DynamicForm"; +``` + +- Use the DynamicForm control in your code as follows: + +```jsx + + onCancelled={() => { console.log('Cancelled') }} + onSubmitted={async (listItem) => { let itemdata = await listItem.get(); console.log(itemdata["ID"]); }}> + +``` +![DynamicForm](../assets/DynamicForm.png) + +## Implementation + +The `DynamicForm` can be configured with the following properties: + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| context | WebPartContext or ExtensionContext | yes | The context object of the SPFx loaded webpart or customizer. | +| listId | string | yes | Guid of the list.| +| listItemId | number | no | list item ID. | +| contentTypeId | string | no | content type ID | +| disabled | boolean | no | Option allow to be enable or disable. Default value is `false`| +| onSubmitted | (ListItem: any) => void | no | Method that returns ListItem object. | +| onCancelled | () => void | no | Handler when form has been cancelled. | diff --git a/src/DynamicForm.ts b/src/DynamicForm.ts new file mode 100644 index 000000000..c1d45cf8a --- /dev/null +++ b/src/DynamicForm.ts @@ -0,0 +1 @@ +export * from './controls/dynamicForm/index'; \ No newline at end of file diff --git a/src/controls/dynamicForm/DynamicForm.module.scss b/src/controls/dynamicForm/DynamicForm.module.scss new file mode 100644 index 000000000..6a3d6eac4 --- /dev/null +++ b/src/controls/dynamicForm/DynamicForm.module.scss @@ -0,0 +1,71 @@ +.FieldEditor { + padding: 4px 3px; +} + +.fieldLabel { + font-weight : 600; + font-size : 12px; + padding-top : 5px; + padding-bottom : 5px; + font-family : "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + font-size : 14px; + font-weight : 600; + color : rgb(50, 49, 48); + box-sizing : border-box; + box-shadow : none; + margin : 0px; + padding : 5px 0px; + overflow-wrap : break-word; +} + +.fieldIcon { + align-self : center; + font-size : 13px; + color : #605e5c; + margin-right: 8px; +} + +.feildDisplay { + display : inline-block; + vertical-align: top; + width : 100%; + font-size : 14px; + font-weight : 400; + outline : 0; + padding : 6px 0 7px 0; + border : 2px solid #ffffff; + margin-left : -2px; +} + +.fieldRequired::after { + content : " *"; + color : rgb(164, 38, 44); + padding-right: 12px; +} + +.buttons { + padding: 15px 4px; +} + +.filePicker +{ + padding: 7px; + align-items: center; +} + +.errormessage{ + animation-name: css-102, css-115; + animation-duration: 0.367s; + animation-timing-function: cubic-bezier(0.1, 0.9, 0.2, 1); + animation-fill-mode: both; + font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 12px; + font-weight: 400; + color: rgb(164, 38, 44); + margin: 0px; + padding-top: 5px; + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx new file mode 100644 index 000000000..2dab40917 --- /dev/null +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -0,0 +1,319 @@ +import * as React from 'react'; +import styles from './DynamicForm.module.scss'; +import { IDropdownOption, IDropdownProps, Dropdown } from 'office-ui-fabric-react/lib/components/Dropdown'; +import { DefaultButton, PrimaryButton, Stack, IStackTokens } from 'office-ui-fabric-react'; +import { IDynamicFormProps } from './IDynamicFormProps'; +import { IDynamicFormState } from './IDynamicFormState'; +import { IDynamicFieldProps } from './dynamicField/IDynamicFieldProps'; +import SPservice from "../../services/SPService"; +import { DynamicField } from './dynamicField'; +import { IItemAddResult, IItemUpdateResult } from "@pnp/sp/items"; +import { sp } from "@pnp/sp/presets/all"; +import { ProgressIndicator } from 'office-ui-fabric-react/lib/ProgressIndicator'; + +const stackTokens: IStackTokens = { childrenGap: 20 }; +/** + * DynamicForm Class Control + */ +export class DynamicForm extends React.Component { + private _spservice: SPservice; + constructor(props: IDynamicFormProps) { + super(props); + // Initialize pnp sp + sp.setup({ + spfxContext: this.props.context + }); + // Initialize state + this.state = { + fieldCollection: [] + }; + // Get SPService Factory + this._spservice = new SPservice(this.props.context); + } + + /** + * Lifecycle hook when component is mounted + */ + public componentDidMount() { + this.getFieldInformations(); + } + + /** + * Default React component render method + */ + public render(): JSX.Element { + return ( +
+ {this.state.fieldCollection.length === 0 ?
: +
+ {this.state.fieldCollection.map((v, i) => { + return ; + })} + + this.onSubmitClick()} /> + + +
+ } +
+ ); + } + + //trigger when the user submits the form. + private onSubmitClick = () => { + try { + let shouldbereturnback = false; + let fields = this.state.fieldCollection; + fields.forEach((val, ind) => { + if (val.required) { + if (val.newValue === null) { + if (val.fieldDefaultValue === null || val.fieldDefaultValue === '' || val.fieldDefaultValue.length === 0) { + val.fieldDefaultValue = ''; + shouldbereturnback = true; + } + } + else if (val.newValue === '') { + val.fieldDefaultValue = ''; + shouldbereturnback = true; + } + } + }); + if (shouldbereturnback) { + this.setState({ fieldCollection: fields }); + return; + } + + var objects = {}; + this.state.fieldCollection.forEach(async (val, ind) => { + if (val.newValue !== null && val.newValue !== undefined) { + let value = val.newValue; + if (val.fieldType === "Lookup") { + objects[val.columnInternalName + "Id"] = value[0].key; + } + else if (val.fieldType === "LookupMulti") { + value = []; + val.newValue.forEach(element => { + value.push(element.key); + }); + objects[val.columnInternalName + "Id"] = { results: value }; + } + else if (val.fieldType === "TaxonomyFieldType") { + objects[val.columnInternalName] = { + "__metadata": { "type": "SP.Taxonomy.TaxonomyFieldValue" }, + "Label": value[0].name, + 'TermGuid': value[0].key, + 'WssId': '-1' + }; + } + else if (val.fieldType === "TaxonomyFieldTypeMulti") { + objects[val.hiddenFieldName] = val.newValue.map(term => `-1#;${term.name}|${term.key};`).join('#'); + } + else if (val.fieldType === "User") { + objects[val.columnInternalName + "Id"] = val.newValue; + } + else if (val.fieldType === "Choice") { + objects[val.columnInternalName] = val.newValue.key; + } + else if (val.fieldType === "MultiChoice") { + objects[val.columnInternalName] = { results: val.newValue }; + } + else if (val.fieldType === "UserMulti") { + objects[val.columnInternalName + "Id"] = { results: val.newValue }; + } + else { + objects[val.columnInternalName] = val.newValue; + } + } + }); + + if (this.props.listItemId !== undefined && this.props.listItemId !== null && this.props.listItemId !== 0) { + sp.web.lists.getById(this.props.listId).items.getById(this.props.listItemId).update(objects).then((iur: IItemUpdateResult) => { + this.props.onSubmitted(iur.item); + }).catch((error: any) => { + console.log("Error", error); + }); + + } + else { + sp.web.lists.getById(this.props.listId).items.add(objects).then((iar: IItemAddResult) => { + console.log(iar); + this.props.onSubmitted(iar.item); + }).catch((error: any) => { + console.log("Error", error); + }); + } + } catch (error) { + console.log(`Error onchange`, error); + return null; + } + } + + // trigger when the user change any value in the form + private onchange = async (internalName: string, newValue: any) => { + try { + console.log(internalName, newValue); + let fieldcol = this.state.fieldCollection; + let field = fieldcol.filter((element, i) => { return element.columnInternalName === internalName; })[0]; + field.newValue = newValue; + if (field.fieldType == "User" && newValue.length !== 0) { + let result = await sp.web.ensureUser(newValue[0].secondaryText); + field.newValue = result.data.Id; + } + else if (field.fieldType == "UserMulti" && newValue.length !== 0) { + field.newValue = []; + newValue.forEach(async element => { + let result = await sp.web.ensureUser(element.secondaryText); + field.newValue.push(result.data.Id); + }); + } + } catch (error) { + console.log(`Error onchange`, error); + return null; + } + } + + //getting all the fields information as part of get ready process + private getFieldInformations = async (): Promise<{ key: string; name: string }[]> => { + const { context, listId, listItemId } = this.props; + let contentTypeId = this.props.contentTypeId; + let arrayItems: { key: string; name: string }[] = []; + try { + let splist = await sp.web.lists.getById(listId); + let item = null; + if (listItemId !== undefined && listItemId !== null && listItemId !== 0) + item = await splist.items.getById(listItemId).get(); + + if (contentTypeId === undefined || contentTypeId + '' === '') { + let defaultcontenttype = await splist.contentTypes.select("Id", "Name").get(); + contentTypeId = defaultcontenttype[0]["Id"].StringValue + ''; + } + let listFeilds = await this._spservice.getListInfo(listId, contentTypeId, context.pageContext.web.absoluteUrl); + let tempFields: IDynamicFieldProps[] = []; + let order: number = 0; + listFeilds["value"].forEach(async (field) => { + order++; + field.order = order; + let hiddenname = ""; + let TermSetId = ""; + let lookupListID = ""; + let lookupField = ""; + let choices: IDropdownOption[] = []; + let defaultValue = null; + let selectedtags: any = []; + let richText = false; + if (item !== null) + defaultValue = item[field.InternalName]; + else + defaultValue = field.DefaultValue; + if (field["TypeAsString"] === 'Choice' || field["TypeAsString"] === 'MultiChoice') { + field["Choices"].forEach(element => { + choices.push({ key: element, text: element }); + }); + } + else if (field["TypeAsString"] === "Note") { + richText = field["RichText"]; + } + else if (field["TypeAsString"] === "Lookup") { + lookupListID = field["LookupList"]; + lookupField = field["LookupField"]; + if (item !== null) { + defaultValue = await this._spservice.getLookUpValue(listId, listItemId, field.InternalName, context.pageContext.web.absoluteUrl); + } + else { + defaultValue = []; + } + + } + else if (field["TypeAsString"] === "LookupMulti") { + lookupListID = field["LookupList"]; + lookupField = field["LookupField"]; + if (item !== null) { + defaultValue = await this._spservice.getLookUpValues(listId, listItemId, field.InternalName, context.pageContext.web.absoluteUrl); + } + else { + defaultValue = []; + } + } + else if (field["TypeAsString"] === "TaxonomyFieldTypeMulti") { + let response = await this._spservice.getInternalName(this.props.listId, field.InternalName, this.props.context.pageContext.web.absoluteUrl); + hiddenname = response["value"]; + TermSetId = field["TermSetId"]; + if (item !== null) { + item[field.InternalName].forEach(element => { + selectedtags.push({ key: element.TermGuid, name: element.Label }); + }); + + defaultValue = selectedtags; + } + else { + if (defaultValue !== "") { + defaultValue.split(/#|;/).forEach(element => { + if (element.indexOf('|') !== -1) + selectedtags.push({ key: element.split('|')[1], name: element.split('|')[0] }); + }); + + defaultValue = selectedtags; + } + } + if (defaultValue === "") + defaultValue = null; + } + else if (field["TypeAsString"] === "TaxonomyFieldType") { + + TermSetId = field["TermSetId"]; + if (item !== null) { + let response = await this._spservice.getSingleManagedMtadataLabel(listId, listItemId, field.InternalName); + selectedtags.push({ key: response["TermID"], name: response["Label"] }); + defaultValue = selectedtags; + } + else { + if (defaultValue !== "") { + selectedtags.push({ key: defaultValue.split('|')[1], name: defaultValue.split('|')[0].split('#')[1] }); + defaultValue = selectedtags; + } + } + if (defaultValue === "") + defaultValue = null; + } + else if (field["TypeAsString"] === "DateTime") { + if (item !== null) + defaultValue = new Date(item[field.InternalName]); + else if (defaultValue === '[today]') { + defaultValue = new Date(); + } + + } + else if (field["TypeAsString"] === "UserMulti") { + if (item !== null) + defaultValue = await this._spservice.getUserEmailsById(listId, listItemId, field.InternalName, context.pageContext.web.absoluteUrl); + else { + defaultValue = []; + } + } + else if (field["TypeAsString"] === "Thumbnail") { + if (defaultValue !== null) { + defaultValue = context.pageContext.web.absoluteUrl.split('/sites/')[0] + JSON.parse(defaultValue).serverRelativeUrl; + } + } + else if (field["TypeAsString"] === "User") { + if (item !== null) { + let useremails: string[] = []; + useremails.push(await this._spservice.getUserEmailById(parseInt(item[field.InternalName + "Id"])) + ''); + defaultValue = useremails; + } + else { + defaultValue = []; + } + } + tempFields.push({ newValue: null, fieldTermSetId: TermSetId, options: choices, lookupListID: lookupListID, lookupField: lookupField, changedvalue: defaultValue, fieldType: field.TypeAsString, fieldTitle: field.Title, fieldDefaultValue: defaultValue, context: this.props.context, disabled: this.props.disabled, listId: this.props.listId, columnInternalName: field.InternalName, label: field.Title, onChanged: this.onchange, required: field.Required, hiddenFieldName: hiddenname, Order: field.order, isRichText: richText }); + tempFields.sort((a, b) => a.Order - b.Order); + }); + + this.setState({ fieldCollection: tempFields }); + return arrayItems; + } catch (error) { + console.log(`Error get field informations`, error); + return null; + } + } +} diff --git a/src/controls/dynamicForm/IDynamicFormProps.ts b/src/controls/dynamicForm/IDynamicFormProps.ts new file mode 100644 index 000000000..df9cfbd6a --- /dev/null +++ b/src/controls/dynamicForm/IDynamicFormProps.ts @@ -0,0 +1,13 @@ +import { ExtensionContext } from '@microsoft/sp-extension-base'; +import { WebPartContext } from '@microsoft/sp-webpart-base'; +import { Web } from "@pnp/sp/webs"; + +export interface IDynamicFormProps { + context: WebPartContext | ExtensionContext; + disabled?: boolean; + listId: string; + onSubmitted?: (ListItem: any) => void; + onCancelled?: () => void; + listItemId?: number; + contentTypeId?: string; +} \ No newline at end of file diff --git a/src/controls/dynamicForm/IDynamicFormState.ts b/src/controls/dynamicForm/IDynamicFormState.ts new file mode 100644 index 000000000..8f6eab29d --- /dev/null +++ b/src/controls/dynamicForm/IDynamicFormState.ts @@ -0,0 +1,8 @@ + +import { IDynamicFieldProps } from './dynamicField/IDynamicFieldProps'; +export interface IDynamicFormState { + fieldCollection: IDynamicFieldProps[]; +} + + + diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.tsx b/src/controls/dynamicForm/dynamicField/DynamicField.tsx new file mode 100644 index 000000000..4b932a7d3 --- /dev/null +++ b/src/controls/dynamicForm/dynamicField/DynamicField.tsx @@ -0,0 +1,153 @@ +import * as React from 'react'; +import styles from '../DynamicForm.module.scss'; +import { IDropdownOption, IDropdownProps, Dropdown } from 'office-ui-fabric-react/lib/components/Dropdown'; +import { DatePicker } from 'office-ui-fabric-react'; +import { IDynamicFieldProps } from './IDynamicFieldProps'; +import { IDynamicFieldState } from './IDynamicFieldState'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { PeoplePicker, PrincipalType } from "../../peoplepicker"; +import { FilePicker, IFilePickerResult } from '../../filePicker'; +import { TaxonomyPicker, IPickerTerms } from "../../taxonomyPicker"; +import { ListItemPicker } from '../../listItemPicker'; +import { RichText } from "../../richText"; +import { Icon } from 'office-ui-fabric-react'; +import { Shimmer } from 'office-ui-fabric-react/lib/Shimmer'; +import { Toggle } from 'office-ui-fabric-react/lib/Toggle'; +import { Image } from 'office-ui-fabric-react/lib/Image'; +import { Stack, Link } from 'office-ui-fabric-react/lib/'; +import { sp } from "@pnp/sp/presets/all"; + +export class DynamicField extends React.Component { + + constructor(props: IDynamicFieldProps) { + super(props); + sp.setup({ + spfxContext: this.props.context + }); + this.state = { changedvalue: null }; + } + + public componentDidUpdate() { + if (this.props.fieldDefaultValue === "" && this.state.changedvalue === null) { + this.setState({ changedvalue: "" }); + } + } + + public render(): JSX.Element { + const { options, fieldTermSetId, lookupListID, lookupField, fieldType, fieldDefaultValue, fieldTitle, context, className, disabled, label, placeHolder, placeholder, value, required, isRichText } = this.props; + + const dropdownOptions: IDropdownProps = { + className: className, + options: options, + disabled: (disabled), + placeHolder: placeholder || placeHolder + }; + + let labelText = fieldTitle != null ? fieldTitle : label; + let defaultValue = fieldDefaultValue; + let empty = null; + try { + return ( +
+ {(fieldType === 'loading') ? : + (fieldType === 'Text') ?
{ this.props.onChanged(this.props.columnInternalName, newtext); this.setState({ changedvalue: newtext }); }} disabled={disabled} onBlur={() => { this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null; }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}>
: + (fieldType === 'Note' && isRichText === false) ?
{ this.props.onChanged(this.props.columnInternalName, newtext); this.setState({ changedvalue: newtext }); }} disabled={disabled} onBlur={() => { (this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null); }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}>
: + (fieldType === 'Note' && isRichText === true) ?
{ this.props.onChanged(this.props.columnInternalName, newtext); return newtext; }} isEditMode={disabled}>
: + // (fieldType === 'Location') ?
{ this.props.onChanged(this.props.columnInternalName, newtext); this.changedvalue = newtext; this.setState({}); }} disabled={disabled}>
: + (fieldType === 'Choice') ?
{ this.props.onChanged(this.props.columnInternalName, option); this.setState({ changedvalue: option }); }} disabled={disabled} onBlur={() => { (this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null); }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null} />
: + (fieldType === 'MultiChoice') ?
{ (this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null); }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null} />
: + (fieldType === 'Lookup') ?
{ this.props.onChanged(this.props.columnInternalName, newvalue); this.setState({ changedvalue: newvalue }); }} context={this.props.context} />{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'LookupMulti') ?
{ this.props.onChanged(this.props.columnInternalName, newvalue); this.setState({ changedvalue: newvalue }); }} context={this.props.context} />{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'Number') ?
{ this.props.onChanged(this.props.columnInternalName, newtext); this.setState({ changedvalue: newtext }); }} disabled={disabled} onBlur={() => { (this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null); }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}>
: + (fieldType === 'Currency') ?
{ this.props.onChanged(this.props.columnInternalName, newtext); this.setState({ changedvalue: newtext }); }} disabled={disabled} onBlur={() => { (this.state.changedvalue === null && defaultValue === "" ? this.setState({ changedvalue: "" }) : empty = null); }} errorMessage={(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}>
: + (fieldType === 'DateTime') ?
{ return date.toLocaleDateString(context.pageContext.web.languageName); }} value={(this.state.changedvalue !== null && this.state.changedvalue !== "") ? this.state.changedvalue : defaultValue} onSelectDate={(newdate) => { this.props.onChanged(this.props.columnInternalName, newdate); this.setState({ changedvalue: newdate }); }} disabled={disabled} />{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'Boolean') ?
{ this.props.onChanged(this.props.columnInternalName, checkedvalue); this.setState({ changedvalue: checkedvalue }); }} disabled={disabled} />{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'User') ?
{ this.props.onChanged(this.props.columnInternalName, items); this.setState({ changedvalue: items }); }} disabled={disabled} /> {(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'UserMulti') ?
{ this.props.onChanged(this.props.columnInternalName, items); this.setState({ changedvalue: items }); }} disabled={disabled} />{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'URL') ?
{defaultValue === null ? null : {defaultValue["Description"]}}{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'Thumbnail') ?
{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'TaxonomyFieldTypeMulti') ?
{ this.props.onChanged(this.props.columnInternalName, newValue); this.setState({ changedvalue: newValue }); }} isTermSetSelectable={false} />
{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + (fieldType === 'TaxonomyFieldType') ?
{ this.props.onChanged(this.props.columnInternalName, newValue); this.setState({ changedvalue: newValue }); }} isTermSetSelectable={false} />
{(this.state.changedvalue === '' && this.props.required) ? 'You can\'t leave this blank.' : null}
: + null // + } +
+ ); + } catch (error) { + console.log(`Error in DynamicField render`, error); + return null; + } + } + + private MultiChoice_selection = (event: React.FormEvent, item: IDropdownOption) => { + try { + let seleteditemarr; + if (this.state.changedvalue === null && this.props.fieldDefaultValue != null) { + seleteditemarr = []; + this.props.fieldDefaultValue.forEach(element => { + seleteditemarr.push(element); + }); + } + else + seleteditemarr = (this.state.changedvalue === "" || this.state.changedvalue === null) ? [] : this.state.changedvalue; + if (item.selected) { + seleteditemarr.push(item.key); + } + else { + let i = seleteditemarr.indexOf(item.key); + if (i >= 0) { + seleteditemarr.splice(i, 1); + } + } + this.setState({ changedvalue: seleteditemarr }); + this.props.onChanged(this.props.columnInternalName, seleteditemarr); + } catch (error) { + console.log(`Error MultiChoice_selection`, error); + } + } + + private saveIntoSharePoint = async (files: IFilePickerResult[]) => { + try { + files.forEach(async (file, i) => { + if (file.fileAbsoluteUrl == null) { + let resultcontent = await file.downloadFileContent(); + let fileresult = await sp.web.getFolderByServerRelativeUrl(this.props.context.pageContext.web.serverRelativeUrl + "/SiteAssets/Lists/" + this.props.listId).files.add(file.fileName, resultcontent, false); + this.setState({ + changedvalue: { + "__metadata": { "type": "SP.FieldUrlValue" }, + "Description": file.fileName, + "Url": document.location.origin + fileresult.data.ServerRelativeUrl + } + }); + this.props.onChanged(this.props.columnInternalName, this.state.changedvalue); + } + else { + if (this.props.fieldType === "Thumbnail") { + this.setState({ + changedvalue: JSON.stringify({ + "fileName": file.fileName, + "serverUrl": file.fileAbsoluteUrl.replace(file.fileAbsoluteUrl.replace(/.*\/\/[^\/]*/, ''), ''), + "serverRelativeUrl": file.fileAbsoluteUrl.replace(/.*\/\/[^\/]*/, '') + }) + }); + this.props.onChanged(this.props.columnInternalName, this.state.changedvalue); + this.setState({}); + } + else if (this.props.fieldType === "URL") { + { + this.setState({ + changedvalue: { + "__metadata": { "type": "SP.FieldUrlValue" }, + "Description": file.fileName, + "Url": file.fileAbsoluteUrl + } + }); + this.props.onChanged(this.props.columnInternalName, this.state.changedvalue); + } + } + } + }); + } catch (error) { + console.log(`Error save Into SharePoint`, error); + } + } +} diff --git a/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts b/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts new file mode 100644 index 000000000..5b43d4633 --- /dev/null +++ b/src/controls/dynamicForm/dynamicField/IDynamicFieldProps.ts @@ -0,0 +1,29 @@ +import { ExtensionContext } from '@microsoft/sp-extension-base'; +import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; +import { WebPartContext } from '@microsoft/sp-webpart-base'; + +export interface IDynamicFieldProps { + context: WebPartContext | ExtensionContext; + className?: string; + disabled?: boolean; + listId: string; + columnInternalName: string; + label?: string; + placeHolder?: string; + placeholder?: string; + onChanged?: (columnInternalName: string, newValue: any) => void; + value?: any; + required: boolean; + newValue?: any; + fieldType: string; + fieldTitle: string; + fieldDefaultValue: any; + options: IDropdownOption[]; + fieldTermSetId: string; + lookupListID?: string; + lookupField?: string; + changedvalue: any; + hiddenFieldName?: string; + Order: number; + isRichText:boolean; +} \ No newline at end of file diff --git a/src/controls/dynamicForm/dynamicField/IDynamicFieldState.ts b/src/controls/dynamicForm/dynamicField/IDynamicFieldState.ts new file mode 100644 index 000000000..73b366f8b --- /dev/null +++ b/src/controls/dynamicForm/dynamicField/IDynamicFieldState.ts @@ -0,0 +1,11 @@ +import { ExtensionContext } from '@microsoft/sp-extension-base'; +import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown"; +import { WebPartContext } from '@microsoft/sp-webpart-base'; +import { LibsOrderBy } from "../../../services/ISPService"; + +export interface IDynamicFieldState { + /** + * The options available to the listPicker + */ + changedvalue:any; +} diff --git a/src/controls/dynamicForm/dynamicField/index.ts b/src/controls/dynamicForm/dynamicField/index.ts new file mode 100644 index 000000000..82e215d0a --- /dev/null +++ b/src/controls/dynamicForm/dynamicField/index.ts @@ -0,0 +1,3 @@ +export * from './IDynamicFieldState'; +export * from './IDynamicFieldProps'; +export * from './DynamicField'; \ No newline at end of file diff --git a/src/controls/dynamicForm/index.ts b/src/controls/dynamicForm/index.ts new file mode 100644 index 000000000..78d427513 --- /dev/null +++ b/src/controls/dynamicForm/index.ts @@ -0,0 +1,3 @@ +export * from './IDynamicFormState'; +export * from './IDynamicFormProps'; +export * from './DynamicForm'; \ No newline at end of file diff --git a/src/services/SPService.ts b/src/services/SPService.ts index 85d555aae..6b74ac75b 100644 --- a/src/services/SPService.ts +++ b/src/services/SPService.ts @@ -358,4 +358,176 @@ export default class SPService implements ISPService { return; } + + public async getLookUpValue(listId: string, listItemID: number, fieldName: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/Title&$expand=${fieldName}&$filter= ID eq ${listItemID}`; + + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + return [{ key: results.value[0][fieldName].ID, name: results.value[0][fieldName].Title }]; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + public async getLookUpValues(listId: string, listItemID: number, fieldName: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/ID,${fieldName}/Title&$expand=${fieldName}&$filter= ID eq ${listItemID}`; + + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + let emails = []; + results.value[0][fieldName].forEach(element => { + emails.push({ key: element.ID, name: element.Title }); + }); + return emails; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + public async getInternalName(listId: string, fieldName: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/Fields/getByInternalNameOrTitle('${fieldName}_0')/InternalName?@listId=guid'${encodeURIComponent(listId)}'`; + + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + return results; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + public async getUserEmailsById(listId: string, listItemId: number, fieldName: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/items?@listId=guid'${encodeURIComponent(listId)}'&$select=${fieldName}/UserName&$expand=${fieldName}&$filter= ID eq ${listItemId}`; + + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + let emails = []; + results.value[0][fieldName].forEach(element => { + emails.push(element.UserName); + }); + return emails; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + public async getUserEmailById(userId: number, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/getuserbyid(${userId})?$select=UserPrincipalName`; + + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + return results.UserPrincipalName; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + public async getSingleManagedMtadataLabel(listId: string, listItemId: number, fieldName: string): Promise { + try { + const webAbsoluteUrl = this._context.pageContext.web.absoluteUrl; + let apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/RenderListDataAsStream?@listId=guid'${encodeURIComponent(listId)}'`; + const data = await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1,{ + body: JSON.stringify({ + parameters: { + RenderOptions: 2, + ViewXml: ` + + + + + + + + ${listItemId} + + + + 1 + ` + } + }) + }); + if (data.ok) { + const results = await data.json(); + if (results) { + return results.Row[0][fieldName]; + } + } + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + + public async getListInfo(listId: string, contentTypeId: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + let apiUrl = ''; + if (contentTypeId !== undefined && contentTypeId !== '') { + apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/contenttypes('${contentTypeId}')/fields?@listId=guid'${encodeURIComponent(listId)}'&$filter=ReadOnlyField eq false and Hidden eq false and (FromBaseType eq false or StaticName eq 'Title')`; + } + else { + apiUrl = `${webAbsoluteUrl}/_api/web/lists(@listId)/fields?@listId=guid'${encodeURIComponent(listId)}'&$filter=ReadOnlyField eq false and Hidden eq false and (FromBaseType eq false or StaticName eq 'Title')`; + } + const data = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + if (data.ok) { + const results = await data.json(); + if (results) { + return results; + } + } + + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index ace118853..6f59c0b0e 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -164,6 +164,7 @@ import { } from "./IControlsTestProps"; import { DragDropFiles } from "../../../DragDropFiles"; import { SitePicker } from "../../../controls/sitePicker/SitePicker"; +import { DynamicForm } from '../../../controls/dynamicForm'; // Used to render document card /** @@ -818,6 +819,10 @@ export default class ControlsTest extends React.Component +
+ {/* Change the list Id and list item id before you start to test this control */} + {/* { console.log('Cancelled'); }} onSubmitted={async (listItem) => { let itemdata = await listItem.get(); console.log(itemdata["ID"]); }}> */} +