From c2d9244d00e86e8dfd009b26461fde8bbae5aabd Mon Sep 17 00:00:00 2001 From: Claudio Savino Date: Tue, 25 Apr 2023 10:59:26 +0200 Subject: [PATCH] fix: introduced field `computed` prop Introduced field `computed` prop --- CHANGELOG.md | 3 ++ src/Base.ts | 1 + src/Field.ts | 44 +++++++++++---------- src/models/FieldProps.ts | 2 + src/models/FormInterface.ts | 1 + src/props.ts | 4 +- tests/data/_.nested.ts | 2 + tests/data/forms/flat/form.q.ts | 2 +- tests/data/forms/nested/form.x.ts | 66 +++++++++++++++++++++++++++++++ 9 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 tests/data/forms/nested/form.x.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1994ec..6bfdca57 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 6.3.1 (master) +- Introduced field `computed` prop. To handle nested array fields computed values. + # 6.3.0 (master) - Introduced Forms Composer - Introduced Functional Computed Field Props diff --git a/src/Base.ts b/src/Base.ts index c3b281a4..d9df5b38 100755 --- a/src/Base.ts +++ b/src/Base.ts @@ -278,6 +278,7 @@ export default class Base implements BaseInterface { const props = { $value: _.get(initial[SeparatedPropsMode.values], path), + $computed: _try(SeparatedPropsMode.computed), $label: _try(SeparatedPropsMode.labels), $placeholder: _try(SeparatedPropsMode.placeholders), $default: _try(SeparatedPropsMode.defaults), diff --git a/src/Field.ts b/src/Field.ts index 7280c3ac..0b9e4fe0 100755 --- a/src/Field.ts +++ b/src/Field.ts @@ -298,21 +298,7 @@ export default class Field extends Base implements FieldInterface { newVal = newVal.trim(); } if (this.$value === newVal) return; - // handle numbers - if (this.state.options.get(OptionsEnum.autoParseNumbers, this)) { - if (_.isNumber(this.$initial)) { - if ( - new RegExp("^-?\\d+(,\\d+)*(\\.\\d+([eE]\\d+)?)?$", "g").exec(newVal) - ) { - this.$value = this.$converter(_.toNumber(newVal)); - this.$changed ++; - if (!this.actionRunning) { - this.state.form.$changed ++; - }; - return; - } - } - } + if (this.handleSetNumberValue(newVal)) return; this.$value = this.$converter(newVal); this.$changed ++; if (!this.actionRunning) { @@ -320,6 +306,22 @@ export default class Field extends Base implements FieldInterface { }; } + handleSetNumberValue(newVal: any): boolean { + if (!this.state.options.get(OptionsEnum.autoParseNumbers, this)) + return false; + + if (_.isNumber(this.$initial) || this.type == 'number') { + if (new RegExp("^-?\\d+(,\\d+)*(\\.\\d+([eE]\\d+)?)?$", "g").exec(newVal)) { + this.$value = this.$converter(_.toNumber(newVal)); + this.$changed ++; + if (!this.actionRunning) { + this.state.form.$changed ++; + }; + return true; + } + } + } + get actionRunning() { return this.submitting || this.clearing || this.resetting; } @@ -580,10 +582,10 @@ export default class Field extends Base implements FieldInterface { .find((s) => s.substring(structPath.length) === "[]") : !!Array.isArray(_.get(struct, this.path)); - const { $type, $input, $output, $converter } = $props; + const { $type, $input, $output, $converter, $computed } = $props; if (_.isPlainObject($data)) { - const { type, input, output, converter } = $data; + const { type, input, output, converter, computed } = $data; this.name = _.toString($data.name || $key); this.$type = $type || type || "text"; @@ -595,8 +597,8 @@ export default class Field extends Base implements FieldInterface { fallbackValueOption, isEmptyArray, type: this.type, - unified: $data.value, - separated: $props.$value, + unified: computed || $data.value, + separated: $computed || $props.$value, fallback: $props.$initial, }); @@ -634,8 +636,8 @@ export default class Field extends Base implements FieldInterface { fallbackValueOption, isEmptyArray, type: this.type, - unified: $data, - separated: $props.$value, + unified: $computed || $data, + separated: $computed || $props.$value, }); this._value = retrieveFieldPropFunc(value) diff --git a/src/models/FieldProps.ts b/src/models/FieldProps.ts index 0351d581..abfb7e28 100644 --- a/src/models/FieldProps.ts +++ b/src/models/FieldProps.ts @@ -6,6 +6,7 @@ export enum FieldPropsEnum { fields = "fields", ref= "ref", type = "type", + computed = "computed", value = "value", initial = "initial", default = "default", @@ -80,6 +81,7 @@ export enum FieldPropsOccurrence { } export enum SeparatedPropsMode { + computed = 'computed', values = 'values', labels = 'labels', placeholders = 'placeholders', diff --git a/src/models/FormInterface.ts b/src/models/FormInterface.ts index 30022d29..5c44be85 100644 --- a/src/models/FormInterface.ts +++ b/src/models/FormInterface.ts @@ -33,6 +33,7 @@ export default interface FormInterface extends BaseInterface { export interface FieldsDefinitions { struct?: string[]; fields?: any; + computed?: any; values?: any; labels?: any; placeholders?: any; diff --git a/src/props.ts b/src/props.ts index 76c82941..9066741e 100755 --- a/src/props.ts +++ b/src/props.ts @@ -8,7 +8,7 @@ export interface PropsGroupsInterface { validation: string[]; separated: string[]; occurrences: { - [index: string]: "some" | "every"; + [index: string]: FieldPropsOccurrence; }; } @@ -64,6 +64,7 @@ export const props: PropsGroupsInterface = { FieldPropsEnum.disabled, ], separated: [ + SeparatedPropsMode.computed, SeparatedPropsMode.values, SeparatedPropsMode.labels, SeparatedPropsMode.placeholders, @@ -91,6 +92,7 @@ export const props: PropsGroupsInterface = { SeparatedPropsMode.refs, ], functions: [ + FieldPropsEnum.computed, FieldPropsEnum.observers, FieldPropsEnum.interceptors, FieldPropsEnum.converter, diff --git a/tests/data/_.nested.ts b/tests/data/_.nested.ts index fec6aff4..6f566923 100755 --- a/tests/data/_.nested.ts +++ b/tests/data/_.nested.ts @@ -30,6 +30,7 @@ import $V2 from "./forms/nested/form.v2"; import $V3 from "./forms/nested/form.v3"; import $V4 from "./forms/nested/form.v4"; import $Z from "./forms/nested/form.z"; +import $X from "./forms/nested/form.x"; export default { $A, @@ -61,4 +62,5 @@ export default { $V3, $V4, $Z, + $X, }; diff --git a/tests/data/forms/flat/form.q.ts b/tests/data/forms/flat/form.q.ts index 9205e97a..acd74267 100755 --- a/tests/data/forms/flat/form.q.ts +++ b/tests/data/forms/flat/form.q.ts @@ -3,7 +3,7 @@ import { Form } from '../../../../src'; const fields = [{ name: 'username', label: 'Username', - value: 'SteveJobs', + computed: () => 'SteveJobs', // test computed value }, { name: 'email', label: 'Email', diff --git a/tests/data/forms/nested/form.x.ts b/tests/data/forms/nested/form.x.ts new file mode 100644 index 00000000..c7ced07f --- /dev/null +++ b/tests/data/forms/nested/form.x.ts @@ -0,0 +1,66 @@ +import { expect } from "chai"; +import { Form } from "../../../../src"; + +export default new Form( + { + fields: [ + "products[].name", + "products[].qty", + "products[].amount", + "products[].total", + "total" + ], + computed: { + 'products[].total': ({ field }) => { + const qty = field.container()?.$('qty')?.value; + const amount = field.container()?.$('amount')?.value; + return qty * amount; + }, + + 'total': ({ form }) => + form.$("products")?.reduce((acc, field) => acc + field.$("total").value, 0), + + }, + labels: { + "products[].name": "Product Name", + "products[].qty": "Quantity", + "products[].amount": "Amount $" + }, + placeholders: { + "products[].name": "Insert Product Name" + }, + types: { + "products[].qty": "number", + "products[].amount": "number", + "products[].total": "number" + }, + }, + { + name: '$x', + options: { + strictSelect: false, + autoParseNumbers: true, + }, + hooks: { + onInit(form) { + form.$("products").add(); + form.$("products").add(); + form.$("products[0].qty").set(2); + form.$("products[0].amount").set(5); + form.$("products[1].qty").set(3); + form.$("products[1].amount").set(5); + + describe("Check computed values", () => { + it("form $x products[0].total value", () => + expect(form.$('products[0].total').value).to.be.equal(10)); + + it("form $x products[1].total value", () => + expect(form.$('products[1].total').value).to.be.equal(15)); + + it("form $x total value", () => + expect(form.$('total').value).to.be.equal(25)); + }); + } + } + } + ); \ No newline at end of file