Skip to content

Instantly share code, notes, and snippets.

@jzaefferer
Created June 6, 2018 18:53
Show Gist options
  • Save jzaefferer/07a5ea0675d2f92ccc87ce69a354d8fd to your computer and use it in GitHub Desktop.
Save jzaefferer/07a5ea0675d2f92ccc87ce69a354d8fd to your computer and use it in GitHub Desktop.
React with jQuery Validation Plugin
/* eslint-disable prefer-arrow-callback */
import $ from 'jquery'
import 'jquery-validation'
import PropTypes from 'prop-types'
import React from 'react'
import env from '../env'
import validateImage from '../lib/image'
const sharedHostDomain = env.npm_package_config_shared_host_domain
export const identifierRegex = /^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$/
export const containerPathRegex = /^(?:\/\.?[a-zA-Z0-9_\\-]*)+$/
export const domainRegex = /^(?:\s?(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))+$/
export const basicAuthRegex = /.+:.+:\$1\$[A-Za-z0-9./]{1,16}\$[A-Za-z0-9./]{21,31}$|\$2\$[A-Za-z0-9]{1,16}\$[A-Za-z0-9./]{40,55}$/
// app-settings.jsx
$.validator.addMethod('image', validateImage, 'Please enter a valid Docker image, like \'wordpress\' or \'sloppy/apache-php\'')
$.validator.addMethod('tag', function (value, element) {
return this.optional(element) || /^[\w][\w.-]{0,127}$/.test(value)
}, "Please enter a valid Docker image tag, like 'latest' or '1.10.0'")
$.validator.addMethod('domain', function (value, element) {
return this.optional(element) || domainRegex.test(value)
}, `Please enter a valid hostname, like 'git${sharedHostDomain}' or 'coffee-to-go.com'.`)
$.validator.addMethod('reserved_domain', function (value, element) {
return this.optional(element) || !/^.+\.(?:sloppy\.io)$/.test(value)
}, `*.sloppy.io subdomains aren't available, did you mean *${sharedHostDomain}?`)
$.validator.addMethod('container_path', function (value, element) {
return this.optional(element) || containerPathRegex.test(value)
}, "Try something like '/var/www' or '/data/db'")
$.validator.addMethod('env_key', function (value, element) {
return this.optional(element) || /^(?!(SLOPPY|MARATHON|MESOS|WEAVE_CIDR))([a-zA-Z_]?[a-zA-Z0-9_.]{0,41})$/.test(value)
}, "Try something like 'DATABASE_URI' or 'MYSQL_USER'")
// new-project.jsx
$.validator.addMethod('identifier', function (value) {
return identifierRegex.test(value)
}, "Please stick to 'a-z', '0-9', and '-' (no '-' at start or end)")
$.validator.addMethod('unique', function (value, element, existing) {
return existing.split(',').indexOf(value) < 0
}, 'Please use a unique name')
$.validator.addMethod('basic_auth', function (value, element) {
return this.optional(element) || basicAuthRegex.test(value)
}, 'Check the Basic Auth format, must be in the form \'Authentication:[username]:[password hash]\'')
// https://jqueryvalidation.org/creditcard-method/
// based on https://en.wikipedia.org/wiki/Luhn_algorithm
$.validator.addMethod('creditcard', function (value) {
// Accept only spaces, digits and dashes
if (/[^0-9 -]+/.test(value)) {
return false
}
let nCheck = 0
let nDigit = 0
let bEven = false
let n
let cDigit
value = value.replace(/\D/g, '')
// Basing min and max length on
// https://developer.ean.com/general_info/Valid_Credit_Card_Types
if (value.length < 13 || value.length > 19) {
return false
}
/* eslint-disable no-plusplus,no-cond-assign */
for (n = value.length - 1; n >= 0; n--) {
cDigit = value.charAt(n)
nDigit = parseInt(cDigit, 10)
if (bEven) {
if ((nDigit *= 2) > 9) {
nDigit -= 9
}
}
nCheck += nDigit
bEven = !bEven
}
/* eslint-enable */
return (nCheck % 10) === 0
}, 'Please enter a valid credit card number.')
$.validator.addMethod('syslogaddress', function (value, element) {
return this.optional(element) || /^udp:\/\/[a-zA-Z0-9][a-zA-Z0-9-.]*(?::\d{2,5})?$/.test(value)
}, 'Must use udp protocol with hostname, port is optional')
export default class Validator extends React.Component {
static propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onChange: PropTypes.func,
onSubmit: PropTypes.func,
validateOnMount: PropTypes.bool,
}
static childContextTypes = {
processing: PropTypes.bool,
}
getChildContext() {
return {
processing: this.state.processing,
}
}
componentWillMount() {
this.setState({
processing: false,
})
}
componentDidMount() {
this.validator = $(this.form).validate({
submitHandler: () => {
if (this.props.onSubmit) {
this.setState({ processing: true })
Promise.resolve()
.then(() => this.props.onSubmit())
.then(() => this.setState({ processing: false }))
.catch((error) => {
this.setState({ processing: false })
throw error
})
}
return false
},
errorElement: 'div',
errorClass: 'Input__error',
highlight: (element) => {
// add data so that React component can keep the extra class intact
$(element).parents('.Input, .EditableInput').addClass('has__error').data('has-error', true)
},
unhighlight: (element) => {
$(element).parents('.Input, .EditableInput').removeClass('has__error').data('has-error', false)
},
errorPlacement: (errorElement, element) => {
element.parents('.Input, .EditableInput').append(errorElement)
},
})
if (this.props.validateOnMount) {
this.setIsValid()
}
}
componentDidUpdate() {
if (this.props.validateOnMount) {
this.setIsValid()
}
}
setIsValid() {
const { onChange } = this.props
const isValid = $(this.form).valid()
if (onChange && isValid !== this.isValid) {
this.isValid = isValid
onChange(isValid)
}
}
resetForm() {
this.validator.resetForm()
}
render() {
const { children, className } = this.props
return <form ref={(el) => { this.form = el }} className={className}>
{children}
</form>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment