Skip to content
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

Added file handling in DynamicForm #1625

Merged
merged 20 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 13 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
5 changes: 5 additions & 0 deletions src/controls/dynamicForm/DynamicForm.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,8 @@
}
}
}

.selectedFileContainer {
display: flex;
margin: 10px 0px;
}
187 changes: 172 additions & 15 deletions src/controls/dynamicForm/DynamicForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IStackTokens, Stack } from "office-ui-fabric-react/lib/Stack";
import * as React from "react";
import { IUploadImageResult } from "../../common/SPEntities";
import SPservice from "../../services/SPService";
import { IFilePickerResult } from "../filePicker";
import { FilePicker, IFilePickerResult } from "../filePicker";
import { DynamicField } from "./dynamicField";
import {
DateFormat,
Expand All @@ -27,6 +27,7 @@ import {
DialogFooter,
DialogType,
} from "office-ui-fabric-react/lib/Dialog";
import { Icon } from 'office-ui-fabric-react';

import "@pnp/sp/lists";
import "@pnp/sp/content-types";
Expand Down Expand Up @@ -109,6 +110,11 @@ export class DynamicForm extends React.Component<
</div>
) : (
<div>
{this.props.enableFileSelection === true &&
this.props.listItemId === undefined &&
this.props.contentTypeId !== undefined &&
this.props.contentTypeId.startsWith("0x0101") &&
this.renderFileSelectionControl()}
{fieldCollection.map((v, i) => {
if (
fieldOverrides &&
Expand Down Expand Up @@ -182,6 +188,9 @@ export class DynamicForm extends React.Component<
onSubmitted,
onBeforeSubmit,
onSubmitError,
enableFileSelection,
validationErrorDialogProps,
returnListItemInstanceOnSubmit
} = this.props;

try {
Expand All @@ -208,19 +217,30 @@ export class DynamicForm extends React.Component<
shouldBeReturnBack = true;
} else if(val.fieldType === "Number"){
shouldBeReturnBack = this.validateNumberOnSubmit(val);
}
}
} else if(val.fieldType === "Number"){
if(val.newValue === null){
val.newValue = val.fieldDefaultValue;
}
shouldBeReturnBack = this.validateNumberOnSubmit(val);
}
});

if (shouldBeReturnBack) {
this.setState({
fieldCollection: fields,
isValidationErrorDialogOpen:
this.props.validationErrorDialogProps
validationErrorDialogProps
?.showDialogOnValidationError === true,
});
return;
}

if (enableFileSelection === true && this.state.selectedFile === undefined) {
michaelmaillot marked this conversation as resolved.
Show resolved Hide resolved
this.setState({
missingSelectedFile: true,
isValidationErrorDialogOpen:
validationErrorDialogProps
?.showDialogOnValidationError === true,
});
return;
Expand Down Expand Up @@ -320,7 +340,7 @@ export class DynamicForm extends React.Component<
if (onSubmitted) {
onSubmitted(
iur.data,
this.props.returnListItemInstanceOnSubmit !== false
returnListItemInstanceOnSubmit !== false
? iur.item
: undefined
);
Expand All @@ -339,25 +359,30 @@ export class DynamicForm extends React.Component<
!contentTypeId.startsWith("0x0120")||
contentTypeId.startsWith("0x01")
) {
// We are adding a new list item
try {
const contentTypeIdField = "ContentTypeId";
//check if item contenttype is passed, then update the object with content type id, else, pass the object
if (contentTypeId === undefined || enableFileSelection === true) {
await this.addFileToLibrary(objects);
}
else {
// We are adding a new list item
try {
const contentTypeIdField = "ContentTypeId";
//check if item contenttype is passed, then update the object with content type id, else, pass the object
contentTypeId !== undefined && contentTypeId.startsWith("0x01") ? objects[contentTypeIdField] = contentTypeId : objects;
const iar = await sp.web.lists.getById(listId).items.add(objects);
if (onSubmitted) {
onSubmitted(
iar.data,
this.props.returnListItemInstanceOnSubmit !== false
returnListItemInstanceOnSubmit !== false
? iar.item
: undefined
);
}
} catch (error) {
if (onSubmitError) {
onSubmitError(objects, error);
} catch (error) {
if (onSubmitError) {
onSubmitError(objects, error);
}
console.log("Error", error);
}
console.log("Error", error);
}
}
else if (contentTypeId.startsWith("0x0120")) {
Expand Down Expand Up @@ -406,6 +431,9 @@ export class DynamicForm extends React.Component<
}
console.log("Error", error);
}
} else if (contentTypeId.startsWith("0x01") && enableFileSelection === true) {
// We are adding a folder or a Document Set
await this.addFileToLibrary(objects);
}

this.setState({
Expand All @@ -420,6 +448,64 @@ export class DynamicForm extends React.Component<
}
};

private addFileToLibrary = async (objects: {}): Promise<void> => {
const {
selectedFile
} = this.state;

const {
listId,
contentTypeId,
onSubmitted,
onSubmitError,
returnListItemInstanceOnSubmit
} = this.props;

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,
"_"
) // 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
const iur = await library.items.getById(folderId).update(objects);
if (onSubmitted) {
onSubmitted(
iur.data,
returnListItemInstanceOnSubmit !== false
? iur.item
: undefined
);
}
} else {
throw new Error(
"Unable to read the ID of the just created folder or Document Set"
);
}
} catch (error) {
if (onSubmitError) {
onSubmitError(objects, error);
}
console.log("Error", error);
}
}

