UNPKG

react-simple-file-input

Version:

Simple wrapper for the HTML input tag and HTML5 FileReader API

338 lines (286 loc) 10.2 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; /** * Map of short-hand aliases accepted by react-simple-file-input to * actual javascript methods */ const 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) */ const 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 */ const 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. */ class FileInput extends Component { /** * Creates a new instance of FileInput React component * @param {ComponentProps} props React props object - see propTypes below * @param {Object} context React context object */ constructor(props, context) { super(props, context); this.handleChange = this.handleChange.bind(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. */ 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 */ handleChange(event){ const { readAs, cancelIf, onCancel, onProgress, abortIf, onChange, multiple } = this.props; const { files } = event.target; if (onChange) { if (multiple) { onChange(files); } else { onChange(files[0]); } } if (readAs) { for (let i = 0; i < files.length; i++) { const file = files[i]; if(cancelIf && cancelIf(file)){ if(onCancel){ onCancel(file); } return; } const fileReader = new window.FileReader(); for(let i = 0; i < SUPPORTED_EVENTS.length; i++){ const handlerName = SUPPORTED_EVENTS[i]; if (this.props[handlerName]) { fileReader[handlerName.toLowerCase()] = (fileReadEvent)=>{ this.props[handlerName](fileReadEvent, file); }; } } if (typeof abortIf !== 'undefined') { fileReader.onprogress = (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); } } } /** * 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 */ render() { const inputProps = {}; for (let property in this.props) { if (this.props.hasOwnProperty(property) && !UNSUPPORTED_BY_INPUT[property]) { inputProps[property] = this.props[property]; } } return( <input {...inputProps} type='file' onChange={ this.handleChange } /> ); } } /** * @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, }; export default FileInput;