Documentation

Report Edit

Multi File Upload

The following example demonstrates how to use the UploadButton component to handle multiple file upload.

By default, UploadButton automatically uploads each file as soon as it is selected, utilizing the XMLHttpRequest object. When multiple files are selected, each file is sent in a separate request. However, you can customize this behavior by defining the onUploadStarting callback. By preserving the selected files and returning false from the callback, the upload is deferred, allowing you to handle the actual upload process manually. This enables the button to load files into memory without initiating the upload, giving you full control over the upload logic.

In this example, XMLHttpRequest is used for uploading files, as it provides better support for progress tracking. Although fetch could be used, its progress tracking capabilities are still somewhat limited.

Selected files will appear in the table with validation applied, highlighting any invalid entries based on file size (>= 1MB) or type (non images).

Choose files
Files to upload:
File nameFile size
Choose image type files with size not greater than 1 MB.
Progress:
ControllerIndex
<ValidationGroup invalid-bind='$page.form.invalid' errors-bind="$page.form.errors">
    <Validator value-bind='$page.form.files.length' onValidate={(val = 0) => val < 1 && "Cannot submit empty form." } />
    <Validator value-bind='$page.form.files' onValidate={(files) => files?.some(f => f.invalid) && "Only images with size less or equal to 1 MB are allowed." } />
    <UploadButton
        text="Choose files"
        url="#"
        multiple
        onUploadStarting="onUploadStarting"
        style='width: fit-content'
        icon='search'
    />
    <div>
        <div text="Files to upload:" style='font-weight: 600; margin-bottom: 4px;'/>
        <Grid
            columns={[
                {
                    header: 'File name',
                    field: 'file.name',
                    class: {
                        invalidfile: bind('$record.invalid.type')
                    }
                },
                {
                    header: 'File size',
                    field: 'size',
                    value: computable('$record.file.size', s => {
                        if (s >= 1e6) return `${(s / 1e6).toFixed(1)} MB`;
                        return `${Math.round(s / 1e3, 1)} KB`;
                    }),
                    align: 'right',
                    defaultWidth: 80,
                    class: {
                        invalidfile: bind('$record.invalid.size')
                    }
                },
                {
                    align: 'center',
                    style: 'padding-left: 16px',
                    items: <cx>
                        <button
                            onClick="onRemoveFile"
                            text='x'
                            tooltip-tpl="Remove file: {$record.file.name}"
                            style="padding: 4px !important"
                            disabled-bind='$page.form.uploadInProgress'
                        />
                    </cx>,
                    defaultWidth: 50,
                },
            ]}
            records-bind='$page.form.files'
            emptyText="Choose image type files with size not greater than 1 MB."
            style='width: 100%'
        />
    </div>
    <div>
        Progress:
        <ProgressBar style='width: 100%' value-bind='$page.form.progress' text-tpl='{$page.form.progress:p;0}'/>
    </div>
    <Repeater records-bind='$page.form.errors' visible-expr='{$page.form.invalid} && {$page.form.visited}'>
        <div class='invalidfile' text-bind='$record.message'></div>
    </Repeater>
    <div style='display: flex; justify-content: space-between;'>
        <Button text='Clear form' onClick='onClearForm' icon='clear' disabled-bind='$page.form.uploadInProgress' />
        <Button text="Upload" onClick="upload" style='width: fit-content; margin-left: auto' disabled-bind='$page.form.uploadInProgress' icon='file' />
    </div>
</ValidationGroup>
Copied!Cx Fiddle