Usage
FileUploader
FileUploader allows users to pick one or more files to upload.
FileUploader itself actually does not upload anything to the server.
FileUploader is a composition of a few subcomponents:
JavaScript Plugin
For full functionality, you need to provide Spirit JavaScript, which will handle toggling of the FileUploader component:
<script src="node_modules/@lmc-eu/spirit-web/js/cjs/spirit-web.min.js" async></script>
You will find the full documentation of the plugin below on this page.
Please consult the main README for how to include JavaScript plugins.
Or, feel free to write the controlling script yourself.
FileUploader
This is the main wrapper for the whole composition. It provides proper spacing for its subcomponents:
<div class="FileUploader" data-spirit-toggle="fileUploader">
<!-- FileUploaderInput -->
<!-- FileUploaderList -->
</div>
Fluid Width
By adding the FileUploader--fluid modifier class, FileUploader can take up all the available horizontal space:
<div class="FileUploader FileUploader--fluid" data-spirit-toggle="fileUploader">
<!-- FileUploaderInput -->
<!-- FileUploaderList -->
</div>
FileUploaderInput
FileUploaderInput is a file picker built around the native HTML file input.
If supported by the device, FileUploaderInput automatically turns on the drag-and-drop functionality (signalized by the has-drag-and-drop state class on the root element).
<div class="FileUploaderInput" data-spirit-element="wrapper">
<label for="file-uploader" class="FileUploaderInput__label">Label</label>
<input
type="file"
id="file-uploader"
name="attachment"
class="FileUploaderInput__input"
data-spirit-element="input"
/>
<div class="FileUploaderInput__dropZone" data-spirit-element="dropZone">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#upload" />
</svg>
<label for="file-uploader" class="FileUploaderInput__dropZoneLabel">
<span class="FileUploaderInput__link link-primary link-underlined">Upload your file</span>
<span class="FileUploaderInput__dragAndDropLabel">or drag and drop here</span>
</label>
<div class="FileUploaderInput__helperText">Max file size is 10 MB</div>
</div>
</div>
Selecting Multiple Files at Once
To pick more than one file, just add the multiple attribute to the native input element:
<input
type="file"
id="file-upload"
name="attachment"
class="FileUploaderInput__input"
data-spirit-element="input"
multiple
/>
Maximum File Size (JavaScript)
The maximum size of the uploaded file that is validated by the JavaScript plugin can be adjusted. The default value is 10 MB. To increase the limit for example to 20 MB, add the data-spirit-max-file-size attribute:
👉 Learn how file sizes are calculated
<div class="FileUploader" data-spirit-toggle="fileUploader">
<div class="FileUploaderInput" data-spirit-element="wrapper" data-spirit-max-file-size="20971520">
<!-- ... -->
</div>
<!-- FileUploaderList -->
</div>
Maximum Number of Files in Queue (JavaScript)
Limit of the maximum number of uploaded files. The default value is 10, but any value can be set via the data-spirit-file-queue-limit attribute:
<div class="FileUploader" data-spirit-toggle="fileUploader">
<div class="FileUploaderInput" data-spirit-element="wrapper" data-spirit-file-queue-limit="2">
<!-- ... -->
</div>
<!-- FileUploaderList -->
</div>
Input Behavior When the Queue Is Filled (JavaScript)
Using the data-spirit-queue-limit-behavior attribute together with the desired limit for the queue, you can set the input/drop zone to be hidden or disabled when the file queue limit is reached. Available options are: hide, disable, or none (default).
If you set the value of data-spirit-queue-limit-behavior to disable, the input will be disabled. When you set it to hide, the input disappears completely. After removing a file from the queue, the input is restored.
<div class="FileUploader" data-spirit-toggle="fileUploader">
<div
class="FileUploaderInput"
data-spirit-element="wrapper"
data-spirit-file-queue-limit="2"
data-spirit-queue-limit-behavior="hide"
>
<!-- ... -->
</div>
<!-- FileUploaderList -->
</div>
Allowed File Types
Use the accept HTML attribute to restrict what file types can be uploaded. This way, it is also validated during drag and drop. For example, to accept Microsoft Word documents:
<input
type="file"
id="file-uploader"
name="attachment"
class="FileUploaderInput__input"
data-spirit-element="input"
accept=".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
/>
Required Input
To mark the input as required, simply add the FileUploaderInput__label--required to the label:
<div class="FileUploaderInput" data-spirit-element="wrapper">
<label for="file-uploader" class="FileUploaderInput__label FileUploaderInput__label--required">Label</label>
<input
type="file"
id="file-uploader"
name="attachment"
class="FileUploaderInput__input"
data-spirit-element="input"
/>
<div class="FileUploaderInput__dropZone" data-spirit-element="dropZone">
<!-- … -->
</div>
</div>
⚠️ We don't use the required attribute on the input element. This is because it triggers the browser's default validation, which can block form submission. Instead, the FileUploaderInput component is used to open the system file dialog, and our JS plugin manages the file(s). Please note, the validation for required files is not automatically handled. Developers need to implement this validation independently, using our JS plugin. This approach provides more flexibility and customization to meet specific validation requirements.
Validation States
Just like any other form component in Spirit, FileUploader implements the Validation state dictionary.
Validation states can be presented either by adding a CSS modifier class (FileUploaderInput--success, FileUploaderInput--warning, FileUploaderInput--danger), or by adding a JS interaction class when controlled by JavaScript (has-success, has-warning, has-danger).
When validated on server:
<div class="FileUploaderInput FileUploaderInput--success" data-spirit-element="wrapper">
<!-- Label -->
<!-- Drop zone with input -->
<div class="FileUploaderInput__validationText">Success Validation Text</div>
</div>
To render validation text as a list, use <ul> element inside of <div>.
<div class="FileUploaderInput FileUploaderInput--success" data-spirit-element="wrapper">
<!-- Label -->
<!-- Drop zone with input -->
<div class="FileUploaderInput__validationText">
<ul>
<li>First validation text</li>
<li>Second validation text</li>
</ul>
</div>
</div>
Disabled State
You can turn on the disabled state by adding the disabled attribute to the native input (FileUploaderInput__input). Do not forget to also add the class FileUploaderInput--disabled or is-disabled to the FileUploaderInput subcomponent as well.
<div class="FileUploaderInput FileUploaderInput--disabled" data-spirit-element="wrapper">
<!-- Label -->
<input
type="file"
id="file-uploader-disabled"
name="attachment"
class="FileUploaderInput__input"
disabled
data-spirit-element="input"
/>
<!-- Drop zone -->
</div>
JavaScript-Controlled Validation Text
When implementing client-side form validation, use JS interaction state classes (has-success, has-warning, has-danger) on the wrapping <div> element and render validation texts in a <div> or <ul> with data-spirit-element="validation_text" attribute. This way your JS remains disconnected from CSS that may or may not be prefixed.
Remember this approach is only valid for vanilla JS implementation. React components mix CSS with JS by design and handle CSS class-name prefixes their own way.
<div class="FileUploaderInput has-success" data-spirit-element="wrapper">
<!-- Label -->
<!-- Drop zone with input -->
<div data-spirit-element="validation_text">Success message inserted by JS</div>
</div>
Localization
There are four types of validation errors that have their default validation text. You can customize those validation texts using data-spirit-message-* that is passed to the components' root element.
<div class="FileUploaderInput has-danger" data-spirit-element="wrapper" data-spirit-message-maxfilesize="File too big!">
<!-- … -->
</div>
Validation Errors:
Name | Usage | Description |
---|---|---|
File Duplicity | data-spirit-message-duplicity="…" | When file is already in the queue |
Max File Size | data-spirit-message-maxfilesize="…" | When file size is over allowed limit |
Max Uploaded Files | data-spirit-message-maxuploadedfiles="…" | When the limit of uploaded files is reached |
Unsupported File Type | data-spirit-message-unsupported="…" | When the file is of unsupported type |
FileUploaderList
FileUploaderList is a simple wrapper that provides an accessible title and the list semantics for the selected files.
<h3 id="attachments" hidden>Attachments</h3>
<ul class="FileUploaderList" aria-labelledby="attachments" data-spirit-element="list">
<!-- Attachments, typically inserted by the JavaScript plugin -->
</ul>
FileUploaderAttachment
FileUploaderAttachment represents the files to be uploaded. It is expected to be a direct descendant of FileUploaderList, so it uses <li> as the root element.
Thanks to the text-truncate helper class, long file names are automatically truncated.
<li class="FileUploaderAttachment">
<!-- File icon: -->
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#file" />
</svg>
<!-- File name: -->
<span class="FileUploaderAttachment__name">
<span class="text-truncate">My resume.docx</span>
</span>
<!-- Remove button: -->
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
While you may insert FileUploaderAttachment into your FileUploaderList, in typical use cases it will live inside a <template> tag in the parent FileUploader. The <template> tag must be inserted inside the main wrapper element that has the data-spirit-toggle="fileUploader" attribute. Our JavaScript FileUploader plugin will then pick up the template and apply it on any attachments the user wants to upload.
<div class="FileUploader" data-spirit-toggle="fileUploader">
<template data-spirit-snippet="item">
<li class="FileUploaderAttachment" data-spirit-populate-field="item">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#file" />
</svg>
<span class="FileUploaderAttachment__name">
<span class="text-truncate" data-spirit-populate-field="name">File name</span>
</span>
<button type="button" class="FileUploaderAttachment__action" data-spirit-populate-field="button">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
</template>
<!-- FileUploaderInput -->
<!-- FileUploaderList -->
</div>
🖼️ Add data-spirit-imagePreview="true" to the FileUploaderAttachment component to generate preview for images in the file list.
<li class="FileUploaderAttachment" data-spirit-populate-field="item" data-spirit-imagePreview="true"></li>
Preview Image
You can add a preview image and image object fit to the FileUploaderAttachment. Object fit is cover by default.
<span class="FileUploaderAttachment__image">
<img src="http://placekitten.com/200/300" alt="" style="--file-uploader-attachment-image-object-fit: contain;" />
</span>
Full example:
<li class="FileUploaderAttachment">
<!-- Preview image: start -->
<span class="FileUploaderAttachment__image">
<img src="http://placekitten.com/200/300" alt="" style="--file-uploader-attachment-image-object-fit: contain;" />
</span>
<!-- Preview image: end -->
<span class="FileUploaderAttachment__name">
<span class="text-truncate">My resume.docx</span>
</span>
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
Custom Actions Slot
You can add custom actions to the FileUploaderAttachment.
<span class="FileUploaderAttachment__slot">
<!-- Custom action: start -->
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Edit</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#edit" />
</svg>
</button>
<!-- Custom action: end -->
</span>
Full example:
<li class="FileUploaderAttachment">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#file" />
</svg>
<span class="FileUploaderAttachment__name">
<span class="text-truncate">My resume.docx</span>
</span>
<span class="FileUploaderAttachment__slot">
<!-- Custom action: start -->
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Edit</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#edit" />
</svg>
</button>
<!-- Custom action: end -->
</span>
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
Attaching Functionality
As long as the FileUploaderAttachment lives inside the <template> tag, no functionality can be attached to it.
👉 Don't use IDs to attach functionality to custom actions. IDs must be unique, and the FileUploaderAttachment template is used for every attachment.
To attach functionality, you need to do so after the queuedFile.fileUploader event has been fired:
const editHandler = (event) => {
alert('Custom action clicked');
};
document.addEventListener('queuedFile.fileUploader', () => {
document.querySelectorAll('[data-element="example-action"]').forEach((element) => {
element.removeEventListener('click', editHandler); // Prevent multiple event listeners
element.addEventListener('click', editHandler);
});
});
Don't forget to remove the event listener when the FileUploaderAttachment is removed from the DOM:
let filesQueueCounter = 0;
const editHandler = (event) => {
alert('Edit button clicked');
};
document.addEventListener('queuedFile.fileUploader', () => {
filesQueueCounter++;
document.querySelectorAll('[data-element="example-action"]').forEach((element) => {
element.removeEventListener('click', editHandler); // Prevent multiple event listeners
element.addEventListener('click', editHandler);
});
});
document.addEventListener('unqueueFile.fileUploader', () => {
filesQueueCounter--;
if (filesQueueCounter === 0) {
document.querySelectorAll('[data-element="example-action"]').forEach((element) => {
element.removeEventListener('click', editHandler);
});
}
});
👉 Read more about fileUploader JavaScript events.
Passing Additional Metadata
When you need to send additional data along with the image you can do it with the meta argument on addToQueue and updateQueue methods of the FileUploader JS plugin. If any data in meta option will be present, the FileUploader adds an additional hidden input with JSON stringified data to the form. The identification of this input (name) will be the name of the file. Thus you can pass to the server any additional text data you need.
const customAddToQueue = (key: string, file: File) => {
// passing additional data using the `meta` argument
return FileUploader.addToQueue(key, file, { fileDescription: 'custom file description' });
};
const customUpdate = (_event: MouseEvent, file: File) => {
return FileUploader.updateQueue(file.name, file, { fileDescription: 'changing the custom description' });
};
Updating Image Preview with Cropped Image
When you are using FileUploader with some kind of image cropper you want to also update the image preview on FileUploader attachment when image changes. You can do this by passing a specific object in shape of coordinates ({ x: number, y: number, cropWidth: number, cropHeight: number, originalWidth: number, originalHeight: number }) to the meta argument. Then the coordinates will be applied to the preview image in the attachment.
const customUpdate = (event: MouseEvent, file: File) => {
const meta = { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 };
return FileUploader.updateQueue(file.name, file, meta);
};
Composition
This is how all subcomponents build up the complete FileUploader:
<!-- FileUploader: start -->
<div class="FileUploader" data-spirit-toggle="fileUploader">
<!-- FileUploaderAttachment template: start -->
<template data-spirit-snippet="item">
<li class="FileUploaderAttachment" data-spirit-populate-field="item">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#file" />
</svg>
<span class="FileUploaderAttachment__name">
<span class="text-truncate" data-spirit-populate-field="name">File name</span>
</span>
<button type="button" class="FileUploaderAttachment__action" data-spirit-populate-field="button">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
</template>
<!-- FileUploaderAttachment template: end -->
<!-- FileUploaderInput: start -->
<div class="FileUploaderInput" data-spirit-element="wrapper">
<label for="file-uploader-with-attachments" class="FileUploaderInput__label">Label</label>
<input
type="file"
id="file-uploader-with-attachments"
name="attachment"
class="FileUploaderInput__input"
data-spirit-element="input"
/>
<div class="FileUploaderInput__dropZone" data-spirit-element="dropZone">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#upload" />
</svg>
<label for="file-uploader-with-attachments" class="FileUploaderInput__dropZoneLabel">
<span class="FileUploaderInput__link link-primary link-underlined">Upload your file</span>
<span class="FileUploaderInput__dragAndDropLabel">or drag and drop here</span>
</label>
<div class="FileUploaderInput__helperText">Max file size is 10 MB</div>
</div>
</div>
<!-- FileUploaderInput: end -->
<!-- FileUploaderList: start -->
<h3 id="attachments" hidden>Attachments</h3>
<ul class="FileUploaderList" aria-labelledby="attachments" data-spirit-element="list">
<!-- FileUploaderAttachment INSERTED BY THE JS PLUGIN: start -->
<li class="FileUploaderAttachment">
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#file" />
</svg>
<span class="FileUploaderAttachment__name">
<span class="text-truncate">My resume.docx</span>
</span>
<button type="button" class="FileUploaderAttachment__action">
<span class="accessibility-hidden">Remove</span>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
</button>
</li>
<!-- FileUploaderAttachment INSERTED BY THE JS PLUGIN: end -->
</ul>
<!-- FileUploaderList: end -->
</div>
<!-- FileUploader: end -->
JavaScript Plugin API
Method | Description |
---|---|
getInstance | Static method which allows you to get the FileUploader instance associated with a DOM element. |
getOrCreateInstance | Static method which allows you to get the FileUploader instance associated with a DOM element, or create a new one in case it wasn’t initialized. |
getFileQueue | Returns a list of files in the queue. |
addToQueue | Adds file to the queue. |
updateQueue | Updates file in the queue. |
removeFromQueue | Removes file from the queue. |
clearFileQueue | Deletes all the files in the queue. |
const myUploaderInstance = FileUploader.getInstance('#myUploader'); // Returns a file uploader instance
const fileList: File[] = myUploaderInstance.getFileQueue();
const input = myUploaderInstance.inputElement; // Returns an input element, for further use
JavaScript Events
Method | Description |
---|---|
queueFile.fileUploader | This event is fired just before the file was added to the queue. |
queuedFile.fileUploader | This event is fired after the file was added to queue. |
unqueueFile.fileUploader | This event is fired just before the file was removed from the queue. |
unqueuedFile.fileUploader | This event is fired after the file was removed from queue. |
error.fileUploader | This event is fired when an error occurs when adding files to the queue. A specific error message is also returned together with the event. |
Implementation Notes
Name Attribute
⚠️ The name attribute on hidden input will be always set as an array [] for both single-file and multiple-file usage. Make sure you handle it correctly in your code.
The name attribute on the default input element is used for the component's picked attachments, which are actually hidden inputs using this attribute as an array. The name attribute remains on the original input element until the first attachment is picked, then the original name attribute disappears and its value is used for individual attachments. If the attachment is removed from the queue, its original value is returned to the input to avoid possible error states due to the missing name attribute.
Example: So if you set name="attachments" to the default input element, the attachments will then show name="attachments[]".