File Uploader

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="fileUploader" class="FileUploaderInput__label">Label</label>
                                                  <input type="file" id="fileUploader" 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="fileUploader" 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="fileUpload"
                                                  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:


                                                
                                                <div class="FileUploader" data-spirit-toggle="fileUploader">
                                                  <div class="FileUploaderInput" data-spirit-element="wrapper" data-spirit-max-file-size="20000000">
                                                    <!-- ... -->
                                                  </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="fileUploader"
                                                  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 required attribute to the native input element and the FileUploaderInput__label--required to the label:


                                                
                                                <div class="FileUploaderInput" data-spirit-element="wrapper">
                                                  <label for="fileUploader" class="FileUploaderInput__label FileUploaderInput__label--required">Label</label>
                                                  <input
                                                    type="file"
                                                    id="fileUploader"
                                                    name="attachment"
                                                    class="FileUploaderInput__input"
                                                    data-spirit-element="input"
                                                    required
                                                  />
                                                  <div class="FileUploaderInput__dropZone" data-spirit-element="dropZone">
                                                    <!-- … -->
                                                  </div>
                                                </div>

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>

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="fileUploaderDisabled"
                                                    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> 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="fileUploaderWithAttachments" class="FileUploaderInput__label">Label</label>
                                                    <input
                                                      type="file"
                                                      id="fileUploaderWithAttachments"
                                                      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="fileUploaderWithAttachments" 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[]".