CustomInput.jsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. const i18next = require("i18next");
  4. const t = i18next.t;
  5. const isLength = (string, min, max) => {
  6. return !(typeof string !== "string" || string.length < min || string.length > max);
  7. };
  8. const regex = {
  9. azAZ09_: /^[A-Za-z0-9_]+$/,
  10. azAZ09: /^[A-Za-z0-9]+$/,
  11. az09_: /^[a-z0-9_]+$/,
  12. az09: /^[a-z0-9]+$/,
  13. emailSimple: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
  14. password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]/,
  15. ascii: /^[\x00-\x7F]+$/,
  16. };
  17. const dictionary = {
  18. username: {
  19. inputType: "text",
  20. minLength: 2,
  21. maxLength: 32,
  22. isInput: true,
  23. regex: regex.azAZ09_,
  24. errors: {
  25. format: t("general:invalidUsernameFormat", { characters: `a-z, A-Z, 0-9${ t("general:and") } _` }),
  26. },
  27. },
  28. email: {
  29. inputType: "email",
  30. minLength: 3,
  31. maxLength: 254,
  32. isInput: true,
  33. regex: regex.emailSimple,
  34. errors: {
  35. format: t("general:invalidEmailFormat"),
  36. },
  37. },
  38. password: {
  39. inputType: "password",
  40. minLength: 6,
  41. maxLength: 200,
  42. isInput: true,
  43. regex: regex.password,
  44. errors: {
  45. format: t("general:invalidPasswordFormat", { characters: "$@$!%*?&" }),
  46. },
  47. },
  48. uniqueCode: {
  49. inputType: "text",
  50. minLength: 8,
  51. maxLength: 8,
  52. isInput: true,
  53. regex: regex.azAZ09,
  54. errors: {
  55. length: t("general:invalidCodeLength", { length: 8 }),
  56. format: t("general:invalidCodeFormat"),
  57. },
  58. },
  59. stationName: {
  60. inputType: "text",
  61. minLength: 2,
  62. maxLength: 16,
  63. isInput: true,
  64. regex: regex.az09_,
  65. errors: {
  66. //format: t("general:invalidUsernameFormat", { characters: `a-z, A-Z, 0-9${ t("general:and") } _` }),
  67. format: t("general:invalidStationNameFormat", { characters: `a-z, 0-9${ t("general:and") } _` }),
  68. },
  69. },
  70. stationDisplayName: {
  71. inputType: "text",
  72. minLength: 2,
  73. maxLength: 32,
  74. isInput: true,
  75. regex: regex.azAZ09_,
  76. errors: {
  77. //format: t("general:invalidUsernameFormat", { characters: `a-z, A-Z, 0-9${ t("general:and") } _` }),
  78. format: t("general:invalidStationDisplayNameFormat", { characters: `a-z, A-Z, 0-9${ t("general:and") } _` }),
  79. },
  80. },
  81. stationDescription: {
  82. inputType: "text",
  83. minLength: 2,
  84. maxLength: 200,
  85. isTextarea: true,
  86. errors: {},
  87. },
  88. stationPrivacy: {
  89. isRadio: true,
  90. options: [
  91. {
  92. text: "Public - Lorem ipsum lorem ipsum lorem ipsum",
  93. value: "public",
  94. },
  95. {
  96. text: "Unlisted - Lorem ipsum lorem ipsum lorem ipsum",
  97. value: "unlisted",
  98. },
  99. {
  100. text: "Private - Lorem ipsum lorem ipsum lorem ipsum",
  101. value: "private",
  102. },
  103. ],
  104. },
  105. };
  106. export default class CustomInput extends Component {
  107. static propTypes = {
  108. type: PropTypes.string,
  109. original: PropTypes.string,
  110. name: PropTypes.string,
  111. label: PropTypes.string,
  112. //showLabel: PropTypes.boolean,
  113. placeholder: PropTypes.string,
  114. onRef: PropTypes.func,
  115. };
  116. static defaultProps = {
  117. type: "",
  118. original: "",
  119. name: "",
  120. label: "",
  121. //showLabel: true,
  122. placeholder: "",
  123. valid: false,
  124. onRef: () => {},
  125. };
  126. static initialize = (context) => {
  127. context.input = {}; // eslint-disable-line no-param-reassign
  128. };
  129. static hasInvalidInput = (input, properties) => {
  130. let invalid = false;
  131. if (properties) {
  132. properties.forEach((property) => {
  133. if (!input[property].isValid()) invalid = true;
  134. });
  135. } else {
  136. Object.keys(input).forEach((key) => {
  137. if (!input[key].isValid()) invalid = true;
  138. });
  139. }
  140. return invalid;
  141. };
  142. static isTheSame = (input, properties) => {
  143. let invalid = false;
  144. const value = input[properties[0]].getValue();
  145. properties.forEach((key) => {
  146. if (input[key].getValue() !== value) invalid = true;
  147. });
  148. return invalid;
  149. };
  150. constructor(props) {
  151. super(props);
  152. this.state = {
  153. inputType: dictionary[props.type].inputType,
  154. isTextarea: (typeof dictionary[props.type].isTextarea === "boolean") ? dictionary[props.type].isTextarea : false,
  155. isInput: (typeof dictionary[props.type].isInput === "boolean") ? dictionary[props.type].isInput : false,
  156. isRadio: (typeof dictionary[props.type].isRadio === "boolean") ? dictionary[props.type].isRadio : false,
  157. options: (typeof dictionary[props.type].options === "object") ? dictionary[props.type].options : null,
  158. value: "",
  159. original: this.props.original,
  160. errors: [],
  161. pristine: true,
  162. disabled: false,
  163. valid: false,
  164. };
  165. // More values/functions needs like isEmpty, isRequired
  166. }
  167. componentDidMount() {
  168. this.props.onRef(this);
  169. }
  170. componentWillUnmount() {
  171. this.props.onRef(null);
  172. }
  173. onBlur = () => {
  174. this.validate();
  175. };
  176. onFocus = () => {
  177. this.setState({
  178. pristine: false,
  179. });
  180. };
  181. onChange = (event) => {
  182. this.setState({
  183. value: event.target.value,
  184. });
  185. };
  186. setValue = (value, original = false) => {
  187. const state = {
  188. value,
  189. };
  190. if (original) state.original = value;
  191. this.setState(state, () => {
  192. if (this.state.isRadio) {
  193. this.validate();
  194. }
  195. });
  196. };
  197. getValue = () => {
  198. return this.state.value;
  199. };
  200. listErrors = () => {
  201. let errors = this.state.errors;
  202. let key = 0;
  203. if (errors.length > 0) {
  204. errors = errors.map((error) => {
  205. key++;
  206. return (<li key={ key }>{ error }</li>);
  207. });
  208. return (
  209. <ul className="validation-errors">
  210. { errors }
  211. </ul>
  212. );
  213. } return "";
  214. };
  215. isValid = () => {
  216. return this.state.valid;
  217. };
  218. isOriginal = () => {
  219. return this.state.original === this.state.value;
  220. };
  221. isPristine = () => {
  222. return this.state.pristine;
  223. };
  224. validate = (cb = () => {}) => {
  225. if (!this.state.isRadio) {
  226. const errors = [];
  227. const info = dictionary[this.props.type];
  228. const value = this.state.value;
  229. if (!isLength(value, info.minLength, info.maxLength)) errors.push((info.errors.length) ? info.errors.length : t("general:valueMustBeBetween", {
  230. min: info.minLength,
  231. max: info.maxLength
  232. }));
  233. if (info.regex && !info.regex.test(value)) errors.push(info.errors.format);
  234. this.setState({
  235. errors,
  236. valid: errors.length === 0,
  237. }, cb);
  238. } else {
  239. this.setState({
  240. valid: this.state.value !== null,
  241. }, cb);
  242. }
  243. };
  244. componentWillMount() {
  245. if (this.props.original) {
  246. this.setValue(this.props.original, true);
  247. }
  248. }
  249. componentDidUpdate(prevProps, prevState) {
  250. if (this.props.original !== prevProps.original) {
  251. this.setValue(this.props.original, true);
  252. }
  253. }
  254. render() {
  255. if (this.state.isTextarea || this.state.isInput) {
  256. const ElementType = (this.state.isInput) ? "input" : "textarea";
  257. return (
  258. <label htmlFor={this.props.name}>
  259. {(this.props.showLabel) ? <span>{this.props.label}</span> : null}
  260. <ElementType
  261. placeholder={this.props.placeholder}
  262. type={this.state.inputType}
  263. name={this.props.name}
  264. value={this.state.value}
  265. className={(this.state.errors.length > 0) ? "has-validation-errors" : ""}
  266. onBlur={this.onBlur}
  267. onFocus={this.onFocus}
  268. onChange={this.onChange}
  269. ref={(input) => this.inputElement = input}
  270. />
  271. {this.listErrors()}
  272. </label>
  273. );
  274. } else {
  275. let optionsArr = this.state.options.map((option) => {
  276. let checked = option.value === this.state.value;
  277. return (
  278. <label key={ option.value }>
  279. <input type="radio" name={ this.props.type } value={ option.value } checked={ checked } onChange={ this.onChange }/>
  280. <span>{ option.text }</span>
  281. </label>
  282. );
  283. });
  284. return (
  285. <label htmlFor={this.props.name}>
  286. {(this.props.showLabel) ? <span>{this.props.label}</span> : null}
  287. <div>
  288. { optionsArr }
  289. {/*<ElementType
  290. placeholder={this.props.placeholder}
  291. type={this.state.inputType}
  292. name={this.props.name}
  293. value={this.state.value}
  294. className={(this.state.errors.length > 0) ? "has-validation-errors" : ""}
  295. onBlur={this.onBlur}
  296. onFocus={this.onFocus}
  297. onChange={this.onChange}
  298. ref={(input) => this.inputElement = input}
  299. />*/}
  300. </div>
  301. {this.listErrors()}
  302. </label>
  303. );
  304. }
  305. }
  306. }