diff --git a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md
index f06cc92d8f..c75286ae17 100644
--- a/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/file-uploader-web/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Added
+
+- We added configuration options to execute actions after both successful and unsuccessful file uploads.
+
## [2.3.0] - 2025-08-15
### Fixed
diff --git a/packages/pluggableWidgets/file-uploader-web/package.json b/packages/pluggableWidgets/file-uploader-web/package.json
index 343a19b049..3ad72cefd7 100644
--- a/packages/pluggableWidgets/file-uploader-web/package.json
+++ b/packages/pluggableWidgets/file-uploader-web/package.json
@@ -1,7 +1,7 @@
{
"name": "@mendix/file-uploader-web",
"widgetName": "FileUploader",
- "version": "2.3.0",
+ "version": "2.4.0",
"description": "Upload files via drag-and-drop or file dialog. Supports multiple file uploads and image preview thumbnails.",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
diff --git a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml
index f72de5e2c0..97bd0f8408 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml
+++ b/packages/pluggableWidgets/file-uploader-web/src/FileUploader.xml
@@ -195,6 +195,23 @@
Object creation timeout
Consider uploads unsuccessful if the Action to create new files/images does not create new objects within the configured amount of seconds.
+
+ On upload success
+ The action to be called if file content uploaded successfully.
+
+
+ On upload success
+ The action to be called if image content uploaded successfully.
+
+
+ On upload failure
+ The action to be called if file content upload was not successful.
+
+
+
+ On upload failure
+ The action to be called if image content upload was not successful.
+
Enable custom buttons
diff --git a/packages/pluggableWidgets/file-uploader-web/src/package.xml b/packages/pluggableWidgets/file-uploader-web/src/package.xml
index 83f1f22572..65511b3abf 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/package.xml
+++ b/packages/pluggableWidgets/file-uploader-web/src/package.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts
index 112de33ae8..10c96aedb6 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/stores/FileStore.ts
@@ -102,18 +102,30 @@ export class FileStore {
this.fileStatus = "uploading";
});
+ // create object
try {
- // request object item
this._objectItem = await this._rootStore.objectCreationHelper.request();
+ } catch (_e: unknown) {
+ runInAction(() => {
+ this.fileStatus = "uploadingError";
+ this._rootStore.objectCreationHelper.reportCreationFailure();
+ });
+ return;
+ }
+
+ // upload content to object
+ try {
await saveFile(this._objectItem, this._file!);
await this.fetchMxObject();
runInAction(() => {
this.fileStatus = "done";
+ this._rootStore.objectCreationHelper.reportUploadSuccess(this._objectItem!);
});
} catch (_e: unknown) {
runInAction(() => {
this.fileStatus = "uploadingError";
+ this._rootStore.objectCreationHelper.reportUploadFailure(this._objectItem!);
});
}
}
diff --git a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
index 9a34132a93..eb44dfb68f 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/stores/FileUploaderStore.ts
@@ -1,4 +1,4 @@
-import { DynamicValue, ListValue, ObjectItem } from "mendix";
+import { DynamicValue, ObjectItem } from "mendix";
import { FileUploaderContainerProps, UploadModeEnum } from "../../typings/FileUploaderProps";
import { action, computed, makeObservable, observable } from "mobx";
import { Big } from "big.js";
@@ -26,7 +26,6 @@ export class FileUploaderStore {
_uploadMode: UploadModeEnum;
_maxFileSizeMiB = 0;
_maxFileSize = 0;
- _ds?: ListValue;
_maxFilesPerUpload: DynamicValue;
errorMessage?: string = undefined;
@@ -90,19 +89,15 @@ export class FileUploaderStore {
}
updateProps(props: FileUploaderContainerProps): void {
- if (props.uploadMode === "files") {
- this.objectCreationHelper.updateProps(props.createFileAction);
- this._ds = props.associatedFiles;
- } else {
- this.objectCreationHelper.updateProps(props.createImageAction);
- this._ds = props.associatedImages;
- }
+ this.objectCreationHelper.updateProps(props);
// Update max files properties
this._maxFilesPerUpload = props.maxFilesPerUpload;
this.translations.updateProps(props);
- this.updateProcessor.processUpdate(this._ds);
+ this.updateProcessor.processUpdate(
+ props.uploadMode === "files" ? props.associatedFiles : props.associatedImages
+ );
}
processExistingFileItem(item: ObjectItem): void {
diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts
index 95bdc8eb08..d814016447 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/utils/ObjectCreationHelper.ts
@@ -1,7 +1,22 @@
-import { ActionValue, ObjectItem } from "mendix";
+import { ActionValue, ListActionValue, ObjectItem } from "mendix";
+import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
+import { FileUploaderContainerProps } from "../../typings/FileUploaderProps";
+
+type UpdateProps = Pick<
+ FileUploaderContainerProps,
+ | "uploadMode"
+ | "createFileAction"
+ | "createImageAction"
+ | "onUploadFailureFile"
+ | "onUploadSuccessFile"
+ | "onUploadFailureImage"
+ | "onUploadSuccessImage"
+>;
export class ObjectCreationHelper {
private objectCreationAction?: ActionValue;
+ private onUploadFailure?: ListActionValue;
+ private onUploadSuccess?: ListActionValue;
private requestingEnabled = false;
private currentWaiting: Array<[(v: ObjectItem) => void, (e: Error) => void]> = [];
private itemCreationTimer?: number = undefined;
@@ -25,8 +40,16 @@ export class ObjectCreationHelper {
}
}
- updateProps(createAction?: ActionValue): void {
- this.objectCreationAction = createAction;
+ updateProps(props: UpdateProps): void {
+ if (props.uploadMode === "files") {
+ this.objectCreationAction = props.createFileAction;
+ this.onUploadFailure = props.onUploadFailureFile;
+ this.onUploadSuccess = props.onUploadSuccessFile;
+ } else {
+ this.objectCreationAction = props.createImageAction;
+ this.onUploadFailure = props.onUploadFailureImage;
+ this.onUploadSuccess = props.onUploadSuccessImage;
+ }
}
request(): Promise {
@@ -55,6 +78,20 @@ export class ObjectCreationHelper {
this.executeCreation();
}
+ reportCreationFailure(): void {
+ console.warn(`File object creation has failed.`);
+ }
+
+ reportUploadFailure(item: ObjectItem): void {
+ console.warn(`Uploading file content to ${item.id} has failed.`);
+ executeAction(this.onUploadFailure?.get(item));
+ }
+
+ reportUploadSuccess(item: ObjectItem): void {
+ console.warn(`Uploading file content to ${item.id} has succeeded.`);
+ executeAction(this.onUploadSuccess?.get(item));
+ }
+
private executeCreation(): void {
if (!this.requestingEnabled) {
// we are not yet able to create objects, wait till next run
diff --git a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts
index 572115e616..5ba6b30d15 100644
--- a/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts
+++ b/packages/pluggableWidgets/file-uploader-web/src/utils/__tests__/ObjectCreationHelper.spec.ts
@@ -6,7 +6,7 @@ describe("ObjectCreationHelper", () => {
jest.useFakeTimers();
const createAction = actionValue(true, false);
const helper = new ObjectCreationHelper("grid1", 10);
- helper.updateProps(createAction);
+ helper.updateProps({ uploadMode: "files", createFileAction: createAction });
helper.enable();
const req1 = helper.request();
const req2 = helper.request();
@@ -34,7 +34,7 @@ describe("ObjectCreationHelper", () => {
jest.useFakeTimers();
const helper = new ObjectCreationHelper("grid1", 10);
const createAction = actionValue(true, false);
- helper.updateProps(createAction);
+ helper.updateProps({ uploadMode: "files", createFileAction: createAction });
helper.enable();
const req1 = helper.request();
const req2 = helper.request();
@@ -48,7 +48,7 @@ describe("ObjectCreationHelper", () => {
jest.useFakeTimers();
const helper = new ObjectCreationHelper("grid1", 10);
const createAction = actionValue(true, false);
- helper.updateProps(createAction);
+ helper.updateProps({ uploadMode: "files", createFileAction: createAction });
const req1 = helper.request();
const req2 = helper.request();
jest.advanceTimersByTime(1000);
diff --git a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts
index c76a4c15a1..751fbf1fee 100644
--- a/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts
+++ b/packages/pluggableWidgets/file-uploader-web/typings/FileUploaderProps.d.ts
@@ -76,6 +76,10 @@ export interface FileUploaderContainerProps {
removeSuccessMessage: DynamicValue;
removeErrorMessage: DynamicValue;
objectCreationTimeout: number;
+ onUploadSuccessFile?: ListActionValue;
+ onUploadSuccessImage?: ListActionValue;
+ onUploadFailureFile?: ListActionValue;
+ onUploadFailureImage?: ListActionValue;
enableCustomButtons: boolean;
customButtons: CustomButtonsType[];
}
@@ -115,6 +119,10 @@ export interface FileUploaderPreviewProps {
removeSuccessMessage: string;
removeErrorMessage: string;
objectCreationTimeout: number | null;
+ onUploadSuccessFile: {} | null;
+ onUploadSuccessImage: {} | null;
+ onUploadFailureFile: {} | null;
+ onUploadFailureImage: {} | null;
enableCustomButtons: boolean;
customButtons: CustomButtonsPreviewType[];
}