Merge pull request #78 from houdiniproject/houdini_form_improvement
Server invalidations are now sticky
This commit is contained in:
commit
1b8652b992
6 changed files with 116 additions and 21 deletions
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"`)
|
||||
}
|
||||
|
|
8
types/mobx-react-form/index.d.ts
vendored
8
types/mobx-react-form/index.d.ts
vendored
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue