@@ -33,6 +33,7 @@ import "@pnp/sp/content-types";
33
33
import "@pnp/sp/folders" ;
34
34
import "@pnp/sp/items" ;
35
35
import { sp } from "@pnp/sp" ;
36
+ import { ICustomFormatting , ICustomFormattingBodySection } from "./ICustomFormatting" ;
36
37
37
38
const stackTokens : IStackTokens = { childrenGap : 20 } ;
38
39
@@ -102,9 +103,17 @@ export class DynamicForm extends React.Component<
102
103
* Default React component render method
103
104
*/
104
105
public render ( ) : JSX . Element {
105
- const { fieldCollection , hiddenByFormula , isSaving, validationErrors } = this . state ;
106
+ const { customFormatting , fieldCollection , isSaving } = this . state ;
106
107
107
- const fieldOverrides = this . props . fieldOverrides ;
108
+ const bodySections = customFormatting ?. body || [ ] ;
109
+ if ( bodySections . length > 0 ) {
110
+ const specifiedFields : string [ ] = bodySections . reduce ( ( prev , cur ) => {
111
+ prev . push ( ...cur . fields ) ;
112
+ return prev ;
113
+ } , [ ] ) ;
114
+ const omittedFields = fieldCollection . filter ( f => ! specifiedFields . includes ( f . columnInternalName ) ) . map ( f => f . columnInternalName ) ;
115
+ bodySections [ bodySections . length - 1 ] . fields . push ( ...omittedFields ) ;
116
+ }
108
117
109
118
return (
110
119
< div >
@@ -117,33 +126,20 @@ export class DynamicForm extends React.Component<
117
126
</ div >
118
127
) : (
119
128
< div >
120
- { fieldCollection . map ( ( v , i ) => {
121
- if ( hiddenByFormula . find ( h => h === v . columnInternalName ) ) {
122
- return null ;
123
- }
124
- let validationErrorMessage : string = "" ;
125
- if ( validationErrors [ v . columnInternalName ] ) {
126
- validationErrorMessage = validationErrors [ v . columnInternalName ] ;
127
- }
128
- if (
129
- fieldOverrides &&
130
- Object . prototype . hasOwnProperty . call (
131
- fieldOverrides ,
132
- v . columnInternalName
133
- )
134
- ) {
135
- v . disabled = v . disabled || isSaving ;
136
- return fieldOverrides [ v . columnInternalName ] ( v ) ;
137
- }
138
- return (
139
- < DynamicField
140
- key = { v . columnInternalName }
141
- { ...v }
142
- disabled = { v . disabled || isSaving }
143
- validationErrorMessage = { validationErrorMessage }
144
- />
145
- ) ;
146
- } ) }
129
+ { bodySections . length > 0 && bodySections . map ( ( section , i ) => (
130
+ < >
131
+ < h2 className = { styles . sectionTitle } > { section . displayname } </ h2 >
132
+ < div className = { styles . sectionFormFields } >
133
+ { section . fields . map ( ( f , i ) => (
134
+ < div key = { f } className = { styles . sectionFormField } >
135
+ { this . renderField ( fieldCollection . find ( fc => fc . columnInternalName === f ) as IDynamicFieldProps ) }
136
+ </ div >
137
+ ) ) }
138
+ </ div >
139
+ { i < bodySections . length - 1 && < hr className = { styles . sectionLine } aria-hidden = { true } /> }
140
+ </ >
141
+ ) ) }
142
+ { bodySections . length === 0 && fieldCollection . map ( ( f , i ) => this . renderField ( f ) ) }
147
143
{ ! this . props . disabled && (
148
144
< Stack className = { styles . buttons } horizontal tokens = { stackTokens } >
149
145
< PrimaryButton
@@ -189,6 +185,36 @@ export class DynamicForm extends React.Component<
189
185
) ;
190
186
}
191
187
188
+ private renderField = ( field : IDynamicFieldProps ) : JSX . Element => {
189
+ const { fieldOverrides } = this . props ;
190
+ const { hiddenByFormula, isSaving, validationErrors } = this . state ;
191
+ if ( hiddenByFormula . find ( h => h === field . columnInternalName ) ) {
192
+ return null ;
193
+ }
194
+ let validationErrorMessage : string = "" ;
195
+ if ( validationErrors [ field . columnInternalName ] ) {
196
+ validationErrorMessage = validationErrors [ field . columnInternalName ] ;
197
+ }
198
+ if (
199
+ fieldOverrides &&
200
+ Object . prototype . hasOwnProperty . call (
201
+ fieldOverrides ,
202
+ field . columnInternalName
203
+ )
204
+ ) {
205
+ field . disabled = field . disabled || isSaving ;
206
+ return fieldOverrides [ field . columnInternalName ] ( field ) ;
207
+ }
208
+ return (
209
+ < DynamicField
210
+ key = { field . columnInternalName }
211
+ { ...field }
212
+ disabled = { field . disabled || isSaving }
213
+ validationErrorMessage = { validationErrorMessage }
214
+ />
215
+ ) ;
216
+ }
217
+
192
218
//trigger when the user submits the form.
193
219
private onSubmitClick = async ( ) : Promise < void > => {
194
220
const {
@@ -570,10 +596,16 @@ export class DynamicForm extends React.Component<
570
596
let contentTypeId = this . props . contentTypeId ;
571
597
let contentTypeName : string ;
572
598
try {
599
+
600
+ // Fetch form rendering information from SharePoint
573
601
const listInfo = await this . _spService . getListFormRenderInfo ( listId ) ;
602
+
603
+ // Fetch additional information about fields from SharePoint
604
+ // (Number fields for min and max values, and fields with validation)
574
605
const additionalInfo = await this . _spService . getAdditionalListFormFieldInfo ( listId ) ;
575
-
576
606
const numberFields = additionalInfo . filter ( ( f ) => f . TypeAsString === "Number" || f . TypeAsString === "Currency" ) ;
607
+
608
+ // Build a dictionary of validation formulas and messages
577
609
const validationFormulas : Record < string , Pick < ISPField , "ValidationFormula" | "ValidationMessage" > > = additionalInfo . reduce ( ( prev , cur ) => {
578
610
if ( ! prev [ cur . InternalName ] && cur . ValidationFormula ) {
579
611
prev [ cur . InternalName ] = {
@@ -584,26 +616,15 @@ export class DynamicForm extends React.Component<
584
616
return prev ;
585
617
} , { } ) ;
586
618
587
- const spList = sp . web . lists . getById ( listId ) ;
588
- let item = null ;
589
- let etag : string | undefined = undefined ;
590
- if ( listItemId !== undefined && listItemId !== null && listItemId !== 0 ) {
591
- item = await spList . items . getById ( listItemId ) . get ( ) ;
592
-
593
- if ( onListItemLoaded ) {
594
- await onListItemLoaded ( item ) ;
595
- }
596
-
597
- if ( respectETag !== false ) {
598
- etag = item [ "odata.etag" ] ;
599
- }
600
- }
601
-
619
+ // If no content type ID is provided, use the default (first one in the list)
602
620
if ( contentTypeId === undefined || contentTypeId === "" ) {
603
621
contentTypeId = Object . keys ( listInfo . ContentTypeIdToNameMap ) [ 0 ] ;
604
622
}
605
623
contentTypeName = listInfo . ContentTypeIdToNameMap [ contentTypeId ] ;
606
624
625
+ // Build a dictionary of client validation formulas and messages
626
+ // These are formulas that are added in Edit Form > Edit Columns > Edit Conditional Formula
627
+ // They are evaluated on the client side, and determine whether a field should be hidden or shown
607
628
const clientValidationFormulas = listInfo . ClientForms . Edit [ contentTypeName ] . reduce ( ( prev , cur ) => {
608
629
if ( cur . ClientValidationFormula ) {
609
630
prev [ cur . InternalName ] = {
@@ -614,6 +635,30 @@ export class DynamicForm extends React.Component<
614
635
return prev ;
615
636
} , { } as Record < string , Pick < ISPField , "ValidationFormula" | "ValidationMessage" > > ) ;
616
637
638
+ // Custom Formatting
639
+ let bodySections : ICustomFormattingBodySection [ ] ;
640
+ if ( listInfo . ClientFormCustomFormatter && listInfo . ClientFormCustomFormatter [ contentTypeId ] ) {
641
+ const customFormatInfo = JSON . parse ( listInfo . ClientFormCustomFormatter [ contentTypeId ] ) as ICustomFormatting ;
642
+ bodySections = customFormatInfo . bodyJSONFormatter . sections ;
643
+ }
644
+
645
+ // Load SharePoint list item
646
+ const spList = sp . web . lists . getById ( listId ) ;
647
+ let item = null ;
648
+ let etag : string | undefined = undefined ;
649
+ if ( listItemId !== undefined && listItemId !== null && listItemId !== 0 ) {
650
+ item = await spList . items . getById ( listItemId ) . get ( ) ;
651
+
652
+ if ( onListItemLoaded ) {
653
+ await onListItemLoaded ( item ) ;
654
+ }
655
+
656
+ if ( respectETag !== false ) {
657
+ etag = item [ "odata.etag" ] ;
658
+ }
659
+ }
660
+
661
+ // Build the field collection
617
662
const tempFields : IDynamicFieldProps [ ] = [ ] ;
618
663
let order : number = 0 ;
619
664
const hiddenFields =
@@ -817,7 +862,10 @@ export class DynamicForm extends React.Component<
817
862
clientValidationFormulas,
818
863
fieldCollection : tempFields ,
819
864
validationFormulas,
820
- etag
865
+ etag,
866
+ customFormatting : {
867
+ body : bodySections ,
868
+ }
821
869
} , ( ) => this . performValidation ( true ) ) ;
822
870
823
871
} catch ( error ) {
0 commit comments