Skip to content

2033 [DynamicForm] Add itemsQueryCountLimit for Lookup fields #2034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/documentation/docs/controls/DynamicForm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
170 changes: 85 additions & 85 deletions src/controls/dynamicForm/DynamicForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class DynamicFormBase extends React.Component<
// Custom Formatting - Header
let headerContent: JSX.Element;
if (!customFormattingDisabled && customFormatting?.header) {
headerContent = <div className={styles.header}>
headerContent = <div className={styles.header}>
{this._customFormatter.renderCustomFormatContent(customFormatting.header, this.getFormValuesForValidation(), true)}
</div>
}
Expand All @@ -196,8 +196,8 @@ export class DynamicFormBase extends React.Component<
let footerContent: JSX.Element;
if (!customFormattingDisabled && customFormatting?.footer) {
footerContent = <div className={styles.footer}>
{this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true)}
</div>
{this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true)}
</div>
}

// Content Type
Expand Down Expand Up @@ -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) => (
<>
<h2 className={styles.sectionTitle}>{section.displayname}</h2>
<div className={styles.sectionFormFields}>
{section.fields
.filter(f => fieldCollection.find(fc => fc.label === f))
.map((f, i) => (
<div key={f} className={styles.sectionFormField}>
{this.renderField(fieldCollection.find(fc => fc.label === f) as IDynamicFieldProps)}
</div>
))}
</div>
{i < bodySections.length - 1 && <hr className={styles.sectionLine} aria-hidden={true} />}
</>
))}
<>
<h2 className={styles.sectionTitle}>{section.displayname}</h2>
<div className={styles.sectionFormFields}>
{section.fields
.filter(f => fieldCollection.find(fc => fc.label === f))
.map((f, i) => (
<div key={f} className={styles.sectionFormField}>
{this.renderField(fieldCollection.find(fc => fc.label === f) as IDynamicFieldProps)}
</div>
))}
</div>
{i < bodySections.length - 1 && <hr className={styles.sectionLine} aria-hidden={true} />}
</>
))}
{(bodySections.length === 0 || customFormattingDisabled) && fieldCollection.map((f, i) => this.renderField(f))}
{footerContent}
{!this.props.disabled && (
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -342,6 +342,7 @@ export class DynamicFormBase extends React.Component<
{...field}
disabled={field.disabled || isSaving}
validationErrorMessage={validationErrorMessage}
itemsQueryCountLimit={this.props.itemsQueryCountLimit}
/>
);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
}

/**
Expand All @@ -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 = {};

Expand All @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<IDynamicFieldProps[]> {
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<IDynamicFieldProps[]> {
const { useModernTaxonomyPicker } = this.props;
const tempFields: IDynamicFieldProps[] = [];
let order: number = 0;
const hiddenFields = this.props.hiddenFields !== undefined ? this.props.hiddenFields : [];
Expand All @@ -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 = "";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 14 additions & 9 deletions src/controls/dynamicForm/IDynamicFormProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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<IDynamicFormProps, 'className'> & { };
export type IDynamicFormStyleProps = Pick<IDynamicFormProps, 'className'> & {};

export interface IDynamicFormSubComponentStyles {
fieldStyles: IDynamicFieldStyles;
Expand All @@ -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;
}