houdini/javascripts/src/lib/houdini_form.ts

176 lines
3.8 KiB
TypeScript
Raw Normal View History

// License: LGPL-3.0-or-later
2018-06-28 13:24:09 -05:00
import {Field, Form} from "mobx-react-form";
2018-06-29 13:13:31 -05:00
import {action, computed, IValueDidChange, observable, runInAction} from 'mobx'
import * as _ from 'lodash'
import {ValidationErrorsException} from "../../api";
2018-06-29 13:13:31 -05:00
import validator = require("validator");
export class HoudiniForm extends Form {
2018-06-29 12:42:56 -05:00
@observable
private $serverError:string
plugins() {
return {
vjf: validator
2018-06-29 12:42:56 -05:00
};
}
2018-06-29 12:42:56 -05:00
public makeField(key:any, path:any, data:any, props:any, update:boolean, state:any) {
return new HoudiniField(key, path, data, props, update, state);
}
2018-06-29 12:42:56 -05:00
@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
}
}
2018-06-29 12:42:56 -05:00
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 !== "")
}
}
2018-06-28 13:24:09 -05:00
export function areWeOrAnyParentSubmitting(f:Field|Form ) : boolean
{
let currentItem: Field|Form = f
let isSubmitting:boolean = f.submitting
while (!isSubmitting && currentItem && !(currentItem instanceof Form)){
currentItem = currentItem.container()
isSubmitting = currentItem.submitting
}
return isSubmitting
}
interface PathToFormField {
[props: string]: string
}
type FormFieldToPath = PathToFormField
/**
* tool for converting between the form's data structure
* to the AJAX datastructure and properly assigning
* errors if AJAX request fails
* As an example for the, consider the the following form structure:
* {
* // a tab in a Wizard
* nonprofitTab: {
* organization_name: {some field info}
* }
* }
*
* We want to create a data structure for AJAX like so:
*
* In the database
*
*/
export class StaticFormToErrorAndBackConverter<T> {
pathToForm: PathToFormField
formToPath: FormFieldToPath
constructor(pathToForm: PathToFormField) {
this.pathToForm = pathToForm
this.formToPath = _.invert(pathToForm)
}
2018-06-29 12:42:56 -05:00
convertFormToObject(form: HoudiniForm|Form): T {
let output = {}
2018-06-29 12:42:56 -05:00
let hForm = form as HoudiniForm
for (let pathToFormKey in this.pathToForm) {
if (this.pathToForm.hasOwnProperty(pathToFormKey)) {
let formPath = this.pathToForm[pathToFormKey]
2018-06-29 12:42:56 -05:00
if (hForm.$(formPath).value && _.trim(hForm.$(formPath).value) !== "")
_.set(output, pathToFormKey, hForm.$(formPath).value)
}
}
return output as T
}
@action.bound
2018-06-29 12:42:56 -05:00
convertErrorToForm(errorException: ValidationErrorsException, form: HoudiniForm|Form): void {
runInAction(() => {
2018-06-29 12:42:56 -05:00
let hForm = form as HoudiniForm
_.forEach(errorException.item.errors, (error) => {
let message = error.messages.join(", ")
_.forEach(error.params, (p) => {
2018-06-29 12:42:56 -05:00
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}"`)
}
})
})
})
}
2018-06-28 13:24:09 -05:00
}