Welcome to the nuBuilder Forums!

Register and log in to access exclusive forums and content available only to registered users.

configure Uppy

Questions related to using nuBuilder Forte.
kev1n
nuBuilder Team
Posts: 4416
Joined: Sun Oct 14, 2018 6:43 pm
Has thanked: 74 times
Been thanked: 472 times
Contact:

Re: configure Uppy

Unread post by kev1n »

When uploading files, there needs to be a way to identify which files belong to which record. One approach could be to store each uploaded file in a database table. This table would include the filename, the file path (if needed), and the associated record ID.

With this structure, the relevant files can then be displayed using an embedded iFrame.

What do you think?
johan
Posts: 422
Joined: Sun Feb 27, 2011 11:16 am
Location: Belgium
Been thanked: 3 times

Re: configure Uppy

Unread post by johan »

Kev1n

Sounds good. Do you have an example?

Johan
kev1n
nuBuilder Team
Posts: 4416
Joined: Sun Oct 14, 2018 6:43 pm
Has thanked: 74 times
Been thanked: 472 times
Contact:

Re: configure Uppy

Unread post by kev1n »

The first step is to create a new table with a primary key, along with fields for the file name, record ID, and possibly the form ID. This setup is particularly useful if multiple forms use the upload feature, as it allows you to clearly identify which file belongs to which form and record.

Next, you’ll need to update the upload procedure so that after a successful upload, the file information is inserted into the new table.

One important detail to consider is that a new record doesn't have a record ID yet, which complicates things.

The simplest solution is to only allow file uploads for records that have already been saved. There are ways to handle uploads for unsaved records as well, but I would recommend starting with the simpler approach. I can try to create an example later today.
johan
Posts: 422
Joined: Sun Feb 27, 2011 11:16 am
Location: Belgium
Been thanked: 3 times

Re: configure Uppy

Unread post by johan »

Thanks a lot dit your help.
kev1n
nuBuilder Team
Posts: 4416
Joined: Sun Oct 14, 2018 6:43 pm
Has thanked: 74 times
Been thanked: 472 times
Contact:

Re: configure Uppy

Unread post by kev1n »

Follow these steps to set up file uploads with nuBuilder and Uppy.
If anything is unclear or you need more details, just let me know!

Index
  1. Create the Files Upload Table
  2. Create a PHP Procedure
  3. Update the Uppy Object
  4. Create a Run/iFrame Object
  5. Refresh the iFrame
Screenshot 2025-07-03 043348.png
(Expand to view)

1. Create the Files Upload Table

In phpMyAdmin (or your preferred database tool), run the following SQL to create a table for storing uploaded file metadata:

Code: Select all

