Skip to content

Commit 5db10f8

Browse files
author
Tom German
committed
Refactor DynamicForm to use RenderListDataAsStream
1 parent 04f3e60 commit 5db10f8

File tree

6 files changed

+542
-5
lines changed

6 files changed

+542
-5
lines changed

src/common/SPEntities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export interface ISPField {
5656
LookupDisplayUrl?: string;
5757
TypeAsString?: string;
5858
ResultType?: string;
59+
ValidationFormula?: string;
60+
ValidationMessage?: string;
61+
MinimumValue?: number;
62+
MaximumValue?: number;
5963
}
6064

6165
/**

src/controls/dynamicForm/DynamicForm.tsx

Lines changed: 276 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @microsoft/spfx/no-async-await */
22
import { SPHttpClient } from "@microsoft/sp-http";
3-
import { sp } from "@pnp/sp/presets/all";
3+
import { IRenderListDataAsStreamResult, sp } from "@pnp/sp/presets/all";
44
import * as strings from "ControlStrings";
55
import {
66
DefaultButton,
@@ -10,7 +10,7 @@ import { IDropdownOption } from "office-ui-fabric-react/lib/components/Dropdown"
1010
import { ProgressIndicator } from "office-ui-fabric-react/lib/ProgressIndicator";
1111
import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
1212
import * as React from "react";
13-
import { IUploadImageResult } from "../../common/SPEntities";
13+
import { ISPField, IUploadImageResult } from "../../common/SPEntities";
1414
import SPservice from "../../services/SPService";
1515
import { IFilePickerResult } from "../filePicker";
1616
import { DynamicField } from "./dynamicField";
@@ -69,6 +69,8 @@ export class DynamicForm extends React.Component<
6969
// Initialize state
7070
this.state = {
7171
fieldCollection: [],
72+
validationFormulas: {},
73+
clientValidationFormulas: {},
7274
isValidationErrorDialogOpen: false,
7375
};
7476
// Get SPService Factory
@@ -81,7 +83,7 @@ export class DynamicForm extends React.Component<
8183
* Lifecycle hook when component is mounted
8284
*/
8385
public componentDidMount(): void {
84-
this.getFieldInformations()
86+
this.getListInformation()
8587
.then(() => {
8688
/* no-op; */
8789
})
@@ -463,6 +465,277 @@ export class DynamicForm extends React.Component<
463465
});
464466
};
465467

