UNPKG

react-easy-input

Version:

A react input component that has simple validation and masking

201 lines (157 loc) 8.26 kB
React = require("react") converterModule = require("./converters") invalidModule = require('./Invalid') get = invalidModule.get set = invalidModule.set # TODO/Extra # add some props # required, invalidClass, validateImmediately(show red on empty required fields onload), validateOnblur (show red only after/not during field editing) # add more converters # numeric, integer, decimal, percent # EVENTUALLY # add masking, mask=[/[^@]+/,"@",/\d/,"."] # re-exports converters = converterModule.converters ; module.exports.converters = converters Invalid = invalidModule.Invalid ; module.exports.Invalid = Invalid isInvalid = invalidModule.isInvalid ; module.exports.isInvalid = isInvalid # helper function resetCursor = (thisFromInput) -> if get(thisFromInput, ["refs", "input", "selectionStart"], "") inputSelectionStart = thisFromInput.refs.input.selectionStart moveCursor = () -> cursorPos = get(thisFromInput, ["refs", "input", "selectionStart"], "") if cursorPos if inputSelectionStart thisFromInput.refs.input.selectionStart = inputSelectionStart thisFromInput.refs.input.selectionEnd = inputSelectionStart setTimeout( moveCursor, 0) class Input extends React.Component constructor: (props) -> super(props) this.hasCompletedTheInitialFilter = false render: -> props = this.props # extract values from props expectedProps = [] expectedProps.push("invalidStyle" ); if props.invalidStyle then invalidStyle = props.invalidStyle else invalidStyle = null expectedProps.push("linkTo" ); if props.linkTo then linkTo = props.linkTo else linkTo = null expectedProps.push("className" ); if props.className then className = props.className else className = "easy-input" expectedProps.push("classAdd" ); if props.classAdd then classAdd = props.classAdd else classAdd = "" expectedProps.push("incomingFilter" ); if props.incomingFilter then incomingFilter = props.incomingFilter else incomingFilter = null expectedProps.push("outgoingFilter" ); if props.outgoingFilter then outgoingFilter = props.outgoingFilter else outgoingFilter = null expectedProps.push("value" ); if props.value then value = props.value else value = null # create a mutable version of props newProps = {} for each in Object.keys(props) if not expectedProps.includes(each) newProps[each] = props[each] # # retrieve converters # if converters[newProps.type] then converter = converters[newProps.type] else converter = {} if not outgoingFilter then outgoingFilter = converter.outgoingFilter if not incomingFilter then incomingFilter = converter.incomingFilter # FIXME, wrap the incomingFilter to make sure it always receives non-Invalid() values # # calculate value # if newProps.this and linkTo # retrieve the actual value from the component's state newProps.value = get(newProps.this.state, linkTo, "") # Check the initial value if incomingFilter and !this.hasCompletedTheInitialFilter initialValue = newProps.value newProps.value = incomingFilter(newProps.value) this.hasCompletedTheInitialFilter = true if initialValue != newProps.value # if the initial value is different, call the onchange if newProps.onChange newProps.onChange({target:{value:newProps.value}}) # preserve the validity/un-validityness valueIsInvalid = isInvalid newProps.value #FIXME, there could be a better solution than this # always run outgoing filters if outgoingFilter then newProps.value = outgoingFilter(newProps.value) # always convert null values to "" (otherwise react will complain) if newProps.value is null or newProps.value is undefined then newProps.value = "" if valueIsInvalid then newProps.value = new Invalid(newProps.value) # # Compute onChange # if not newProps.onChange # first reset the cursor, then handle the change newProps.onChange = (e) => newValue = e.target.value resetCursor(this) # if there is a converter function, then run the function before it returns to state # for example convert "True" into the boolean: true, # or convert the string "Jan 12 2017" to dateTime(1,12,2017) if incomingFilter newValue = incomingFilter(newValue) # create a copy of state instead of mutating the original copyOfState = Object.assign(newProps.this.state) invalidModule.set(copyOfState, linkTo, newValue) props.this.setState(copyOfState) else newProps.onChange = (e) => e.persist() # react will destroy e if we don't tell it to persist newValue = e.target.value resetCursor(this) # if there is a converter function, then run the function before it returns to state # for example convert "True" into the boolean: true, # or convert the string "Jan 12 2017" to dateTime(1,12,2017) if incomingFilter newValue = incomingFilter(newValue) props.onChange({target:{value:newValue}}) # # Calculate styling/css class # # add additional classes newProps.className = className + " " + classAdd displayInvalid = no # if 'invalid' prop was set to something (true/false) if typeof newProps.invalid == 'bool' # and if 'invalid' is true if newProps.invalid is true # then display it displayInvalid = yes # if 'invalid' is false, dont add error class # if 'invalid' prop was not set, but the state value is indeed invalid, then displayInvalid else if isInvalid newProps.value # then display it displayInvalid = yes if displayInvalid is yes # add the error css class className = "easy-input-error " + className # check if there is an invalid style if invalidStyle # if there is one then attach it newProps.style = invalidStyle # set a reference for fixing the cursor jump newProps.ref = "input" # return the react input component return React.createElement('input', newProps, null) module.exports.Input = Input # validate onblur instructions (implement in next few versions) # onChange={ # (e)=>{ # var val = e.target.value # e.persist() # setTimeout(() => { # e.target.onblur = () => { # console.log(`val is:`,val) # var matches = val.match(/^.+@.+\..+$/) # console.log(`val.match(/\\d+/) is:`,val.match(/\d+/)) # if (!matches) { # console.log(`doesnt match`) # e.target.setCustomValidity("Needs to be a number") # e.target.classList.add('input-err') # // this.refs.form.reportValidity() # } else { # console.log(`matches`) # } # } # }, 0); # this.setState({Nums:val}) # } # } # />