Browse Source

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

Johannes Schill 7 years ago
parent
commit
a8e184c025

+ 42 - 52
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<HTMLInputElement> {
+  validationEvents: ValidationEvents;
+  hideErrorMessage?: boolean;
 
-export class Input extends PureComponent<Props & React.HTMLProps<HTMLInputElement>> {
+  // 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> {
   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 (
       <div className="our-custom-wrapper-class">
-        <input {...restProps} onBlur={this.onBlurWithValidation} className={inputClassName} />
+        <input {...inputElementProps} className={inputClassName} />
         {error && !hideErrorMessage && <span>{error}</span>}
       </div>
     );

+ 11 - 0
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;
+};

+ 19 - 16
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<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
-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<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>

+ 5 - 1
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[];
+}

+ 2 - 1
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,
 };