Merge pull request #78 from houdiniproject/houdini_form_improvement

Server invalidations are now sticky
This commit is contained in:
Eric Schultz 2018-06-29 12:43:47 -05:00 committed by GitHub
commit 1b8652b992
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 21 deletions

View file

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

View file

@ -5,6 +5,8 @@ export interface StandardFieldComponentProps
{
inError:boolean
error?:string
inStickyError?:boolean
stickyError?:string
children?:React.ReactNode
[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 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>
{this.renderChildren()}
{errorDiv}
{stickyErrorDiv }
</div>

View file

@ -5,11 +5,15 @@ import * as _ from 'lodash'
import {Field} from "../../../../types/mobx-react-form";
import LabeledFieldComponent from "./LabeledFieldComponent";
import {injectIntl, InjectedIntl} from 'react-intl';
import {HoudiniField} from "../../lib/houdini_form";
export const BasicField = injectIntl(observer((props:{field:Field, intl?:InjectedIntl, wrapperClassName?:string}) =>{
let field = props.field as HoudiniField
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"/>
</LabeledFieldComponent>

View file

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

View file

@ -1,22 +1,92 @@
// License: LGPL-3.0-or-later
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 * as _ from 'lodash'
import {ValidationErrorsException} from "../../api";
import {initializationDefinition} from "../../../types/mobx-react-form";
export class HoudiniForm extends Form {
@observable
private $serverError:string
plugins() {
return {
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
{
let currentItem: Field|Form = f
@ -66,13 +136,14 @@ export class StaticFormToErrorAndBackConverter<T> {
this.formToPath = _.invert(pathToForm)
}
convertFormToObject(form: Form): T {
convertFormToObject(form: HoudiniForm|Form): T {
let output = {}
let hForm = form as HoudiniForm
for (let pathToFormKey in this.pathToForm) {
if (this.pathToForm.hasOwnProperty(pathToFormKey)) {
let formPath = this.pathToForm[pathToFormKey]
if (form.$(formPath).value && _.trim(form.$(formPath).value) !== "")
_.set(output, pathToFormKey, form.$(formPath).value)
if (hForm.$(formPath).value && _.trim(hForm.$(formPath).value) !== "")
_.set(output, pathToFormKey, hForm.$(formPath).value)
}
}
@ -82,13 +153,15 @@ export class StaticFormToErrorAndBackConverter<T> {
}
@action.bound
convertErrorToForm(errorException: ValidationErrorsException, form: Form): void {
convertErrorToForm(errorException: ValidationErrorsException, form: HoudiniForm|Form): void {
runInAction(() => {
let hForm = form as HoudiniForm
_.forEach(errorException.item.errors, (error) => {
let message = error.messages.join(", ")
_.forEach(error.params, (p) => {
if (this.pathToForm[p])
form.$(this.pathToForm[p]).invalidate(message)
if (this.pathToForm[p]) {
(hForm.$(this.pathToForm[p]) as HoudiniField).invalidateFromServer(message)
}
else {
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{}
export declare class Field implements Base, FieldProperties, FieldMethods, FieldHandlers {
constructor(...props:any[])
readonly bindings: string;
readonly changed: boolean;
readonly default: boolean;
@ -144,8 +145,6 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
sync(e: Field): any;
}
@ -256,7 +255,6 @@ interface initializationDefinition {
fields?:FieldDefinitions[]
}
export class Form implements Base {
@ -290,7 +288,7 @@ export class Form implements Base {
intercept(obj: any);
invalidate(msg: string);
invalidate(msg?: string);
map(callback: (i: Field) => void);
@ -306,6 +304,8 @@ export class Form implements Base {
update(obj: any): void;
readonly submitting: boolean;
protected validator :any
}