Multi File Upload
This example demonstrates how to use the UploadButton component to handle multiple file uploads with validation and progress tracking.
<div controller={PageController}>
<ValidationGroup invalid={m.form.invalid} errors={m.form.errors}>
<Validator
value={m.form.files}
onValidate={(files: FileEntry[] = []) =>
files.length < 1 && "Please select at least one file."
}
/>
<Validator
value={m.form.files}
onValidate={(files: FileEntry[]) =>
files?.some((f) => f.invalid) &&
"Only images with size up to 1 MB are allowed."
}
/>
<UploadButton
text="Choose files"
url="#"
multiple
onUploadStarting="onUploadStarting"
icon="search"
/>
<LabelsTopLayout vertical mod="stretch">
<LabeledContainer label="Files to upload:">
<Grid
records={m.form.files}
recordAlias={m.$record}
emptyText="Select image files (max 1 MB each)."
columns={[
{
header: "File name",
field: "file.name",
class: expr(m.$record.invalid.type, (invalid) =>
invalid ? "text-red-600 italic" : "",
),
},
{
header: "Size",
value: computable(m.$record.file.size, (s) =>
formatFileSize(s ?? 0),
),
align: "right",
defaultWidth: 80,
class: expr(m.$record.invalid.size, (invalid) =>
invalid ? "text-red-600 italic" : "",
),
},
{
align: "center",
defaultWidth: 50,
items: (
<Button
icon="x"
mod="hollow"
onClick="onRemoveFile"
disabled={m.form.uploadInProgress}
/>
),
},
]}
/>
</LabeledContainer>
<LabeledContainer label="Progress:" visible={m.form.uploadInProgress}>
<ProgressBar value={m.form.progress} />
</LabeledContainer>
</LabelsTopLayout>
<div visible={isNonEmpty(m.form.errors)} class="mt-4">
<Repeater
records={m.form.errors}
recordAlias={m.$error}
visible={expr(
m.form.invalid,
m.form.visited,
(invalid, visited) => invalid && visited,
)}
>
<div class="text-red-600 italic" text={m.$error.message} />
</Repeater>
</div>
<div class="flex justify-between mt-4">
<Button
text="Clear"
onClick="onClearForm"
icon="trash"
disabled={m.form.uploadInProgress}
/>
<Button
text="Upload"
onClick="upload"
icon="upload"
disabled={m.form.uploadInProgress}
/>
</div>
</ValidationGroup>
</div>
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
How It Works
By default, UploadButton automatically uploads each file as soon as it is selected using XMLHttpRequest. When multiple files are selected, each file is sent in a separate request.
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, giving you full control over the upload logic.
onUploadStarting(xhr, instance, file) {
// Validate the file
const invalidSize = file.size > 1e6;
const invalidType = !file.type.startsWith("image/");
// Store the file for later upload
instance.store.update(m.form.files, append, {
file,
invalid: invalidSize || invalidType ? { size: invalidSize, type: invalidType } : undefined,
});
// Return false to prevent automatic upload
return false;
}
Progress Tracking
XMLHttpRequest provides better support for upload progress tracking than fetch:
function uploadFilesWithProgress(url, files, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
files.forEach((file) => formData.append("file", file));
xhr.upload.onprogress = (e) =>
e.lengthComputable && onProgress(e.loaded / e.total);
xhr.onload = () =>
xhr.status >= 200 && xhr.status < 300
? resolve(xhr)
: reject(new Error(`Failed: ${xhr.status}`));
xhr.open("POST", url);
xhr.send(formData);
});
}
Validation
Use ValidationGroup with Validator components to validate file selection:
<ValidationGroup invalid={m.form.invalid} errors={m.form.errors}>
<Validator
value={m.form.files}
onValidate={(files = []) =>
files.length < 1 && "Please select at least one file."
}
/>
<Validator
value={m.form.files}
onValidate={(files) =>
files?.some((f) => f.invalid) && "Only valid images allowed."
}
/>
{/* ... form content ... */}
</ValidationGroup>