Is there any way to add a confirmation to a FileUploadField?

Our app allows users to attach an uploaded document to a patient note, and since users tend to be … user-y … I’d like to add a confirmation step when clicking the upload button if there is already a document attached, to make sure they really want to overwrite it. I’m not seeing a way to do that; there’s no on-click event or anything.

Hello @jon.craig,

You can use FileUploadDialog, which will open when you click an additional button. Then instead of the FileUploadField component you will need to use the composite component - FileUploadField(to display fileName),Button (to upload file) and Button (to clear field).

  1. XML descriptor
        <hbox spacing="true">
            <upload id="uploadField" showFileName="true" editable="false"/>
            <button id="showUploadDialogBtn" caption="Upload"/>
            <button id="clearBtn" caption="Clear"/>
        </hbox>
  1. Screen controller
    @Inject
    private Dialogs dialogs;
    @Inject
    private Screens screens;
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    @Inject
    private DataManager dataManager;

    @Inject
    private FileUploadField uploadField;
    
    @Subscribe("showUploadDialogBtn")
    protected void onShowUploadDialogBtnClick(Button.ClickEvent event) {
        // check your permission here
        if (uploadField.getValue() != null) {
            dialogs.createOptionDialog(Dialogs.MessageType.CONFIRMATION)
                    .withCaption("Confirm")
                    .withMessage("Are you sure you want to overwrite the file?")
                    .withActions(
                            new DialogAction(DialogAction.Type.YES, Action.Status.PRIMARY)
                                    .withHandler(actionPerformedEvent -> openFileUploadDialog()),
                            new DialogAction(DialogAction.Type.NO)
                    )
                    .show();
        } else {
            openFileUploadDialog();
        }
    }

    protected void openFileUploadDialog() {
        FileUploadDialog dialog = (FileUploadDialog) screens.create("fileUploadDialog", OpenMode.DIALOG);
        dialog.addAfterCloseListener(afterCloseEvent -> {
            UUID fileId = dialog.getFileId();
            String fileName = dialog.getFileName();

            if (fileId != null) {
                File file = fileUploadingAPI.getFile(fileId);

                FileDescriptor fileDescriptor = fileUploadingAPI.getFileDescriptor(fileId, fileName);
                try {
                    fileUploadingAPI.putFileIntoStorage(fileId, fileDescriptor);
                    dataManager.commit(fileDescriptor);

                    uploadField.setValue(fileDescriptor);
                } catch (FileStorageException e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        });
        screens.show(dialog);
    }

    @Subscribe("clearBtn")
    public void onClearBtnClick(Button.ClickEvent event) {
        uploadField.clear();
    }

To use such a component in different screens, you can create a composite component by following the instructions in our documentation. You can find an example of using the FileUploadDialog in our documentation.

Regards,
Gleb

That may work. How then does the reference to the uploaded file get stored into the patient note record? I don’t see anywhere that is wired up.

The openFileUploadDialog() method sets the FileDescriptor to the FileUploadField using the uploadField.setValue(fileDescriptor) function. Define dataContainer and property to FileUploadField in XML to bind the field and the entity you need.

Then your XML descriptor might look something like this:

        <hbox spacing="true">
            <upload id="uploadField"
                    showFileName="true"
                    editable="false"
                    dataContainer="patientDc"
                    property="file"/>
            <button id="showUploadDialogBtn" caption="Upload"/>
            <button id="clearBtn" caption="Clear"/>
        </hbox>

Regards,
Gleb

Ah, right, it’s all automatic. I’m still used to much, much more limited frameworks and environments.

This functionality in our existing system is several hundred lines of code, and it’s much more limited even still.

Thank you @durygin =)

Just a little note @durygin - the editable property of the FileUploadField is NOT on the property sheet in the designer. Bug/oversight?

Thank you for reporting the issue. I’ve created an issue for CUBA Studio.

Regards,
Gleb

Hmmm - another issue @durygin - the suggested solution seems to work fine (file uploads correctly, etc, etc) but when I go to save the record, I get “Unique constraint violation occurred (sys_file_pkey)” with the following in the log:

10:45:59.316 ERROR c.h.cuba.core.sys.ServiceInterceptor    - Exception: 
javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.3.6-cuba): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "sys_file_pkey"
  Detail: Key (id)=(65246079-db62-46a2-97e4-6d5ef5e23e78) already exists.
Error Code: 0
Call: INSERT INTO SYS_FILE (ID, CREATE_DATE, CREATE_TS, CREATED_BY, DELETE_TS, DELETED_BY, EXT, NAME, FILE_SIZE, UPDATE_TS, UPDATED_BY, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	bind => [65246079-db62-46a2-97e4-6d5ef5e23e78, 2020-02-06 10:45:43.443, 2020-02-06 10:45:59.255, admin, null, null, jpg, quad-and-tx.jpg, 1899678, 2020-02-06 10:45:59.255, null, 1]
Query: InsertObjectQuery(com.haulmont.cuba.core.entity.FileDescriptor-65246079-db62-46a2-97e4-6d5ef5e23e78 [new,managed])
	at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:979) ~[org.eclipse.persistence.jpa-2.7.3-6-cuba.jar:na]
	at com.haulmont.cuba.core.sys.persistence.PersistenceImplSupport$ContainerResourceSynchronization.detachAll(PersistenceImplSupport.java:496) ~[cuba-core-7.1.3.jar:7.1.3]
	at com.haulmont.cuba.core.sys.persistence.PersistenceImplSupport$ContainerResourceSynchronization.beforeCommit(PersistenceImplSupport.java:449) ~[cuba-core-7.1.3.jar:7.1.3]
	at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:96) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:922) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE]

So it seems something else in the framework is trying to save the file’s record again?

Interestingly, commenting out the dataManager.commit call makes everything work - no pkey exception and everything is stored correctly in the DB and in file storage.

Hi @jon.craig,

This is normal behavior, since a dataManager.commit(fileDescriprtor) function call saves the file to the database. And then you try to save the same file a second time and therefore an exception is thrown. A fileUploadingAPI.putFileIntoStorage(fileId, fileDescriptor) function call saves the file to FileStorage.
If you want to save the file yourself, then remove the dataManager.commit(fileDescriprtor) function call (as described in your message above).

Regards,
Gleb

Well - I’m not saving it myself per se - this is in a standard edit screen. The screen itself is saving it (that second time), so removing the commit fixes the issue.

@jon.craig Try adding fileStoragePutMode="MANUAL" attribute for FileUploadField. This step should prevent the file from being saved again.

Regards,
Gleb

Is there a downside to letting the screen do it? That’s how I have it now (dm.commit removed) and it seems to be working fine. Is there something I should be aware of that makes that a bad idea?

Hello @jon.craig,

The fileStoragePutMode attribute defines how the file and the corresponding FileDescriptor are stored.

  • In the IMMEDIATE mode it is done right after uploading file to the temporary storage of the client tier.
  • In the MANUAL mode, you should do it programmatically in a FileUploadSucceedListener.

Setting an IMMEDIATE value for fileStoragePutMode attribute is equivalent to calling functions:

fileUploadingAPI.putFileIntoStorage(fileIdId, fileDescriptor);
dataManager.commit(fileDescriptor);

Regards,
Gleb