UNPKG

ldx-widgets

Version:

widgets

309 lines (255 loc) 9.35 kB
React = require 'react' createClass = require 'create-react-class' PropTypes = require 'prop-types' ProgressBar = React.createFactory(require './progress_bar') AlertModal = React.createFactory(require './alert_modal') utils = require '../utils' {IMAGE_TYPES} = require '../constants/file_types' {div, input, button, ul, li} = require 'react-dom-factories' ###& @general File input component for file uploads @props.openAlertModal - [Function] - Required Handler used for opening the alert modal widget to display error messages @props.onChange - [Function] - Optional Event handler fired after on or more files are selected. Fires in an Async manner for images to check dimensions @props.onFileRemove - [Function] - Optional Event handler fired when a file is removed from the input @props.wrapperClass - [String] - Optional CSS class that wraps the component @props.className - [String] - Optional CSS class on the input element itself @props.multiple - [Boolean] - Optional Multiple file input @props.chooseFileText - [String] - Optional Placeholder text for the 'Choose' button @props.removeFileText - [String] - Optional Placeholder text for the 'Remove' button @props.name - [String] - Required Name attribute for the input element @props.maxSize - [Number] - Optional The maximum size in bytes for a file @props.resolution - [Object] - Optional Only applicable to image files. Enforces minimum and maximum length and width for images. ``` resolution: max: h: null w: null min: h: null w: null ``` @props.disabled - [Boolean] - Optional Disables the input element @props.displayFilename - [Boolean] - Optional Toggle the display of the filename @props.uploadProgress - [Object|String|Number] - Optional Tracks the upload progress of the file with the associated `name` prop &### FileInput = createClass displayName: 'FileInput' propTypes: openAlertModal: PropTypes.func.isRequired onChange: PropTypes.func wrapperClass: PropTypes.string className: PropTypes.string multiple: PropTypes.bool chooseFileText: PropTypes.string removeFileText: PropTypes.string name: PropTypes.string.isRequired maxSize: PropTypes.number resolution: PropTypes.object disabled: PropTypes.bool displayFilename: PropTypes.bool uploadProgress: PropTypes.oneOfType [ PropTypes.object PropTypes.string PropTypes.number ] getDefaultProps: -> wrapperClass: 'file-input-wrapper' className: 'file-input' multiple: false chooseFileText: 'Choose file...' removeFileText: 'Remove file' maxSize: 1048576 # Default max size of 1MB disabled: no displayFilename: yes displayProgress: yes name: '' openAlertModal: -> getInitialState: -> fileInputKey: 0 valid: true inputHasFile: false filename: '' render: -> {name, className, wrapperClass, multiple, uploadProgress, chooseFileText, removeFileText, showFileRemove, disabled, hideUpload, displayFilename, displayProgress} = @props {fileInputKey, inputHasFile, filename} = @state multiple = if multiple then 'multiple' else '' # If an object is passed, use that name property value. Otherwise, use the string directly. uploadProgress = uploadProgress[name] if typeof uploadProgress is 'object' wrapperClass += ' is-uploading' if uploadProgress? and uploadProgress isnt '' div { className: wrapperClass }, [ input { key: fileInputKey className: className type: 'file' ref: 'file' name: name multiple: multiple onChange: @handleChange style: display: if inputHasFile then 'none' else 'block' disabled: disabled } div { key: 'overlay' className: "file-overlay #{if uploadProgress? and uploadProgress isnt '' then 'is-uploading' else ''}" }, [ div { key: 'tools' className: 'overlay-tools' }, [ div { key: 'filename' className: 'filename' }, filename if displayFilename button { key: 'add' className: 'add-file' onClick: @handleFileClick }, chooseFileText unless hideUpload and inputHasFile button { key: 'remove' className: 'remove-file' onClick: @handleFileRemove }, removeFileText if inputHasFile or showFileRemove ] ProgressBar { key: 'progress' progress: uploadProgress } if uploadProgress? and uploadProgress isnt '' and displayProgress ] if not disabled ] clear: -> @setState fileInputKey: @state.fileInputKey + 1 inputHasFile: false filename: '' validate: (file, img) -> {fileTypes, maxSize, resolution} = @props {files} = @refs.file status = errors: [] URL = window.URL or window.webkitURL extension = file.name.split('.').pop().toLowerCase() # Check file size if maxSize? and file.size > maxSize then status.errors.push t "Size must be less than __maxSize__", maxSize: utils.bytesToSize(maxSize) # Check file extensions if fileTypes? # Use an array of qualified image types if typeof fileTypes is 'object' types = fileTypes.join(', ') # If set to imagesOnly, only image formats will be accepted else if typeof fileTypes is 'string' and fileTypes is 'imagesOnly' types = IMAGE_TYPES.join(', ') # Check the file type to see if it's allowed if types.length and types.search(extension) is -1 then status.errors.push t "File type must be __fileType__", fileType: types.toUpperCase() # For images, enforce resolution restrictions if resolution? and img? and URL? {min, max} = resolution resErrors = [] if max? if max.h and img.height > max.h then resErrors.push t "Less than __value__ in __measure__", value: "#{max.h}px", measure: 'height' if max.w and img.width > max.w then resErrors.push t "Less than __value__ in __measure__", value: "#{max.w}px", measure: 'width' if min? if min.h and img.height < min.h then resErrors.push t "Greater than __value__ in __measure__", value: "#{min.h}px", measure: 'height' if min.w and img.width < min.w then resErrors.push t "Greater than __value__ in __measure__", value: "#{min.w}px", measure: 'width' if resErrors.length status.errors.push(t 'Resolution requirements') status.errors.push(resErrors) return status showErrorMessage: (errors) -> @stopFilesLoop = true messages = [] # Parse the validation errors for error, index in errors if typeof error is 'object' and error.length for sErr in error messages.push li { key: index className: 'sub-item' }, sErr else messages.push li { key: index className: 'item' }, error if messages.length msg = ul { key: 'list' className: 'error-message-list' }, messages else msg = t "Selected files must match requirements" @props.openAlertModal msg @clear() handleFileRemove: -> # Report back the name of the element @props.onFileRemove?(@getValue(), @clear) handleFileClick: (e) -> e.stopPropagation() utils.synthesizeMouseEvent(@refs.file, 'click') handleChange: (e) -> {uploadProgress, onChange, resolution, name} = @props {files} = @refs.file @stopFilesLoop = false # Check for the URL class so we can use createObjectURL to assign data to images created dynamically # This is used to enforce resolution rules, and is not supported by IE9 URL = window.URL or window.webkitURL # If an upload is in progress, do nothing if uploadProgress[name]? then return e.preventDefault() # Set the filename to the first file @setState filename: "#{files[0].name} (#{utils.bytesToSize(files[0].size, 2)})" for file, index in files extension = file.name.split('.').pop().toLowerCase() isImage = extension in IMAGE_TYPES isLastFile = index is files.length - 1 # If an invalid file is detected, stop scanning if @stopFilesLoop @stopFilesLoop = false @setState filename: '' return # If an image and resolution constraints defined, wait for img onload if resolution? and isImage and URL? img = new Image() img.onload = => v = @validate(file, img) if v.errors.length then @showErrorMessage(v.errors) else if isLastFile and v onChange?(@getValue()) @setState inputHasFile: true img.src = URL.createObjectURL(file) # For all other files, run validation normally else v = @validate(file) if v.errors.length then @showErrorMessage(v.errors) else if isLastFile and v onChange?(@getValue()) @setState inputHasFile: true getValue: -> value = files: @refs.file.files name: @refs.file.name maxSize: @props.maxSize ref: @ return value module.exports = FileInput