EU System

File uploads

Combination of fields and buttons to add documents or media to a form.

When to use

When there is a need to allow users to attach anything to a form (documents, pictures, videos, etc).

Implementation

In order to automatically attach event listeners on your file upload forms, add the following script to your page:

document.addEventListener('DOMContentLoaded', function() {
  ECL.fileUploads();
});
{#
  Parameters:
    - "id" (string) (default: 'default-id'): the id of the file input
    - "name" (string) (default: 'default-name'): the name of the file input
    - "value" (string) (default: 'No file selected.'): the name of the file selected
    - "label_browse" (string) (default: 'Browse'): the label of the Browse button
    - "has_upload" (boolean) (default: true): define if form upload has a "upload" button
    - "label_upload" (string)  (default: 'Upload'): the label of the Upload button
    - "is_disabled" (boolean) (default: false): define if form upload is disabled
    - "is_multiple" (boolean) (default: false): define if form upload is multiple
    - "has_error" (boolean) (default: false): define if form upload has error
    - "extra_classes" (string) (default: '')
    - "extra_attributes" (array) (default: []): format: [
        {
          "name" (string) (default: ''),
          "value" (string) (default: '')
        },
        ...
      ]
#}

{% include '@ecl/generic-component-form-file-upload' %}
/* Normal file upload */
{
  "id": "example-input-id-1",
  "name": "example-input-name-1"
}

/* Disabled file upload */
{
  "id": "example-input-id-2",
  "name": "example-input-name-2",
  "is_disabled": true
}

/* Multiple file upload */
{
  "id": "example-input-id-3",
  "name": "example-input-name-3",
  "is_multiple": true
}

/* File upload with error */
{
  "id": "example-input-id-4",
  "name": "example-input-name-4",
  "has_error": true
}

<!-- Normal file upload -->
<div class="ecl-file-upload">
  <div class="ecl-file-upload__value" tabindex="0">No file selected.</div>
  <label for="example-input-id-1">
    <span class="ecl-file-upload__browse" role="button" aria-controls="example-input-id-1" tabindex="0">Browse</span>
  </label>
  <button class="ecl-file-upload__upload" tabindex="0" type="submit">
    Upload
  </button>
  <input class="ecl-file-upload__input" name="example-input-name-1" type="file" id="example-input-id-1">
</div>

<!-- Disabled file upload -->
<div class="ecl-file-upload ecl-file-upload--is-disabled">
  <div class="ecl-file-upload__value" tabindex="0">No file selected.</div>
  <label for="example-input-id-2">
    <span class="ecl-file-upload__browse" role="button" aria-controls="example-input-id-2" tabindex="0">Browse</span>
  </label>
  <input class="ecl-file-upload__input" name="example-input-name-2" type="file" id="example-input-id-2" disabled>
</div>

<!-- Multiple file upload -->
<div class="ecl-file-upload">
  <div class="ecl-file-upload__value" tabindex="0">No file selected.</div>
  <label for="example-input-id-3">
    <span class="ecl-file-upload__browse" role="button" aria-controls="example-input-id-3" tabindex="0">Browse</span>
  </label>
  <button class="ecl-file-upload__upload" tabindex="0" type="submit">
    Upload
  </button>
  <input class="ecl-file-upload__input" name="example-input-name-3" type="file" id="example-input-id-3" multiple>
</div>

<!-- File upload with error -->
<div class="ecl-file-upload ecl-file-upload--has-error">
  <div class="ecl-file-upload__value" tabindex="0">No file selected.</div>
  <label for="example-input-id-4">
    <span class="ecl-file-upload__browse" role="button" aria-controls="example-input-id-4" tabindex="0">Browse</span>
  </label>
  <button class="ecl-file-upload__upload" tabindex="0" type="submit">
    Upload
  </button>
  <input class="ecl-file-upload__input" name="example-input-name-4" type="file" id="example-input-id-4">
</div>

  • Content:
    /**
     * File uploads related behaviors.
     */
    
    import { queryAll } from '@ecl/eu-base/helpers/dom';
    
    /**
     * @param {object} options Object containing configuration overrides
     */
    export const fileUploads = ({
      selector: selector = '.ecl-file-upload',
      inputSelector: inputSelector = '.ecl-file-upload__input',
      valueSelector: valueSelector = '.ecl-file-upload__value',
      browseSelector: browseSelector = '.ecl-file-upload__browse',
    } = {}) => {
      // SUPPORTS
      if (
        !('querySelector' in document) ||
        !('addEventListener' in window) ||
        !document.documentElement.classList
      )
        return null;
    
      // SETUP
      // set file upload element NodeLists
      const fileUploadContainers = queryAll(selector);
    
      // ACTIONS
      function updateFileName(element, files) {
        if (files.length === 0) return;
    
        let filename = '';
    
        for (let i = 0; i < files.length; i += 1) {
          const file = files[i];
          if ('name' in file) {
            if (i > 0) {
              filename += ', ';
            }
            filename += file.name;
          }
        }
    
        // Show the selected filename in the field.
        const messageElement = element;
        messageElement.innerHTML = filename;
      }
    
      // EVENTS
      function eventValueChange(e) {
        if ('files' in e.target) {
          const fileUploadElements = queryAll(valueSelector, e.target.parentNode);
    
          fileUploadElements.forEach(fileUploadElement => {
            updateFileName(fileUploadElement, e.target.files);
          });
        }
      }
    
      function eventBrowseKeydown(e) {
        // collect header targets, and their prev/next
        const isModifierKey = e.metaKey || e.altKey;
    
        const inputElements = queryAll(inputSelector, e.target.parentNode);
    
        inputElements.forEach(inputElement => {
          // don't catch key events when ⌘ or Alt modifier is present
          if (isModifierKey) return;
    
          // catch enter/space, left/right and up/down arrow key events
          // if new panel show it, if next/prev move focus
          switch (e.keyCode) {
            case 13:
            case 32:
              e.preventDefault();
              inputElement.click();
              break;
            default:
              break;
          }
        });
      }
    
      // BIND EVENTS
      function bindFileUploadEvents(fileUploadContainer) {
        // bind all file upload change events
        const fileUploadInputs = queryAll(inputSelector, fileUploadContainer);
        fileUploadInputs.forEach(fileUploadInput => {
          fileUploadInput.addEventListener('change', eventValueChange);
        });
    
        // bind all file upload keydown events
        const fileUploadBrowses = queryAll(browseSelector, fileUploadContainer);
        fileUploadBrowses.forEach(fileUploadBrowse => {
          fileUploadBrowse.addEventListener('keydown', eventBrowseKeydown);
        });
      }
    
      // UNBIND EVENTS
      function unbindFileUploadEvents(fileUploadContainer) {
        const fileUploadInputs = queryAll(inputSelector, fileUploadContainer);
        // unbind all file upload change events
        fileUploadInputs.forEach(fileUploadInput => {
          fileUploadInput.removeEventListener('change', eventValueChange);
        });
    
        const fileUploadBrowses = queryAll(browseSelector, fileUploadContainer);
        // bind all file upload keydown events
        fileUploadBrowses.forEach(fileUploadBrowse => {
          fileUploadBrowse.removeEventListener('keydown', eventBrowseKeydown);
        });
      }
    
      // DESTROY
      function destroy() {
        fileUploadContainers.forEach(fileUploadContainer => {
          unbindFileUploadEvents(fileUploadContainer);
        });
      }
    
      // INIT
      function init() {
        if (fileUploadContainers.length) {
          fileUploadContainers.forEach(fileUploadContainer => {
            bindFileUploadEvents(fileUploadContainer);
          });
        }
      }
    
      init();
    
      // REVEAL API
      return {
        init,
        destroy,
      };
    };
    
    // module exports
    export default fileUploads;
    
  • URL: /components/raw/eu-component-form-file-upload/eu-component-form-file-upload.js
  • Filesystem Path: ../../src/systems/eu/eu-component/eu-component-form/eu-component-form-file-upload/eu-component-form-file-upload.js
  • Size: 3.8 KB
  • Content:
    /*
     * File upload
     * @define file-upload
     */
    
    // Import base and generic
    @import '@ecl/ec-base/ec-base';
    @import '@ecl/generic-component-form-file-upload/generic-component-form-file-upload';
    
    // Use generic mixin
    @include exports('ec-component-form-file-upload') {
      @include ecl-file-upload();
    }
    
  • URL: /components/raw/eu-component-form-file-upload/eu-component-form-file-upload.scss
  • Filesystem Path: ../../src/systems/eu/eu-component/eu-component-form/eu-component-form-file-upload/eu-component-form-file-upload.scss
  • Size: 297 Bytes
  • Handle: @ecl/eu-component-form-file-upload
  • Tags: atom
  • Preview:
  • Filesystem Path: ../../src/systems/eu/eu-component/eu-component-form/eu-component-form-file-upload/eu-component-form-file-upload.twig