CustomInput.jsx 8.5 KB

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