lbry-desktop/ui/js/component/form.js

295 lines
7.8 KiB
JavaScript
Raw Normal View History

2017-06-06 23:19:12 +02:00
import React from "react";
import FileSelector from "./file-selector.js";
2017-06-15 21:30:56 +02:00
import SimpleMDE from "react-simplemde-editor";
import style from "react-simplemde-editor/dist/simplemde.min.css";
2016-11-22 21:19:08 +01:00
2017-06-15 21:30:56 +02:00
let formFieldCounter = 0,
2017-06-06 23:19:12 +02:00
formFieldFileSelectorTypes = ["file", "directory"],
formFieldNestedLabelTypes = ["radio", "checkbox"];
2016-11-22 21:19:08 +01:00
2017-04-10 14:32:40 +02:00
function formFieldId() {
2017-06-06 23:19:12 +02:00
return "form-field-" + ++formFieldCounter;
2017-04-10 14:32:40 +02:00
}
2017-04-09 17:06:23 +02:00
2017-06-08 06:42:19 +02:00
export class FormField extends React.PureComponent {
2017-05-17 10:10:25 +02:00
static propTypes = {
type: React.PropTypes.string.isRequired,
2017-04-12 16:55:19 +02:00
prefix: React.PropTypes.string,
postfix: React.PropTypes.string,
2017-06-06 23:19:12 +02:00
hasError: React.PropTypes.bool,
};
2017-05-17 10:10:25 +02:00
constructor(props) {
super(props);
2017-06-06 23:19:12 +02:00
this._fieldRequiredText = __("This field is required");
2017-05-17 10:10:25 +02:00
this._type = null;
this._element = null;
2017-06-15 21:30:56 +02:00
this._extraElementProps = {};
2017-05-17 10:10:25 +02:00
this.state = {
2017-04-10 14:32:40 +02:00
isError: null,
errorMessage: null,
2017-05-17 10:10:25 +02:00
};
}
componentWillMount() {
2017-06-06 23:19:12 +02:00
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) {
this._element = "input";
this._type = this.props.type;
2017-06-06 23:19:12 +02:00
} else if (this.props.type == "text-number") {
this._element = "input";
this._type = "text";
2017-06-15 21:30:56 +02:00
} else if (this.props.type == "SimpleMDE") {
this._element = SimpleMDE;
this._type = "textarea";
this._extraElementProps.options = {
hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"],
2017-06-15 21:30:56 +02:00
};
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
2017-06-06 23:19:12 +02:00
this._element = "input";
this._type = "hidden";
} else {
// Non <input> field, e.g. <select>, <textarea>
this._element = this.props.type;
}
2017-05-17 10:10:25 +02:00
}
2017-05-19 15:17:41 +02:00
componentDidMount() {
/**
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468
*/
2017-06-06 23:19:12 +02:00
if (this.props.type == "directory") {
this.refs.field.webkitdirectory = true;
}
2017-05-19 15:17:41 +02:00
}
handleFileChosen(path) {
this.refs.field.value = path;
2017-06-06 23:19:12 +02:00
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 });
2017-05-19 15:17:41 +02:00
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);
}
}
2017-05-17 10:10:25 +02:00
showError(text) {
this.setState({
2017-04-10 14:32:40 +02:00
isError: true,
errorMessage: text,
});
2017-05-17 10:10:25 +02:00
}
focus() {
this.refs.field.focus();
2017-05-17 10:10:25 +02:00
}
getValue() {
2017-06-06 23:19:12 +02:00
if (this.props.type == "checkbox") {
return this.refs.field.checked;
2017-06-15 21:30:56 +02:00
} else if (this.props.type == "SimpleMDE") {
return this.refs.field.simplemde.value();
} else {
return this.refs.field.value;
}
2017-05-17 10:10:25 +02:00
}
getSelectedElement() {
2016-09-20 12:38:46 +02:00
return this.refs.field.options[this.refs.field.selectedIndex];
2017-05-17 10:10:25 +02:00
}
2017-06-15 21:30:56 +02:00
getOptions() {
return this.refs.field.options;
}
2017-05-17 10:10:25 +02:00
render() {
// Pass all unhandled props to the field element
2017-04-09 17:06:23 +02:00
const otherProps = Object.assign({}, this.props),
2017-06-06 23:19:12 +02:00
isError = this.state.isError !== null
? this.state.isError
: this.props.hasError,
elementId = this.props.id ? this.props.id : formFieldId(),
renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
2017-04-10 14:32:40 +02:00
delete otherProps.type;
2017-04-09 17:06:23 +02:00
delete otherProps.label;
2017-04-10 14:32:40 +02:00
delete otherProps.hasError;
2017-04-12 16:55:19 +02:00
delete otherProps.className;
delete otherProps.postfix;
delete otherProps.prefix;
2017-06-06 23:19:12 +02:00
const element = (
<this._element
id={elementId}
type={this._type}
name={this.props.name}
ref="field"
placeholder={this.props.placeholder}
className={
"form-field__input form-field__input-" +
this.props.type +
" " +
(this.props.className || "") +
(isError ? "form-field__input--error" : "")
}
{...otherProps}
2017-06-15 21:30:56 +02:00
{...this._extraElementProps}
2017-06-06 23:19:12 +02:00
>
{this.props.children}
</this._element>
);
2017-06-06 06:21:55 +02:00
2017-06-06 23:19:12 +02:00
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>
);
}
2017-05-17 10:10:25 +02:00
}
2016-11-22 21:19:08 +01:00
2017-06-08 06:42:19 +02:00
export class FormRow extends React.PureComponent {
2017-05-17 10:10:25 +02:00
static propTypes = {
2017-06-06 23:19:12 +02:00
label: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element,
]),
2017-07-19 01:00:13 +02:00
errorMessage: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.object,
]),
2017-04-10 14:32:40 +02:00
// helper: React.PropTypes.html,
2017-06-06 23:19:12 +02:00
};
2017-05-17 10:10:25 +02:00
constructor(props) {
super(props);
2017-06-06 23:19:12 +02:00
this._fieldRequiredText = __("This field is required");
2017-05-17 10:10:25 +02:00
2017-06-08 23:15:34 +02:00
this.state = this.getStateFromProps(props);
}
componentWillReceiveProps(nextProps) {
this.setState(this.getStateFromProps(nextProps));
}
getStateFromProps(props) {
return {
isError: !!props.errorMessage,
2017-06-08 23:15:34 +02:00
errorMessage: typeof props.errorMessage === "string"
? props.errorMessage
2017-07-19 01:00:13 +02:00
: props.errorMessage instanceof Error
? props.errorMessage.toString()
: "",
2017-05-17 10:10:25 +02:00
};
}
showError(text) {
2017-04-10 14:32:40 +02:00
this.setState({
isError: true,
errorMessage: text,
});
2017-05-17 10:10:25 +02:00
}
showRequiredError() {
2017-04-12 16:55:19 +02:00
this.showError(this._fieldRequiredText);
2017-05-17 10:10:25 +02:00
}
clearError(text) {
2017-04-12 16:55:19 +02:00
this.setState({
isError: false,
2017-06-06 23:19:12 +02:00
errorMessage: "",
2017-04-12 16:55:19 +02:00
});
2017-05-17 10:10:25 +02:00
}
getValue() {
2017-04-10 14:32:40 +02:00
return this.refs.field.getValue();
2017-05-17 10:10:25 +02:00
}
getSelectedElement() {
2017-04-10 20:12:07 +02:00
return this.refs.field.getSelectedElement();
2017-05-17 10:10:25 +02:00
}
2017-06-15 21:30:56 +02:00
getOptions() {
return this.refs.field.getOptions();
}
2017-05-17 10:10:25 +02:00
focus() {
2017-04-12 16:55:19 +02:00
this.refs.field.focus();
2017-05-17 10:10:25 +02:00
}
render() {
2017-04-10 14:32:40 +02:00
const fieldProps = Object.assign({}, this.props),
2017-06-06 23:19:12 +02:00
elementId = formFieldId(),
renderLabelInFormField = formFieldNestedLabelTypes.includes(
this.props.type
);
2017-04-10 14:32:40 +02:00
if (!renderLabelInFormField) {
delete fieldProps.label;
}
delete fieldProps.helper;
delete fieldProps.errorMessage;
2017-04-10 14:32:40 +02:00
2017-06-06 23:19:12 +02:00
return (
<div className="form-row">
{this.props.label && !renderLabelInFormField
? <div
className={
"form-row__label-row " +
(this.props.labelPrefix ? "form-row__label-row--prefix" : "")
}
>
<label
htmlFor={elementId}
className={
"form-field__label " +
(this.state.isError ? "form-field__label--error" : "")
}
>
{this.props.label}
</label>
</div>
: ""}
<FormField ref="field" hasError={this.state.isError} {...fieldProps} />
{!this.state.isError && this.props.helper
? <div className="form-field__helper">{this.props.helper}</div>
: ""}
{this.state.isError
? <div className="form-field__error">{this.state.errorMessage}</div>
: ""}
</div>
);
2017-04-10 14:32:40 +02:00
}
2017-05-17 10:10:25 +02:00
}