ldx-widgets
Version:
widgets
259 lines (218 loc) • 7.79 kB
text/coffeescript
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} = React.DOM
FileInput = createClass
displayName: 'FileInput'
propTypes:
openAlertModal: PropTypes.func.isRequired
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
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
name: ''
openAlertModal: ->
## Keep this commented out. This is how to form the resolution prop:
# resolution:
# max:
# h: null
# w: null
# min:
# h: null
# w: null
getInitialState: ->
fileInputKey: 0
valid: true
inputHasFile: false
filename: ''
render: ->
{name, className, wrapperClass, multiple, uploadProgress, chooseFileText, removeFileText, showFileRemove, disabled, hideUpload} = @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
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 ''
] 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