react-simple-file-input
Version:
Simple wrapper for the HTML input tag and HTML5 FileReader API
476 lines (372 loc) • 14.6 kB
JavaScript
/**
* ISC License
*
* Copyright (c) 2018, Aleck Greenham
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var PropTypes = _interopDefault(require('prop-types'));
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
/**
* Map of short-hand aliases accepted by react-simple-file-input to
* actual javascript methods
*/
var READ_METHOD_ALIASES = {
buffer: 'readAsArrayBuffer',
binary: 'readAsBinaryString',
dataUrl: 'readAsDataURL',
text: 'readAsText'
};
/**
* List of props that are accepted by the component and that should be
* passed to the FileReader instance (after down-casing the name and checking
* that they are indeed defined)
*/
var SUPPORTED_EVENTS = ['onLoadStart', 'onLoadEnd', 'onLoad', 'onAbort', 'onError'];
/**
* A look-up of properties supported by the React component, but should
* not be passed down to the input component
*/
var UNSUPPORTED_BY_INPUT = {
readAs: true,
abortIf: true,
cancelIf: true,
onCancel: true
};
/**
* @class Provides a wrapper around a basic input field that provides an API that is
* consistent with React and JSX conventions.
*
* It supports all of the properties available to a file input field, and all of the
* events supplied by a FileReader with a few additional ones as well.
*/
var FileInput = function (_Component) {
inherits(FileInput, _Component);
/**
* Creates a new instance of FileInput React component
* @param {ComponentProps} props React props object - see propTypes below
* @param {Object} context React context object
*/
function FileInput(props, context) {
classCallCheck(this, FileInput);
var _this = possibleConstructorReturn(this, (FileInput.__proto__ || Object.getPrototypeOf(FileInput)).call(this, props, context));
_this.handleChange = _this.handleChange.bind(_this);
return _this;
}
/**
* Checks that the necessary APIs are available in the browser environment
* when the component is mounted and displays a warning if anything is
* missing.
*/
createClass(FileInput, [{
key: 'componentDidMount',
value: function componentDidMount() {
if (window && !window.File || !window.FileReader) {
console.warn('Browser does not appear to support API react-simple-file-input relies upon');
}
}
/**
* Function that is called to handle every change event that is triggered by the
* input component.
*
* @param {Event} event file input field event to handle
*/
}, {
key: 'handleChange',
value: function handleChange(event) {
var _this2 = this;
var _props = this.props,
readAs = _props.readAs,
cancelIf = _props.cancelIf,
onCancel = _props.onCancel,
onProgress = _props.onProgress,
abortIf = _props.abortIf,
onChange = _props.onChange,
multiple = _props.multiple;
var files = event.target.files;
if (onChange) {
if (multiple) {
onChange(files);
} else {
onChange(files[0]);
}
}
if (readAs) {
var _loop = function _loop(i) {
var file = files[i];
if (cancelIf && cancelIf(file)) {
if (onCancel) {
onCancel(file);
}
return {
v: void 0
};
}
var fileReader = new window.FileReader();
var _loop2 = function _loop2(_i) {
var handlerName = SUPPORTED_EVENTS[_i];
if (_this2.props[handlerName]) {
fileReader[handlerName.toLowerCase()] = function (fileReadEvent) {
_this2.props[handlerName](fileReadEvent, file);
};
}
};
for (var _i = 0; _i < SUPPORTED_EVENTS.length; _i++) {
_loop2(_i);
}
if (typeof abortIf !== 'undefined') {
fileReader.onprogress = function (event) {
if (abortIf(event, file)) {
fileReader.abort();
} else if (onProgress) {
onProgress(event, file);
}
};
} else if (onProgress) {
fileReader.onprogress = onProgress;
}
fileReader[READ_METHOD_ALIASES[readAs]](file);
};
for (var i = 0; i < files.length; i++) {
var _ret = _loop(i);
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
}
}
}
/**
* Renders an input component that receives all of the props passed to this component
* less those that are not supported by the input component.
* @returns {Component} input component configured according to the props passed
* to this component
*/
}, {
key: 'render',
value: function render() {
var inputProps = {};
for (var property in this.props) {
if (this.props.hasOwnProperty(property) && !UNSUPPORTED_BY_INPUT[property]) {
inputProps[property] = this.props[property];
}
}
return React__default.createElement('input', _extends({}, inputProps, {
type: 'file',
onChange: this.handleChange
}));
}
}]);
return FileInput;
}(React.Component);
/**
* @callback onChangeCallback called after the user has finished selecting file(s)
* using the browser dialog.
* @param {File|Array.<File>} files Either a single File object representing the
* file the user has selected, or if the multiple prop was used then an
* array of File objects representing the files the user selected.
*/
/**
* @callback cancelIfFunction called after the onChangeCallback for every file the
* user has selected. If it returns a truthy value, the read operation for
* that file is never started - the other files may still be read.
* @param {File} file a File object representing one of the files the user has
* selected.
* @return {Boolean} Whether to cancel reading the file
*/
/**
* @callback onCancelCallback called when cancelIfFunction() returns a truthy value
* @param {File} file File object that caused cancelIfFunction() to return a truthy
* value
*/
/**
* @callback onLoadStartCallback called when a file starts to be loaded
* @param {ProgressEvent} event The event object representing the progress of reading
* the file
* @param {File} file The File object containing information about the file
*/
/**
* @callback abortIfFunction called every time a file read operation progresses.
* If it returns a truthy value, the read operation for that file is
* aborted immediately and the onAbortCallback is called instead of
* the onProgressCallback.
* @param {ProgressEvent} event The event object representing the progress of reading
* the file
* @param {File} file a File object representing one of the files the user has
* selected.
* @return {Boolean} Whether to abort reading the file
*/
/**
* @callback onAbortCallback called when a file load operation has been aborted
* because the abortIfFunction has returned a truthy value.
* @param {UIEvent} event The event object representing the progress of reading
* the file
* @param {File} file The File object containing information about the file
*/
/**
* @callback onProgressCallback called every time the file read progresses. It is
* NOT called if the abortIfCallback is defined and returns a truthy value.
* @param {ProgressEvent} event The event object representing the progress of reading
* the file
* @param {File} file The File object containing information about the file
*/
/**
* @callback onLoadCallback called when a file has finished successfully being loaded
* This callback is NOT called if the load operation is aborted or results
* in an error.
* @param {UIEvent} event The event object representing the progress of reading
* the file
* @param {File} file The File object containing information about the file
*/
/**
* @callback onErrorCallback called when a file read results in an error
* @param {UIEvent} event The event object representing the progress of reading
* the file
* @param {File} file The File object containing information about the file
*/
/**
* @callback onLoadEndCallback called when a file has finished loading, either
* after onLoadCallback, onErrorCallback or onAbortCallback, depending on
* the result of the file load operation.
* @param {ProgressEvent} event The event object representing the progress of
* reading the file
* @param {File} file The File object containing information about the file
*/
/**
* @typedef {Object} ComponentProps Props object accepted by FileInput
*/
FileInput.propTypes = {
/**
* @property {String} [id] id attribute to pass to input field
*/
id: PropTypes.string,
/**
* @property {String} [className] class attribute to pass to input field
*/
className: PropTypes.string,
/**
* @property {Boolean} [multiple=false] Whether to allow the user to select more than
* one file at a time from the browser dialog that appears.
*/
multiple: PropTypes.bool,
/**
* @property {'buffer'|'binary'|'dataUrl'|'text'} [readAs] When 'buffer', files
* are read as array buffers; when 'binary', as binary strings; when
* 'dataUrl' they are read as data urls and when 'text' they are read as
* text. If not provided, the files selected by the user are not read
* at all, and only the onChange handler is called.
*/
readAs: PropTypes.oneOf(Object.keys(READ_METHOD_ALIASES)),
/**
* @property {onChangeCallback} [onChange] Callback that handles when the files
* have been selected
*/
onChange: PropTypes.func,
/**
* @property {cancelIfFunction} [cancelIf] Function to handle whether each file
* should be read, based on its File object
*/
cancelIf: PropTypes.func,
/**
* @property {onCancelCallback} [onCancel] Callback to handle if a file read is
* cancelled
*/
onCancel: PropTypes.func,
/**
* @property {onLoadStartCallback} [onLoadStart] Callback to handle when a file
* has started being loaded
*/
onLoadStart: PropTypes.func,
/**
* @property {abortIfFunction} [abortIf] Function to handle whether each file
* should abort loading the file
*/
abortIf: PropTypes.func,
/**
* @property {onAbortCallback} [onAbort] Callback to handle when loading a file
* is aborted
*/
onAbort: PropTypes.func,
/**
* @property {onProgressCallback} [onProgress] Callback to handle when loading a
* file has progressed (and not been aborted)
*/
onProgress: PropTypes.func,
/**
* @property {onLoadCallback} [onLoad] Callback to handle when a file has been
* successfully loaded
*/
onLoad: PropTypes.func,
/**
* @property {onErrorCallback} [onError] Callback to handle when loading a file
* results in an error
*/
onError: PropTypes.func,
/**
* @property {onLoadEndCallback} [onLoadEnd] Callback to handle when a file load
* operation has completed - whether it was successful, aborted or
* resulted in an error
*/
onLoadEnd: PropTypes.func
};
exports.default = FileInput;