From ff369dcaa15e236d9f1bfcc7b3c0c438502a7db8 Mon Sep 17 00:00:00 2001 From: guidozam Date: Wed, 24 Jan 2024 12:50:33 +0100 Subject: [PATCH] Added customIcons property on DynamicForm. --- .../docs/controls/DynamicForm.md | 1 + src/controls/dynamicForm/DynamicForm.tsx | 43 ++++++++-------- src/controls/dynamicForm/IDynamicFormProps.ts | 14 +++++- .../dynamicForm/dynamicField/DynamicField.tsx | 49 ++++++++++--------- .../dynamicField/IDynamicFieldProps.ts | 31 ++++++------ .../controlsTest/components/ControlsTest.tsx | 18 ++++--- 6 files changed, 89 insertions(+), 67 deletions(-) diff --git a/docs/documentation/docs/controls/DynamicForm.md b/docs/documentation/docs/controls/DynamicForm.md index ce2326d4d..6eb1b8413 100644 --- a/docs/documentation/docs/controls/DynamicForm.md +++ b/docs/documentation/docs/controls/DynamicForm.md @@ -62,6 +62,7 @@ The `DynamicForm` can be configured with the following properties: | fieldOverrides | {[columnInternalName: string] : {(fieldProperties: IDynamicFieldProps): React.ReactElement\}} | no | Key value pair for fields you want to override. Key is the internal field name, value is the function to be called for the custom element to render. | | respectEtag | boolean | no | Specifies if the form should respect the ETag of the item. Default - `true` | | validationErrorDialogProps | IValidationErrorDialogProps | no | Specifies validation error dialog properties | +| customIcons | { [ columnInternalName: string ]: string } | no | Specifies custom icons for the form. The key of this dictionary is the column internal name, the value is the Fluent UI icon name. | ## Validation Error Dialog Properties `IValidationErrorDialogProps` | Property | Type | Required | Description | diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index b4cffa172..53eb12f1c 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -148,7 +148,7 @@ export class DynamicForm extends React.Component< */ public render(): JSX.Element { const { customFormatting, fieldCollection, hiddenByFormula, infoErrorMessages, isSaving } = this.state; - + const customFormattingDisabled = this.props.useCustomFormatting === false; // Custom Formatting - Header @@ -177,7 +177,7 @@ export class DynamicForm extends React.Component< footerContent = this._customFormatter.renderCustomFormatContent(customFormatting.footer, this.getFormValuesForValidation(), true) as JSX.Element; } - // Content Type + // Content Type let contentTypeId = this.props.contentTypeId; if (this.state.contentTypeId !== undefined) contentTypeId = this.state.contentTypeId; @@ -301,7 +301,7 @@ export class DynamicForm extends React.Component< ); } - private updateFormMessages(type: MessageBarType, message: string): void { + private updateFormMessages(type: MessageBarType, message: string): void { const { infoErrorMessages } = this.state; const newMessages = infoErrorMessages.slice(); newMessages.push({ type, message }); @@ -320,7 +320,7 @@ export class DynamicForm extends React.Component< validationErrorDialogProps, returnListItemInstanceOnSubmit } = this.props; - + let contentTypeId = this.props.contentTypeId; if (this.state.contentTypeId !== undefined) contentTypeId = this.state.contentTypeId; @@ -491,7 +491,7 @@ export class DynamicForm extends React.Component< } else { objects[columnInternalName] = null; } - } + } } } @@ -615,7 +615,7 @@ export class DynamicForm extends React.Component< } console.log("Error", error); } - } + } this.setState({ isSaving: false, @@ -646,7 +646,7 @@ export class DynamicForm extends React.Component< returnListItemInstanceOnSubmit } = this.props; - + if (selectedFile !== undefined) { try { const idField = "ID"; @@ -660,14 +660,14 @@ export class DynamicForm extends React.Component< "_" ) // Replace not allowed chars in folder name : ""; // Empty string will be replaced by SPO with Folder Item ID - + const fileCreatedResult = await library.rootFolder.files.addChunked(encodeURI(itemTitle), await selectedFile.downloadFileContent()); const fields = await fileCreatedResult.file.listItemAllFields(); - + if (fields[idField]) { // Read the ID of the just created folder or Document Set const folderId = fields[idField]; - + // Set the content type ID for the target item objects[contentTypeIdField] = contentTypeId; // Update the just created folder or Document Set @@ -784,7 +784,7 @@ export class DynamicForm extends React.Component< fieldCollection: fieldCol, validationErrors }, () => { - if (validate) this.performValidation(); + if (validate) this.performValidation(); }); }; @@ -821,7 +821,7 @@ export class DynamicForm extends React.Component< * @param formulas A Record / dictionary-like object, where key is internal column name and value is an object with ValidationFormula and ValidationMessage properties * @param returnMessages Determines whether a Record of error messages is returned or an array of column names that have failed validation * @param requireValue Set to true if the formula should only be evaluated when the field has a value - * @returns + * @returns */ private evaluateFormulas = ( formulas: Record>, @@ -853,12 +853,12 @@ export class DynamicForm extends React.Component< } /** - * Used for validation. Returns a Record of field values, where key is internal column name and value is the field value. + * Used for validation. Returns a Record of field values, where key is internal column name and value is the field value. * Expands certain properties and stores many of them as primitives (strings, numbers or bools) so the expression evaluator * can process them. For example: a User column named Person will have values stored as Person, Person.email, Person.title etc. - * This is so the expression evaluator can process expressions like '=[$Person.title] == "Contoso Employee 1138"' + * This is so the expression evaluator can process expressions like '=[$Person.title] == "Contoso Employee 1138"' * @param fieldCollection Optional. Could be used to compare field values in state with previous state. - * @returns + * @returns */ private getFormValuesForValidation = (fieldCollection?: IDynamicFieldProps[]): Context => { const { fieldCollection: fieldColFromState } = this.state; @@ -909,10 +909,11 @@ export class DynamicForm extends React.Component< listItemId, disabledFields, respectETag, + customIcons, onListItemLoaded, } = this.props; let contentTypeId = this.props.contentTypeId; - + try { // Fetch form rendering information from SharePoint @@ -987,13 +988,14 @@ export class DynamicForm extends React.Component< numberFields, listId, listItemId, - disabledFields + disabledFields, + customIcons ); // Get installed languages for Currency fields let installedLanguages: IInstalledLanguageInfo[]; if (tempFields.filter(f => f.fieldType === "Currency").length > 0) { - installedLanguages = await sp.web.regionalSettings.getInstalledLanguages(); + installedLanguages = await sp.web.regionalSettings.getInstalledLanguages(); } this.setState({ @@ -1026,10 +1028,10 @@ export class DynamicForm extends React.Component< * @param listId SharePoint List ID * @param listItemId SharePoint List Item ID * @param disabledFields Fields that should be disabled due to configuration - * @returns + * @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[]): Promise { + private async buildFieldCollection(listInfo: IRenderListDataAsStreamClientFormResult, contentTypeName: string, item: any, numberFields: ISPField[], listId: string, listItemId: number, disabledFields: string[], customIcons: {[key: string]: string}): Promise { const tempFields: IDynamicFieldProps[] = []; let order: number = 0; const hiddenFields = this.props.hiddenFields !== undefined ? this.props.hiddenFields : []; @@ -1286,6 +1288,7 @@ export class DynamicForm extends React.Component< minimumValue: minValue, maximumValue: maxValue, showAsPercentage: showAsPercentage, + customIcon: customIcons ? customIcons[field.InternalName] : undefined }); // This may not be necessary now using RenderListDataAsStream diff --git a/src/controls/dynamicForm/IDynamicFormProps.ts b/src/controls/dynamicForm/IDynamicFormProps.ts index 43da7003e..218bce6da 100644 --- a/src/controls/dynamicForm/IDynamicFormProps.ts +++ b/src/controls/dynamicForm/IDynamicFormProps.ts @@ -51,7 +51,13 @@ export interface IDynamicFormProps { /** * Key value pair for fields you want to override. Key is the internal field name, value is the function to be called for the custom element to render */ - fieldOverrides?: {[columnInternalName: string] : {(fieldProperties: IDynamicFieldProps): React.ReactElement}}; + fieldOverrides?: { + [columnInternalName: string]: { + ( + fieldProperties: IDynamicFieldProps + ): React.ReactElement; + }; + }; /** * Specifies if onSubmitted event should pass PnPJS list item (IItem) as a second parameter. Default - true @@ -110,4 +116,10 @@ export interface IDynamicFormProps { * Only used when enableFileSelection is true */ supportedFileExtensions?: string[]; + + /** + * Specify a set of custom icons to be used. + * The key is the field internal name and the value is the Fluent UI icon name. + */ + customIcons?: { [columnInternalName: string]: string }; } diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.tsx b/src/controls/dynamicForm/dynamicField/DynamicField.tsx index dc4deb6f4..7ee2e9786 100644 --- a/src/controls/dynamicForm/dynamicField/DynamicField.tsx +++ b/src/controls/dynamicForm/dynamicField/DynamicField.tsx @@ -79,7 +79,8 @@ export class DynamicField extends React.Component
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
{ this.onChange(newText); }} disabled={disabled} onBlur={this.onBlur} - errorMessage={errorText || customNumberErrorMessage} - min={minimumValue} + errorMessage={errorText || customNumberErrorMessage} + min={minimumValue} max={maximumValue} /> {descriptionEl} ; @@ -294,7 +295,7 @@ export class DynamicField extends React.Component
- + {labelEl}
{ this.onChange(newText); }} disabled={disabled} onBlur={this.onBlur} - errorMessage={errorText || customNumberErrorMessage} - min={minimumValue} + errorMessage={errorText || customNumberErrorMessage} + min={minimumValue} max={maximumValue} /> {descriptionEl} ; @@ -315,7 +316,7 @@ export class DynamicField extends React.Component
- + {labelEl}
{ @@ -348,7 +349,7 @@ export class DynamicField extends React.Component
- + {labelEl}
cv.secondaryText) : (value ? value : defaultValue); return
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
- + {labelEl}
@@ -516,7 +517,7 @@ export class DynamicField extends React.Component
- + {labelEl}
@@ -687,7 +688,7 @@ export class DynamicField extends React.Component void; // eslint-disable-line @typescript-eslint/no-explicit-any /** Represents the value of the field as updated by the user. Only updated by fields when changed. */ newValue?: any; // eslint-disable-line @typescript-eslint/no-explicit-any - + /** Represents a stringified value of the field. Used in custom formatting and validation. */ stringValue: any; // eslint-disable-line @typescript-eslint/no-explicit-any @@ -50,7 +50,7 @@ export interface IDynamicFieldProps { /** If validation raises an error message, it can be stored against the field here for display by DynamicField */ validationErrorMessage?: string; - + /** Field Term Set ID, used in Taxonomy / Metadata fields */ fieldTermSetId?: string; @@ -64,16 +64,16 @@ export interface IDynamicFieldProps { lookupField?: string; // changedValue: any; // eslint-disable-line @typescript-eslint/no-explicit-any - + /** Equivalent to HiddenListInternalName, used for Taxonomy Metadata fields */ hiddenFieldName?: string; - + /** Order of the field in the form */ Order: number; - + /** Used for files / image uploads */ additionalData?: FieldChangeAdditionalData; - + // Related to various field types options?: IDropdownOption[]; isRichText?: boolean; @@ -84,4 +84,5 @@ export interface IDynamicFieldProps { maximumValue?: number; minimumValue?: number; showAsPercentage?: boolean; + customIcon?: string; } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 603c9c5bd..7e7ac2138 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -818,6 +818,9 @@ export default class ControlsTest extends React.Component @@ -2589,5 +2593,5 @@ export default class ControlsTest extends React.Component { // console.log('selected folder', folder); - // + // }