Commit a8e184c0 by Johannes Schill

react-panel: Clean up input validation and increase code readability

parent 69ae3d2e
import React, { PureComponent } from 'react';
import { ValidationRule } from 'app/types';
import { ValidationEvents, ValidationRule } from 'app/types';
import { validate } from 'app/core/utils/validate';
export enum InputStatus {
Default = 'default',
Loading = 'loading',
Invalid = 'invalid',
Valid = 'valid',
}
......@@ -15,80 +14,71 @@ export enum InputTypes {
Email = 'email',
}
interface Props {
status?: InputStatus;
validationRules: ValidationRule[];
hideErrorMessage?: boolean;
onBlurWithStatus?: (evt, status: InputStatus) => void;
emptyToNull?: boolean;
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
const validator = (value: string, validationRules: ValidationRule[]) => {
const errors = validationRules.reduce((acc, currRule) => {
if (!currRule.rule(value)) {
return acc.concat(currRule.errorMessage);
}
return acc;
}, []);
return errors.length > 0 ? errors : null;
};
interface Props extends React.HTMLProps<HTMLInputElement> {
validationEvents: ValidationEvents;
hideErrorMessage?: boolean;
// Override event props and append status as argument
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
}
export class Input extends PureComponent<Props & React.HTMLProps<HTMLInputElement>> {
export class Input extends PureComponent<Props> {
state = {
error: null,
};
get status() {
const { error } = this.state;
if (error) {
return InputStatus.Invalid;
}
return InputStatus.Valid;
return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
}
onBlurWithValidation = evt => {
const { validationRules, onBlurWithStatus, onBlur } = this.props;
get isInvalid() {
return this.status === InputStatus.Invalid;
}
let errors = null;
if (validationRules) {
errors = validator(evt.currentTarget.value, validationRules);
validatorAsync = (validationRules: ValidationRule[]) => {
return evt => {
const errors = validate(evt.currentTarget.value, validationRules);
this.setState(prevState => {
return {
...prevState,
error: errors ? errors[0] : null,
};
});
}
};
};
if (onBlurWithStatus) {
onBlurWithStatus(evt, errors ? InputStatus.Invalid : InputStatus.Valid);
populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
const inputElementProps = { ...restProps };
Object.keys(EventsWithValidation).forEach(eventName => {
inputElementProps[eventName] = async evt => {
if (validationEvents[eventName]) {
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
}
if (onBlur) {
onBlur(evt);
if (restProps[eventName]) {
restProps[eventName].apply(null, [evt, this.status]);
}
};
});
return inputElementProps;
};
render() {
const {
status,
validationRules,
onBlurWithStatus,
onBlur,
className,
hideErrorMessage,
emptyToNull,
...restProps
} = this.props;
const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
const { error } = this.state;
let inputClassName = 'gf-form-input';
inputClassName = this.status === InputStatus.Invalid ? inputClassName + ' invalid' : inputClassName;
const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : '');
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
return (
<div className="our-custom-wrapper-class">
<input {...restProps} onBlur={this.onBlurWithValidation} className={inputClassName} />
<input {...inputElementProps} className={inputClassName} />
{error && !hideErrorMessage && <span>{error}</span>}
</div>
);
......
import { ValidationRule } from 'app/types';
export const validate = (value: string, validationRules: ValidationRule[]) => {
const errors = validationRules.reduce((acc, currRule) => {
if (!currRule.rule(value)) {
return acc.concat(currRule.errorMessage);
}
return acc;
}, []);
return errors.length > 0 ? errors : null;
};
......@@ -10,9 +10,9 @@ import config from 'app/core/config';
import { QueryInspector } from './QueryInspector';
import { Switch } from 'app/core/components/Switch/Switch';
import { Input } from 'app/core/components/Form';
import { InputStatus } from 'app/core/components/Form/Input';
import { InputStatus, EventsWithValidation } from 'app/core/components/Form/Input';
import { isValidTimeSpan } from 'app/core/utils/rangeutil';
import { ValidationRule } from 'app/types';
import { ValidationEvents } from 'app/types';
// Services
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
......@@ -42,7 +42,9 @@ interface LoadingPlaceholderProps {
}
const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
const validationRules: ValidationRule[] = [
const timeRangeValidationEvents: ValidationEvents = {
[EventsWithValidation.onBlur]: [
{
rule: value => {
if (!value) {
......@@ -52,7 +54,8 @@ const validationRules: ValidationRule[] = [
},
errorMessage: 'Not a valid timespan',
},
];
],
};
export class QueriesTab extends PureComponent<Props, State> {
element: any;
......@@ -322,8 +325,8 @@ export class QueriesTab extends PureComponent<Props, State> {
type="text"
className="gf-form-input max-width-8"
placeholder="1h"
onBlurWithStatus={this.onOverrideTime}
validationRules={validationRules}
onBlur={this.onOverrideTime}
validationEvents={timeRangeValidationEvents}
hideErrorMessage={true}
/>
</div>
......@@ -338,8 +341,8 @@ export class QueriesTab extends PureComponent<Props, State> {
type="text"
className="gf-form-input max-width-8"
placeholder="1h"
onBlurWithStatus={this.onTimeShift}
validationRules={validationRules}
onBlur={this.onTimeShift}
validationEvents={timeRangeValidationEvents}
hideErrorMessage={true}
/>
</div>
......
export interface ValidationRule {
rule: (value: string) => boolean;
rule: (valueToValidate: string) => boolean;
errorMessage: string;
}
export interface ValidationEvents {
[eventName: string]: ValidationRule[];
}
......@@ -30,7 +30,7 @@ import {
AppNotificationTimeout,
} from './appNotifications';
import { DashboardSearchHit } from './search';
import { ValidationRule } from './form';
import { ValidationEvents, ValidationRule } from './form';
export {
Team,
TeamsState,
......@@ -89,6 +89,7 @@ export {
AppNotificationTimeout,
DashboardSearchHit,
UserState,
ValidationEvents,
ValidationRule,
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment