Server invalidations are now sticky

This commit is contained in:
Eric Schultz 2018-06-29 12:42:56 -05:00
parent 6eeb2639f2
commit e5861d41f5
6 changed files with 116 additions and 21 deletions

View file

@ -12,18 +12,28 @@ export interface LabeledFieldComponentProps
labelText: string labelText: string
inError:boolean inError:boolean
error?:string error?:string
inStickyError?:boolean
stickyError?:string
className?:string className?:string
} }
@observer @observer
export default class LabeledFieldComponent extends React.Component<LabeledFieldComponentProps, {}> { export default class LabeledFieldComponent extends React.Component<LabeledFieldComponentProps, {}> {
render() { render() {
let className = this.props.className || "" let classNames:string[] = []
if (this.props.className)
classNames.push(this.props.className)
let inError = this.props.inError && this.props.error !== null && this.props.error !== ""; let inError = this.props.inError && this.props.error !== null && this.props.error !== "";
className += " form-group" let inStickyError = this.props.inStickyError && this.props.stickyError !== null && this.props.stickyError !== ""
className += inError ? " has-error" : ""
return <fieldset className={className}><label htmlFor={this.props.inputId} className="control-label">{this.props.labelText}</label> classNames.push("form-group")
<StandardFieldComponent inError={inError} error={this.props.error} >{this.props.children}</StandardFieldComponent> if(inError || inStickyError){
classNames.push("has-error")
}
return <fieldset className={classNames.join(" ")}><label htmlFor={this.props.inputId} className="control-label">{this.props.labelText}</label>
<StandardFieldComponent inError={inError} error={this.props.error} inStickyError={inStickyError} stickyError={this.props.stickyError}>{this.props.children}</StandardFieldComponent>
</fieldset>; </fieldset>;
} }
} }

View file

@ -5,6 +5,8 @@ export interface StandardFieldComponentProps
{ {
inError:boolean inError:boolean
error?:string error?:string
inStickyError?:boolean
stickyError?:string
children?:React.ReactNode children?:React.ReactNode
[additional_properties:string]: any [additional_properties:string]: any
} }
@ -24,9 +26,13 @@ export default class StandardFieldComponent extends React.Component<StandardFiel
let errorMessage = this.props.inError ? this.props.error : undefined let errorMessage = this.props.inError ? this.props.error : undefined
let errorDiv = this.props.inError? <div className="help-block" role="alert">{errorMessage}</div> : "" let errorDiv = this.props.inError? <div className="help-block" role="alert">{errorMessage}</div> : ""
let stickyErrorMessage = this.props.inStickyError ? this.props.stickyError : undefined
let stickyErrorDiv = this.props.inStickyError ? <div className="help-block" role="alert">{stickyErrorMessage}</div> : ""
return <div> return <div>
{this.renderChildren()} {this.renderChildren()}
{errorDiv} {errorDiv}
{stickyErrorDiv }
</div> </div>

View file

@ -5,11 +5,15 @@ import * as _ from 'lodash'
import {Field} from "../../../../types/mobx-react-form"; import {Field} from "../../../../types/mobx-react-form";
import LabeledFieldComponent from "./LabeledFieldComponent"; import LabeledFieldComponent from "./LabeledFieldComponent";
import {injectIntl, InjectedIntl} from 'react-intl'; import {injectIntl, InjectedIntl} from 'react-intl';
import {HoudiniField} from "../../lib/houdini_form";
export const BasicField = injectIntl(observer((props:{field:Field, intl?:InjectedIntl, wrapperClassName?:string}) =>{ export const BasicField = injectIntl(observer((props:{field:Field, intl?:InjectedIntl, wrapperClassName?:string}) =>{
let field = props.field as HoudiniField
return <LabeledFieldComponent return <LabeledFieldComponent
inputId={props.field.id} labelText={props.field.label} inError={props.field.hasError} error={props.field.error} className={props.wrapperClassName} > inputId={props.field.id} labelText={field.label} inError={field.hasError} error={field.error}
inStickyError={field.hasServerError} stickyError={field.serverError}
className={props.wrapperClassName} >
<input {...props.field.bind()} className="form-control"/> <input {...props.field.bind()} className="form-control"/>
</LabeledFieldComponent> </LabeledFieldComponent>

View file

@ -90,6 +90,8 @@ export class RegistrationPageForm extends HoudiniForm {
if (e instanceof ValidationErrorsException) { if (e instanceof ValidationErrorsException) {
this.converter.convertErrorToForm(e, f) this.converter.convertErrorToForm(e, f)
} }
this.invalidateFromServer(e['error'])
//set error to the form //set error to the form
} }
} }

View file

@ -1,21 +1,91 @@
// License: LGPL-3.0-or-later // License: LGPL-3.0-or-later
import {Field, Form} from "mobx-react-form"; import {Field, Form} from "mobx-react-form";
import {action, runInAction} from 'mobx' import {observable, action, runInAction, computed, IValueDidChange} from 'mobx'
import validator = require("validator") import validator = require("validator")
import * as _ from 'lodash' import * as _ from 'lodash'
import {ValidationErrorsException} from "../../api"; import {ValidationErrorsException} from "../../api";
import {initializationDefinition} from "../../../types/mobx-react-form";
export class HoudiniForm extends Form { export class HoudiniForm extends Form {
@observable
private $serverError:string
plugins() { plugins() {
return { return {
vjf: validator vjf: validator
}; };
} }
public makeField(key:any, path:any, data:any, props:any, update:boolean, state:any) {
return new HoudiniField(key, path, data, props, update, state);
} }
@computed
public get serverError():string {
return this.$serverError
}
@computed
public get hasServerError():boolean{
return (this.$serverError && this.$serverError !== null && this.$serverError !== "") &&
!this.submitting
}
@action
invalidateFromServer(message:string) {
this.invalidate()
this.$serverError = message
}
}
export class HoudiniField extends Field {
constructor(...props:any[]) {
super(...props)
this.observe({
key: 'areWeOrAnyParentSubmitting', call: (obj: { form: HoudiniForm,
field: HoudiniField,
change: IValueDidChange<boolean> }) => {
if (obj.change.newValue) {
this.$serverError = null
}
}
})
}
@observable private $serverError:string
@action
invalidateFromServer(message:string) {
this.$serverError = message
}
@computed
public get serverError():string {
return this.$serverError
}
@computed get areWeOrAnyParentSubmitting() :boolean {
return areWeOrAnyParentSubmitting(this)
}
@computed
public get hasServerError():boolean{
return (this.$serverError && this.$serverError !== null && this.$serverError !== "")
}
}
export function areWeOrAnyParentSubmitting(f:Field|Form ) : boolean export function areWeOrAnyParentSubmitting(f:Field|Form ) : boolean
{ {
@ -66,13 +136,14 @@ export class StaticFormToErrorAndBackConverter<T> {
this.formToPath = _.invert(pathToForm) this.formToPath = _.invert(pathToForm)
} }
convertFormToObject(form: Form): T { convertFormToObject(form: HoudiniForm|Form): T {
let output = {} let output = {}
let hForm = form as HoudiniForm
for (let pathToFormKey in this.pathToForm) { for (let pathToFormKey in this.pathToForm) {
if (this.pathToForm.hasOwnProperty(pathToFormKey)) { if (this.pathToForm.hasOwnProperty(pathToFormKey)) {
let formPath = this.pathToForm[pathToFormKey] let formPath = this.pathToForm[pathToFormKey]
if (form.$(formPath).value && _.trim(form.$(formPath).value) !== "") if (hForm.$(formPath).value && _.trim(hForm.$(formPath).value) !== "")
_.set(output, pathToFormKey, form.$(formPath).value) _.set(output, pathToFormKey, hForm.$(formPath).value)
} }
} }
@ -82,13 +153,15 @@ export class StaticFormToErrorAndBackConverter<T> {
} }
@action.bound @action.bound
convertErrorToForm(errorException: ValidationErrorsException, form: Form): void { convertErrorToForm(errorException: ValidationErrorsException, form: HoudiniForm|Form): void {
runInAction(() => { runInAction(() => {
let hForm = form as HoudiniForm
_.forEach(errorException.item.errors, (error) => { _.forEach(errorException.item.errors, (error) => {
let message = error.messages.join(", ") let message = error.messages.join(", ")
_.forEach(error.params, (p) => { _.forEach(error.params, (p) => {
if (this.pathToForm[p]) if (this.pathToForm[p]) {
form.$(this.pathToForm[p]).invalidate(message) (hForm.$(this.pathToForm[p]) as HoudiniField).invalidateFromServer(message)
}
else { else {
console.warn(`We couldn't find a form element for path: "${p}"`) console.warn(`We couldn't find a form element for path: "${p}"`)
} }

View file

@ -31,6 +31,7 @@ interface FieldHooks{
interface Base extends SharedFieldFormMethods, SharedFormFieldProperties{} interface Base extends SharedFieldFormMethods, SharedFormFieldProperties{}
export declare class Field implements Base, FieldProperties, FieldMethods, FieldHandlers { export declare class Field implements Base, FieldProperties, FieldMethods, FieldHandlers {
constructor(...props:any[])
readonly bindings: string; readonly bindings: string;
readonly changed: boolean; readonly changed: boolean;
readonly default: boolean; readonly default: boolean;
@ -145,8 +146,6 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
sync(e: Field): any; sync(e: Field): any;
} }
interface FormHooks { interface FormHooks {
@ -256,7 +255,6 @@ interface initializationDefinition {
fields?:FieldDefinitions[] fields?:FieldDefinitions[]
} }
export class Form implements Base { export class Form implements Base {
@ -290,7 +288,7 @@ export class Form implements Base {
intercept(obj: any); intercept(obj: any);
invalidate(msg: string); invalidate(msg?: string);
map(callback: (i: Field) => void); map(callback: (i: Field) => void);
@ -307,6 +305,8 @@ export class Form implements Base {
readonly submitting: boolean; readonly submitting: boolean;
protected validator :any
} }