coral-spectrum/coral-component-fileupload/src/scripts/FileUpload.js
/**
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {BaseComponent} from '../../../coral-base-component';
import {BaseFormField} from '../../../coral-base-formfield';
import FileUploadItem from './FileUploadItem';
import base from '../templates/base';
import {transform, commons, validate} from '../../../coral-utils';
import {Decorator} from '../../../coral-decorator';
const CLASSNAME = '_coral-FileUpload';
const XHR_EVENT_NAMES = ['loadstart', 'progress', 'load', 'error', 'loadend', 'readystatechange', 'abort', 'timeout'];
/**
Enumeration for {@link FileUpload} HTTP methods that can be used to upload files.
@typedef {Object} FileUploadMethodEnum
@property {String} POST
Send a POST request. Used when creating a resource.
@property {String} PUT
Send a PUT request. Used when replacing a resource.
@property {String} PATCH
Send a PATCH request. Used when partially updating a resource.
*/
const method = {
POST: 'POST',
PUT: 'PUT',
PATCH: 'PATCH'
};
/**
@class Coral.FileUpload
@classdesc A FileUpload component that manages the upload process of multiple files. Child elements of FileUpload can
be given special attributes to enable functionality:
- <code>[coral-fileupload-select]</code>. Click to choose file(s), replacing existing files.
- <code>[coral-fileupload-dropzone]</code>. Drag and drop files to choose file(s), replacing existing files.
- <code>[coral-fileupload-clear]</code>. Click to remove all files from the queue.
- <code>[coral-fileupload-submit]</code>. Click to start uploading.
- <code>[coral-fileupload-abort]</code>. Click to abort all uploads.
- <code>[coral-fileupload-abortfile="filename.txt"]</code>. Click to abort a specific file, leaving it in the queue.
- <code>[coral-fileupload-removefile="filename.txt"]</code>. Click to remove a specific file from the queue.
- <code>[coral-fileupload-uploadfile="filename.txt"]</code>. Click to start uploading a specific file.
@htmltag coral-fileupload
@extends {HTMLElement}
@extends {BaseComponent}
@extends {BaseFormField}
*/
const FileUpload = Decorator(class extends BaseFormField(BaseComponent(HTMLElement)) {
/** @ignore */
constructor() {
super();
// Events
this._delegateEvents(commons.extend(this._events, {
// Clickable hooks
'click [coral-fileupload-submit]': '_onSubmitButtonClick',
'click [coral-fileupload-clear]': 'clear',
'click [coral-fileupload-select]': '_showFileDialog',
'click [coral-fileupload-abort]': 'abort',
'click [coral-fileupload-abortfile]': '_onAbortFileClick',
'click [coral-fileupload-removefile]': '_onRemoveFileClick',
'click [coral-fileupload-uploadfile]': '_onUploadFileClick',
// Drag & Drop zones
'dragenter [coral-fileupload-dropzone]': '_onDragAndDrop',
'dragover [coral-fileupload-dropzone]': '_onDragAndDrop',
'dragleave [handle="input"]': '_onDragAndDrop',
'drop [handle="input"]': '_onDragAndDrop',
// Accessibility
'capture:focus [coral-fileupload-select]': '_onButtonFocusIn',
'capture:focus [handle="input"]': '_onInputFocusIn',
'capture:blur [handle="input"]': '_onInputFocusOut'
}));
// Prepare templates
this._elements = {};
base.call(this._elements, {commons});
// Pre-define labellable element
this._labellableElement = this._elements.input;
// Used for items
this._uploadQueue = [];
// this should refer to the fileupload
this._doAddDragClass = this._doAddDragClass.bind(this);
this._doRemoveDragClass = this._doRemoveDragClass.bind(this);
this._positionInputOnDropZone = this._positionInputOnDropZone.bind(this);
// Reposition the input under the specified dropzone
this._observer = new MutationObserver(this._positionInputOnDropZone);
this._observer.observe(this, {
childList: true,
attributes: true,
attributeFilter: ['coral-fileupload-dropzone'],
subtree: true
});
}
/**
Name used to submit the data in a form.
@type {String}
@default ""
@htmlattribute name
@htmlattributereflected
*/
get name() {
return this._elements.input.name;
}
set name(value) {
this._reflectAttribute('name', value);
this._elements.input.name = value;
}
/**
This field's current value.
@type {String}
@default ""
@htmlattribute value
*/
get value() {
const item = this._uploadQueue ? this._getQueueItem(0) : null;
// The first selected filename, or the empty string if no files are selected.
return item ? `C:\\fakepath\\${item.file.name}` : '';
}
set value(value) {
if (value === '' || value === null) {
this._clearQueue();
this._clearFileInputValue();
} else {
// Throws exception if value is different than an empty string or null
throw new Error('Coral.FileUpload accepts a filename, which may only be programmatically set to empty string.');
}
}
/**
Whether this field is disabled or not.
@type {Boolean}
@default false
@htmlattribute disabled
@htmlattributereflected
*/
get disabled() {
return this._elements.input.disabled;
}
set disabled(value) {
this._elements.input.disabled = transform.booleanAttr(value);
this._reflectAttribute('disabled', this.disabled);
this.classList.toggle('is-disabled', this.disabled);
this._setElementState();
}
/**
Inherited from {@link BaseFormField#invalid}.
*/
get invalid() {
return super.invalid;
}
set invalid(value) {
super.invalid = value;
this._elements.input.setAttribute('aria-invalid', this.invalid);
this._setElementState();
}
/**
Whether this field is required or not.
@type {Boolean}
@default false
@htmlattribute required
@htmlattributereflected
*/
get required() {
return this._elements.input.required;
}
set required(value) {
this._elements.input.required = transform.booleanAttr(value);
this._reflectAttribute('required', this.required);
this.classList.toggle('is-required', this.required);
this._setElementState();
}
/**
Whether this field is readOnly or not. Indicating that the user cannot modify the value of the control.
@type {Boolean}
@default false
@htmlattribute readonly
@htmlattributereflected
*/
get readOnly() {
return this._readOnly || false;
}
set readOnly(value) {
this._readOnly = transform.booleanAttr(value);
this._reflectAttribute('readonly', this._readOnly);
this._setElementState();
}
/**
The names of the currently selected files.
When {@link Coral.FileUpload#multiple} is <code>false</code>, this will be an array of length 1.
@type {Array.<String>}
*/
get values() {
let values = this._uploadQueue.map((item) => `C:\\fakepath\\${item.file.name}`);
if (values.length && !this.multiple) {
values = [values[0]];
}
return values;
}
set values(values) {
if (Array.isArray(values)) {
if (values.length) {
this.value = values[0];
} else {
this.value = '';
}
}
}
/**
Inherited from {@link BaseFormField#labelledBy}.
*/
get labelledBy() {
return super.labelledBy;
}
set labelledBy(value) {
super.labelledBy = value;
// The specified labelledBy property.
const labelledBy = this.labelledBy;
// An array of element ids to label control, the last being the select button element id.
const ids = [];
const button = this.querySelector('[coral-fileupload-select]');
if (button) {
ids.push(button.id);
}
// If a labelledBy property exists,
if (labelledBy) {
// prepend the labelledBy value to the ids array
ids.unshift(labelledBy);
}
// Set aria-labelledby attribute on the labellable element joining ids array into space-delimited list of ids.
this._elements.input.setAttribute('aria-labelledby', ids.join(' '));
if (labelledBy) {
// Set label for attribute
const labelElement = document.getElementById(labelledBy);
if (labelElement && labelElement.tagName === 'LABEL') {
labelElement.setAttribute('for', this._elements.input.id);
this._labelElement = labelElement;
}
}
// Remove label for attribute
else if (this._labelElement) {
this._labelElement.removeAttribute('for');
}
}
/**
Array of additional parameters as key:value to send in addition of files.
A parameter must contain a <code>name</code> key:value and optionally a <code>value</code> key:value.
@type {Array.<Object>}
@default []
*/
get parameters() {
return this._parameters || [];
}
set parameters(values) {
// Verify that every item has a name
const isValid = Array.isArray(values) && values.every((el) => el && el.name);
if (isValid) {
this._parameters = values;
if (!this.async) {
Array.prototype.forEach.call(this.querySelectorAll('input[type="hidden"]'), (input) => {
input.parentNode.removeChild(input);
});
// Add extra parameters
this.parameters.forEach((param) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = param.name;
input.value = param.value;
this.appendChild(input);
});
}
}
}
/**
Whether files should be uploaded asynchronously via XHR or synchronously e.g. within a
<code><form></code> tag. One option excludes the other. Setting a new <code>async</code> value removes all
files from the queue.
@type {Boolean}
@default false
@htmlattribute async
@htmlattributereflected
*/
get async() {
return this._async || false;
}
set async(value) {
this._async = transform.booleanAttr(value);
this._reflectAttribute('async', this._async);
// Sync extra parameters in case of form submission
if (!this._async) {
this.parameters = this.parameters;
}
// Clear file selection
if (this._uploadQueue) {
this._clearQueue();
this._clearFileInputValue();
}
}
/**
The URL where the upload request should be sent. When used within a <code><form></code> tag to upload
synchronously, the action of the form is used. If an element is clicked that has a
<code>[coral-fileupload-submit]</code> attribute as well as a <code>[formaction]</code> attribute, the action of
the clicked element will be used. Set this property before calling {@link Coral.FileUpload#upload} to reset the
action set by a click.
@type {String}
@default ""
@htmlattribute action
@htmlattributereflected
*/
get action() {
return this._action || '';
}
set action(value) {
this._action = transform.string(value);
this._reflectAttribute('action', this._action);
// Reset button action as action was set explicitly
this._buttonAction = null;
}
/**
The HTTP method to use when uploading files asynchronously. When used within a <code><form></code> tag to
upload synchronously, the method of the form is used. If an element is clicked that has a
<code>[coral-fileupload-submit]</code> attribute as well as a <code>[formmethod]</code> attribute, the method of
the clicked element will be used. Set this property before calling {@link FileUpload#upload} to reset the
method set by a click.
See {@link FileUploadMethodEnum}.
@type {String}
@default FileUploadMethodEnum.POST
@htmlattribute method
@htmlattributereflected
*/
get method() {
return this._method || method.POST;
}
set method(value) {
value = transform.string(value).toUpperCase();
this._method = validate.enumeration(method)(value) && value || method.POST;
this._reflectAttribute('method', this._method);
// Reset button method as method was set explcitly
this._buttonMethod = null;
}
/**
Whether more than one file can be chosen at the same time to upload.
@type {Boolean}
@default false
@htmlattribute multiple
@htmlattributereflected
*/
get multiple() {
return this._elements.input.multiple;
}
set multiple(value) {
this._elements.input.multiple = transform.booleanAttr(value);
this._reflectAttribute('multiple', this.multiple);
}
/**
File size limit in bytes for one file. The value of 0 indicates unlimited, which is also the default.
@type {Number}
@htmlattribute sizelimit
@htmlattributereflected
@default 0
*/
get sizeLimit() {
return this._sizeLimit || 0;
}
set sizeLimit(value) {
this._sizeLimit = transform.number(value);
this._reflectAttribute('sizelimit', this._sizeLimit);
}
/**
MIME types allowed for uploading (proper MIME types, wildcard '*' and file extensions are supported). To specify
more than one value, separate the values with a comma (e.g.
<code><input accept="audio/*,video/*,image/*" /></code>.
@type {String}
@default ""
@htmlattribute accept
@htmlattributereflected
*/
get accept() {
return this._elements.input.accept;
}
set accept(value) {
this._elements.input.accept = value;
this._reflectAttribute('accept', this.accept);
}
/**
Whether the upload should start immediately after file selection.
@type {Boolean}
@default false
@htmlattribute autostart
@htmlattributereflected
*/
get autoStart() {
return this._autoStart || false;
}
set autoStart(value) {
this._autoStart = transform.booleanAttr(value);
this._reflectAttribute('autostart', this._autoStart);
}
/**
Files to be uploaded.
@readonly
@default []
@type {Array.<Object>}
*/
get uploadQueue() {
return this._uploadQueue;
}
/** @private */
_onButtonFocusIn(event) {
// Get the input
const input = this._elements.input;
// Get the button
const button = event.matchedTarget;
// Move the input to after the button
// This lets the next focused item be the correct one according to tab order
button.parentNode.insertBefore(input, button.nextElementSibling);
if (event.relatedTarget !== input) {
// Make sure the input gets focused on FF
window.setTimeout(() => {
input.focus();
}, 100);
}
}
/** @private */
_onInputFocusIn() {
// Get the input
const input = event.matchedTarget;
const button = this.querySelector('[coral-fileupload-select]');
if (button) {
// Remove from the tab order so shift+tab works
button.tabIndex = -1;
// So shifting focus backwards with screen reader doesn't create a focus trap
button.setAttribute('aria-hidden', true);
// Mark the button as focused
button.classList.add('is-focused');
window.requestAnimationFrame(() => {
if (input.classList.contains('focus-ring')) {
button.classList.add('focus-ring');
}
});
}
}
/** @private */
_onInputFocusOut() {
// Unmark all the focused buttons
const button = this.querySelector('[coral-fileupload-select].is-focused');
button.classList.remove('is-focused');
button.classList.remove('focus-ring');
// Wait a frame so that shifting focus backwards with screen reader doesn't create a focus trap
window.requestAnimationFrame(() => {
button.tabIndex = 0;
// @a11y: aria-hidden is removed to prevent focus trap when navigating backwards using a screen reader's
// virtual cursor
button.removeAttribute('aria-hidden');
});
}
/** @private */
_onAbortFileClick(event) {
if (!this.async) {
throw new Error('Coral.FileUpload does not support aborting file(s) upload on synchronous mode.');
}
// Get file to abort
const fileName = event.target.getAttribute('coral-fileupload-abortfile');
if (fileName) {
this._abortFile(fileName);
}
}
/** @private */
_onRemoveFileClick(event) {
if (!this.async) {
throw new Error('Coral.FileUpload does not support removing a file from the queue on synchronous mode.');
} else {
// Get file to remove
const fileName = event.target.getAttribute('coral-fileupload-removefile');
if (fileName) {
this._clearFile(fileName);
}
}
}
/** @private */
_onUploadFileClick(event) {
if (!this.async) {
throw new Error('Coral.FileUpload does not support uploading a file from the queue on synchronous mode.');
}
// Get file to upload
const fileName = event.target.getAttribute('coral-fileupload-uploadfile');
if (fileName) {
this.upload(fileName);
}
}
/** @private */
_onDragAndDrop(event) {
// Set dragging classes
if (event.type === 'dragenter' || event.type === 'dragover') {
this._addDragClass();
} else if (event.type === 'dragleave' || event.type === 'drop') {
this._removeDragClass();
}
this.trigger(`coral-fileupload:${event.type}`);
}
/** @private */
_addDragClass() {
window.clearTimeout(this._removeClassTimeout);
this._removeClassTimeout = window.setTimeout(this._doAddDragClass, 10);
}
/** @private */
_doAddDragClass() {
this.classList.add('is-dragging');
const dropZone = this.querySelector('[coral-fileupload-dropzone]');
if (dropZone) {
dropZone.classList.add('is-dragging');
}
// Put the input on top to enable file drop
this._elements.input.classList.remove('is-unselectable');
}
/** @private */
_removeDragClass() {
window.clearTimeout(this._removeClassTimeout);
this._removeClassTimeout = window.setTimeout(this._doRemoveDragClass, 10);
}
/** @private */
_doRemoveDragClass() {
this.classList.remove('is-dragging');
const dropZone = this.querySelector('[coral-fileupload-dropzone]');
if (dropZone) {
dropZone.classList.remove('is-dragging');
}
// Disable user interaction with the input
this._elements.input.classList.add('is-unselectable');
}
/**
Handles clicks to submit buttons
@private
*/
_onSubmitButtonClick(event) {
const target = event.matchedTarget;
// Override or reset the action/method given the button's configuration
this._buttonAction = target.getAttribute('formaction');
// Make sure the method provided by the button is valid
const buttonMethod = transform.string(target.getAttribute('formmethod')).toUpperCase();
this._buttonMethod = validate.enumeration(method)(buttonMethod) && buttonMethod || null;
// Start the file upload
this.upload();
}
/**
Handles changes to the input element.
@private
*/
_onInputChange(event) {
// Stop the current event
event.stopPropagation();
if (this.disabled) {
return;
}
let files = [];
const items = [];
// Retrieve files for select event
if (event.target.files && event.target.files.length) {
this._clearQueue();
files = event.target.files;
// Verify if multiple file upload is allowed
if (!this.multiple) {
files = [files[0]];
}
}
// Retrieve files for drop event
else if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length) {
this._clearQueue();
files = event.dataTransfer.files;
// Verify if multiple file upload is allowed
if (!this.multiple) {
files = [files[0]];
}
} else {
return;
}
// Initialize items
for (let i = 0 ; i < files.length ; i++) {
items.push(new FileUploadItem(files[i]));
}
// Verify if file is allowed to be uploaded and trigger events accordingly
items.forEach((item) => {
// If file is not found in uploadQueue using filename
if (!this._getQueueItemByFilename(item.file.name)) {
// Check file size
if (this.sizeLimit && item.file.size > this.sizeLimit) {
this.trigger('coral-fileupload:filesizeexceeded', {item});
}
// Check mime type
else if (this.accept && !item._isMimeTypeAllowed(this.accept)) {
this.trigger('coral-fileupload:filemimetyperejected', {item});
} else {
// Add item to queue
this._uploadQueue.push(item);
this.trigger('coral-fileupload:fileadded', {item});
}
}
});
if (this.autoStart) {
this.upload();
}
// Explicitly re-emit the change event
if (this._triggerChangeEvent) {
this.trigger('change');
}
// Clear file input once files are added to the queue to make sure next file selection will trigger a change event
if (this.async) {
this._clearFileInputValue();
}
}
/**
Sets the disabled/readonly state of elements with the associated special attributes
@private
*/
_setElementState() {
Array.prototype.forEach.call(this.querySelectorAll(
'[coral-fileupload-select],' +
'[coral-fileupload-dropzone],' +
'[coral-fileupload-submit],' +
'[coral-fileupload-clear],' +
'[coral-fileupload-abort],' +
'[coral-fileupload-abortfile],' +
'[coral-fileupload-removefile],' +
'[coral-fileupload-uploadfile]'
), (item) => {
item.classList.toggle('is-invalid', this.invalid);
item.classList.toggle('is-disabled', this.disabled);
item.classList.toggle('is-required', this.required);
item.classList.toggle('is-readOnly', this.readOnly);
item[this.disabled || this.readOnly ? 'setAttribute' : 'removeAttribute']('disabled', '');
});
}
/** @private */
_clearQueue() {
this._uploadQueue.slice().forEach((item) => {
this._clearFile(item.file.name);
});
}
/**
Clear file selection on the file input
@private
*/
_clearFileInputValue() {
this._elements.input.value = '';
}
/**
Remove a file from the upload queue.
@param {String} filename
The filename of the file to remove.
@private
*/
_clearFile(filename) {
const item = this._getQueueItemByFilename(filename);
if (item) {
// Abort file upload
this._abortFile(filename);
// Remove file from queue
this._uploadQueue.splice(this._getQueueIndex(filename), 1);
this.trigger('coral-fileupload:fileremoved', {item});
}
}
/**
Uploads a file in the queue. If an array is provided as the first argument, it is used as the parameters.
@param filename {String}
The name of the file to upload.
@private
*/
_uploadFile(filename) {
const item = this._getQueueItemByFilename(filename);
if (item) {
this._abortFile(filename);
this._ajaxUpload(item);
}
}
/** @private */
_showFileDialog() {
// Show the dialog
// This ONLY works when the call stack traces back to another click event!
this._elements.input.click();
}
/**
Abort specific file upload.
@param {String} filename
The filename identifies the file to abort.
@private
*/
_abortFile(filename) {
const item = this._getQueueItemByFilename(filename);
if (item && item._xhr) {
item._xhr.abort();
item._xhr = null;
}
}
/**
Handles the ajax upload.
@private
*/
_ajaxUpload(item) {
// Use the action/method provided by the last button click, if provided
const action = this._buttonAction || this.action;
const requestMethod = this._buttonMethod ? this._buttonMethod.toUpperCase() : this.method;
// We merge the global parameters with the specific file parameters and send them all together
const parameters = this.parameters.concat(item.parameters);
const formData = new FormData();
parameters.forEach((additionalParameter) => {
formData.append(additionalParameter.name, additionalParameter.value);
});
formData.append('_charset_', 'utf-8');
formData.append(this.name, item._originalFile);
// Store the XHR on the item itself
item._xhr = new XMLHttpRequest();
// Opening before being able to set response type to avoid IE11 InvalidStateError
item._xhr.open(requestMethod, action);
// Reflect specific xhr properties
item._xhr.timeout = item.timeout;
item._xhr.responseType = item.responseType;
item._xhr.withCredentials = item.withCredentials;
XHR_EVENT_NAMES.forEach((name) => {
// Progress event is the only event among other ProgressEvents that can trigger multiple times.
// Hence it's the only one that gives away usable progress information.
const isProgressEvent = name === 'progress';
(isProgressEvent ? item._xhr.upload : item._xhr).addEventListener(name, (event) => {
const detail = {
item: item,
action: action,
method: requestMethod
};
if (isProgressEvent) {
detail.lengthComputable = event.lengthComputable;
detail.loaded = event.loaded;
detail.total = event.total;
}
this.trigger(`coral-fileupload:${name}`, detail);
});
});
item._xhr.send(formData);
}
/** @private */
_getLabellableElement() {
return this;
}
/** @private */
_getQueueItemByFilename(filename) {
return this._getQueueItem(this._getQueueIndex(filename));
}
/** @private */
_getQueueItem(index) {
return index > -1 ? this._uploadQueue[index] : null;
}
/** @private */
_getQueueIndex(filename) {
let index = -1;
this._uploadQueue.some((item, i) => {
if (item.file.name === filename) {
index = i;
return true;
}
return false;
});
return index;
}
/** @private */
_getTargetChangeInput() {
return this._elements.input;
}
/** @ignore */
_positionInputOnDropZone() {
const input = this._elements.input;
const dropZone = this.querySelector('[coral-fileupload-dropzone]');
if (dropZone) {
const size = dropZone.getBoundingClientRect();
input.style.top = `${parseInt(dropZone.offsetTop, 10)}px`;
input.style.left = `${parseInt(dropZone.offsetLeft, 10)}px`;
input.style.width = `${parseInt(size.width, 10)}px`;
input.style.height = `${parseInt(size.height, 10)}px`;
} else {
input.style.width = '0';
input.style.height = '0';
}
}
/**
Uploads the given filename, or all the files into the queue. It accepts extra parameters that are sent with the
file.
@param {String} [filename]
The name of the file to upload.
*/
upload(filename) {
if (!this.async) {
if (typeof filename === 'string') {
throw new Error('Coral.FileUpload does not support uploading a file from the queue on synchronous mode.');
}
let form = this.closest('form');
if (!form) {
form = document.createElement('form');
form.method = this.method.toLowerCase();
form.enctype = 'multipart/form-data';
form.action = this.action;
form.hidden = true;
form.appendChild(this._elements.input);
Array.prototype.forEach.call(this.querySelectorAll('input[type="hidden"]'), (hiddenInput) => {
form.appendChild(hiddenInput);
});
// Make sure the form is connected before submission
this.appendChild(form);
}
const input = document.createElement('input');
input.type = 'hidden';
input.name = '_charset_';
input.value = 'utf-8';
form.submit();
} else if (typeof filename === 'string') {
this._uploadFile(filename);
} else {
this._uploadQueue.forEach((item) => {
this._abortFile(item.file.name);
this._ajaxUpload(item);
});
}
}
/**
Remove a file or all files from the upload queue.
@param {String} [filename]
The filename of the file to remove. If a filename is not provided, all files will be removed.
*/
clear(filename) {
if (!this.async) {
if (typeof filename === 'string') {
throw new Error('Coral.FileUpload does not support removing a file from the queue on synchronous mode.');
}
this._clearQueue();
this._clearFileInputValue();
} else if (typeof filename === 'string') {
this._clearFile(filename);
} else {
this._clearQueue();
}
}
/**
Abort upload of a given file or all files in the queue.
@param {String} [filename]
The filename of the file to abort. If a filename is not provided, all files will be aborted.
*/
abort(filename) {
if (!this.async) {
throw new Error('Coral.FileUpload does not support aborting file(s) upload on synchronous mode.');
}
if (typeof filename === 'string') {
// Abort a single file
this._abortFile(filename);
} else {
// Abort all files
this._uploadQueue.forEach((item) => {
this._abortFile(item.file.name);
});
}
}
static get _attributePropertyMap() {
return commons.extend(super._attributePropertyMap, {
sizelimit: 'sizeLimit',
autostart: 'autoStart'
});
}
/** @ignore */
static get observedAttributes() {
return super.observedAttributes.concat([
'async',
'action',
'method',
'multiple',
'sizelimit',
'accept',
'autostart'
]);
}
/** @ignore */
render() {
super.render();
this.classList.add(CLASSNAME);
const button = this.querySelector('[coral-fileupload-select]');
if (button) {
button.id = button.id || commons.getUID();
}
// If no labelledby is specified, ensure input is at labelledby the select button
this.labelledBy = this.labelledBy;
// Fetch additional parameters if any
const parameters = [];
Array.prototype.forEach.call(this.querySelectorAll('input[type="hidden"]'), (input) => {
parameters.push({
name: input.name,
value: input.value
});
});
this.parameters = parameters;
// Remove the input if it's already there
// A fresh input is preferred to value = '' as it may not work in all browsers
const inputElement = this.querySelector('[handle="input"]');
if (inputElement) {
inputElement.parentNode.removeChild(inputElement);
}
// Add the input to the component
this.appendChild(this._elements.input);
// IE11 requires one more frame or the resize listener <object> will appear as an overlaying white box
window.requestAnimationFrame(() => {
// Handles the repositioning of the input to allow dropping files
commons.addResizeListener(this, this._positionInputOnDropZone);
});
}
});
export default FileUpload;