diff --git a/docs/documentation/docs/controls/DynamicForm.md b/docs/documentation/docs/controls/DynamicForm.md index 1957dfb9a..1521fa379 100644 --- a/docs/documentation/docs/controls/DynamicForm.md +++ b/docs/documentation/docs/controls/DynamicForm.md @@ -66,6 +66,7 @@ The `DynamicForm` can be configured with the following properties: | 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. | | storeLastActiveTab | boolean | no | When uploading files: Specifies if last active tab will be stored after the Upload panel has been closed. Note: the value of selected tab is stored in the queryString hash. Default - `true` | +| folderPath | string | no | Server relative or library relative folder to create the item in. This option is only available for document libraries and works only when the contentTypeId is specified and has a base type of type Document or Folder. Defaults to the root folder of the library. | ## Validation Error Dialog Properties `IValidationErrorDialogProps` | Property | Type | Required | Description | diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index 7ec17b159..06d0c84b2 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -31,6 +31,7 @@ import "@pnp/sp/lists"; import "@pnp/sp/content-types"; import "@pnp/sp/folders"; import "@pnp/sp/items"; +import { IFolder } from "@pnp/sp/folders"; import { IInstalledLanguageInfo } from "@pnp/sp/presets/all"; import { cloneDeep, isEqual } from "lodash"; import { ICustomFormatting, ICustomFormattingBodySection, ICustomFormattingNode } from "../../common/utilities/ICustomFormatting"; @@ -599,7 +600,8 @@ export class DynamicForm extends React.Component< const library = await sp.web.lists.getById(listId); const folderFileName = this.getFolderName(objects); - const newFolder = await library.rootFolder.addSubFolderUsingPath(folderFileName); + const folder = !this.props.folderPath ? library.rootFolder : await this.getFolderByPath(this.props.folderPath, library.rootFolder); + const newFolder = await folder.addSubFolderUsingPath(folderFileName); const fields = await newFolder.listItemAllFields(); if (fields[idField]) { @@ -675,8 +677,9 @@ export class DynamicForm extends React.Component< "_" ).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 fileCreatedResult = await library.rootFolder.files.addChunked(encodeURI(itemTitle), await selectedFile.downloadFileContent()); + + 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]) { @@ -1496,4 +1499,26 @@ export class DynamicForm extends React.Component< return folderNameValue.replace(/["|*|:|<|>|?|/|\\||]/g, "_").trim(); } + + /** + * Returns a pnp/sp folder object based on the folderPath and the library the folder is in. + * The folderPath can be a server relative path, but should be in the same library. + * @param folderPath The path to the folder coming from the component properties + * @param rootFolder The rootFolder object of the library + * @returns + */ + private getFolderByPath = async (folderPath: string, rootFolder: IFolder): Promise => { + const libraryFolder = await rootFolder(); + const normalizedFolderPath = decodeURIComponent(folderPath).toLowerCase().replace(/\/$/, ""); + const serverRelativeLibraryPath = libraryFolder.ServerRelativeUrl.toLowerCase().replace(/\/$/, ""); + + // In case of a server relative path in the same library, return the folder + if (`${normalizedFolderPath}/`.startsWith(`${serverRelativeLibraryPath}/`)) { + return sp.web.getFolderByServerRelativePath(normalizedFolderPath); + } + + // In other cases, expect a list-relative path and return the folder + const folder = sp.web.getFolderByServerRelativePath(`${serverRelativeLibraryPath}/${normalizedFolderPath}`); + return folder; + }; } diff --git a/src/controls/dynamicForm/IDynamicFormProps.ts b/src/controls/dynamicForm/IDynamicFormProps.ts index 0c9cdcff1..cdf048794 100644 --- a/src/controls/dynamicForm/IDynamicFormProps.ts +++ b/src/controls/dynamicForm/IDynamicFormProps.ts @@ -139,4 +139,11 @@ export interface IDynamicFormProps { * @default true */ storeLastActiveTab?: boolean; + + /** + * Library relative folder to create the item in. + * This option is only available for document libraries and works only when the contentTypeId is specified and has a base type of type Document or Folder. + * Defaults to the root folder. + */ + folderPath?: string; }