From 5db10f8dd8aa842ba62a618d3b0d328ceb800fc0 Mon Sep 17 00:00:00 2001 From: Tom German Date: Thu, 28 Sep 2023 02:01:57 +0100 Subject: [PATCH 01/43] Refactor DynamicForm to use RenderListDataAsStream --- src/common/SPEntities.ts | 4 + src/controls/dynamicForm/DynamicForm.tsx | 279 +++++++++++++++++- src/controls/dynamicForm/IDynamicFormState.ts | 3 + src/services/ISPService.ts | 202 +++++++++++++ src/services/SPService.ts | 51 +++- src/services/SPServiceMock.ts | 8 +- 6 files changed, 542 insertions(+), 5 deletions(-) diff --git a/src/common/SPEntities.ts b/src/common/SPEntities.ts index 5d5bac307..0a9f9afab 100644 --- a/src/common/SPEntities.ts +++ b/src/common/SPEntities.ts @@ -56,6 +56,10 @@ export interface ISPField { LookupDisplayUrl?: string; TypeAsString?: string; ResultType?: string; + ValidationFormula?: string; + ValidationMessage?: string; + MinimumValue?: number; + MaximumValue?: number; } /** diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index 86a5a8b70..925cc84b7 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -1,6 +1,6 @@ /* eslint-disable @microsoft/spfx/no-async-await */ import { SPHttpClient } from "@microsoft/sp-http"; -import { sp } from "@pnp/sp/presets/all"; +import { IRenderListDataAsStreamResult, sp } from "@pnp/sp/presets/all"; import * as strings from "ControlStrings"; import { DefaultButton, @@ -10,7 +10,7 @@ import { IDropdownOption } from "office-ui-fabric-react/lib/components/Dropdown" import { ProgressIndicator } from "office-ui-fabric-react/lib/ProgressIndicator"; import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack"; import * as React from "react"; -import { IUploadImageResult } from "../../common/SPEntities"; +import { ISPField, IUploadImageResult } from "../../common/SPEntities"; import SPservice from "../../services/SPService"; import { IFilePickerResult } from "../filePicker"; import { DynamicField } from "./dynamicField"; @@ -69,6 +69,8 @@ export class DynamicForm extends React.Component< // Initialize state this.state = { fieldCollection: [], + validationFormulas: {}, + clientValidationFormulas: {}, isValidationErrorDialogOpen: false, }; // Get SPService Factory @@ -81,7 +83,7 @@ export class DynamicForm extends React.Component< * Lifecycle hook when component is mounted */ public componentDidMount(): void { - this.getFieldInformations() + this.getListInformation() .then(() => { /* no-op; */ }) @@ -463,6 +465,277 @@ export class DynamicForm extends React.Component< }); }; + private getListInformation = async(): Promise => { + const { + listId, + listItemId, + disabledFields, + respectETag, + onListItemLoaded, + } = this.props; + let contentTypeId = this.props.contentTypeId; + let contentTypeName: string; + try { + const listInfo = await this._spService.getListFormRenderInfo(listId); + const additionalInfo = await this._spService.getAdditionalListFormFieldInfo(listId); + + const numberFields = additionalInfo.filter((f) => f.TypeAsString === "Number" || f.TypeAsString === "Currency"); + const validationFormulas: Record> = additionalInfo.reduce((prev, cur) => { + if (!prev[cur.InternalName] && cur.ValidationFormula) { + prev[cur.InternalName] = { + ValidationFormula: cur.ValidationFormula, + ValidationMessage: cur.ValidationMessage, + }; + } + return prev; + }, {}); + + const spList = sp.web.lists.getById(listId); + let item = null; + let etag: string | undefined = undefined; + if (listItemId !== undefined && listItemId !== null && listItemId !== 0) { + item = await spList.items.getById(listItemId).get(); + + if (onListItemLoaded) { + await onListItemLoaded(item); + } + + if (respectETag !== false) { + etag = item["odata.etag"]; + } + } + + if (contentTypeId === undefined || contentTypeId === "") { + contentTypeId = Object.keys(listInfo.ContentTypeIdToNameMap)[0]; + } + contentTypeName = listInfo.ContentTypeIdToNameMap[contentTypeId]; + + const clientValidationFormulas = listInfo.ClientForms.Edit[contentTypeName].reduce((prev, cur) => { + if (cur.ClientValidationFormula) { + prev[cur.InternalName] = { + ValidationFormula: cur.ClientValidationFormula, + ValidationMessage: cur.ClientValidationMessage, + }; + } + return prev; + }, {} as Record>); + + const tempFields: IDynamicFieldProps[] = []; + let order: number = 0; + const hiddenFields = + this.props.hiddenFields !== undefined ? this.props.hiddenFields : []; + let defaultDayOfWeek: number = 0; + + for (let i = 0, len = listInfo.ClientForms.Edit[contentTypeName].length; i < len; i++) { + const field = listInfo.ClientForms.Edit[contentTypeName][i]; + + // Handle only fields that are not marked as hidden + if (hiddenFields.indexOf(field.InternalName) < 0) { + order++; + let hiddenName = ""; + let termSetId = ""; + let anchorId = ""; + let lookupListId = ""; + let lookupField = ""; + const choices: IDropdownOption[] = []; + let defaultValue = null; + const selectedTags: any = []; // eslint-disable-line @typescript-eslint/no-explicit-any + let richText = false; + let dateFormat: DateFormat | undefined; + let principalType = ""; + let minValue: number | undefined; + let maxValue: number | undefined; + let showAsPercentage: boolean | undefined; + if (item !== null) { + defaultValue = item[field.InternalName]; + } else { + defaultValue = field.DefaultValue; + } + + if (field.FieldType === "Choice" || field.FieldType === "MultiChoice") { + field.Choices.forEach((element) => { + choices.push({ key: element, text: element }); + }); + } else if (field.FieldType === "Note") { + richText = field.RichText; + } else if (field.FieldType === "Number" || field.FieldType === "Currency") { + const numberField = numberFields.find(f => f.InternalName === field.InternalName); + if (numberField) { + minValue = numberField.MinimumValue; + maxValue = numberField.MaximumValue; + } + showAsPercentage = field.ShowAsPercentage; + } else if (field.FieldType === "Lookup" || field.FieldType === "MultiLookup") { + lookupListId = field.LookupListId; + lookupField = field.LookupFieldName; + if (item !== null) { + defaultValue = await this._spService.getLookupValues( + listId, + listItemId, + field.InternalName, + lookupField, + this.webURL + ); + } else { + defaultValue = []; + } + } else if (field.FieldType === "TaxonomyFieldTypeMulti") { + hiddenName = field.HiddenListInternalName; + termSetId = field.TermSetId; + anchorId = field.AnchorId; + if (item !== null) { + item[field.InternalName].forEach((element) => { + selectedTags.push({ + key: element.TermGuid, + name: element.Label, + }); + }); + + defaultValue = selectedTags; + } else { + if (defaultValue !== null && defaultValue !== "") { + defaultValue.split(/#|;/).forEach((element) => { + if (element.indexOf("|") !== -1) + selectedTags.push({ + key: element.split("|")[1], + name: element.split("|")[0], + }); + }); + + defaultValue = selectedTags; + } + } + if (defaultValue === "") defaultValue = null; + } else if (field.FieldType === "TaxonomyFieldType") { + termSetId = field.TermSetId; + anchorId = field.AnchorId; + if (item !== null) { + const response = + await this._spService.getSingleManagedMetadataLabel( + listId, + listItemId, + field.InternalName + ); + if (response) { + selectedTags.push({ + key: response.TermID, + name: response.Label, + }); + defaultValue = selectedTags; + } + } else { + if (defaultValue !== "") { + selectedTags.push({ + key: defaultValue.split("|")[1], + name: defaultValue.split("|")[0].split("#")[1], + }); + defaultValue = selectedTags; + } + } + if (defaultValue === "") defaultValue = null; + } else if (field.FieldType === "DateTime") { + if (item !== null && item[field.InternalName]) { + defaultValue = new Date(item[field.InternalName]); + } else if (defaultValue === "[today]") { + defaultValue = new Date(); + } + + dateFormat = field.DateFormat || "DateOnly"; + defaultDayOfWeek = (await this._spService.getRegionalWebSettings()).FirstDayOfWeek; + } else if (field.FieldType === "UserMulti") { + if (item !== null) { + defaultValue = this._spService.getUsersUPNFromFieldValue( + listId, + listItemId, + field.InternalName, + this.webURL + ); + } else { + defaultValue = []; + } + principalType = field.PrincipalAccountType; + } else if (field.FieldType === "Thumbnail") { + if (defaultValue) { + defaultValue = JSON.parse(defaultValue).serverRelativeUrl; + } + } else if (field.FieldType === "User") { + if (item !== null) { + const userEmails: string[] = []; + userEmails.push( + (await this._spService.getUserUPNFromFieldValue( + listId, + listItemId, + field.InternalName, + this.webURL + )) + "" + ); + defaultValue = userEmails; + } else { + defaultValue = []; + } + principalType = field.PrincipalAccountType; + } else if (field.FieldType === "Location") { + defaultValue = JSON.parse(defaultValue); + } else if (field.FieldType === "Boolean") { + defaultValue = Boolean(Number(defaultValue)); + } + + tempFields.push({ + newValue: null, + fieldTermSetId: termSetId, + fieldAnchorId: anchorId, + options: choices, + lookupListID: lookupListId, + lookupField: lookupField, + changedValue: defaultValue, + fieldType: field.FieldType, + fieldTitle: field.Title, + fieldDefaultValue: defaultValue, + context: this.props.context, + disabled: + this.props.disabled || + (disabledFields && + disabledFields.indexOf(field.InternalName) > -1), + listId: this.props.listId, + columnInternalName: field.InternalName, + label: field.Title, + onChanged: this.onChange, + required: field.Required, + hiddenFieldName: hiddenName, + Order: order, + isRichText: richText, + dateFormat: dateFormat, + firstDayOfWeek: defaultDayOfWeek, + listItemId: listItemId, + principalType: principalType, + description: field.Description, + minimumValue: minValue, + maximumValue: maxValue, + showAsPercentage: showAsPercentage, + }); + + // This may not be necessary now using RenderListDataAsStream + tempFields.sort((a, b) => a.Order - b.Order); + } + } + + // Do formatting and validation parsing here + console.log('Validation Formulas', validationFormulas); + console.log('Client Side Validation Formulas', clientValidationFormulas); + + this.setState({ + clientValidationFormulas, + fieldCollection: tempFields, + validationFormulas, + etag + }); + + } catch (error) { + console.log(`Error get field informations`, error); + return null; + } + } + //getting all the fields information as part of get ready process private getFieldInformations = async (): Promise => { const { diff --git a/src/controls/dynamicForm/IDynamicFormState.ts b/src/controls/dynamicForm/IDynamicFormState.ts index b182c3caa..a8645e991 100644 --- a/src/controls/dynamicForm/IDynamicFormState.ts +++ b/src/controls/dynamicForm/IDynamicFormState.ts @@ -1,7 +1,10 @@ +import { ISPField } from '../../common/SPEntities'; import { IDynamicFieldProps } from './dynamicField/IDynamicFieldProps'; export interface IDynamicFormState { fieldCollection: IDynamicFieldProps[]; + validationFormulas: Record>; + clientValidationFormulas: Record>; isSaving?: boolean; etag?: string; isValidationErrorDialogOpen: boolean; diff --git a/src/services/ISPService.ts b/src/services/ISPService.ts index 8a86e11d7..b40febf75 100644 --- a/src/services/ISPService.ts +++ b/src/services/ISPService.ts @@ -44,6 +44,197 @@ export interface IContentTypesOptions { group?: string; } +/** + * Interfaces and Types for RenderListDataAsStream + * when RenderOptions = 64 + * Not currently provided by @pnp/sp + */ + +// These interfaces and types are not represented in the @pnp/sp package + +export enum IMEMode { + Auto = 0, + Inactive = 1, + Active = 2, + Disabled = 3 +} +export type ClientFormFieldInfoFieldType = "Attachments" | "Text" | "Number" | "Boolean" | "Choice" | "MultiChoice" | "User" | "UserMulti" | "Note" | "DateTime" | "URL" | "Lookup" | "MultiLookup" | "Hyperlink" | "Thumbnail" | "Currency" | "Location" | "TaxonomyFieldType" | "TaxonomyFieldTypeMulti"; +export type ClientFormFieldInfoType = "Attachments" | "Text" | "Number" | "Boolean" | "Choice" | "User" | "Note" | "DateTime" | "URL" | "Lookup" | "URL" | "Thumbnail" | "Currency" | "Location"; +export interface IClientFormBaseInfo { + Id: string; + Title: string; + InternalName: string; + Hidden: boolean; + IMEMode: IMEMode; + Name: string; + Required: boolean; + Direction: string; + FieldType: ClientFormFieldInfoFieldType; + Description: string; + ReadOnlyField: boolean; + IsAutoHyperLink: boolean; + Type: ClientFormFieldInfoType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + DefaultValue: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + DefaultValueTyped: any; + ClientValidationFormula: string; + ClientValidationMessage: string; + CustomFormatter: string; +} +export interface IClientFormImageFieldInfo extends IClientFormBaseInfo { + FieldType: "Thumbnail"; + Type: "Thumbnail"; +} +export interface IClientFormHyperlinkFieldInfo extends IClientFormBaseInfo { + FieldType: "Hyperlink"; + Type: "URL"; +} +export interface IClientFormLocationFieldInfo extends IClientFormBaseInfo { + FieldType: "Location"; + Type: "Location"; +} +export interface IClientFormBooleanFieldInfo extends IClientFormBaseInfo { + FieldType: "Boolean"; + Type: "Boolean"; +} +export interface IClientFormTextFieldInfo extends IClientFormBaseInfo { + FieldType: "Text" | "Note"; + Type: "Text" | "Note"; + MaxLength: number; + RichText: boolean; + AppendOnly: boolean; + RichTextMode: number; + NumberOfLines: number; + AllowHyperlink: false; + RestrictedMode: boolean; +} +export interface IClientFormNumericFieldBaseInfo extends IClientFormBaseInfo { + FieldType: "Number" | "Currency"; + Type: "Number" | "Currency"; + ShowAsPercentage: boolean; + CommaSeparator: boolean; + Unit: string; +} +export interface IClientFormNumberFieldInfo extends IClientFormNumericFieldBaseInfo { + FieldType: "Number"; + Type: "Number"; +} +export interface IClientFormCurrencyFieldInfo extends IClientFormNumericFieldBaseInfo { + FieldType: "Currency"; + Type: "Currency"; +} +export interface IClientFormChoiceFieldInfo extends IClientFormBaseInfo { + FieldType: "Choice" | "MultiChoice"; + Type: "Choice"; + FillInChoice: boolean; + MultiChoices: string[]; + Choices: string[]; + FormatType: number; +} +export interface IClientFormDateFieldInfo extends IClientFormBaseInfo { + FieldType: "DateTime"; + Type: "DateTime"; + DisplayFormat: number; + TimeZoneDifference: string; + CalendarType: 1, + ShowWeekNumber: boolean; + TimeSeparator: string; + FirstDayOfWeek: number; + FirstWeekOfYear: number; + HijriAdjustment: number; + WorkWeek: string; + LocaleId: string; + LanguageId: string; + MinJDay: number; + MaxJDay: number; + HoursMode24: boolean; + HoursOptions: string[]; + DefaultValueFormatted: string; + DateFormat: "DateTime" | "DateOnly"; + TimeFormat: string; +} +export interface IClientFormBaseLookupFieldInfo extends IClientFormBaseInfo { + FieldType: "User" | "UserMulti" | "Lookup" | "MultiLookup" | "TaxonomyFieldType" | "TaxonomyFieldTypeMulti"; + Type: "User" | "Lookup"; + DependentLookup: boolean; + AllowMultipleValues: boolean; +} +export interface IClientFormTaxonomyFieldInfo extends IClientFormBaseLookupFieldInfo { + FieldType: "TaxonomyFieldType" | "TaxonomyFieldTypeMulti"; + Type: "Lookup"; + Throttled: boolean; + LookupListId: string; + ChoiceCount: number; + Choices: string[]; + SspId: string; + TermSetId: string; + AnchorId: string; + AllowFillIn: boolean; + WidthCss: string; + LcId: number; + IsSpanTermSets: boolean; + IsSpanTermStores: boolean; + IsAddTerms: boolean; + IsDocTagsEnabled: boolean; + IsEnhancedImageTagsEnabled: boolean; + IsUseCommaAsDelimiter: boolean; + Disable: boolean; + WebServiceUrl: string; + HiddenListInternalName: string; +} +export interface IClientFormUserFieldInfo extends IClientFormBaseLookupFieldInfo { + FieldType: "User" | "UserMulti"; + Type: "User"; + Presence: boolean; + WithPicture: boolean; + DefaultRender: boolean; + WithPictureDetail: boolean; + ListFormUrl: string; + UserDisplayUrl: string; + EntitySeparator: string; + PictureOnly: boolean; + PictureSize: string; + UserInfoListId: string; + SharePointGroupID: number; + PrincipalAccountType: string; + SearchPrincipalSource: number; + ResolvePrincipalSource: number; + UserNoQueryPermission: boolean; + UserDisplayOptions: string; +} +export interface IClientFormLookupFieldInfo extends IClientFormBaseLookupFieldInfo { + FieldType: "Lookup" | "MultiLookup"; + Type: "Lookup"; + BaseDisplayFormUrl: string; + Throttled: boolean; + LookupListId: string; + LookupListUrl: string; + LookupFieldName: string; +} +export type ClientFormFieldInfo = IClientFormTextFieldInfo | IClientFormNumberFieldInfo | IClientFormChoiceFieldInfo | IClientFormDateFieldInfo | IClientFormLookupFieldInfo | IClientFormUserFieldInfo | IClientFormTaxonomyFieldInfo | IClientFormImageFieldInfo | IClientFormHyperlinkFieldInfo | IClientFormLocationFieldInfo | IClientFormCurrencyFieldInfo | IClientFormBooleanFieldInfo; +export interface IClientFormInfoByContentType { + [contentType: string]: ClientFormFieldInfo[]; +} +export interface IClientFormInfoByDisplayMode { + [displayMode: string]: IClientFormInfoByContentType; +} +export interface IClientFormRenderModes { + [formName: string]: { + RenderType: number; + } +} +export interface IClientFormRenderModeByContentType { + [contentType: string]: IClientFormRenderModes; +} +export interface IRenderListDataAsStreamClientFormResult { + ClientForms: IClientFormInfoByDisplayMode; + ContentTypeIdToNameMap: Record; + ClientFormCustomFormatter: Record; + EnableAttachments: "true" | "false"; + FormRenderModes: IClientFormRenderModeByContentType; +} + export interface ISPService { /** * Get the lists from SharePoint @@ -63,4 +254,15 @@ export interface ISPService { * @param options Options used to order and filter during the API query. */ getContentTypes(options?: IContentTypesOptions): Promise; + + /** + * Get form rendering information for a SharePoint list. + */ + getListFormRenderInfo(listId: string): Promise; + + /** + * Get additional form rendering and validation information for a SharePoint list. + * Captures information not returned by RenderListDataAsStream with RenderOptions = 64 + */ + getAdditionalListFormFieldInfo(listId: string, webUrl?: string): Promise; } diff --git a/src/services/SPService.ts b/src/services/SPService.ts index f78ee82c4..4928f786b 100644 --- a/src/services/SPService.ts +++ b/src/services/SPService.ts @@ -4,7 +4,7 @@ import filter from 'lodash/filter'; import find from 'lodash/find'; import { ISPContentType, ISPField, ISPList, ISPLists, IUploadImageResult } from "../common/SPEntities"; import { SPHelper, urlCombine } from "../common/utilities"; -import { IContentTypesOptions, IFieldsOptions, ILibsOptions, ISPService, LibsOrderBy } from "./ISPService"; +import { IContentTypesOptions, IFieldsOptions, ILibsOptions, IRenderListDataAsStreamClientFormResult, ISPService, LibsOrderBy } from "./ISPService"; interface ICachedListItems { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -726,6 +726,55 @@ export default class SPService implements ISPService { return result; } + /** + * Get form rendering information for a SharePoint list. + */ + async getListFormRenderInfo(listId: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + const apiRequestPath = `/_api/web/lists(guid'${listId}')/RenderListDataAsStream`; + + const apiUrl = urlCombine(webAbsoluteUrl, apiRequestPath, false); + const response = await this._context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, { + body: JSON.stringify({ + "parameters": { + "RenderOptions": 64, + "ViewXml":"", + "AddRequiredFields":true + } + }) + }); + + if (response.ok) { + const result = await response.json() as IRenderListDataAsStreamClientFormResult; + return result; + } + return null; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + + /** + * Get additional form rendering and validation information for a SharePoint list. + * Captures information not returned by RenderListDataAsStream with RenderOptions = 64 + */ + async getAdditionalListFormFieldInfo(listId: string, webUrl?: string): Promise { + try { + const webAbsoluteUrl = !webUrl ? this._context.pageContext.web.absoluteUrl : webUrl; + const apiRequestPath = `/_api/web/lists(guid'${listId}')/Fields?$filter=TypeAsString eq 'Number' or TypeAsString eq 'Currency' or ValidationFormula ne null`; + + const apiUrl = urlCombine(webAbsoluteUrl, apiRequestPath, false); + const response = await this._context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1); + const result = await response.json(); + return result.value; + } catch (error) { + console.dir(error); + return Promise.reject(error); + } + } + private _filterListItemsFieldValuesAsText(items: any[], internalColumnName: string, filterText: string | undefined, substringSearch: boolean): any[] { // eslint-disable-line @typescript-eslint/no-explicit-any const lowercasedFilterText = filterText.toLowerCase(); diff --git a/src/services/SPServiceMock.ts b/src/services/SPServiceMock.ts index f7569a264..03f6c9e8a 100644 --- a/src/services/SPServiceMock.ts +++ b/src/services/SPServiceMock.ts @@ -1,4 +1,4 @@ -import { ISPService, ILibsOptions, IFieldsOptions, IContentTypesOptions } from "./ISPService"; +import { ISPService, ILibsOptions, IFieldsOptions, IContentTypesOptions, IRenderListDataAsStreamClientFormResult } from "./ISPService"; import { ISPContentType, ISPField, ISPLists } from "../common/SPEntities"; export default class SPServiceMock implements ISPService { @@ -9,6 +9,12 @@ export default class SPServiceMock implements ISPService { this._includeDelay = includeDelay; this._delayTimeout = delayTimeout || 500; } + getListFormRenderInfo(listId: string): Promise { + throw new Error("Method not implemented."); + } + getAdditionalListFormFieldInfo(listId: string, webUrl?: string): Promise { + throw new Error("Method not implemented."); + } public getFields(options?: IFieldsOptions): Promise { throw new Error("Method not implemented."); } From d42db426faf4a5b4901623dc4a2c0212bee7c7a5 Mon Sep 17 00:00:00 2001 From: Tom German Date: Sat, 30 Sep 2023 00:38:34 +0100 Subject: [PATCH 02/43] Amended ControlsTest config propertyPane --- .../ControlsTestWebPart.manifest.json | 87 ++++- .../controlsTest/ControlsTestWebPart.ts | 47 ++- .../controlsTest/IControlsTestWebPartProps.ts | 25 +- .../controlsTest/components/ControlsTest.tsx | 296 +++--------------- .../components/IControlsTestProps.ts | 4 + .../components/IControlsTestState.ts | 48 --- src/webparts/controlsTest/loc/en-us.js | 4 +- src/webparts/controlsTest/loc/fr-fr.js | 4 +- src/webparts/controlsTest/loc/mystrings.d.ts | 2 + .../propertyPane/IListPickerState.ts | 7 + .../PropertyPaneControlToggles.ts | 82 +++++ .../propertyPane/PropertyPaneListPicker.ts | 76 +++++ .../propertyPane/controls/ControlToggles.tsx | 76 +++++ .../propertyPane/controls/ListPicker.tsx | 119 +++++++ 14 files changed, 558 insertions(+), 319 deletions(-) create mode 100644 src/webparts/controlsTest/propertyPane/IListPickerState.ts create mode 100644 src/webparts/controlsTest/propertyPane/PropertyPaneControlToggles.ts create mode 100644 src/webparts/controlsTest/propertyPane/PropertyPaneListPicker.ts create mode 100644 src/webparts/controlsTest/propertyPane/controls/ControlToggles.tsx create mode 100644 src/webparts/controlsTest/propertyPane/controls/ListPicker.tsx diff --git a/src/webparts/controlsTest/ControlsTestWebPart.manifest.json b/src/webparts/controlsTest/ControlsTestWebPart.manifest.json index 8460d9d62..ca0a6996f 100644 --- a/src/webparts/controlsTest/ControlsTestWebPart.manifest.json +++ b/src/webparts/controlsTest/ControlsTestWebPart.manifest.json @@ -7,21 +7,74 @@ "manifestVersion": 2, "requiresCustomScript": false, "supportsThemeVariants": true, - "supportedHosts": ["SharePointWebPart", "TeamsTab"], - "preconfiguredEntries": [{ - "groupId": "45165954-80f9-44c1-9967-cd38ae92a33b", - "group": { - "default": "Under Development" - }, - "title": { - "default": "ControlsTest" - }, - "description": { - "default": "ControlsTest description" - }, - "officeFabricIconFontName": "Share", - "properties": { - "description": "ControlsTest" + "supportedHosts": [ + "SharePointWebPart", + "TeamsTab" + ], + "preconfiguredEntries": [ + { + "groupId": "45165954-80f9-44c1-9967-cd38ae92a33b", + "group": { + "default": "Under Development" + }, + "title": { + "default": "Controls Test" + }, + "description": { + "default": "Web Part to test controls" + }, + "officeFabricIconFontName": "Share", + "properties": { + "description": "Controls Test", + "controlVisibility": { + "all": false, + "accessibleAccordion": false, + "adaptiveCardDesignerHost": false, + "adaptiveCardHost": false, + "animatedDialog": false, + "Carousel": false, + "ChartControl": false, + "ComboBoxListItemPicker": false, + "Dashboard": false, + "DateTimePicker": false, + "DragDropFiles": false, + "DynamicForm": false, + "EnhancedThemeProvider": false, + "FieldCollectionData": false, + "FieldPicker": false, + "FilePicker": false, + "FileTypeIcon": false, + "FolderExplorer": false, + "FolderPicker": false, + "GridLayout": false, + "IconPicker": false, + "IFrameDialog": false, + "IFramePanel": false, + "ListPicker": false, + "ListView": false, + "LocationPicker": false, + "Map": false, + "ModernAudio": false, + "ModernTaxonomyPicker": false, + "Pagination": false, + "PeoplePicker": false, + "Placeholder": false, + "Progress": false, + "RichText": false, + "SecurityTrimmedControl": false, + "SiteBreadcrumb": false, + "SitePicker": false, + "TaxonomyPicker": false, + "TaxonomyTree": false, + "Teams": false, + "TestControl": false, + "Toolbar": false, + "TreeView": false, + "UploadFiles": false, + "VariantThemeProvider": false, + "WebPartTitle": false + } + } } - }] -} + ] +} \ No newline at end of file diff --git a/src/webparts/controlsTest/ControlsTestWebPart.ts b/src/webparts/controlsTest/ControlsTestWebPart.ts index dc6ad1d85..89a5608f0 100644 --- a/src/webparts/controlsTest/ControlsTestWebPart.ts +++ b/src/webparts/controlsTest/ControlsTestWebPart.ts @@ -17,7 +17,9 @@ import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; import ControlsTest from './components/ControlsTest'; import { IControlsTestProps } from './components/IControlsTestProps'; -import { IControlsTestWebPartProps } from './IControlsTestWebPartProps'; +import { ControlVisibility, IControlsTestWebPartProps } from './IControlsTestWebPartProps'; +import { PropertyPaneListPicker } from './propertyPane/PropertyPaneListPicker'; +import { PropertyPaneControlToggles } from './propertyPane/PropertyPaneControlToggles'; /** * Web part to test the React controls @@ -86,13 +88,21 @@ export default class ControlsTestWebPart extends BaseClientSideWebPart { + this.context.propertyPane.open(); + }, updateProperty: (value: string) => { this.properties.title = value; + if (this.context.propertyPane.isPropertyPaneOpen()) { + this.context.propertyPane.refresh(); + } }, - totalPages: this.properties.totalPages + totalPages: this.properties.paginationTotalPages } ); @@ -117,13 +127,38 @@ export default class ControlsTestWebPart extends BaseClientSideWebPart { + this.properties.dynamicFormListId = newValue; + this.render(); + this.context.propertyPane.refresh(); + } + }) + ] + }, + { + groupName: strings.ControlsGroupName, + groupFields: [ + new PropertyPaneControlToggles('controlVisibility', { + controlVisibility: this.properties.controlVisibility, + label: 'Toggle controls', + onPropertyChange: (newValue: ControlVisibility) => { + this.properties.controlVisibility = newValue; + this.render(); + this.context.propertyPane.refresh(); + } }) ] } diff --git a/src/webparts/controlsTest/IControlsTestWebPartProps.ts b/src/webparts/controlsTest/IControlsTestWebPartProps.ts index 392e1cf23..00320a723 100644 --- a/src/webparts/controlsTest/IControlsTestWebPartProps.ts +++ b/src/webparts/controlsTest/IControlsTestWebPartProps.ts @@ -1,5 +1,28 @@ +export type ValidControls = "all" | + "accessibleAccordion" | "adaptiveCardDesignerHost" | "adaptiveCardHost" | + "animatedDialog" | "Carousel" | "ChartControl" | + "ComboBoxListItemPicker" | "Dashboard" | "DateTimePicker" | + "DragDropFiles" | "DynamicForm" | "EnhancedThemeProvider" | + "FieldCollectionData" | "FieldPicker" | "FilePicker" | + "FileTypeIcon" | "FolderExplorer" | "FolderPicker" | + "GridLayout" | "IconPicker" | "IFrameDialog" | + "IFramePanel" | "ListPicker" | "ListView" | + "LocationPicker" | "Map" | "ModernAudio" | + "ModernTaxonomyPicker" | "Pagination" | "PeoplePicker" | + "Placeholder" | "Progress" | "RichText" | + "SecurityTrimmedControl" | "SiteBreadcrumb" | "SitePicker" | + "TaxonomyPicker" | "TaxonomyTree" | "Teams" | + "TestControl" | "Toolbar" | "TreeView" | + "UploadFiles" | "VariantThemeProvider" | "WebPartTitle"; + +export type ControlVisibility = { + [K in ValidControls]: boolean; +} + export interface IControlsTestWebPartProps { title: string; description: string; - totalPages: number; + paginationTotalPages: number; + dynamicFormListId: string; + controlVisibility: ControlVisibility } diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 204c563e3..5f0250716 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -509,54 +509,7 @@ export default class ControlsTest extends React.Component { - const { - toggleAll, - showAllFilters, - isWebPartTitleDivVisible, - isDynamicFormDivVisible, - isTeamsDivVisible, - isAccessibleAccordionDivVisible, - isTaxonomyPickerDivVisible, - isDateTimePickerDivVisible, - isRichTextDivVisible, - isPlaceholderDivVisible, - isPeoplePickerDivVisible, - isDragDropFilesDivVisible, - isListViewDivVisible, - isChartControlDivVisible, - isMapDivVisible, - isModernAudioDivVisible, - isFileTypeIconDivVisible, - isSecurityTrimmedControlDivVisible, - isSitePickerDivVisible, - isListPickerDivVisible, - isFieldPickerDivVisible, - isIconPickerDivVisible, - isComboBoxListItemPickerDivVisible, - isIFrameDialogDivVisible, - isIFramePanelDivVisible, - isFolderPickerDivVisible, - isCarouselDivVisible, - isSiteBreadcrumbDivVisible, - isFilePickerDivVisible, - isProgressDivVisible, - isGridLayoutDivVisible, - isFolderExplorerDivVisible, - isTreeViewDivVisible, - isPaginationDivVisible, - isFieldCollectionDataDivVisible, - isDashboardDivVisible, - isToolbarDivVisible, - isAnimatedDialogDivVisible, - isLocationPickerDivVisible, - isModernTaxonomyPickerDivVisible, - isAdaptiveCardHostDivVisible, - isVariantThemeProviderDivVisible, - isEnhancedThemeProviderDivVisible, - isAdaptiveCardDesignerHostDivVisible, - isTaxonomyTreeDivVisible, - isTestControlDivVisible, - isUploadFilesDivVisible, - } = this.state; + const { controlVisibility } = this.props; // Size options for the icon size dropdown const sizeOptions: IDropdownOption[] = [ @@ -1028,58 +933,10 @@ export default class ControlsTest extends React.Component

Choose which controls to display

- - { this.setState({ showAllFilters: checked })}} className={styles.toggleFilter} /> -
- -