File Uploads
To upload a file with WireDrupal, add the \Drupal\wire\WithFileUploads
trait to your component.
As with any other input field types, you can use wire:model
on file inputs.
Here's an example of a component that handles uploading an image:
use Drupal\wire\TemporaryUploadedFile;
use Drupal\wire\View;
use Drupal\wire\WireComponent;
use Drupal\wire\WithFileUploads;
use Drupal\file\Entity\File;
/**
* Implementation for PostJob Wire Component.
*
* @WireComponent(
* id = "upload_image",
* label = @Translation("Upload Image Example"),
* )
*/
class UploadImage extends WireComponent {
use WithFileUploads;
public $image;
public function saveImage(): void {
$tempFile = $this->image ?? NULL;
if (!$tempFile instanceof TemporaryUploadedFile) {
return;
}
// Store file into temporary directory.
if (!$filepath = $tempFile->store()) {
return;
}
// Create File entity.
try {
$file = File::create([
'filename' => basename($filepath),
'uri' => $filepath,
'status' => 1,
'uid' => \Drupal::currentUser()->id(),
]);
$file->save();
} catch (\Exception $e) {
watchdog_exception('wire_save_file', $e);
}
}
public function render(): ?View {
return View::fromTpl('upload_image');
}
}
And the TWIG associated with above:
<div>
<input
wire:model="image"
type="file"
accept="image/*"
id="image"
>
<button wire:click="saveImage">Save Image</button>
</div>
When an image is chosen, the wire:model
directive will store the file in your temporary directory while triggering saveImage
action will attempt to create a Drupal \Drupal\file\Entity\File
instance.
It is possible to preview the file before it is being saved to Drupal's File System(from Temp directory).
To achieve this, the temporaryUrl
method is available as part of Wire's TemporaryUploadedFile
object which will retrieve the path to the temporarily stored file.
However, due to TwigSandboxPolicy
, in order to use the method you need to register the method as allowed in your settings.php
file as following:
$settings['twig_sandbox_allowed_classes'] = [
'\Drupal\wire\TemporaryUploadedFile',
];
After witch you can use the method as following:
<div>
<input
wire:model="image"
type="file"
accept="image/*"
id="image"
>
{% if image %}
<img src="{{ image.temporaryUrl() }}" style="width: 100px">
{% endif %}
<button wire:click="saveImage">Save Image</button>
</div>
It is possible to remove the file from temporary directory.
To achieve this, add a new action into your template:
{% if image %}
<button wire:click="removeImage">Remove Image</button>
{% endif %}
And the called method itself:
public function removeImage(): void {
$this->removeUpload('image', $this->image->getFilename());
}
Multiple file uploads are handled automatically by detecting the multiple attribute on the <input> tag. E.g:
<input
wire:model="image"
type="file"
accept="image/*"
id="image"
multiple
>
*Make sure your property is defined as array in the PHP component.
You can display a loading indicator scoped to the file upload like so:
<div wire:loading wire:target="image">Uploading...</div>
Now, while the file is uploading the "Uploading..." message will be shown and then hidden when the upload is finished.
This works with any other Loading States API.
On every file upload JavaScript events are dispatched on the <input>
element for custom JavaScript to listen to.
Here are the dispatched events:
Event | Description |
---|---|
wire-upload-start | Dispatched when the upload starts |
wire-upload-finish | Dispatches if the upload is successfully finished |
wire-upload-error | Dispatches if the upload fails in some way |
wire-upload-progress | Dispatches an event containing the upload progress percentage as the upload progresses |
Above are useful for showing for example the progress bar.
Here is an example implementation with AlpineJS:
<div
x-data="{ isUploading: false, progress: 0 }"
x-on:wire-upload-start="isUploading = true"
x-on:wire-upload-finish="isUploading = false"
x-on:wire-upload-error="isUploading = false"
x-on:wire-upload-progress="progress = $event.detail.progress"
>
<input
wire:model="image"
type="file"
accept="image/*"
id="image"
>
{# Progress Bar #}
<div x-show="isUploading">
<progress max="100" x-bind:value="progress"></progress>
</div>
{% if image %}
<img src="{{ image.temporaryUrl() }}" style="width: 100px">
<button wire:click="removeImage">Remove Image</button>
{% endif %}
<button wire:click="saveImage">Save Image</button>
</div>
To integrate with file-uploading libraries or have a more granular controll over the behaviour, Wire exposes dedicated JavaScript functions.
<script>
let file = document.querySelector('input[type="file"]').files[0];
// Upload a file.
@this.upload('myFile', file, (uploadedFilename) => {
// Success callback.
}, () => {
// Error callback.
}, (event) => {
// Progress callback.
})
// Upload multiple files.
@this.uploadMultiple('myFiles', [file], successCallback, errorCallback, progressCallback);
// Remove single file from multiple uploaded files.
@this.removeUpload('myFiles', uploadedFilename, successCallback)
</script>
Note: the
@this
directive compiles to the following string for JavaScript to interpret:Wire.find([component-id])
See A complete Drag and Drop file uploader with progressbar using Wire Drupal, Alpinejs and tailwindcss for a complete example.