file-upload.js

  1. import { queryOne, formatBytes } from '@ecl/dom-utils';
  2. import EventManager from '@ecl/event-manager';
  3. /**
  4. * @param {HTMLElement} element DOM element for component instantiation and scope
  5. * @param {Object} options
  6. * @param {String} options.groupSelector Selector for file upload form group
  7. * @param {String} options.buttonSelector Selector for file upload button
  8. * @param {String} options.listSelector Selector for list of file names
  9. * @param {String} options.labelChoose Label choose state
  10. * @param {String} options.labelReplace Label replace state
  11. * @param {Boolean} options.attachChangeListener Whether or not to bind change events on toggle
  12. */
  13. export class FileUpload {
  14. /**
  15. * @static
  16. * Shorthand for instance creation and initialisation.
  17. *
  18. * @param {HTMLElement} root DOM element for component instantiation and scope
  19. *
  20. * @return {FileUpload} An instance of FileUpload.
  21. */
  22. static autoInit(root, { FILE_UPLOAD: defaultOptions = {} } = {}) {
  23. const fileUpload = new FileUpload(root, defaultOptions);
  24. fileUpload.init();
  25. root.ECLFileUpload = fileUpload;
  26. return fileUpload;
  27. }
  28. /**
  29. * @event FileUpload#onSelection
  30. */
  31. /**
  32. * An array of supported events for this component.
  33. *
  34. * @type {Array<string>}
  35. * @memberof FileUpload
  36. */
  37. supportedEvents = ['onSelection'];
  38. constructor(
  39. element,
  40. {
  41. groupSelector = '[data-ecl-file-upload-group]',
  42. buttonSelector = '[data-ecl-file-upload-button]',
  43. listSelector = '[data-ecl-file-upload-list]',
  44. labelChoose = 'data-ecl-file-upload-label-choose',
  45. labelReplace = 'data-ecl-file-upload-label-replace',
  46. attachChangeListener = true,
  47. } = {},
  48. ) {
  49. // Check element
  50. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  51. throw new TypeError(
  52. 'DOM element should be given to initialize this widget.',
  53. );
  54. }
  55. this.element = element;
  56. this.eventManager = new EventManager();
  57. // Options
  58. this.groupSelector = groupSelector;
  59. this.buttonSelector = buttonSelector;
  60. this.listSelector = listSelector;
  61. this.labelChoose = labelChoose;
  62. this.labelReplace = labelReplace;
  63. this.attachChangeListener = attachChangeListener;
  64. // Private variables
  65. this.fileUploadGroup = null;
  66. this.fileUploadInput = null;
  67. this.fileUploadButton = null;
  68. this.fileUploadList = null;
  69. // Bind `this` for use in callbacks
  70. this.handleChange = this.handleChange.bind(this);
  71. }
  72. /**
  73. * Initialise component.
  74. */
  75. init() {
  76. if (!ECL) {
  77. throw new TypeError('Called init but ECL is not present');
  78. }
  79. ECL.components = ECL.components || new Map();
  80. this.fileUploadGroup = this.element.closest(this.groupSelector);
  81. this.fileUploadInput = this.element;
  82. this.fileUploadButton = queryOne(this.buttonSelector, this.fileUploadGroup);
  83. this.fileUploadList = queryOne(this.listSelector, this.fileUploadGroup);
  84. // Bind events on input
  85. if (this.attachChangeListener && this.fileUploadInput) {
  86. this.fileUploadInput.addEventListener('change', this.handleChange);
  87. }
  88. // Set ecl initialized attribute
  89. this.element.setAttribute('data-ecl-auto-initialized', 'true');
  90. ECL.components.set(this.element, this);
  91. }
  92. /**
  93. * Register a callback function for a specific event.
  94. *
  95. * @param {string} eventName - The name of the event to listen for.
  96. * @param {Function} callback - The callback function to be invoked when the event occurs.
  97. * @returns {void}
  98. * @memberof FileUpload
  99. * @instance
  100. *
  101. * @example
  102. * // Registering a callback for the 'onSelection' event
  103. * fileUpload.on('onSelection', (event) => {
  104. * console.log('Open event occurred!', event);
  105. * });
  106. */
  107. on(eventName, callback) {
  108. this.eventManager.on(eventName, callback);
  109. }
  110. /**
  111. * Trigger a component event.
  112. *
  113. * @param {string} eventName - The name of the event to trigger.
  114. * @param {any} eventData - Data associated with the event.
  115. * @memberof FileUpload
  116. */
  117. trigger(eventName, eventData) {
  118. this.eventManager.trigger(eventName, eventData);
  119. }
  120. /**
  121. * Destroy component.
  122. */
  123. destroy() {
  124. if (this.attachChangeListener && this.fileUploadInput) {
  125. this.fileUploadInput.removeEventListener('change', this.handleChange);
  126. }
  127. if (this.element) {
  128. this.element.removeAttribute('data-ecl-auto-initialized');
  129. ECL.components.delete(this.element);
  130. }
  131. }
  132. /**
  133. * @param {Event} e
  134. *
  135. * @fires FileUpload#onSelection
  136. */
  137. handleChange(e) {
  138. if (!('files' in e.target)) {
  139. if (this.fileUploadButton.hasAttribute(this.labelChoose)) {
  140. this.fileUploadButton.innerHTML = this.fileUploadButton.getAttribute(
  141. this.labelChoose,
  142. );
  143. }
  144. return;
  145. }
  146. let fileList = '';
  147. // Get file names
  148. Array.prototype.forEach.call(e.target.files, (file) => {
  149. const fileSize = formatBytes(file.size, 1);
  150. const fileExtension = file.name.split('.').pop();
  151. fileList += `<li class="ecl-file-upload__item">
  152. <span class="ecl-file-upload__item-name">${file.name}</span>
  153. <span class="ecl-file-upload__item-meta">(${fileSize} - ${fileExtension})</span>
  154. </li>`;
  155. });
  156. // Update file list
  157. this.fileUploadList.innerHTML = fileList;
  158. // Update button label
  159. if (this.fileUploadButton.hasAttribute(this.labelReplace)) {
  160. this.fileUploadButton.innerHTML = this.fileUploadButton.getAttribute(
  161. this.labelReplace,
  162. );
  163. }
  164. // Trigger custom onSelection event
  165. const eventDetails = { files: e.target.files, event: e };
  166. this.trigger('onSelection', eventDetails);
  167. }
  168. }
  169. export default FileUpload;