CustomInput.jsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. import reactTriggerChange from "react-trigger-change";
  4. const regex = {
  5. azAZ09_: /^[A-Za-z0-9_]+$/,
  6. azAZ09: /^[A-Za-z0-9]+$/,
  7. az09_: /^[a-z0-9_]+$/,
  8. az09: /^[a-z0-9]+$/,
  9. emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
  10. password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]/,
  11. ascii: /^[\x00-\x7F]+$/,
  12. };
  13. const isLength = (string, min, max) => {
  14. return !(typeof string !== "string" || string.length < min || string.length > max);
  15. };
  16. // TODO add features where inputs need to be the same
  17. const validation = {
  18. username: (value) => {
  19. const errors = [];
  20. if (!isLength(value, 2, 32)) errors.push("Username must be between 2 and 32 characters long.");
  21. if (!regex.azAZ09_.test(value)) errors.push("Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.");
  22. return errors;
  23. },
  24. email: (value) => {
  25. const errors = [];
  26. if (!isLength(value, 3, 254)) errors.push("Email must be between 3 and 254 characters long.");
  27. if (value.indexOf("@") !== value.lastIndexOf("@") || !regex.emailSimple.test(value)) errors.push("Invalid email format.");
  28. return errors;
  29. },
  30. password: (value) => {
  31. const errors = [];
  32. if (!isLength(value, 6, 200)) errors.push("Password must be between 6 and 200 characters long.");
  33. if (!regex.password.test(value)) errors.push("Invalid password format.");
  34. return errors;
  35. },
  36. uniqueCode: (value) => {
  37. const errors = [];
  38. if (!isLength(value, 8, 8)) errors.push("Code must be 8 characters long.");
  39. if (!regex.azAZ09.test(value)) errors.push("Invalid code format.");
  40. return errors;
  41. },
  42. };
  43. export default class CustomInput extends Component {
  44. static propTypes = {
  45. type: PropTypes.string,
  46. inputType: PropTypes.string,
  47. name: PropTypes.string,
  48. value: PropTypes.string,
  49. label: PropTypes.string,
  50. placeholder: PropTypes.string,
  51. customInputEvents: PropTypes.object,
  52. validationCallback: PropTypes.func,
  53. onRef: PropTypes.func,
  54. };
  55. static defaultProps = {
  56. type: "text",
  57. inputType: "text",
  58. name: "",
  59. value: "",
  60. label: "",
  61. placeholder: "",
  62. customInputEvents: {},
  63. validationCallback: () => {},
  64. onRef: () => {},
  65. };
  66. static validationCallback = (ctx) => {
  67. return (name, invalid) => {
  68. const inputInvalid = ctx.state.inputInvalid;
  69. inputInvalid[name] = invalid;
  70. ctx.setState({ inputInvalid });
  71. };
  72. };
  73. static hasInvalidInput = (inputInvalid, properties) => {
  74. let invalid = false;
  75. if (properties) {
  76. properties.forEach((property) => {
  77. if (inputInvalid[property]) invalid = true;
  78. });
  79. } else {
  80. Object.keys((key) => {
  81. if (key) invalid = true;
  82. });
  83. }
  84. return invalid;
  85. };
  86. constructor(props) {
  87. super(props);
  88. this.state = {
  89. customInputEvents: props.customInputEvents,
  90. errors: "",
  91. value: props.value,
  92. validateOnChange: false,
  93. };
  94. if (this.state.customInputEvents.onBlur) {
  95. const oldOnBlur = this.state.customInputEvents.onBlur;
  96. this.state.customInputEvents.onBlur = () => {
  97. this.onBlurHandler();
  98. oldOnBlur();
  99. };
  100. } else this.state.customInputEvents.onBlur = this.onBlurHandler;
  101. if (this.state.customInputEvents.onChange) {
  102. const oldOnChange = this.state.customInputEvents.onChange;
  103. this.state.customInputEvents.onChange = (event) => {
  104. this.onChangeHandler(event);
  105. oldOnChange(event);
  106. };
  107. } else this.state.customInputEvents.onChange = this.onChangeHandler;
  108. }
  109. componentDidMount() {
  110. this.props.onRef(this);
  111. }
  112. componentWillUnmount() {
  113. this.props.onRef(null);
  114. }
  115. // Triggered when user stops focusing on the input element
  116. onBlurHandler = () => {
  117. this.validateInput();
  118. };
  119. // Triggered when the input element's value changes
  120. onChangeHandler = (event) => {
  121. this.setState({
  122. value: event.target.value,
  123. }, () => {
  124. if (this.state.validateOnChange === true) {
  125. this.setState({
  126. validateOnChange: false,
  127. });
  128. this.validateInput();
  129. }
  130. });
  131. };
  132. listErrors = () => {
  133. let errors = this.state.errors;
  134. let key = 0;
  135. if (errors.length > 0) {
  136. errors = errors.map((error) => {
  137. key++;
  138. return (<li key={ key }>{ error }</li>);
  139. });
  140. return (
  141. <ul className="validation-errors">
  142. { errors }
  143. </ul>
  144. );
  145. } return "";
  146. };
  147. validateInput = () => {
  148. const value = this.state.value;
  149. const type = this.props.type;
  150. const errors = (validation[type]) ? validation[type](value) : [];
  151. this.setState({ errors });
  152. this.props.validationCallback(this.props.name, errors.length > 0);
  153. };
  154. triggerChangeEvent = (validateOnChange) => {
  155. reactTriggerChange(this.inputElement);
  156. this.setState({ validateOnChange });
  157. };
  158. render() {
  159. return (
  160. <label htmlFor={ this.props.name }>
  161. <span>{ this.props.label }</span>
  162. <input
  163. placeholder={ this.props.placeholder }
  164. type={ this.props.inputType }
  165. name={ this.props.name }
  166. value={ this.props.value }
  167. className={ (this.state.errors.length > 0) ? "has-validation-errors" : "" }
  168. { ...this.state.customInputEvents }
  169. ref={ (input) => this.inputElement = input }
  170. />
  171. { this.listErrors() }
  172. </label>
  173. );
  174. }
  175. }