lbry-desktop/ui/js/component/formField/view.jsx
Alex Liebowitz de08c04158 Add regexp option to FormField
Still needs logic to notify the form when there's invalid input

Add address validation to Send page

Trim address input on Send page

Adds trim prop to FormField

Improve LBRY address regexp

On Send page, don't prevent form submit, and only show error on blur

This isn't a full fix (it only handles blur, it's still the form's
job to do validation on submit). A proper solution to this (that can
generalize to other forms) will probably involve looking at all of the
inputs and asking each one whether it has an error, which will
require some tricky state management.

On Send page, use error message from daemon

Update CHANGELOG.md

Further improve invalid address regexp

 - Remove incorrect check for second character
 - Add "O" to chars excluded from b58 section
 - Allow for 33-character addresses

Don't internationalize daemon error message on Send page

remove console, add i18n
2017-09-06 14:53:57 -04:00

209 lines
5.8 KiB
JavaScript

import React from "react";
import FileSelector from "component/file-selector.js";
import SimpleMDE from "react-simplemde-editor";
import { formFieldNestedLabelTypes, formFieldId } from "../form";
import style from "react-simplemde-editor/dist/simplemde.min.css";
const formFieldFileSelectorTypes = ["file", "directory"];
class FormField extends React.PureComponent {
static propTypes = {
type: React.PropTypes.string.isRequired,
prefix: React.PropTypes.string,
postfix: React.PropTypes.string,
hasError: React.PropTypes.bool,
trim: React.PropTypes.bool,
regexp: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(RegExp),
React.PropTypes.string,
]),
};
static defaultProps = {
trim: false,
};
constructor(props) {
super(props);
this._fieldRequiredText = __("This field is required");
this._type = null;
this._element = null;
this._extraElementProps = {};
this.state = {
isError: null,
errorMessage: null,
};
}
componentWillMount() {
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) {
this._element = "input";
this._type = this.props.type;
} else if (this.props.type == "text-number") {
this._element = "input";
this._type = "text";
} else if (this.props.type == "SimpleMDE") {
this._element = SimpleMDE;
this._type = "textarea";
this._extraElementProps.options = {
hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"],
};
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
this._element = "input";
this._type = "hidden";
} else {
// Non <input> field, e.g. <select>, <textarea>
this._element = this.props.type;
}
}
componentDidMount() {
/**
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468
*/
if (this.props.type == "directory") {
this.refs.field.webkitdirectory = true;
}
}
handleFileChosen(path) {
this.refs.field.value = path;
if (this.props.onChange) {
// Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event("change", { bubbles: true });
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event);
}
}
showError(text) {
this.setState({
isError: true,
errorMessage: text,
});
}
clearError() {
this.setState({
isError: false,
errorMessage: "",
});
}
focus() {
this.refs.field.focus();
}
getValue() {
if (this.props.type == "checkbox") {
return this.refs.field.checked;
} else if (this.props.type == "SimpleMDE") {
return this.refs.field.simplemde.value();
} else {
return this.props.trim
? this.refs.field.value.trim()
: this.refs.field.value;
}
}
getSelectedElement() {
return this.refs.field.options[this.refs.field.selectedIndex];
}
getOptions() {
return this.refs.field.options;
}
validate() {
if ("regexp" in this.props) {
if (!this.getValue().match(this.props.regexp)) {
this.showError(__("Invalid format."));
} else {
this.clearError();
}
}
}
render() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props),
isError = this.state.isError !== null
? this.state.isError
: this.props.hasError,
elementId = this.props.elementId ? this.props.elementId : formFieldId(),
renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
delete otherProps.type;
delete otherProps.label;
delete otherProps.hasError;
delete otherProps.className;
delete otherProps.postfix;
delete otherProps.prefix;
delete otherProps.dispatch;
delete otherProps.regexp;
delete otherProps.trim;
const element = (
<this._element
id={elementId}
type={this._type}
name={this.props.name}
ref="field"
placeholder={this.props.placeholder}
onBlur={() => this.validate()}
className={
"form-field__input form-field__input-" +
this.props.type +
" " +
(this.props.className || "") +
(isError ? "form-field__input--error" : "")
}
{...otherProps}
{...this._extraElementProps}
>
{this.props.children}
</this._element>
);
return (
<div className={"form-field form-field--" + this.props.type}>
{this.props.prefix
? <span className="form-field__prefix">{this.props.prefix}</span>
: ""}
{renderElementInsideLabel
? <label
htmlFor={elementId}
className={
"form-field__label " +
(isError ? "form-field__label--error" : "")
}
>
{element}
{this.props.label}
</label>
: element}
{formFieldFileSelectorTypes.includes(this.props.type)
? <FileSelector
type={this.props.type}
onFileChosen={this.handleFileChosen.bind(this)}
{...(this.props.defaultValue
? { initPath: this.props.defaultValue }
: {})}
/>
: null}
{this.props.postfix
? <span className="form-field__postfix">{this.props.postfix}</span>
: ""}
{isError && this.state.errorMessage
? <div className="form-field__error">{this.state.errorMessage}</div>
: ""}
</div>
);
}
}
export default FormField;