Commit a8e184c0 by Johannes Schill

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

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