// trigger when the user change any value in the form
private onChange = async (
internalName: string,
Expand Down Expand Up @@ -590,8 +676,8 @@ export class DynamicForm extends React.Component<
hiddenName = response.value;
termSetId = field.TermSetId;
anchorId = field.AnchorId;
if (item !== null) {
item[field.InternalName].forEach((element) => {
if (item !== null && item[field.InternalName] !== null && item[field.InternalName].results !== null) {
item[field.InternalName].results.forEach((element) => {
selectedTags.push({
key: element.TermGuid,
name: element.Label,
Expand Down Expand Up @@ -852,6 +938,77 @@ export class DynamicForm extends React.Component<
return errorMessage;
};

private renderFileSelectionControl = (): React.ReactElement => {
const {
selectedFile,
missingSelectedFile
} = this.state;

const labelEl = <label className={styles.fieldRequired + ' ' + styles.fieldLabel}>{strings.DynamicFormChooseFileLabel}</label>;

return <div>
<div className={styles.titleContainer}>
<Icon className={styles.fieldIcon} iconName={"DocumentSearch"} />
{labelEl}
</div>
<FilePicker
buttonLabel={strings.DynamicFormChooseFileButtonText}
accepts={this.props.supportedFileExtensions ? this.props.supportedFileExtensions : [".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls", ".pdf"]}
onSave={(filePickerResult: IFilePickerResult[]) => {
if (filePickerResult.length === 1) {
this.setState({
selectedFile: filePickerResult[0],
missingSelectedFile: false
});
}
else {
this.setState({
missingSelectedFile: true
});
}
}}
required={true}
context={this.props.context}
hideWebSearchTab={true}
hideStockImages={true}
hideLocalMultipleUploadTab={true}
hideLinkUploadTab={true}
hideSiteFilesTab={true}
checkIfFileExists={true}
/>
{selectedFile && <div className={styles.selectedFileContainer}>
<Icon iconName={this.getFileIconFromExtension()} />
{selectedFile.fileName}
</div>}
{missingSelectedFile === true &&
<div className={styles.errormessage}>{strings.DynamicFormRequiredFileMessage}</div>}
</div>;
}

private getFileIconFromExtension = (): string => {
const fileExtension = this.state.selectedFile.fileName.split('.').pop();
switch (fileExtension) {
case 'pdf':
return 'PDF';
case 'docx':
case 'doc':
return 'WordDocument';
case 'pptx':
case 'ppt':
return 'PowerPointDocument';
case 'xlsx':
case 'xls':
return 'ExcelDocument';
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return 'FileImage';
default:
return 'Document';
}
}

private validateNumberOnSubmit = (val:IDynamicFieldProps): boolean => {
let shouldBeReturnBack = false;
if (val.fieldType === "Number" && val.showAsPercentage) {
Expand Down
13 changes: 13 additions & 0 deletions src/controls/dynamicForm/IDynamicFormProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,17 @@ export interface IDynamicFormProps {
* Specify validation error dialog properties
*/
validationErrorDialogProps?: IValidationErrorDialogProps;

/**
* Specify if the form should support the creation of a new list item in a document library attaching a file to it.
* This option is only available for document libraries and works only when the contentTypeId is specified and has a base type of type Document.
* Default - false
*/
enableFileSelection?: boolean;

/**
* Specify the supported file extensions for the file picker. Default - "docx", "doc", "pptx", "ppt", "xlsx", "xls", "pdf"
* Only used when enableFileSelection is true
*/
supportedFileExtensions?: string[];
}
8 changes: 4 additions & 4 deletions src/controls/dynamicForm/IDynamicFormState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

import { IDynamicFieldProps } from './dynamicField/IDynamicFieldProps';
import { IFilePickerResult } from "../filePicker";

export interface IDynamicFormState {
fieldCollection: IDynamicFieldProps[];
isSaving?: boolean;
etag?: string;
isValidationErrorDialogOpen: boolean;
selectedFile?: IFilePickerResult;
missingSelectedFile?: boolean;
}



3 changes: 3 additions & 0 deletions src/loc/bg-bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "Стойността трябва да е по-голяма от {0}",
"DynamicFormNumberValueMustBeBetween": "Стойността трябва да е между {0} и {1}",
"DynamicFormNumberValueMustBeLowerThan": "Стойността трябва да е по-ниска от {0}",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Използвайте това местоположение:",
"ListItemCommentDIalogDeleteSubText": "Наистина ли искате да изтриете този коментар?",
"ListItemCommentsDialogDeleteTitle": "Потвърдете Изтриване на коментар",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/ca-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "El valor ha de ser superior a {0}",
"DynamicFormNumberValueMustBeBetween": "El valor ha d'estar entre {0} i {1}",
"DynamicFormNumberValueMustBeLowerThan": "El valor ha de ser inferior a {0}",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Utilitzeu aquesta ubicació:",
"ListItemCommentDIalogDeleteSubText": "Esteu segur que voleu suprimir aquest comentari?",
"ListItemCommentsDialogDeleteTitle": "Confirmació de la supressió del comentari",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/da-dk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "Værdien skal være større end {0}",
"DynamicFormNumberValueMustBeBetween": "Værdien skal være mellem {0} og {1}",
"DynamicFormNumberValueMustBeLowerThan": "Værdien skal være lavere end {0}",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Brug denne placering:",
"ListItemCommentDIalogDeleteSubText": "Er du sikker på, at du vil slette denne kommentar?",
"ListItemCommentsDialogDeleteTitle": "Bekræft kommentar til sletning",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/de-de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "Der Wert muss größer als {0} sein.",
"DynamicFormNumberValueMustBeBetween": "Der Wert muss zwischen {0} und {1} liegen.",
"DynamicFormNumberValueMustBeLowerThan": "Der Wert muss niedriger als {0} sein.",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Verwenden Sie diesen Speicherort:",
"ListItemCommentDIalogDeleteSubText": "Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?",
"ListItemCommentsDialogDeleteTitle": "Kommentar löschen bestätigen",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/el-gr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "Η τιμή πρέπει να είναι μεγαλύτερη από {0}",
"DynamicFormNumberValueMustBeBetween": "Η τιμή πρέπει να είναι μεταξύ {0} και {1}",
"DynamicFormNumberValueMustBeLowerThan": "Η τιμή πρέπει να είναι μικρότερη από {0}",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Χρησιμοποιήστε αυτήν τη θέση:",
"ListItemCommentDIalogDeleteSubText": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το σχόλιο;",
"ListItemCommentsDialogDeleteTitle": "Επιβεβαίωση διαγραφής σχολίου",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ define([], () => {
DynamicFormNumberValueMustBeGreaterThan: "Value must be greater than {0}",
DynamicFormNumberValueMustBeBetween: "Value must be between {0} and {1}",
DynamicFormNumberValueMustBeLowerThan: "Value must be lower than {0}",
DynamicFormChooseFileLabel: "File",
DynamicFormChooseFileButtonText: "Select file",
DynamicFormRequiredFileMessage: "File is required.",
customDisplayName: "Use this location:",
ListItemCommentDIalogDeleteSubText: "Are you sure that you want to delete this comment?",
ListItemCommentsDialogDeleteTitle: "Confirm Delete Comment",
Expand Down
3 changes: 3 additions & 0 deletions src/loc/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ define([], () => {
"DynamicFormNumberValueMustBeGreaterThan": "El valor debe ser mayor que {0}",
"DynamicFormNumberValueMustBeBetween": "El valor debe estar entre {0} y {1}",
"DynamicFormNumberValueMustBeLowerThan": "El valor debe ser inferior a {0}",
"DynamicFormChooseFileLabel": "File",
"DynamicFormChooseFileButtonText": "Select file",
"DynamicFormRequiredFileMessage": "File is required.",
"customDisplayName": "Utilice esta ubicación:",
"ListItemCommentDIalogDeleteSubText": "¿Está seguro de que desea eliminar este comentario?",
"ListItemCommentsDialogDeleteTitle": "Confirmar comentario de eliminación",
Expand Down
Loading