How to Handle File Uploads with Progress Tracking and Custom Upload Behavior in Element UI
The Element UI Upload component provides a complete file upload solution with built-in progress tracking through XHR-based requests, lifecycle hooks, and customizable HTTP implementations via the httpRequest prop.
Handling file uploads with progress tracking and custom upload behavior in the ElemeFE/element repository relies on a modular architecture that separates UI concerns from network logic. The component found in packages/upload/src/upload.vue orchestrates file selection, validation, and request management, while delegating actual HTTP transmission to a pluggable request handler. Understanding this separation allows developers to intercept uploads for preprocessing, replace the default XHR implementation, or create completely custom user interfaces while retaining progress reporting capabilities.
Understanding the Upload Component Architecture
The Upload component consists of three coordinated modules that handle distinct responsibilities:
upload.vue– The core orchestrator located atpackages/upload/src/upload.vuemanages file selection, enforces limits, executes lifecycle hooks, and coordinates between the UI and network layers.ajax.js– The default XHR implementation atpackages/upload/src/ajax.jshandlesFormDataconstruction, progress events, and response parsing.upload-list.vue– The presentation layer atpackages/upload/src/upload-list.vuerenders file lists and displays<el-progress>bars during active uploads.
This architecture allows you to replace any individual piece—such as substituting the default AJAX transport with a fetch-based implementation—without modifying the component's core logic.
The File Upload Lifecycle
File Selection and Queueing
When users select files through the native <input type="file"> element, the handleChange method in upload.vue captures the FileList and initiates processing:
handleChange(ev) {
const files = ev.target.files;
if (!files) return;
this.uploadFiles(files);
}
(see lines 62-66 of upload.vue)
The uploadFiles method enforces the limit and multiple constraints, then invokes onStart for each file. If autoUpload is enabled (the default), it immediately triggers the upload process; otherwise, files queue until manually started.
Before-Upload Validation
Before any network activity begins, the upload method checks the beforeUpload prop (lines 87-118). This hook supports three return types:
false– Cancels the upload immediatelyFileorBlob– Proceeds with the returned file (synchronous transformation)Promise<File|Blob>– Waits for async operations like image compression or server-side validation before proceeding
If the promise resolves, the component preserves raw-file metadata and proceeds to post; if rejected or returned as false, the upload aborts.
HTTP Request Execution
The post method constructs an options object containing callbacks for progress, success, and error, then delegates to the httpRequest prop (lines 135-161). By default, this references the ajax function, but you can inject any transport mechanism that conforms to the expected interface:
post(rawFile) {
const { uid } = rawFile;
const options = {
headers: this.headers,
withCredentials: this.withCredentials,
file: rawFile,
data: this.data,
filename: this.name,
action: this.action,
onProgress: e => {
this.onProgress(e, rawFile);
},
onSuccess: res => {
this.onSuccess(res, rawFile);
delete this.reqs[uid];
},
onError: err => {
this.onError(err, rawFile);
delete this.reqs[uid];
}
};
const req = this.httpRequest(options);
this.reqs[uid] = req;
}
The request object is stored in this.reqs keyed by the file's uid, enabling individual or bulk abortion via the component's abort(file?) method.
Progress Tracking and UI Updates
In packages/upload/src/ajax.js, the default transport attaches an onprogress listener to the XHR upload object (lines 39-46):
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100;
}
option.onProgress(e);
};
This calculates a percentage and invokes the onProgress callback supplied by upload.vue, which updates the file's percentage property. Meanwhile, upload-list.vue monitors the file status and renders an <el-progress> element whenever status === 'uploading' (lines 40-44).
Implementing Custom Upload Behavior
Custom HTTP Requests with httpRequest
Override the default XHR behavior by providing a function to the httpRequest prop. Your function receives an options object containing headers, withCredentials, file, data, filename, action, onProgress, onSuccess, and onError. This enables integration with alternative transports like fetch, Axios, or cloud storage SDKs.
Async Validation and Transformation
Use the beforeUpload hook to perform client-side validation or file manipulation. Return a Promise to handle async operations such as:
- Image compression or format conversion
- Virus scanning via client-side wasm
- Duplicate checking against existing uploads
The resolved value replaces the original file in the upload pipeline.
Manual Upload Control
Set :auto-upload="false" to queue files without immediate transmission. Trigger uploads manually by calling uploadFiles on the component reference, allowing batch operations or user-initiated submission flows.
Practical Implementation Example
The following implementation demonstrates disabled auto-upload, custom request handling with fetch, file size validation, and progress logging:
<template>
<el-upload
ref="upload"
class="custom-uploader"
action="https://api.example.com/upload"
:auto-upload="false"
:multiple="true"
:limit="5"
:on-start="handleStart"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-error="handleError"
:before-upload="validateFile"
:http-request="customRequest"
>
<el-button slot="trigger" size="small" type="primary">Select Files</el-button>
<el-button
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload"
>
Upload to Server
</el-button>
</el-upload>
</template>
<script>
export default {
methods: {
handleStart(file) {
console.log('Upload starting:', file.name);
},
handleProgress(event, file) {
console.log(`Upload progress for ${file.name}: ${event.percent.toFixed(1)}%`);
},
handleSuccess(response, file) {
console.log('Upload completed:', file.name, response);
},
handleError(error, file) {
console.error('Upload failed:', file.name, error);
},
validateFile(file) {
const maxSize = 2 * 1024 * 1024; // 2MB
if (file.size > maxSize) {
this.$message.error('File size exceeds 2MB limit');
return false;
}
// Async transformation example
return new Promise((resolve) => {
this.compressImage(file, (compressedBlob) => {
const transformedFile = new File(
[compressedBlob],
file.name,
{ type: file.type }
);
resolve(transformedFile);
});
});
},
customRequest(options) {
const formData = new FormData();
if (options.data) {
Object.entries(options.data).forEach(([key, value]) => {
formData.append(key, value);
});
}
formData.append(options.filename, options.file);
return fetch(options.action, {
method: 'POST',
body: formData,
credentials: options.withCredentials ? 'include' : 'same-origin',
headers: options.headers
})
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(options.onSuccess)
.catch(options.onError);
},
submitUpload() {
this.$refs.upload.submit();
},
compressImage(file, callback) {
// Image compression logic here
callback(file);
}
}
};
</script>
This example integrates all major customization points: synchronous validation, asynchronous file transformation, a fetch-based transport replacing the default XHR, and manual upload triggering.
Summary
- Modular Architecture – The Upload component separates concerns between
upload.vue(orchestration),ajax.js(transport), andupload-list.vue(UI), enabling targeted customizations. - Progress Tracking – Built-in progress reporting relies on
xhr.upload.onprogressin the default implementation, passing percentage data through theonProgresscallback to update UI elements. - Custom Transport – Replace the default XHR behavior by providing a custom
http-requestfunction that conforms to the options interface, supporting fetch, Axios, or SDK-based uploads. - Lifecycle Hooks – Use
before-uploadfor validation and transformation (supports async/Promise),on-startfor initiation side effects, andon-success/on-errorfor result handling. - Manual Control – Disable
auto-uploadto queue files indefinitely, then trigger uploads individually or in batches via component methods.
Frequently Asked Questions
How does Element UI calculate and display upload progress?
Element UI calculates upload progress in packages/upload/src/ajax.js by attaching an onprogress event listener to the XMLHttpRequest's upload property. When the browser fires progress events, the code computes e.percent = e.loaded / e.total * 100 and invokes the onProgress callback. This updates the file object's percentage property, which upload-list.vue binds to an <el-progress> component for visual feedback.
Can I use the Fetch API instead of XMLHttpRequest for file uploads?
Yes. Pass a custom function to the http-request prop that accepts the standard options object and returns a Promise or request handle. Your implementation should call options.onProgress with percentage data if needed, then resolve with options.onSuccess or reject with options.onError. This completely replaces the default XHR-based ajax.js implementation.
How do I abort an ongoing file upload?
Call the abort(file) method on the Upload component instance. If you pass a specific file object (or its uid), it aborts that individual request by looking up the stored request handle in this.reqs[uid]. Calling abort() without arguments iterates through all pending requests in this.reqs and aborts each one, effectively canceling all active uploads.
What is the difference between the before-upload and on-start hooks?
The before-upload hook executes before any network request begins and can block or transform the upload—return false to cancel, a File/Blob to replace the file, or a Promise for async validation. The on-start hook fires immediately after before-upload resolves successfully and purely signals that the upload is beginning; it cannot prevent the upload and is used for side effects like logging or UI state changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →