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');
- if (button) {
- 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 = '0px';
- input.style.height = '0px';
- input.style.visibility = 'hidden';
- }
- }
-
- /**
- 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;