Today we’ll implement a progress indicator using Livewire. Fly your app close to your users with Fly.io, get your Laravel app running in minutes!
In the article Chunked File Upload with Livewire we easily uploaded a file in separate, smaller chunks using Livewire’s upload function. Today, we’ll create a progress bar indicator to let our users know how much progress has been made uploading the file.
Version Notice. This article focuses on Livewire v2, details for Livewire v3 would be different though!
What’s in a Progress?
We can imagine progress as an accumulation of “small wins” leading up to a certain expectation. It is a portrait that summarizes how far we’ve come from completing our little wins towards an “expected goal”.
Progress can be measurable—if we can identify our “expected goal” and list down “small wins” that add up to it.
Today, the progress we’re calculating is the successful upload of each chunk out of the total chunks of a file to upload. Each chunk uploaded is a small win towards completely uploading a file.
Our calculation would be as follows: Progress Percentage = “Number of Small Wins So Far” / “Expected Total Wins” * 100%.
This means we’ll need a $chunkCount
to let us know how many chunks we’re expecting ( Expected Total Wins ), $uploadedCount
to keep track of how many chunks have been successfully uploaded ( Number of Small Wins so Far ), and finally, a separate $progressPercentage
for our progress bar to use ( Progress! ).
Stepping Towards Progress
In order to display progress of separate wins for our file upload, it’s important to accumulate each win and update our client with the total progress.
This is where Livewire comes in. Livewire provides us with just the right bridge of data sharing mechanism to keep track of the completed chunk uploads, and data lifecycle hooks to recalculate our progress and make implementing a progress bar a breeze:
- We’ll want to automatically share progress variables between client and server, so we declare public attributes
$chunkCount
,$uploadedCount
, and$progressPercentage
. - We’ll use a progress bar element in our html as our progress indicator and bind its value with the value of
$progressPercentage
- Every time a chunk gets successfully uploaded, we intercept this upload completion in the server by using Livewire’s updated hook. From there we can increment our
$uploadedCount
, and recalculate the$progressPercentage
right before the Livewire component sends back a success response to the Livewire view. Once the Livewire view receives this update in our$progressPercentage
‘s value, it should trigger a re-rendering of the<progress>
element bound to our attribute, thereby updating the displayed progress for our users to see!
The Indicator of Progress
Progress is calculable, and so we’ll first need to declare variables we’ll be using in calculating this value in our Livewire component:
// app/Http/Livewire/ChunkedFileUpload.php
public $chunkCount;
public $uploadedCount;
public $progressPercentage;
We’re building on top of the setup of our article here, where our users can select their file and click on a button to trigger a custom JavaScript function uploadChunks()
to slice and upload chunks from their selected file:
<!-- app/resources/views/livewire/chunked-file-upload.php -->
<input type="file" id="myFile"/>
<button type="button" id="submit" onclick="uploadChunks()">Submit</button>
Every time a file gets submitted for upload, we’ll initialize our progress variables to correspond to a new file’s progress.
uploadChunks()
is triggered every time our user requests for a file upload, so let’s add our progress re-initialization here:
<script>
...
function uploadChunks(){
// File Details
const file = document.querySelector('#myFile').files[0];
+ @this.set('chunkCount', Math.ceil(file.size/@js($chunkSize)));
+ @this.set('uploadedCount', 0);
+ @this.set('progressPercentage', 0);
...
}
</script>
Next, let’s add in a progress bar in our Livewire view. We’ll wire it with the $progressPercentage
attribute declared above. This way, any change happening on the $progressPercentage
attribute will refresh and calculate the value of our progress bar.
<!-- app/resources/views/livewire/chunked-file-upload.php -->
<input type="file" id="myFile"/>
<button type="button" id="submit" onclick="uploadChunks()">Submit</button>
+ @if( $progressPercentage )
+ <progress max="100" wire:model="progressPercentage" /></progress>
+ @endif
Now that we have our progress bar dancing to the tune of the $progressPercentage
attribute, we’re ready to update this progress every time we complete a small win—that is, every time we successfully upload a chunk of our file.
This is How we Progress
We’ll update our progress every time a chunk gets successfully uploaded. In order to do so, we’ll have to answer two questions—how do we know when a chunk completes uploading, and how do we react to this completion?
We’re building over a setup that uses Livewire’s upload function to upload our file chunk:
@this.upload('fileChunk', chunk, ...);
Notice the attributes passed in the function: chunk
is the JavaScript variable that holds reference to a chunk of our file, while fileChunk
maps to a public attribute in our Livewire component declared as $fileChunk
.
When this function runs, the Livewire view does a bunch of uploading process for the variable chunk
into a temporary folder in our server. Once it completes uploading the file chunk, it then updates the mapped attribute $fileChunk
with details on the uploaded chunk. Livewire conveniently provides us the updated
hook which we can use to inject logic after this “update” happens on our variable.
From this hook, we can recalculate the value of $progressPercentage
. Every time a chunk gets uploaded, we increment the number of chunks uploaded $uploadedCount
. Then we’ll divide this by our expected finish—the total number of chunks: $chunkCount
, and finally multiply it by a 100 to get our $progressPercentage
:
public function updatedFileChunk()
{
// Update progress here
$this->uploadedCount += 1;
$this->progressPercentage
= ($this->uploadedCount / $this->chunkCount) * 100;
...
}
Our updatedFileChunk()
hook recalculates and updates our $progressPercentage
right before the Livewire component responds to the client regarding the successful upload.
The change sent back from our Livewire component on the $progressPercentage
refreshes the <progress>
element bound to it with a new value, moving our progress indicator.
That’s it! Try uploading your file now, and see that progress bar moving towards a 100!