From 79b614f4761c75d2dfcf681721dcba14d10c4bb5 Mon Sep 17 00:00:00 2001 From: "niels.soeth" Date: Wed, 30 Jul 2025 07:47:59 +0200 Subject: [PATCH] add itemsQueryCountLimit to DynamicForm --- .../docs/controls/DynamicForm.md | 1 + src/controls/dynamicForm/DynamicForm.tsx | 170 +++++++++--------- src/controls/dynamicForm/IDynamicFormProps.ts | 23 ++- 3 files changed, 100 insertions(+), 94 deletions(-) diff --git a/docs/documentation/docs/controls/DynamicForm.md b/docs/documentation/docs/controls/DynamicForm.md index 4aca7cc0a..f4870a18a 100644 --- a/docs/documentation/docs/controls/DynamicForm.md +++ b/docs/documentation/docs/controls/DynamicForm.md @@ -72,6 +72,7 @@ The `DynamicForm` can be configured with the following properties: | useModernTaxonomyPicker | boolean | no | Specifies if the form should render [ModernTaxonomyPicker](./ModernTaxonomyPicker.md) control for Managed metadata fields. If set to `true`, Dynamic form will render ModernTaxonomyPicker control. If set to `false`, Dynamic form will render TaxonomyPicker control. Default is `false` | | className | string | no | Set CSS Class. | | styles | IStyleFunctionOrObject<IDynamicFormStyleProps, [IDynamicFormStyles](#idynamicformstyles-interface)> | no | Styles to apply on control. See the example [here](#how-to-use-styles-property) | +| itemsQueryCountLimit | number | no | Number of items to display in the lookup fields of the form | | ## Validation Error Dialog Properties `IValidationErrorDialogProps` diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index dce87e453..d83d9bf5b 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -173,7 +173,7 @@ export class DynamicFormBase extends React.Component< // Custom Formatting - Header let headerContent: JSX.Element; if (!customFormattingDisabled && customFormatting?.header) { - headerContent =
+ headerContent =
{this._customFormatter.renderCustomFormatContent(customFormatting.header, this.getFormValuesForValidation(), true)}
} @@ -196,8 +196,8 @@ export class DynamicFormBase extends React.Component< let footerContent: JSX.Element; if (!customFormattingDisabled && customFormatting?.footer) { footerContent =
- {this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true)} -
+ {this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true)} +
} // Content Type @@ -227,20 +227,20 @@ export class DynamicFormBase extends React.Component< {(bodySections.length > 0 && !customFormattingDisabled) && bodySections .filter(bs => bs.fields.filter(bsf => hiddenByFormula.indexOf(bsf) < 0).length > 0) .map((section, i) => ( - <> -

{section.displayname}

-
- {section.fields - .filter(f => fieldCollection.find(fc => fc.label === f)) - .map((f, i) => ( -
- {this.renderField(fieldCollection.find(fc => fc.label === f) as IDynamicFieldProps)} -
- ))} -
- {i < bodySections.length - 1 &&
} - - ))} + <> +

{section.displayname}

+
+ {section.fields + .filter(f => fieldCollection.find(fc => fc.label === f)) + .map((f, i) => ( +
+ {this.renderField(fieldCollection.find(fc => fc.label === f) as IDynamicFieldProps)} +
+ ))} +
+ {i < bodySections.length - 1 &&
} + + ))} {(bodySections.length === 0 || customFormattingDisabled) && fieldCollection.map((f, i) => this.renderField(f))} {footerContent} {!this.props.disabled && ( @@ -296,16 +296,16 @@ export class DynamicFormBase extends React.Component< } const sortedFields = customSort - .map((sortColumn) => sortColumn.toLowerCase()) - .filter((normalizedSortColumn) => fMap.has(normalizedSortColumn)) - .map((normalizedSortColumn) => fMap.get(normalizedSortColumn)) - .filter((field) => field !== undefined); + .map((sortColumn) => sortColumn.toLowerCase()) + .filter((normalizedSortColumn) => fMap.has(normalizedSortColumn)) + .map((normalizedSortColumn) => fMap.get(normalizedSortColumn)) + .filter((field) => field !== undefined); const remainingFields = fields.filter((field) => !sortedFields.includes(field)); const uniqueRemainingFields = Array.from(new Set(remainingFields)); return [...sortedFields, ...uniqueRemainingFields]; -} + } private renderField = (field: IDynamicFieldProps): JSX.Element => { const { fieldOverrides } = this.props; @@ -331,7 +331,7 @@ export class DynamicFormBase extends React.Component< field.columnInternalName ) ) { - return fieldOverrides[field.columnInternalName]({ ...field,disabled: field.disabled || isSaving} ) + return fieldOverrides[field.columnInternalName]({ ...field, disabled: field.disabled || isSaving }) } // Default render @@ -342,6 +342,7 @@ export class DynamicFormBase extends React.Component< {...field} disabled={field.disabled || isSaving} validationErrorMessage={validationErrorMessage} + itemsQueryCountLimit={this.props.itemsQueryCountLimit} /> ); } @@ -509,21 +510,21 @@ export class DynamicFormBase extends React.Component< } // Taxonomy / Managed Metadata fields - if(useModernTaxonomyPicker){ + if (useModernTaxonomyPicker) { //Use ITermInfo[] for modern taxonomy picker if (fieldType === "TaxonomyFieldType") { objects[fieldcolumnInternalName] = { - __metadata: { type: "SP.Taxonomy.TaxonomyFieldValue" }, - Label: value[0]?.labels[0]?.name ?? "", - TermGuid: value[0]?.id ?? "11111111-1111-1111-1111-111111111111", - WssId: "-1", + __metadata: { type: "SP.Taxonomy.TaxonomyFieldValue" }, + Label: value[0]?.labels[0]?.name ?? "", + TermGuid: value[0]?.id ?? "11111111-1111-1111-1111-111111111111", + WssId: "-1", }; } if (fieldType === "TaxonomyFieldTypeMulti") { objects[hiddenFieldName] = field.newValue - .map((term) => `-1#;${term.labels[0]?.name || ""}|${term.id};`) - .join("#"); + .map((term) => `-1#;${term.labels[0]?.name || ""}|${term.id};`) + .join("#"); } } else { @@ -608,7 +609,7 @@ export class DynamicFormBase extends React.Component< contentTypeId === undefined || contentTypeId === "" || (!contentTypeId.startsWith("0x0120") && - contentTypeId.startsWith("0x01")) + contentTypeId.startsWith("0x01")) ) { if (fileSelectRendered === true) { await this.addFileToLibrary(objects); @@ -710,51 +711,51 @@ export class DynamicFormBase extends React.Component< if (selectedFile !== undefined) { - try { - const idField = "ID"; - const contentTypeIdField = "ContentTypeId"; - - const library = await sp.web.lists.getById(listId); - const itemTitle = - selectedFile !== undefined && selectedFile.fileName !== undefined && selectedFile.fileName !== "" - ? (selectedFile.fileName as string).replace( - /["|*|:|<|>|?|/|\\||]/g, - "_" - ).trim() // Replace not allowed chars in folder name and trim empty spaces at the start or end. - : ""; // Empty string will be replaced by SPO with Folder Item ID - - const folder = !this.props.folderPath ? library.rootFolder : await this.getFolderByPath(this.props.folderPath, library.rootFolder); - const fileCreatedResult = await folder.files.addChunked(encodeURI(itemTitle), await selectedFile.downloadFileContent()); - const fields = await fileCreatedResult.file.listItemAllFields(); - - if (fields[idField]) { - // Read the ID of the just created file - const fileId = fields[idField]; - - // Set the content type ID for the target item - objects[contentTypeIdField] = contentTypeId; - // Update the just created file - const iur = await this.updateListItemRetry(library, fileId, objects); - if (onSubmitted) { - onSubmitted( - iur.data, - returnListItemInstanceOnSubmit !== false - ? iur.item - : undefined - ); - } - } else { - throw new Error( - "Unable to read the ID of the just created file" + try { + const idField = "ID"; + const contentTypeIdField = "ContentTypeId"; + + const library = await sp.web.lists.getById(listId); + const itemTitle = + selectedFile !== undefined && selectedFile.fileName !== undefined && selectedFile.fileName !== "" + ? (selectedFile.fileName as string).replace( + /["|*|:|<|>|?|/|\\||]/g, + "_" + ).trim() // Replace not allowed chars in folder name and trim empty spaces at the start or end. + : ""; // Empty string will be replaced by SPO with Folder Item ID + + const folder = !this.props.folderPath ? library.rootFolder : await this.getFolderByPath(this.props.folderPath, library.rootFolder); + const fileCreatedResult = await folder.files.addChunked(encodeURI(itemTitle), await selectedFile.downloadFileContent()); + const fields = await fileCreatedResult.file.listItemAllFields(); + + if (fields[idField]) { + // Read the ID of the just created file + const fileId = fields[idField]; + + // Set the content type ID for the target item + objects[contentTypeIdField] = contentTypeId; + // Update the just created file + const iur = await this.updateListItemRetry(library, fileId, objects); + if (onSubmitted) { + onSubmitted( + iur.data, + returnListItemInstanceOnSubmit !== false + ? iur.item + : undefined ); } - } catch (error) { - if (onSubmitError) { - onSubmitError(objects, error); - } - console.log("Error", error); + } else { + throw new Error( + "Unable to read the ID of the just created file" + ); + } + } catch (error) { + if (onSubmitError) { + onSubmitError(objects, error); } + console.log("Error", error); } + } } /** @@ -776,7 +777,7 @@ export class DynamicFormBase extends React.Component< const { useModernTaxonomyPicker } = this.props; // Init new value(s) field.newValue = newValue; - field.stringValue = newValue? newValue.toString():''; + field.stringValue = newValue ? newValue.toString() : ''; field.additionalData = additionalData; field.subPropertyValues = {}; @@ -791,12 +792,12 @@ export class DynamicFormBase extends React.Component< if (field.fieldType === "Lookup" || field.fieldType === "LookupMulti") { field.stringValue = newValue.map(nv => nv.key + ';#' + nv.name).join(';#'); } - if(useModernTaxonomyPicker){ + if (useModernTaxonomyPicker) { if (field.fieldType === "TaxonomyFieldType" || field.fieldType === "TaxonomyFieldTypeMulti") { if (Array.isArray(newValue) && newValue.length > 0) { - field.stringValue = newValue.map(nv => nv.labels.map(label => label.name).join(';')).join(';'); + field.stringValue = newValue.map(nv => nv.labels.map(label => label.name).join(';')).join(';'); } else { - field.stringValue = ""; + field.stringValue = ""; } } } else { @@ -851,7 +852,7 @@ export class DynamicFormBase extends React.Component< field.stringValue = emails.join(";"); } - const validationErrors = {...this.state.validationErrors}; + const validationErrors = { ...this.state.validationErrors }; if (validationErrors[field.columnInternalName]) delete validationErrors[field.columnInternalName]; this.setState({ @@ -1048,7 +1049,7 @@ export class DynamicFormBase extends React.Component< const spListItem = spList.items.getById(listItemId); if (contentTypeId.startsWith("0x0120") || contentTypeId.startsWith("0x0101")) { - spListItem.select("*","FileLeafRef"); // Explainer: FileLeafRef is not loaded by default. Load it to show the file/folder name in the field. + spListItem.select("*", "FileLeafRef"); // Explainer: FileLeafRef is not loaded by default. Load it to show the file/folder name in the field. } item = await spListItem.get().catch(err => this.updateFormMessages(MessageBarType.error, err.message)); @@ -1117,8 +1118,8 @@ export class DynamicFormBase extends React.Component< * @returns */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - private async buildFieldCollection(listInfo: IRenderListDataAsStreamClientFormResult, contentTypeName: string, item: any, numberFields: ISPField[], listId: string, listItemId: number, disabledFields: string[], customIcons: {[key: string]: string}): Promise { - const{ useModernTaxonomyPicker } = this.props; + private async buildFieldCollection(listInfo: IRenderListDataAsStreamClientFormResult, contentTypeName: string, item: any, numberFields: ISPField[], listId: string, listItemId: number, disabledFields: string[], customIcons: { [key: string]: string }): Promise { + const { useModernTaxonomyPicker } = this.props; const tempFields: IDynamicFieldProps[] = []; let order: number = 0; const hiddenFields = this.props.hiddenFields !== undefined ? this.props.hiddenFields : []; @@ -1129,7 +1130,7 @@ export class DynamicFormBase extends React.Component< // Process fields that are not marked as hidden if (hiddenFields.indexOf(field.InternalName) < 0) { - if(field.Hidden === false) { + if (field.Hidden === false) { order++; let hiddenName = ""; let termSetId = ""; @@ -1265,7 +1266,7 @@ export class DynamicFormBase extends React.Component< } // Setup Taxonomy / Metadata fields - if(useModernTaxonomyPicker){ + if (useModernTaxonomyPicker) { if (field.FieldType === "TaxonomyFieldType") { termSetId = field.TermSetId; anchorId = field.AnchorId !== Guid.empty.toString() ? field.AnchorId : null; @@ -1301,7 +1302,7 @@ export class DynamicFormBase extends React.Component< termSetId = field.TermSetId; anchorId = field.AnchorId !== Guid.empty.toString() ? field.AnchorId : null; if (item && item[field.InternalName]) { - const _selectedTags = await this.getTermsForModernTaxonomyPicker(field.TermSetId,item[field.InternalName]); + const _selectedTags = await this.getTermsForModernTaxonomyPicker(field.TermSetId, item[field.InternalName]); item[field.InternalName].forEach((element) => { selectedTags.push({ key: element.TermGuid, @@ -1730,8 +1731,7 @@ export class DynamicFormBase extends React.Component< try { return await list.items.getById(itemId).update(objects); } - catch (error) - { + catch (error) { if (error.status === 409 && retry < 3) { await timeout(100); return await this.updateListItemRetry(list, itemId, objects, retry + 1); diff --git a/src/controls/dynamicForm/IDynamicFormProps.ts b/src/controls/dynamicForm/IDynamicFormProps.ts index 05c010787..6352b1c03 100644 --- a/src/controls/dynamicForm/IDynamicFormProps.ts +++ b/src/controls/dynamicForm/IDynamicFormProps.ts @@ -2,7 +2,7 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { IItem } from '@pnp/sp/items'; import { IStyle, IStyleFunctionOrObject } from '@fluentui/react'; import React from 'react'; -import { IDynamicFieldProps,IDynamicFieldStyles } from './dynamicField'; +import { IDynamicFieldProps, IDynamicFieldStyles } from './dynamicField'; import { IValidationErrorDialogProps } from './IValidationErrorDialogProps'; export interface IDynamicFormProps { @@ -139,7 +139,7 @@ export interface IDynamicFormProps { * Note: the value of selected tab is stored in the queryString hash. * @default true */ - storeLastActiveTab?: boolean; + storeLastActiveTab?: boolean; /** * Library relative folder to create the item in. @@ -165,11 +165,16 @@ export interface IDynamicFormProps { * CSS Class name to add to the root element. */ className?: string; + + /** + * Number of items to display in the lookup fields of the form. + */ + itemsQueryCountLimit?: number; } -export type IDynamicFormStyleProps = Pick & { }; +export type IDynamicFormStyleProps = Pick & {}; export interface IDynamicFormSubComponentStyles { fieldStyles: IDynamicFieldStyles; @@ -181,15 +186,15 @@ export interface IDynamicFormStyles { sectionFormFields: IStyle; sectionFormField: IStyle; sectionLine: IStyle; - header:IStyle; - footer:IStyle; + header: IStyle; + footer: IStyle; validationErrorDialog: IStyle; buttons: IStyle; actions: IStyle; actionsRight: IStyle; action: IStyle; - /** - * sub component styles for dynamic field - */ - subComponentStyles: IDynamicFormSubComponentStyles; + /** + * sub component styles for dynamic field + */ + subComponentStyles: IDynamicFormSubComponentStyles; } \ No newline at end of file