diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index a261203b3f3..315acbd645c 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,9 +1,8 @@ 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 { + validationEvents: ValidationEvents; + hideErrorMessage?: boolean; -export class Input extends PureComponent> { + // Override event props and append status as argument + onBlur?: (event: React.FocusEvent, status?: InputStatus) => void; + onFocus?: (event: React.FocusEvent, status?: InputStatus) => void; + onChange?: (event: React.FormEvent, status?: InputStatus) => void; +} + +export class Input extends PureComponent { 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); - } - - if (onBlur) { - onBlur(evt); - } + 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 (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 (
- + {error && !hideErrorMessage && {error}}
); diff --git a/public/app/core/utils/validate.ts b/public/app/core/utils/validate.ts new file mode 100644 index 00000000000..34f8125833f --- /dev/null +++ b/public/app/core/utils/validate.ts @@ -0,0 +1,11 @@ +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; +}; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 3c40c8a3568..016299f574e 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -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,17 +42,20 @@ interface LoadingPlaceholderProps { } const LoadingPlaceholder: SFC = ({ text }) =>

{text}

; -const validationRules: ValidationRule[] = [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', }, - errorMessage: 'Not a valid timespan', - }, -]; + ], +}; export class QueriesTab extends PureComponent { element: any; @@ -322,8 +325,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onOverrideTime} - validationRules={validationRules} + onBlur={this.onOverrideTime} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} /> @@ -338,8 +341,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onTimeShift} - validationRules={validationRules} + onBlur={this.onTimeShift} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} /> diff --git a/public/app/types/form.ts b/public/app/types/form.ts index 180b41d8730..95026c30be9 100644 --- a/public/app/types/form.ts +++ b/public/app/types/form.ts @@ -1,4 +1,8 @@ export interface ValidationRule { - rule: (value: string) => boolean; + rule: (valueToValidate: string) => boolean; errorMessage: string; } + +export interface ValidationEvents { + [eventName: string]: ValidationRule[]; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index e60dcb0993d..1a56fad7bf0 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -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, };