2017-06-06 23:19:12 +02:00
|
|
|
import React from "react";
|
|
|
|
import FileSelector from "./file-selector.js";
|
|
|
|
import { Icon } from "./common.js";
|
2016-11-22 21:19:08 +01:00
|
|
|
|
2017-04-10 14:32:40 +02:00
|
|
|
var 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-05-17 10:10:25 +02:00
|
|
|
export class FormField extends React.Component {
|
|
|
|
static propTypes = {
|
2016-08-07 22:10:44 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
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";
|
2016-08-07 22:10:44 +02:00
|
|
|
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-05-12 12:36:08 +02:00
|
|
|
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
2017-06-06 23:19:12 +02:00
|
|
|
this._element = "input";
|
|
|
|
this._type = "hidden";
|
2016-08-07 22:10:44 +02:00
|
|
|
} 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() {
|
2017-05-12 12:36:08 +02:00
|
|
|
/**
|
|
|
|
* 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") {
|
2017-05-12 12:36:08 +02:00
|
|
|
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) {
|
2016-08-07 22:10:44 +02:00
|
|
|
this.setState({
|
2017-04-10 14:32:40 +02:00
|
|
|
isError: true,
|
|
|
|
errorMessage: text,
|
2016-08-07 22:10:44 +02:00
|
|
|
});
|
2017-05-17 10:10:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
focus() {
|
2016-08-07 22:10:44 +02:00
|
|
|
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") {
|
2016-08-07 22:10:44 +02:00
|
|
|
return this.refs.field.checked;
|
|
|
|
} 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
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2016-08-07 22:10:44 +02:00
|
|
|
// 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
|
|
|
|
2016-08-07 22:10:44 +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;
|
2016-08-07 22:10:44 +02:00
|
|
|
|
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}
|
|
|
|
>
|
|
|
|
{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>
|
|
|
|
);
|
2016-08-07 22:10:44 +02:00
|
|
|
}
|
2017-05-17 10:10:25 +02:00
|
|
|
}
|
2016-11-22 21:19:08 +01:00
|
|
|
|
2017-05-17 10:10:25 +02:00
|
|
|
export class FormRow extends React.Component {
|
|
|
|
static propTypes = {
|
2017-06-06 23:19:12 +02:00
|
|
|
label: React.PropTypes.oneOfType([
|
|
|
|
React.PropTypes.string,
|
|
|
|
React.PropTypes.element,
|
|
|
|
]),
|
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 {
|
2017-06-02 02:51:52 +02:00
|
|
|
isError: !!props.errorMessage,
|
2017-06-08 23:15:34 +02:00
|
|
|
errorMessage: typeof props.errorMessage === "string"
|
|
|
|
? props.errorMessage
|
|
|
|
: "",
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2017-06-02 02:51:52 +02:00
|
|
|
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
|
|
|
}
|