CustomInput.jsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. playlistDescription: {
  89. inputType: "text",
  90. minLength: 1,
  91. maxLength: 16,
  92. isInput: true,
  93. regex: regex.ascii,
  94. errors: {
  95. //format: t("general:invalidUsernameFormat", { characters: `a-z, A-Z, 0-9${ t("general:and") } _` }),
  96. format: "Only ascii is allowed",
  97. },
  98. },
  99. stationPrivacy: {
  100. isRadio: true,
  101. options: [
  102. {
  103. text: "Public - Lorem ipsum lorem ipsum lorem ipsum",
  104. value: "public",
  105. },
  106. {
  107. text: "Unlisted - Lorem ipsum lorem ipsum lorem ipsum",
  108. value: "unlisted",
  109. },
  110. {
  111. text: "Private - Lorem ipsum lorem ipsum lorem ipsum",
  112. value: "private",
  113. },
  114. ],
  115. },
  116. youTubeSearchQuery: {
  117. inputType: "text",
  118. isInput: true,
  119. },
  120. };
  121. export default class CustomInput extends Component {
  122. static propTypes = {
  123. type: PropTypes.string,
  124. original: PropTypes.string,
  125. name: PropTypes.string,
  126. label: PropTypes.string,
  127. //showLabel: PropTypes.boolean,
  128. placeholder: PropTypes.string,
  129. onRef: PropTypes.func,
  130. };
  131. static defaultProps = {
  132. type: "",
  133. original: "",
  134. name: "",
  135. label: "",
  136. //showLabel: true,
  137. placeholder: "",
  138. valid: false,
  139. onRef: () => {},
  140. };
  141. static initialize = (context) => {
  142. context.input = {}; // eslint-disable-line no-param-reassign
  143. };
  144. static hasInvalidInput = (input, properties) => {
  145. let invalid = false;
  146. if (properties) {
  147. properties.forEach((property) => {
  148. if (!input[property].isValid()) invalid = true;
  149. });
  150. } else {
  151. Object.keys(input).forEach((key) => {
  152. if (!input[key].isValid()) invalid = true;
  153. });
  154. }
  155. return invalid;
  156. };
  157. static isTheSame = (input, properties) => {
  158. let invalid = false;
  159. const value = input[properties[0]].getValue();
  160. properties.forEach((key) => {
  161. if (input[key].getValue() !== value) invalid = true;
  162. });
  163. return invalid;
  164. };
  165. constructor(props) {
  166. super(props);
  167. this.state = {
  168. inputType: dictionary[props.type].inputType,
  169. isTextarea: (typeof dictionary[props.type].isTextarea === "boolean") ? dictionary[props.type].isTextarea : false,
  170. isInput: (typeof dictionary[props.type].isInput === "boolean") ? dictionary[props.type].isInput : false,
  171. isRadio: (typeof dictionary[props.type].isRadio === "boolean") ? dictionary[props.type].isRadio : false,
  172. options: (typeof dictionary[props.type].options === "object") ? dictionary[props.type].options : null,
  173. value: "",
  174. original: this.props.original,
  175. errors: [],
  176. pristine: true,
  177. disabled: false,
  178. valid: false,
  179. };
  180. // More values/functions needs like isEmpty, isRequired
  181. }
  182. componentDidMount() {
  183. this.props.onRef(this);
  184. }
  185. componentWillUnmount() {
  186. this.props.onRef(null);
  187. }
  188. onBlur = () => {
  189. this.validate();
  190. };
  191. onFocus = () => {
  192. this.setState({
  193. pristine: false,
  194. });
  195. };
  196. onChange = (event) => {
  197. this.setState({
  198. value: event.target.value,
  199. });
  200. };
  201. setValue = (value, original = false) => {
  202. const state = {
  203. value,
  204. };
  205. if (original) state.original = value;
  206. this.setState(state, () => {
  207. if (this.state.isRadio) {
  208. this.validate();
  209. }
  210. });
  211. };
  212. getValue = () => {
  213. return this.state.value;
  214. };
  215. listErrors = () => {
  216. let errors = this.state.errors;
  217. let key = 0;
  218. if (errors.length > 0) {
  219. errors = errors.map((error) => {
  220. key++;
  221. return (<li key={ key }>{ error }</li>);
  222. });
  223. return (
  224. <ul className="validation-errors">
  225. { errors }
  226. </ul>
  227. );
  228. } return "";
  229. };
  230. isValid = () => {
  231. return this.state.valid;
  232. };
  233. isOriginal = () => {
  234. return this.state.original === this.state.value;
  235. };
  236. isPristine = () => {
  237. return this.state.pristine;
  238. };
  239. validate = (cb = () => {}) => {
  240. if (!this.state.isRadio) {
  241. const errors = [];
  242. const info = dictionary[this.props.type];
  243. const value = this.state.value;
  244. if (!isLength(value, info.minLength, info.maxLength)) errors.push((info.errors.length) ? info.errors.length : t("general:valueMustBeBetween", {
  245. min: info.minLength,
  246. max: info.maxLength
  247. }));
  248. if (info.regex && !info.regex.test(value)) errors.push(info.errors.format);
  249. this.setState({
  250. errors,
  251. valid: errors.length === 0,
  252. }, cb);
  253. } else {
  254. this.setState({
  255. valid: this.state.value !== null,
  256. }, cb);
  257. }
  258. };
  259. componentWillMount() {
  260. if (this.props.original) {
  261. this.setValue(this.props.original, true);
  262. }
  263. }
  264. componentDidUpdate(prevProps, prevState) {
  265. if (this.props.original !== prevProps.original) {
  266. this.setValue(this.props.original, true);
  267. }
  268. }
  269. render() {
  270. if (this.state.isTextarea || this.state.isInput) {
  271. const ElementType = (this.state.isInput) ? "input" : "textarea";
  272. return (
  273. <label htmlFor={this.props.name}>
  274. {(this.props.showLabel) ? <span>{this.props.label}</span> : null}
  275. <ElementType
  276. placeholder={this.props.placeholder}
  277. type={this.state.inputType}
  278. name={this.props.name}
  279. value={this.state.value}
  280. className={(this.state.errors.length > 0) ? "has-validation-errors" : ""}
  281. onBlur={this.onBlur}
  282. onFocus={this.onFocus}
  283. onChange={this.onChange}
  284. ref={(input) => this.inputElement = input}
  285. />
  286. {this.listErrors()}
  287. </label>
  288. );
  289. } else {
  290. let optionsArr = this.state.options.map((option) => {
  291. let checked = option.value === this.state.value;
  292. return (
  293. <label key={ option.value }>
  294. <input type="radio" name={ this.props.type } value={ option.value } checked={ checked } onChange={ this.onChange }/>
  295. <span>{ option.text }</span>
  296. </label>
  297. );
  298. });
  299. return (
  300. <label htmlFor={this.props.name}>
  301. {(this.props.showLabel) ? <span>{this.props.label}</span> : null}
  302. <div>
  303. { optionsArr }
  304. {/*<ElementType
  305. placeholder={this.props.placeholder}
  306. type={this.state.inputType}
  307. name={this.props.name}
  308. value={this.state.value}
  309. className={(this.state.errors.length > 0) ? "has-validation-errors" : ""}
  310. onBlur={this.onBlur}
  311. onFocus={this.onFocus}
  312. onChange={this.onChange}
  313. ref={(input) => this.inputElement = input}
  314. />*/}
  315. </div>
  316. {this.listErrors()}
  317. </label>
  318. );
  319. }
  320. }
  321. }