468+
private getListInformation = async(): Promise<void> => {
469+
const {
470+
listId,
471+
listItemId,
472+
disabledFields,
473+
respectETag,
474+
onListItemLoaded,
475+
} = this.props;
476+
let contentTypeId = this.props.contentTypeId;
477+
let contentTypeName: string;
478+
try {
479+
const listInfo = await this._spService.getListFormRenderInfo(listId);
480+
const additionalInfo = await this._spService.getAdditionalListFormFieldInfo(listId);
481+
482+
const numberFields = additionalInfo.filter((f) => f.TypeAsString === "Number" || f.TypeAsString === "Currency");
483+
const validationFormulas: Record<string, Pick<ISPField,"ValidationFormula"|"ValidationMessage">> = additionalInfo.reduce((prev, cur) => {
484+
if (!prev[cur.InternalName] && cur.ValidationFormula) {
485+
prev[cur.InternalName] = {
486+
ValidationFormula: cur.ValidationFormula,
487+
ValidationMessage: cur.ValidationMessage,
488+
};
489+
}
490+
return prev;
491+
}, {});
492+
493+
const spList = sp.web.lists.getById(listId);
494+
let item = null;
495+
let etag: string | undefined = undefined;
496+
if (listItemId !== undefined && listItemId !== null && listItemId !== 0) {
497+
item = await spList.items.getById(listItemId).get();
498+
499+
if (onListItemLoaded) {
500+
await onListItemLoaded(item);
501+
}
502+
503+
if (respectETag !== false) {
504+
etag = item["odata.etag"];
505+
}
506+
}
507+
508+
if (contentTypeId === undefined || contentTypeId === "") {
509+
contentTypeId = Object.keys(listInfo.ContentTypeIdToNameMap)[0];
510+
}
511+
contentTypeName = listInfo.ContentTypeIdToNameMap[contentTypeId];
512+
513+
const clientValidationFormulas = listInfo.ClientForms.Edit[contentTypeName].reduce((prev, cur) => {
514+
if (cur.ClientValidationFormula) {
515+
prev[cur.InternalName] = {
516+
ValidationFormula: cur.ClientValidationFormula,
517+
ValidationMessage: cur.ClientValidationMessage,
518+
};
519+
}
520+
return prev;
521+
}, {} as Record<string, Pick<ISPField, "ValidationFormula" | "ValidationMessage">>);
522+
523+
const tempFields: IDynamicFieldProps[] = [];
524+
let order: number = 0;
525+
const hiddenFields =
526+
this.props.hiddenFields !== undefined ? this.props.hiddenFields : [];
527+
let defaultDayOfWeek: number = 0;
528+
529+
for (let i = 0, len = listInfo.ClientForms.Edit[contentTypeName].length; i < len; i++) {
530+
const field = listInfo.ClientForms.Edit[contentTypeName][i];
531+
532+
// Handle only fields that are not marked as hidden
533+
if (hiddenFields.indexOf(field.InternalName) < 0) {
534+
order++;
535+
let hiddenName = "";
536+
let termSetId = "";
537+
let anchorId = "";
538+
let lookupListId = "";
539+
let lookupField = "";
540+
const choices: IDropdownOption[] = [];
541+
let defaultValue = null;
542+
const selectedTags: any = []; // eslint-disable-line @typescript-eslint/no-explicit-any
543+
let richText = false;
544+
let dateFormat: DateFormat | undefined;
545+
let principalType = "";
546+
let minValue: number | undefined;
547+
let maxValue: number | undefined;
548+
let showAsPercentage: boolean | undefined;
549+
if (item !== null) {
550+
defaultValue = item[field.InternalName];
551+
} else {
552+
defaultValue = field.DefaultValue;
553+
}
554+
555+
if (field.FieldType === "Choice" || field.FieldType === "MultiChoice") {
556+
field.Choices.forEach((element) => {
557+
choices.push({ key: element, text: element });
558+
});
559+
} else if (field.FieldType === "Note") {
560+
richText = field.RichText;
561+
} else if (field.FieldType === "Number" || field.FieldType === "Currency") {
562+
const numberField = numberFields.find(f => f.InternalName === field.InternalName);
563+
if (numberField) {
564+
minValue = numberField.MinimumValue;
565+
maxValue = numberField.MaximumValue;
566+
}
567+
showAsPercentage = field.ShowAsPercentage;
568+
} else if (field.FieldType === "Lookup" || field.FieldType === "MultiLookup") {
569+
lookupListId = field.LookupListId;
570+
lookupField = field.LookupFieldName;
571+
if (item !== null) {
572+
defaultValue = await this._spService.getLookupValues(
573+
listId,
574+
listItemId,
575+
field.InternalName,
576+
lookupField,
577+
this.webURL
578+
);
579+
} else {
580+
defaultValue = [];
581+
}
582+
} else if (field.FieldType === "TaxonomyFieldTypeMulti") {
583+
hiddenName = field.HiddenListInternalName;
584+
termSetId = field.TermSetId;
585+
anchorId = field.AnchorId;
586+
if (item !== null) {
587+
item[field.InternalName].forEach((element) => {
588+
selectedTags.push({
589+
key: element.TermGuid,
590+
name: element.Label,
591+
});
592+
});
593+
594+
defaultValue = selectedTags;
595+
} else {
596+
if (defaultValue !== null && defaultValue !== "") {
597+
defaultValue.split(/#|;/).forEach((element) => {
598+
if (element.indexOf("|") !== -1)
599+
selectedTags.push({
600+
key: element.split("|")[1],
601+
name: element.split("|")[0],
602+
});
603+
});
604+
605+
defaultValue = selectedTags;
606+
}
607+
}
608+
if (defaultValue === "") defaultValue = null;
609+
} else if (field.FieldType === "TaxonomyFieldType") {
610+
termSetId = field.TermSetId;
611+
anchorId = field.AnchorId;
612+
if (item !== null) {
613+
const response =
614+
await this._spService.getSingleManagedMetadataLabel(
615+
listId,
616+
listItemId,
617+
field.InternalName
618+
);
619+
if (response) {
620+
selectedTags.push({
621+
key: response.TermID,
622+
name: response.Label,
623+
});
624+
defaultValue = selectedTags;
625+
}
626+
} else {
627+
if (defaultValue !== "") {
628+
selectedTags.push({
629+
key: defaultValue.split("|")[1],
630+
name: defaultValue.split("|")[0].split("#")[1],
631+
});
632+
defaultValue = selectedTags;
633+
}
634+
}
635+
if (defaultValue === "") defaultValue = null;
636+
} else if (field.FieldType === "DateTime") {
637+
if (item !== null && item[field.InternalName]) {
638+
defaultValue = new Date(item[field.InternalName]);
639+
} else if (defaultValue === "[today]") {
640+
defaultValue = new Date();
641+
}
642+
643+
dateFormat = field.DateFormat || "DateOnly";
644+
defaultDayOfWeek = (await this._spService.getRegionalWebSettings()).FirstDayOfWeek;
645+
} else if (field.FieldType === "UserMulti") {
646+
if (item !== null) {
647+
defaultValue = this._spService.getUsersUPNFromFieldValue(
648+
listId,
649+
listItemId,
650+
field.InternalName,
651+
this.webURL
652+
);
653+
} else {
654+
defaultValue = [];
655+
}
656+
principalType = field.PrincipalAccountType;
657+
} else if (field.FieldType === "Thumbnail") {
658+
if (defaultValue) {
659+
defaultValue = JSON.parse(defaultValue).serverRelativeUrl;
660+
}
661+
} else if (field.FieldType === "User") {
662+
if (item !== null) {
663+
const userEmails: string[] = [];
664+
userEmails.push(
665+
(await this._spService.getUserUPNFromFieldValue(
666+
listId,
667+
listItemId,
668+
field.InternalName,
669+
this.webURL
670+
)) + ""
671+
);
672+
defaultValue = userEmails;
673+
} else {
674+
defaultValue = [];
675+
}
676+
principalType = field.PrincipalAccountType;
677+
} else if (field.FieldType === "Location") {
678+
defaultValue = JSON.parse(defaultValue);
679+
} else if (field.FieldType === "Boolean") {
680+
defaultValue = Boolean(Number(defaultValue));
681+
}
682+
683+
tempFields.push({
684+
newValue: null,
685+
fieldTermSetId: termSetId,
686+
fieldAnchorId: anchorId,
687+
options: choices,
688+
lookupListID: lookupListId,
689+
lookupField: lookupField,
690+
changedValue: defaultValue,
691+
fieldType: field.FieldType,
692+
fieldTitle: field.Title,
693+
fieldDefaultValue: defaultValue,
694+
context: this.props.context,
695+
disabled:
696+
this.props.disabled ||
697+
(disabledFields &&
698+
disabledFields.indexOf(field.InternalName) > -1),
699+
listId: this.props.listId,
700+
columnInternalName: field.InternalName,
701+
label: field.Title,
702+
onChanged: this.onChange,
703+
required: field.Required,
704+
hiddenFieldName: hiddenName,
705+
Order: order,
706+
isRichText: richText,
707+
dateFormat: dateFormat,
708+
firstDayOfWeek: defaultDayOfWeek,
709+
listItemId: listItemId,
710+
principalType: principalType,
711+
description: field.Description,
712+
minimumValue: minValue,
713+
maximumValue: maxValue,
714+
showAsPercentage: showAsPercentage,
715+
});
716+
717+
// This may not be necessary now using RenderListDataAsStream
718+
tempFields.sort((a, b) => a.Order - b.Order);
719+
}
720+
}
721+
722+
// Do formatting and validation parsing here
723+
console.log('Validation Formulas', validationFormulas);
724+
console.log('Client Side Validation Formulas', clientValidationFormulas);
725+
726+
this.setState({
727+
clientValidationFormulas,
728+
fieldCollection: tempFields,
729+
validationFormulas,
730+
etag
731+
});
732+
733+
} catch (error) {
734+
console.log(`Error get field informations`, error);
735+
return null;
736+
}
737+
}
738+
466739
//getting all the fields information as part of get ready process
467740
private getFieldInformations = async (): Promise<void> => {
468741
const {

src/controls/dynamicForm/IDynamicFormState.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11

2+
import { ISPField } from '../../common/SPEntities';
23
import { IDynamicFieldProps } from './dynamicField/IDynamicFieldProps';
34
export interface IDynamicFormState {
45
fieldCollection: IDynamicFieldProps[];
6+
validationFormulas: Record<string, Pick<ISPField, 'ValidationFormula' | 'ValidationMessage'>>;
7+
clientValidationFormulas: Record<string, Pick<ISPField, 'ValidationFormula' | 'ValidationMessage'>>;
58
isSaving?: boolean;
69
etag?: string;
710
isValidationErrorDialogOpen: boolean;

0 commit comments

Comments
 (0)