CREATE TABLE upload(
upload_id INT(11) NOT NULL AUTO_INCREMENT,
upl_filename VARCHAR(255) NOT NULL,
upl_filepath VARCHAR(500) DEFAULT NULL,
upl_record_id CHAR(36) NOT NULL,
upl_created DATETIME DEFAULT CURRENT_TIMESTAMP,
rec_status VARCHAR(20) DEFAULT 'active',
PRIMARY KEY(upload_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

2. Create a PHP Procedure

Create a PHP function (for example, named nu_upload_file_sample) that validates the upload, moves the file, and records its details in the database. Adjust $targetDirectory if you want to store files elsewhere.
uppy_proc.png

Code: Select all

// Allowed file types
$allowedTypes = [
    'image/png',
    'image/jpeg',
    'application/pdf',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'text/plain',
    'text/csv'
];

// Maximum file size
$maxFileSize = 5 * 1024 * 1024; // (5 MB)

// Target directory
// $targetDirectory = $_SERVER['DOCUMENT_ROOT'] . '/';
$targetDirectory = '../uploads/';


try {

	// Sanitize file name
	$fileName = nuSanitizeFilename(basename($_FILES['file']['name']));

	// Check file size
	if ($_FILES['file']['size'] > $maxFileSize) {
		throw new Exception('Exceeded file size limit');
	}

	// Check file type
	$finfo = new finfo(FILEINFO_MIME_TYPE);
	if (!in_array($finfo->file($_FILES['file']['tmp_name']), $allowedTypes)) {
		throw new Exception('Invalid file type');
	}

	// Build target file path
	$targetFile = $targetDirectory . $fileName;

    if (move_uploaded_file($_FILES['file']['tmp_name'], $targetFile)) {
        
        $recordId = isset($_POST["record_id"]) ? $_POST["record_id"] : '';
        insertUploadFile($fileName, $targetFile, $recordId);

        $data = ['url' => $targetFile, 'file' => $fileName, 'message' => 'The file ' . $fileName . ' has been uploaded.'];
        http_response_code(201);
        $result = json_encode($data);
    }
    else {
        throw new Exception(nuTranslate('Unable to move the uploaded file to its final location:') . $targetFile);
    }

} catch(\Throwable $th) {

    $result = nuSetUploadError('Sorry, there was an error uploading your file.');

}

function insertUploadFile($fileName, $filePath, $relatedRecordID) {
    
    $recStatus = 'active';

    $sql = "INSERT INTO `upload` (
                `upl_filename`, 
                `upl_filepath`, 
                `upl_record_id`, 
                `rec_status`
            ) VALUES (?, ?, ?, ?)";


    $params = [
        $fileName,
        $filePath,
        $relatedRecordID,
        $recStatus
    ];
    
    nuRunQuery($sql, $params);

}

3. In your Uppy object, update the code like this (use your upload procedure name and pass the record ID)

Code: Select all

        uppy.setMeta({
                procedure: 'nu_upload_file_sample', // <-- upload procedure to use
                session_id: window.nuSESSION,
                record_id: nuRecordId() // <-- pass record id 
            })
4. Create a Run/iFrame Object

Create a Run/Iframe Object in your form to display the uploaded files. E.g. width object ID iframe_files
Set filter: #RECORD_ID# to display only the files that belong to that record.
Also include the record ID in the browse columns (hidden)

Add this JS to Custom Code/Browse to handle file downloads:

Code: Select all

function downloadFile(fileName) {
    const baseUrl = "http://localhost/nubuilder/uploads/"; // change if different
    const fullUrl = baseUrl + encodeURIComponent(fileName);

    const link = document.createElement("a");
    link.href = fullUrl;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

function nuSelectBrowse(event, element) {
    const cell = nuBrowseRow(element);
    if (cell.column === 0) { // clicked the file name
        const selectedFileName = nuBrowseRow(element).value;
        downloadFile(selectedFileName);
    }
}

3. Update the Uppy Object

In your Uppy initialization, set the procedure name and pass the current record ID to your upload endpoint:

Code: Select all

uppy.setMeta({
	procedure: 'nu_upload_file_sample',
	session_id: window.nuSESSION,
	record_id: nuRecordId()
});
4. Create a Run/iFrame Object
• Add a Run/IFrame Object (e.g., with the ID iframe_files) to your form to display uploaded files.
• Set its filter to #RECORD_ID# so it only shows files for the current record.
• Include the record ID as a (hidden) browse column.

run_files.png
run_browse_columns.png


5. Refresh the iFrame

To automatically refresh the file list after each upload, add this function to your form’s Custom Code:

Code: Select all

function nuOnFileUploadComplete() {
     nuRefreshIframe('iframe_files');
}
You do not have the required permissions to view the files attached to this post.
johan
Posts: 422
Joined: Sun Feb 27, 2011 11:16 am
Location: Belgium
Been thanked: 3 times

Re: configure Uppy

Unread post by johan »

Kev1n
Thanks.
I still have some questions/problems.

1. When uploading I get an error message
Screenshot 2025-07-03 09.14.33.png
. Yet the upload succeeded and the info is in the table

2.When I click on a date in the Iframe, I get, for example, 03-07-03-2025.html and not the file.
Any idea what I am doing wrong?
Screenshot 2025-07-03 09.30.06.png
Screenshot 2025-07-03 09.31.06.png
Johan
You do not have the required permissions to view the files attached to this post.
kev1n
nuBuilder Team
Posts: 4416
Joined: Sun Oct 14, 2018 6:43 pm
Has thanked: 74 times
Been thanked: 472 times
Contact:

Re: configure Uppy

Unread post by kev1n »

My code assumed that the file name is located in the first column (index 0).

If you want the file to be downloaded regardless of where someone clicks on the row, adjust the code like this (untested):

Code: Select all

function nuSelectBrowse(event, element) {
    const selectedFileName = nuBrowseRow(element, 1).value; // file name in the second column
    downloadFile(selectedFileName);
}
johan
Posts: 422
Joined: Sun Feb 27, 2011 11:16 am
Location: Belgium
Been thanked: 3 times

Re: configure Uppy

Unread post by johan »

Kev1n

I've switched the columns and now it works.

Thanks for your help.

Johan
kev1n
nuBuilder Team
Posts: 4416
Joined: Sun Oct 14, 2018 6:43 pm
Has thanked: 74 times
Been thanked: 472 times
Contact:

Re: configure Uppy

Unread post by kev1n »

Regarding the error message: I encountered the same issue initially when my upload directory didn’t exist yet. It seems like the error handling isn’t quite working correctly, and there may be other cases where an error could occur as well.
I’ll take a closer look at it when I get the chance.
If you discover anything in the meantime, please let me know.
johan
Posts: 422
Joined: Sun Feb 27, 2011 11:16 am
Location: Belgium
Been thanked: 3 times

Re: configure Uppy

Unread post by johan »

Kev1n

I asked gemini to solve the error.
This is it's solution and it works.

Code: Select all

<div id="#uppy_div#"></div>

<script>
// Voer de initialisatiefunctie uit zodra het DOM geladen is
$(document).ready(function() {
    nuInitUppy();
});

function nuInitUppy() {
    // Gebruik const voor variabelen die niet opnieuw worden toegewezen
    const $objId = $('#' + '#this_object_id#');
    const targetDivId = '#uppy_div#'; // Bewaar de ID zonder '#' prefix, als je die later nodig hebt

    // Declareer uppyResponseMessage in de scope van nuInitUppy
    // Dit zorgt ervoor dat het toegankelijk is voor alle nested functies en callbacks.
    let uppyResponseMessage = '';

    // Initialiseer Uppy
    const uppy = nuUppyCreate(); // Aanname: nuUppyCreate() retourneert een Uppy-instantie

    uppy.use(Uppy.Dashboard, {
        inline: true,
        bundle: true,
        // Zorg ervoor dat nuCSSNumber betrouwbare getallen retourneert, anders kan dit problemen geven.
        // Optioneel: Voeg fallback-waarden toe als $objId.nuCSSNumber() null of undefined kan zijn.
        height: $objId.nuCSSNumber('height') || 400, // Voorbeeld fallback
        width: $objId.nuCSSNumber('width') || '100%', // Voorbeeld fallback
        target: '#' + targetDivId, // Zorg voor de '#' prefix voor de selector
        showProgressDetails: true,
        replaceTargetContent: true,
        method: 'post'
    })
    .use(Uppy.XHRUpload, {
        endpoint: 'core/nuapi.php',
        shouldRetry: (xhr) => { return false; }, // Geen retries bij falen
        // onAfterResponse is voor het inspecteren van de *initiële* serverrespons,
        // vaak gebruikt voor het verwerken van headers of niet-standaard responsen.
        // Het is beter om de algemene status en berichten af te handelen in 'complete'.
        async onAfterResponse(xhr, response) {
            // Parsen van de JSON-respons is hier nuttig voor specifieke HTTP-statussen
            // of als de server een niet-standaard 'message' veld gebruikt.
            try {
                const jsonData = JSON.parse(xhr.responseText);
                // Je kunt hier de uppyResponseMessage instellen op basis van een fout,
                // maar de 'complete' handler is vaak robuuster.
                if (xhr.status === 401) {
                    uppyResponseMessage = jsonData.message || 'Authenticatie mislukt.';
                    throw new Error(uppyResponseMessage); // Gooi een error om Uppy te laten weten dat er een probleem is
                }
                // Optioneel: Als de server een algemeen bericht stuurt bij succes
                // if (xhr.status >= 200 && xhr.status < 300 && jsonData.message) {
                //     uppyResponseMessage = jsonData.message;
                // }

            } catch (e) {
                // Fout bij parsen of andere XHR-gerelateerde fouten
                uppyResponseMessage = 'Fout bij verwerken van server respons.';
                // Je kunt hier besluiten om de error door te gooien of niet, afhankelijk van Uppy's gedrag.
                // console.error("Error parsing XHR response:", e);
                // throw e; // Gooi de error door als dit een kritieke fout is voor Uppy
            }
        }
    });

    // Deze callback wordt uitgevoerd voordat de upload begint.
    // Dit is de juiste plaats om meta-data in te stellen voor de upload.
    uppy.on('upload', (data) => { // 'data' object bevat de bestanden die worden geüpload
        uppy.setMeta({
            procedure: 'nu_upload_file_sample', // Server-side procedure om te gebruiken
            session_id: window.nuSESSION,
            record_id: nuRecordId() // Record ID waaraan de upload gekoppeld moet worden
        });
    });

    // Deze callback wordt geactiveerd wanneer *alle* uploads (succesvol of mislukt) zijn voltooid.
    uppy.on('complete', (result) => {
        // Reset het bericht voor elke nieuwe upload-sessie
        uppyResponseMessage = '';

        if (result.successful.length > 0) {
            // Als er succesvolle uploads zijn, pak het bericht van de eerste succesvolle upload.
            // Dit veronderstelt dat je PHP-script een 'message' veld retourneert bij succes.
            uppyResponseMessage = result.successful[0].response.body.message || 'Bestand(en) succesvol geüpload.';
        } else if (result.failed.length > 0) {
            // Als er mislukte uploads zijn, pak de foutmelding.
            // Hier proberen we de foutmelding uit de serverrespons te halen,
            // anders vallen we terug op Uppy's eigen foutbericht.
            const firstFailed = result.failed[0];
            uppyResponseMessage = firstFailed.response && firstFailed.response.body && firstFailed.response.body.message
                                  ? firstFailed.response.body.message
                                  : firstFailed.error.message || 'Fout bij het uploaden van bestand(en).';
        } else {
            // Geen bestanden geselecteerd of geüpload
            uppyResponseMessage = 'Geen bestanden verwerkt.';
        }

        // Roep de custom handler functie aan met het uiteindelijke bericht
        if (window.nuOnFileUploadComplete) {
            nuOnFileUploadComplete('FS', $objId.attr('id'), result, uppyResponseMessage);
        }
    });

    // Optioneel: Voeg een algemene foutafhandeling toe voor Uppy.
    // Dit vangt fouten op die niet direct in onAfterResponse worden afgehandeld.
    uppy.on('error', (error) => {
        console.error('Uppy error:', error);
        // Je kunt uppyResponseMessage hier ook instellen als een generieke foutboodschap.
        // uppyResponseMessage = error.message || 'Er is een algemene uploadfout opgetreden.';
    });
}
</script>
Post Reply