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).
<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>