From 0b0d58a864fade9d74576471ff6bd4a7d9c9d0df Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 8 Apr 2022 18:23:15 -0400 Subject: [PATCH 01/24] Accept and track `dottedKey` instead of `handle` for nested field handling. --- resources/js/components/field-conditions/ValidatorMixin.js | 4 ++-- resources/js/components/publish/Container.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/js/components/field-conditions/ValidatorMixin.js b/resources/js/components/field-conditions/ValidatorMixin.js index 05c1f88b5d..f7ec8fa078 100644 --- a/resources/js/components/field-conditions/ValidatorMixin.js +++ b/resources/js/components/field-conditions/ValidatorMixin.js @@ -8,11 +8,11 @@ export default { }, methods: { - showField(field) { + showField(field, dottedKey) { let passes = new Validator(field, this.values, this.$store, this.storeName).passesConditions(); this.$store.commit(`publish/${this.storeName}/setHiddenField`, { - handle: field.handle, + dottedKey: dottedKey || field.handle, hidden: ! passes, }); diff --git a/resources/js/components/publish/Container.vue b/resources/js/components/publish/Container.vue index a065517c22..16e60b1b62 100644 --- a/resources/js/components/publish/Container.vue +++ b/resources/js/components/publish/Container.vue @@ -114,7 +114,7 @@ export default { state.values = values; }, setHiddenField(state, field) { - state.hiddenFields[field.handle] = field.hidden; + state.hiddenFields[field.dottedKey] = field.hidden; }, setMeta(state, meta) { state.meta = meta; From 2893ac5d15cb6bf8a124faaf71cae3c9e5273167 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 8 Apr 2022 18:25:17 -0400 Subject: [PATCH 02/24] Pass `dottedKey` using `errorKey` (for now, wip). --- resources/js/components/fieldtypes/bard/Set.vue | 8 ++++++-- resources/js/components/fieldtypes/grid/Row.vue | 8 ++++++-- resources/js/components/fieldtypes/replicator/Set.vue | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/resources/js/components/fieldtypes/bard/Set.vue b/resources/js/components/fieldtypes/bard/Set.vue index 9ea26bb2c4..bec4f460a9 100644 --- a/resources/js/components/fieldtypes/bard/Set.vue +++ b/resources/js/components/fieldtypes/bard/Set.vue @@ -36,7 +36,7 @@
Date: Fri, 8 Apr 2022 18:27:10 -0400 Subject: [PATCH 03/24] Omit hidden values and nested values using dotted keys. --- .../components/data-list/HasHiddenFields.js | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/resources/js/components/data-list/HasHiddenFields.js b/resources/js/components/data-list/HasHiddenFields.js index 131f2cf10b..9746db869e 100644 --- a/resources/js/components/data-list/HasHiddenFields.js +++ b/resources/js/components/data-list/HasHiddenFields.js @@ -7,11 +7,26 @@ export default { }, visibleValues() { - return _.omit(this.values, (_, handle) => { - return this.hiddenFields[handle]; - }); + let visibleValues = clone(this.values); + + let hiddenKeys = _.chain(this.hiddenFields) + .pick(hidden => hidden) + .keys() + .each(dottedKey => { + eval('delete visibleValues.' + this.dottedKeyToJsProperty(dottedKey)); + }); + + return visibleValues; + }, + + }, + + methods: { + + dottedKeyToJsProperty(dottedKey) { + return dottedKey.replace(/\.*(\d+)\./g, '[$1].'); }, - } + }, } From 22b9843a6e5e2ff7426085230885ef8214719e72 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 8 Apr 2022 19:00:26 -0400 Subject: [PATCH 04/24] Handle hidden fields within bard json values. --- .../components/data-list/HasHiddenFields.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/resources/js/components/data-list/HasHiddenFields.js b/resources/js/components/data-list/HasHiddenFields.js index 9746db869e..046582c356 100644 --- a/resources/js/components/data-list/HasHiddenFields.js +++ b/resources/js/components/data-list/HasHiddenFields.js @@ -13,6 +13,12 @@ export default { .pick(hidden => hidden) .keys() .each(dottedKey => { + let parent = this.dottedKeyToParentHandle(dottedKey); + + if (this.parentFieldSubmitsJson(parent)) { + return visibleValues[parent] = this.forgetFromChildJson(visibleValues[parent], dottedKey); + } + eval('delete visibleValues.' + this.dottedKeyToJsProperty(dottedKey)); }); @@ -27,6 +33,42 @@ export default { return dottedKey.replace(/\.*(\d+)\./g, '[$1].'); }, + dottedKeyToParentHandle(dottedKey) { + return dottedKey.replace(/\..*$/, ''); + }, + + dottedKeyToChildHandle(dottedKey) { + return dottedKey.replace(/^[^\.]*\./, ''); + }, + + getParentFieldType(handle) { + let fields = []; + + this.$store.state.publish[this.publishContainer].blueprint.sections.forEach(section => { + fields = fields.concat(section.fields); + }); + + let parentFields = _.object(_.map(fields, function(field) { + return [field.handle, field]; + })); + + return parentFields[handle].type; + }, + + parentFieldSubmitsJson(handle) { + return this.getParentFieldType(handle) === 'bard'; + }, + + forgetFromChildJson(json, dottedKey) { + let updatedJsonObject = JSON.parse(json); + + let childKey = this.dottedKeyToChildHandle(dottedKey); + + eval('delete updatedJsonObject' + this.dottedKeyToJsProperty(childKey)); + + return JSON.stringify(updatedJsonObject); + }, + }, } From 5d32ec69b362cab234b6ef1ab9ad133935da0c25 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 11 Apr 2022 19:38:12 -0400 Subject: [PATCH 05/24] Add field conditions value `Omitter.js` helper class. --- .../js/components/field-conditions/Omitter.js | 75 +++++++++ .../js/tests/FieldConditionsOmitter.test.js | 145 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 resources/js/components/field-conditions/Omitter.js create mode 100644 resources/js/tests/FieldConditionsOmitter.test.js diff --git a/resources/js/components/field-conditions/Omitter.js b/resources/js/components/field-conditions/Omitter.js new file mode 100644 index 0000000000..b90bb281e1 --- /dev/null +++ b/resources/js/components/field-conditions/Omitter.js @@ -0,0 +1,75 @@ +import { clone } from '../../bootstrap/globals.js' + +export default class { + constructor(values, jsonFields) { + this.values = clone(values); + this.jsonFields = clone(jsonFields || []).sort(); + } + + omit(hiddenKeys) { + this.jsonDecode() + .omitHiddenFields(hiddenKeys) + .jsonEncode(); + + return this.values; + } + + jsonDecode() { + this.jsonFields.forEach(dottedKey => { + this.jsonDecodeValue(dottedKey); + }); + + return this; + } + + omitHiddenFields(hiddenKeys) { + hiddenKeys.forEach(dottedKey => { + this.forgetValue(dottedKey); + }); + + return this; + } + + jsonEncode() { + clone(this.jsonFields).reverse().forEach(dottedKey => { + this.jsonEncodeValue(dottedKey); + }); + + return this; + } + + dottedKeyToJsPath(dottedKey) { + return dottedKey.replace(/\.*(\d+)\./g, '[$1].'); + } + + jsonDecodeValue(dottedKey) { + let values = clone(this.values); + let jsPath = this.dottedKeyToJsPath(dottedKey); + let value = eval('values.' + jsPath); + let decodedFieldValue = JSON.parse(value); + + eval('values.' + jsPath + ' = decodedFieldValue'); + + this.values = values; + } + + jsonEncodeValue(dottedKey) { + let values = clone(this.values); + let jsPath = this.dottedKeyToJsPath(dottedKey); + let fieldValue = eval('values.' + jsPath); + let encodedFieldValue = JSON.stringify(fieldValue); + + eval('values.' + jsPath + ' = encodedFieldValue'); + + this.values = values; + } + + forgetValue(dottedKey) { + let values = clone(this.values); + let jsPath = this.dottedKeyToJsPath(dottedKey); + + eval('delete values.' + jsPath); + + this.values = values; + } +} diff --git a/resources/js/tests/FieldConditionsOmitter.test.js b/resources/js/tests/FieldConditionsOmitter.test.js new file mode 100644 index 0000000000..683e509bf9 --- /dev/null +++ b/resources/js/tests/FieldConditionsOmitter.test.js @@ -0,0 +1,145 @@ +import Omitter from '../components/field-conditions/Omitter.js'; + +test('it omits values at top level', () => { + let values = { + first_name: 'Han', + last_name: 'Solo', + ship: 'Falcon', + bff: 'Chewy', + }; + + let omitted = new Omitter(values).omit([ + 'last_name', + 'bff', + ]); + + let expected = { + first_name: 'Han', + ship: 'Falcon', + }; + + expect(new Omitter(values).omit(['last_name', 'bff'])).toEqual(expected); +}); + +test('it omits nested values', () => { + let values = { + first_name: 'Han', + last_name: 'Solo', + ship: { + name: 'Falcon', + completed_kessel_run: 'less than 12 parsecs', + junk: true, + }, + bffs: [ + { + name: 'Chewy', + type: 'Wookie', + }, + { + name: 'Leia', + type: 'Woman', + crush: [ + { + name: 'Lando', + type: 'Man', + } + ], + }, + ], + }; + + let omitted = new Omitter(values).omit([ + 'last_name', + 'ship.completed_kessel_run', + 'bffs.0.type', + 'bffs.1.crush.0.name', + ]); + + let expected = { + first_name: 'Han', + ship: { + name: 'Falcon', + junk: true, + }, + bffs: [ + { + name: 'Chewy', + }, + { + name: 'Leia', + type: 'Woman', + crush: [ + { + type: 'Man', + } + ], + }, + ], + }; + + expect(omitted).toEqual(expected); +}); + +test('it omits nested json field values', () => { + let values = { + first_name: 'Han', + last_name: 'Solo', + ship: { + name: 'Falcon', + completed_kessel_run: 'less than 12 parsecs', + junk: true, + }, + bffs: JSON.stringify([ + { + name: 'Chewy', + type: 'Wookie', + }, + { + name: 'Leia', + type: 'Woman', + crush: JSON.stringify([ + { + name: 'Lando', + type: 'Man', + }, + ]), + }, + ]), + }; + + let jsonFields = [ + 'bffs.1.crush', // Intentionally passing deeper JSON value first to ensure the Omitter properly sorts these before decoding + 'bffs', + ]; + + let omitted = new Omitter(values, jsonFields).omit([ + 'last_name', + 'ship.completed_kessel_run', + 'bffs.0.type', + 'bffs.1.crush.0.name', + ]); + + let expected = { + first_name: 'Han', + ship: { + name: 'Falcon', + junk: true, + }, + bffs: JSON.stringify([ + { + name: 'Chewy', + }, + { + name: 'Leia', + type: 'Woman', + crush: JSON.stringify([ + { + type: 'Man', + }, + ]), + }, + ]), + }; + + expect(omitted).toEqual(expected); +}); From 570f13b2373bbe7948f449447709b84849df543d Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 11 Apr 2022 19:39:36 -0400 Subject: [PATCH 06/24] Track JSON-submitting fields. --- resources/js/components/publish/Container.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/js/components/publish/Container.vue b/resources/js/components/publish/Container.vue index 16e60b1b62..2442b91ec6 100644 --- a/resources/js/components/publish/Container.vue +++ b/resources/js/components/publish/Container.vue @@ -97,6 +97,7 @@ export default { blueprint: initial.blueprint, values: initial.values, hiddenFields: {}, + jsonSubmittingFields: [], meta: initial.meta, localizedFields: initial.localizedFields, site: initial.site, @@ -116,6 +117,9 @@ export default { setHiddenField(state, field) { state.hiddenFields[field.dottedKey] = field.hidden; }, + setFieldSubmitsJson(state, dottedKey) { + state.jsonSubmittingFields.push(dottedKey); + }, setMeta(state, meta) { state.meta = meta; }, From 8dc9608c49178d8a45fcdcaeb89aab73d9f7a1b9 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 11 Apr 2022 19:39:53 -0400 Subject: [PATCH 07/24] Configure Bard fields as JSON-submitting (temporarily using error key, WIP). --- resources/js/components/fieldtypes/bard/BardFieldtype.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index d518d6e783..95242e17a7 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -286,6 +286,8 @@ export default { this.$nextTick(() => this.mounted = true); this.pageHeader = document.querySelector('.global-header'); + + this.$store.commit(`publish/${this.storeName}/setFieldSubmitsJson`, this.errorKeyPrefix || this.handle); }, beforeDestroy() { From 31bfee10590c625889df32c328c7b155f5cfd1a2 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Mon, 11 Apr 2022 19:40:53 -0400 Subject: [PATCH 08/24] Remove old filtering logic in favour of more robust `Omitter.js` implementation. --- .../components/data-list/HasHiddenFields.js | 68 +++---------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/resources/js/components/data-list/HasHiddenFields.js b/resources/js/components/data-list/HasHiddenFields.js index 046582c356..8944bba13d 100644 --- a/resources/js/components/data-list/HasHiddenFields.js +++ b/resources/js/components/data-list/HasHiddenFields.js @@ -1,3 +1,5 @@ +import HiddenValuesOmitter from '../field-conditions/Omitter.js'; + export default { computed: { @@ -6,69 +8,19 @@ export default { return this.$store.state.publish[this.publishContainer].hiddenFields; }, - visibleValues() { - let visibleValues = clone(this.values); + jsonSubmittingFields() { + return this.$store.state.publish[this.publishContainer].jsonSubmittingFields; + }, - let hiddenKeys = _.chain(this.hiddenFields) + visibleValues() { + let hiddenFields = _.chain(this.hiddenFields) .pick(hidden => hidden) .keys() - .each(dottedKey => { - let parent = this.dottedKeyToParentHandle(dottedKey); - - if (this.parentFieldSubmitsJson(parent)) { - return visibleValues[parent] = this.forgetFromChildJson(visibleValues[parent], dottedKey); - } - - eval('delete visibleValues.' + this.dottedKeyToJsProperty(dottedKey)); - }); - - return visibleValues; - }, - - }, - - methods: { - - dottedKeyToJsProperty(dottedKey) { - return dottedKey.replace(/\.*(\d+)\./g, '[$1].'); - }, - - dottedKeyToParentHandle(dottedKey) { - return dottedKey.replace(/\..*$/, ''); - }, - - dottedKeyToChildHandle(dottedKey) { - return dottedKey.replace(/^[^\.]*\./, ''); - }, - - getParentFieldType(handle) { - let fields = []; - - this.$store.state.publish[this.publishContainer].blueprint.sections.forEach(section => { - fields = fields.concat(section.fields); - }); - - let parentFields = _.object(_.map(fields, function(field) { - return [field.handle, field]; - })); - - return parentFields[handle].type; - }, - - parentFieldSubmitsJson(handle) { - return this.getParentFieldType(handle) === 'bard'; - }, - - forgetFromChildJson(json, dottedKey) { - let updatedJsonObject = JSON.parse(json); - - let childKey = this.dottedKeyToChildHandle(dottedKey); - - eval('delete updatedJsonObject' + this.dottedKeyToJsProperty(childKey)); + .value(); - return JSON.stringify(updatedJsonObject); + return new HiddenValuesOmitter(this.values, this.jsonSubmittingFields).omit(hiddenFields); }, - }, + } } From 318db14d9faaa1de24c4e3ee582b4d89ae2a4aae Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 15:41:12 -0400 Subject: [PATCH 09/24] Fix non-existent key issues when deleting bard sets, etc. --- .../js/components/field-conditions/Omitter.js | 17 ++++++-- .../js/tests/FieldConditionsOmitter.test.js | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/resources/js/components/field-conditions/Omitter.js b/resources/js/components/field-conditions/Omitter.js index b90bb281e1..a0ebf183af 100644 --- a/resources/js/components/field-conditions/Omitter.js +++ b/resources/js/components/field-conditions/Omitter.js @@ -1,4 +1,5 @@ import { clone } from '../../bootstrap/globals.js' +import { data_get } from '../../bootstrap/globals.js' export default class { constructor(values, jsonFields) { @@ -42,11 +43,16 @@ export default class { return dottedKey.replace(/\.*(\d+)\./g, '[$1].'); } + missingValue(dottedKey) { + return data_get(clone(this.values), dottedKey) === null; + } + jsonDecodeValue(dottedKey) { + if (this.missingValue(dottedKey)) return; + let values = clone(this.values); + let decodedFieldValue = JSON.parse(data_get(values, dottedKey)); let jsPath = this.dottedKeyToJsPath(dottedKey); - let value = eval('values.' + jsPath); - let decodedFieldValue = JSON.parse(value); eval('values.' + jsPath + ' = decodedFieldValue'); @@ -54,10 +60,11 @@ export default class { } jsonEncodeValue(dottedKey) { + if (this.missingValue(dottedKey)) return; + let values = clone(this.values); + let encodedFieldValue = JSON.stringify(data_get(values, dottedKey)); let jsPath = this.dottedKeyToJsPath(dottedKey); - let fieldValue = eval('values.' + jsPath); - let encodedFieldValue = JSON.stringify(fieldValue); eval('values.' + jsPath + ' = encodedFieldValue'); @@ -65,6 +72,8 @@ export default class { } forgetValue(dottedKey) { + if (this.missingValue(dottedKey)) return; + let values = clone(this.values); let jsPath = this.dottedKeyToJsPath(dottedKey); diff --git a/resources/js/tests/FieldConditionsOmitter.test.js b/resources/js/tests/FieldConditionsOmitter.test.js index 683e509bf9..2b0583a276 100644 --- a/resources/js/tests/FieldConditionsOmitter.test.js +++ b/resources/js/tests/FieldConditionsOmitter.test.js @@ -143,3 +143,42 @@ test('it omits nested json field values', () => { expect(omitted).toEqual(expected); }); + +test('it doesnt error when operating on non-existent keys', () => { + let values = { + first_name: 'Han', + last_name: 'Solo', + bffs: JSON.stringify([ + { + name: 'Chewy', + type: 'Wookie', + }, + ]), + }; + + let jsonFields = [ + 'bffs', + 'middle_name', // non-existent field + 'bffs.0.crush', // non-existent field + ]; + + let omitted = new Omitter(values, jsonFields).omit([ + 'last_name', + 'middle_name', // non-existent field + 'bffs.0.name', + 'bffs.1.name', // non-existent field + 'bffs.0.crush', // non-existent field + 'bffs.0.crush.0.name', // non-existent field + ]); + + let expected = { + first_name: 'Han', + bffs: JSON.stringify([ + { + type: 'Wookie', + }, + ]), + }; + + expect(omitted).toEqual(expected); +}); From 06251c05fcd4c8afcfff1a7d461d38630dad5c42 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 16:29:42 -0400 Subject: [PATCH 10/24] Ensure it properly handles null values vs undefined/missing values. --- .../js/components/field-conditions/Omitter.js | 12 +++++++---- .../js/tests/FieldConditionsOmitter.test.js | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/resources/js/components/field-conditions/Omitter.js b/resources/js/components/field-conditions/Omitter.js index a0ebf183af..e5a06b6cd5 100644 --- a/resources/js/components/field-conditions/Omitter.js +++ b/resources/js/components/field-conditions/Omitter.js @@ -1,5 +1,4 @@ import { clone } from '../../bootstrap/globals.js' -import { data_get } from '../../bootstrap/globals.js' export default class { constructor(values, jsonFields) { @@ -44,15 +43,19 @@ export default class { } missingValue(dottedKey) { - return data_get(clone(this.values), dottedKey) === null; + var properties = Array.isArray(dottedKey) ? dottedKey : dottedKey.split('.'); + var value = properties.reduce((prev, curr) => prev && prev[curr], clone(this.values)); + + return value === undefined; } jsonDecodeValue(dottedKey) { if (this.missingValue(dottedKey)) return; let values = clone(this.values); - let decodedFieldValue = JSON.parse(data_get(values, dottedKey)); let jsPath = this.dottedKeyToJsPath(dottedKey); + let fieldValue = eval('values.' + jsPath); + let decodedFieldValue = JSON.parse(fieldValue); eval('values.' + jsPath + ' = decodedFieldValue'); @@ -63,8 +66,9 @@ export default class { if (this.missingValue(dottedKey)) return; let values = clone(this.values); - let encodedFieldValue = JSON.stringify(data_get(values, dottedKey)); let jsPath = this.dottedKeyToJsPath(dottedKey); + let fieldValue = eval('values.' + jsPath); + let encodedFieldValue = JSON.stringify(fieldValue); eval('values.' + jsPath + ' = encodedFieldValue'); diff --git a/resources/js/tests/FieldConditionsOmitter.test.js b/resources/js/tests/FieldConditionsOmitter.test.js index 2b0583a276..955eaac8f5 100644 --- a/resources/js/tests/FieldConditionsOmitter.test.js +++ b/resources/js/tests/FieldConditionsOmitter.test.js @@ -144,6 +144,27 @@ test('it omits nested json field values', () => { expect(omitted).toEqual(expected); }); +test('it omits null hidden values', () => { + let values = { + first_name: 'Han', + last_name: 'Solo', + ship: 'Falcon', + bff: null, // this is null, but should still get removed + }; + + let omitted = new Omitter(values).omit([ + 'last_name', + 'bff', + ]); + + let expected = { + first_name: 'Han', + ship: 'Falcon', + }; + + expect(new Omitter(values).omit(['last_name', 'bff'])).toEqual(expected); +}); + test('it doesnt error when operating on non-existent keys', () => { let values = { first_name: 'Han', From ee77a46715b979ed3490ecc762fcacb94456bac2 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 17:26:53 -0400 Subject: [PATCH 11/24] Make it a bit more forgiving. --- resources/js/components/field-conditions/Omitter.js | 5 ++++- resources/js/tests/FieldConditionsOmitter.test.js | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/js/components/field-conditions/Omitter.js b/resources/js/components/field-conditions/Omitter.js index e5a06b6cd5..885be92fd8 100644 --- a/resources/js/components/field-conditions/Omitter.js +++ b/resources/js/components/field-conditions/Omitter.js @@ -3,7 +3,10 @@ import { clone } from '../../bootstrap/globals.js' export default class { constructor(values, jsonFields) { this.values = clone(values); - this.jsonFields = clone(jsonFields || []).sort(); + + this.jsonFields = clone(jsonFields || []) + .filter((field, index) => jsonFields.indexOf(field) === index) + .sort(); } omit(hiddenKeys) { diff --git a/resources/js/tests/FieldConditionsOmitter.test.js b/resources/js/tests/FieldConditionsOmitter.test.js index 955eaac8f5..9cf05dd695 100644 --- a/resources/js/tests/FieldConditionsOmitter.test.js +++ b/resources/js/tests/FieldConditionsOmitter.test.js @@ -165,7 +165,7 @@ test('it omits null hidden values', () => { expect(new Omitter(values).omit(['last_name', 'bff'])).toEqual(expected); }); -test('it doesnt error when operating on non-existent keys', () => { +test('it gracefully handles errors', () => { let values = { first_name: 'Han', last_name: 'Solo', @@ -179,6 +179,7 @@ test('it doesnt error when operating on non-existent keys', () => { let jsonFields = [ 'bffs', + 'bffs', // duplicate field 'middle_name', // non-existent field 'bffs.0.crush', // non-existent field ]; @@ -187,6 +188,7 @@ test('it doesnt error when operating on non-existent keys', () => { 'last_name', 'middle_name', // non-existent field 'bffs.0.name', + 'bffs.0.name', // duplicate field 'bffs.1.name', // non-existent field 'bffs.0.crush', // non-existent field 'bffs.0.crush.0.name', // non-existent field From dd641edc9a524583de99763281d185e10ca8169d Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 17:27:21 -0400 Subject: [PATCH 12/24] But this should only store unique field keys anyway. --- resources/js/components/publish/Container.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/js/components/publish/Container.vue b/resources/js/components/publish/Container.vue index 2442b91ec6..12fe9ff1cf 100644 --- a/resources/js/components/publish/Container.vue +++ b/resources/js/components/publish/Container.vue @@ -118,7 +118,9 @@ export default { state.hiddenFields[field.dottedKey] = field.hidden; }, setFieldSubmitsJson(state, dottedKey) { - state.jsonSubmittingFields.push(dottedKey); + if (state.jsonSubmittingFields.indexOf(dottedKey) === -1) { + state.jsonSubmittingFields.push(dottedKey); + } }, setMeta(state, meta) { state.meta = meta; From 497c1bfa17ade8a5faa3fbfe143bd7913bd1c423 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 19:45:12 -0400 Subject: [PATCH 13/24] =?UTF-8?q?Move=20=E2=80=98filled=E2=80=99=20logic?= =?UTF-8?q?=20from=20`Field`=20to=20`Fields`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Fields/Field.php | 24 +----------------------- src/Fields/Fields.php | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/Fields/Field.php b/src/Fields/Field.php index fc756cb723..01c4335475 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -19,7 +19,6 @@ class Field implements Arrayable protected $value; protected $parent; protected $parentField; - protected $filled = false; protected $validationContext; public function __construct($handle, array $config) @@ -33,8 +32,7 @@ public function newInstance() return (new static($this->handle, $this->config)) ->setParent($this->parent) ->setParentField($this->parentField) - ->setValue($this->value) - ->setFilled($this->filled); + ->setValue($this->value); } public function setHandle(string $handle) @@ -201,11 +199,6 @@ public function isFilterable() return (bool) $this->get('filterable'); } - public function isFilled() - { - return (bool) $this->filled; - } - public function toPublishArray() { return array_merge($this->preProcessedConfig(), [ @@ -232,13 +225,6 @@ public function toBlueprintArray() ]; } - public function setFilled($filled) - { - $this->filled = $filled; - - return $this; - } - public function setValue($value) { $this->value = $value; @@ -246,14 +232,6 @@ public function setValue($value) return $this; } - public function fillValue($value) - { - $this->value = $value; - $this->filled = true; - - return $this; - } - public function value() { return $this->value; diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php index ba494006ed..4616f34dbd 100644 --- a/src/Fields/Fields.php +++ b/src/Fields/Fields.php @@ -15,6 +15,7 @@ class Fields protected $fields; protected $parent; protected $parentField; + protected $filled; public function __construct($items = [], $parent = null, $parentField = null) { @@ -58,6 +59,13 @@ public function setParentField($field) return $this; } + public function setFilled($dottedKeys) + { + $this->filled = $dottedKeys; + + return $this; + } + public function items() { return $this->items; @@ -84,7 +92,8 @@ public function newInstance() ->setParent($this->parent) ->setParentField($this->parentField) ->setItems($this->items) - ->setFields($this->fields); + ->setFields($this->fields) + ->setFilled($this->filled); } public function localizable() @@ -118,13 +127,13 @@ public function toPublishArray() public function addValues(array $values) { + $filled = array_keys($values); + $fields = $this->fields->map(function ($field) use ($values) { - return Arr::has($values, $field->handle()) - ? $field->newInstance()->fillValue(Arr::get($values, $field->handle())) - : $field->newInstance(); + return $field->newInstance()->setValue(Arr::get($values, $field->handle())); }); - return $this->newInstance()->setFields($fields); + return $this->newInstance()->setFilled($filled)->setFields($fields); } public function values() @@ -136,8 +145,8 @@ public function values() public function validatableValues() { - return $this->values()->filter(function ($value, $handle) { - return $this->fields->get($handle)->isFilled(); + return $this->values()->filter(function ($field, $handle) { + return in_array($handle, $this->filled); }); } From 672f41743d814d4efe07188bd9b89ebac89e394e Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 19:46:03 -0400 Subject: [PATCH 14/24] Ensure `validatableValues()` gets called recursively. --- src/Fieldtypes/Bard.php | 2 +- src/Fieldtypes/Grid.php | 2 +- src/Fieldtypes/Replicator.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index ca0d8d320d..f8ac4899a4 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -438,7 +438,7 @@ public function preProcessValidatable($value) $processed = $this->fields($values['type']) ->addValues($values) ->preProcessValidatables() - ->values() + ->validatableValues() ->all(); $item['attrs']['values'] = array_merge($values, $processed); diff --git a/src/Fieldtypes/Grid.php b/src/Fieldtypes/Grid.php index 5b98b6db20..cea3ae4c61 100644 --- a/src/Fieldtypes/Grid.php +++ b/src/Fieldtypes/Grid.php @@ -235,7 +235,7 @@ public function preProcessValidatable($value) $processed = $this->fields() ->addValues($values) ->preProcessValidatables() - ->values() + ->validatableValues() ->all(); return array_merge($values, $processed); diff --git a/src/Fieldtypes/Replicator.php b/src/Fieldtypes/Replicator.php index 74c1b17b7f..906318ac72 100644 --- a/src/Fieldtypes/Replicator.php +++ b/src/Fieldtypes/Replicator.php @@ -253,7 +253,7 @@ public function preProcessValidatable($value) $processed = $this->fields($values['type']) ->addValues($values) ->preProcessValidatables() - ->values() + ->validatableValues() ->all(); return array_merge($values, $processed); From 037e1549db4aaec68488d1cbf9d972b1b9db368e Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 12 Apr 2022 19:47:37 -0400 Subject: [PATCH 15/24] Pass tests again (should add tests for recursive fields next though). --- tests/Fields/FieldsTest.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index 2a97950929..ab7063aa42 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -555,8 +555,6 @@ public function adding_values_sets_filled_status_on_fields_for_validation() $fields = $fields->addValues(['one' => 'foo']); - $this->assertTrue($fields->get('one')->isFilled()); - $this->assertFalse($fields->get('two')->isFilled()); $this->assertEquals(['one' => 'foo', 'two' => null], $fields->values()->all()); $this->assertEquals(['one' => 'foo'], $fields->validatableValues()->all()); } @@ -564,8 +562,7 @@ public function adding_values_sets_filled_status_on_fields_for_validation() /** @test */ public function it_processes_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype - { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { public function process($data) { return $data.' processed'; @@ -604,8 +601,7 @@ public function process($data) /** @test */ public function it_preprocesses_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype - { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { public function preProcess($data) { return $data.' preprocessed'; @@ -644,8 +640,7 @@ public function preProcess($data) /** @test */ public function it_augments_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype - { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { public function augment($data) { return $data.' augmented'; @@ -701,8 +696,7 @@ public function shallowAugment($data) /** @test */ public function it_gets_meta_data_from_all_fields() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype - { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { public function preload() { return 'meta data from field '.$this->field->handle().' is '.($this->field->value() * 2); From 450bd30486ba9f7fd11bf9c547de20876edfe5fd Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 12 Apr 2022 23:51:34 +0000 Subject: [PATCH 16/24] Apply fixes from StyleCI --- tests/Fields/FieldsTest.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index ab7063aa42..5313c7b1aa 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -562,7 +562,8 @@ public function adding_values_sets_filled_status_on_fields_for_validation() /** @test */ public function it_processes_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype + { public function process($data) { return $data.' processed'; @@ -601,7 +602,8 @@ public function process($data) /** @test */ public function it_preprocesses_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype + { public function preProcess($data) { return $data.' preprocessed'; @@ -640,7 +642,8 @@ public function preProcess($data) /** @test */ public function it_augments_each_fields_values_by_its_fieldtype() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype + { public function augment($data) { return $data.' augmented'; @@ -696,7 +699,8 @@ public function shallowAugment($data) /** @test */ public function it_gets_meta_data_from_all_fields() { - FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype { + FieldtypeRepository::shouldReceive('find')->with('fieldtype')->andReturn(new class extends Fieldtype + { public function preload() { return 'meta data from field '.$this->field->handle().' is '.($this->field->value() * 2); From b6aa19d934a878fb9609fcd128f5f5da4a48eee0 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 13 Apr 2022 16:01:32 -0400 Subject: [PATCH 17/24] Fix inconsistency between Grid and Replicator/Bard validation attributes. --- src/Fieldtypes/Grid.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fieldtypes/Grid.php b/src/Fieldtypes/Grid.php index cea3ae4c61..76d6ebb0d8 100644 --- a/src/Fieldtypes/Grid.php +++ b/src/Fieldtypes/Grid.php @@ -163,8 +163,8 @@ public function extraValidationAttributes(): array $attributes = $this->fields()->validator()->attributes(); return collect($this->field->value())->map(function ($row, $index) use ($attributes) { - return collect($row)->except('_id')->mapWithKeys(function ($value, $handle) use ($attributes, $index) { - return [$this->rowRuleFieldPrefix($index).'.'.$handle => $attributes[$handle] ?? null]; + return collect($attributes)->except('_id')->mapWithKeys(function ($attribute, $handle) use ($attributes, $index) { + return [$this->rowRuleFieldPrefix($index).'.'.$handle => $attribute]; }); })->reduce(function ($carry, $rules) { return $carry->merge($rules); From c881d95be93d69ee7a7968c0922d3c02cb9a8937 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 13 Apr 2022 16:02:17 -0400 Subject: [PATCH 18/24] Pass same grid values as rest of payload to pass test again. --- tests/Fields/ValidatorTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Fields/ValidatorTest.php b/tests/Fields/ValidatorTest.php index 707ac0c34f..3eb0684a15 100644 --- a/tests/Fields/ValidatorTest.php +++ b/tests/Fields/ValidatorTest.php @@ -328,7 +328,9 @@ public function it_replaces_this_in_sets() ['type' => 'replicator_set', 'nested_replicator' => [['type' => 'replicator_set', 'nested_replicator' => [['type' => 'replicator_set']]]]], ], 'replicator_with_nested_grid' => [ - ['type' => 'replicator_set', 'nested_grid' => [[]]], + ['type' => 'replicator_set', 'nested_grid' => [ + ['text' => null, 'not_in_blueprint' => 'test'], + ]], ], 'replicator_with_nested_bard' => [ ['type' => 'replicator_set', 'nested_bard' => [['type' => 'set', 'attrs' => ['values' => ['type' => 'bard_set']]]]], From 5b7daef51cef4e58aa17eab1b7e1582acb8fc0c3 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 13 Apr 2022 16:19:45 -0400 Subject: [PATCH 19/24] Remove `validateValues()` requirement for when using `preProcessValidatables()`. --- src/Fields/Fields.php | 25 +++++++++++++++++-------- src/Fields/Validator.php | 2 +- src/Fieldtypes/Bard.php | 2 +- src/Fieldtypes/Grid.php | 2 +- src/Fieldtypes/Replicator.php | 2 +- tests/Fields/FieldsTest.php | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php index 4616f34dbd..558d7d0aa1 100644 --- a/src/Fields/Fields.php +++ b/src/Fields/Fields.php @@ -16,6 +16,7 @@ class Fields protected $parent; protected $parentField; protected $filled; + protected $withValidatableValues = false; public function __construct($items = [], $parent = null, $parentField = null) { @@ -66,6 +67,13 @@ public function setFilled($dottedKeys) return $this; } + public function withValidatableValues() + { + $this->withValidatableValues = true; + + return $this; + } + public function items() { return $this->items; @@ -138,16 +146,17 @@ public function addValues(array $values) public function values() { - return $this->fields->mapWithKeys(function ($field) { + $values = $this->fields->mapWithKeys(function ($field) { return [$field->handle() => $field->value()]; }); - } - public function validatableValues() - { - return $this->values()->filter(function ($field, $handle) { - return in_array($handle, $this->filled); - }); + if ($this->withValidatableValues) { + $values = $values->filter(function ($field, $handle) { + return in_array($handle, $this->filled); + }); + } + + return $values; } public function process() @@ -168,7 +177,7 @@ public function preProcessValidatables() { return $this->newInstance()->setFields( $this->fields->map->preProcessValidatable() - ); + )->withValidatableValues(); } public function augment() diff --git a/src/Fields/Validator.php b/src/Fields/Validator.php index 7f2e1267c4..2e4a4f6e5f 100644 --- a/src/Fields/Validator.php +++ b/src/Fields/Validator.php @@ -96,7 +96,7 @@ public function withReplacements($replacements) public function validate() { return LaravelValidator::validate( - $this->fields->preProcessValidatables()->validatableValues()->all(), + $this->fields->preProcessValidatables()->values()->all(), $this->rules(), $this->customMessages, $this->attributes() diff --git a/src/Fieldtypes/Bard.php b/src/Fieldtypes/Bard.php index f8ac4899a4..ca0d8d320d 100644 --- a/src/Fieldtypes/Bard.php +++ b/src/Fieldtypes/Bard.php @@ -438,7 +438,7 @@ public function preProcessValidatable($value) $processed = $this->fields($values['type']) ->addValues($values) ->preProcessValidatables() - ->validatableValues() + ->values() ->all(); $item['attrs']['values'] = array_merge($values, $processed); diff --git a/src/Fieldtypes/Grid.php b/src/Fieldtypes/Grid.php index 76d6ebb0d8..a1baa35615 100644 --- a/src/Fieldtypes/Grid.php +++ b/src/Fieldtypes/Grid.php @@ -235,7 +235,7 @@ public function preProcessValidatable($value) $processed = $this->fields() ->addValues($values) ->preProcessValidatables() - ->validatableValues() + ->values() ->all(); return array_merge($values, $processed); diff --git a/src/Fieldtypes/Replicator.php b/src/Fieldtypes/Replicator.php index 906318ac72..74c1b17b7f 100644 --- a/src/Fieldtypes/Replicator.php +++ b/src/Fieldtypes/Replicator.php @@ -253,7 +253,7 @@ public function preProcessValidatable($value) $processed = $this->fields($values['type']) ->addValues($values) ->preProcessValidatables() - ->validatableValues() + ->values() ->all(); return array_merge($values, $processed); diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index 5313c7b1aa..793233c731 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -556,7 +556,7 @@ public function adding_values_sets_filled_status_on_fields_for_validation() $fields = $fields->addValues(['one' => 'foo']); $this->assertEquals(['one' => 'foo', 'two' => null], $fields->values()->all()); - $this->assertEquals(['one' => 'foo'], $fields->validatableValues()->all()); + $this->assertEquals(['one' => 'foo'], $fields->preProcessValidatables()->values()->all()); } /** @test */ From 2a351083b38122b72ce716f16bbc406109cdaaff Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 13 Apr 2022 16:38:06 -0400 Subject: [PATCH 20/24] =?UTF-8?q?Rename=20`error-key`=20stuff=20to=20`fiel?= =?UTF-8?q?d-path`,=20since=20we=E2=80=99re=20using=20for=20more=20than=20?= =?UTF-8?q?error=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/components/fieldtypes/Fieldtype.vue | 2 +- .../components/fieldtypes/bard/BardFieldtype.vue | 4 ++-- resources/js/components/fieldtypes/bard/Set.vue | 12 ++++-------- resources/js/components/fieldtypes/grid/Cell.vue | 4 ++-- resources/js/components/fieldtypes/grid/Grid.vue | 4 ++-- resources/js/components/fieldtypes/grid/Row.vue | 15 ++++++--------- .../js/components/fieldtypes/grid/Stacked.vue | 2 +- .../js/components/fieldtypes/grid/StackedRow.vue | 2 +- resources/js/components/fieldtypes/grid/Table.vue | 2 +- resources/js/components/fieldtypes/grid/View.vue | 4 ++-- .../js/components/fieldtypes/replicator/Field.vue | 8 ++++---- .../fieldtypes/replicator/Replicator.vue | 6 +++--- .../js/components/fieldtypes/replicator/Set.vue | 14 +++++--------- resources/js/components/publish/Field.vue | 6 +++--- 14 files changed, 37 insertions(+), 48 deletions(-) diff --git a/resources/js/components/fieldtypes/Fieldtype.vue b/resources/js/components/fieldtypes/Fieldtype.vue index 3fc9060904..7ad70a143e 100644 --- a/resources/js/components/fieldtypes/Fieldtype.vue +++ b/resources/js/components/fieldtypes/Fieldtype.vue @@ -22,7 +22,7 @@ export default { default: false }, namePrefix: String, - errorKeyPrefix: String, + fieldPathPrefix: String, }, methods: { diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index 95242e17a7..c2b6914dbd 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -245,7 +245,7 @@ export default { if (! this.storeState) return []; return Object.values(this.setIndexes).filter((setIndex) => { - const prefix = `${this.errorKeyPrefix || this.handle}.${setIndex}.`; + const prefix = `${this.fieldPathPrefix || this.handle}.${setIndex}.`; return Object.keys(this.storeState.errors).some(key => key.startsWith(prefix)); }) @@ -287,7 +287,7 @@ export default { this.pageHeader = document.querySelector('.global-header'); - this.$store.commit(`publish/${this.storeName}/setFieldSubmitsJson`, this.errorKeyPrefix || this.handle); + this.$store.commit(`publish/${this.storeName}/setFieldSubmitsJson`, this.fieldPathPrefix || this.handle); }, beforeDestroy() { diff --git a/resources/js/components/fieldtypes/bard/Set.vue b/resources/js/components/fieldtypes/bard/Set.vue index bec4f460a9..5982024297 100644 --- a/resources/js/components/fieldtypes/bard/Set.vue +++ b/resources/js/components/fieldtypes/bard/Set.vue @@ -36,14 +36,14 @@
diff --git a/resources/js/components/fieldtypes/grid/Cell.vue b/resources/js/components/fieldtypes/grid/Cell.vue index cad23c5bc7..bdd304185d 100644 --- a/resources/js/components/fieldtypes/grid/Cell.vue +++ b/resources/js/components/fieldtypes/grid/Cell.vue @@ -9,7 +9,7 @@ :meta="meta" :handle="field.handle" :name-prefix="namePrefix" - :error-key-prefix="errorKey" + :field-path-prefix="fieldPath" :read-only="grid.isReadOnly" @input="$emit('updated', $event)" @meta-updated="$emit('meta-updated', $event)" @@ -59,7 +59,7 @@ export default { type: Array, required: true }, - errorKey: { + fieldPath: { type: String, required: true } diff --git a/resources/js/components/fieldtypes/grid/Grid.vue b/resources/js/components/fieldtypes/grid/Grid.vue index 9b3ba8b578..0125a8b369 100644 --- a/resources/js/components/fieldtypes/grid/Grid.vue +++ b/resources/js/components/fieldtypes/grid/Grid.vue @@ -117,7 +117,7 @@ export default { reactiveProvide: { name: 'grid', - include: ['config', 'isReorderable', 'isReadOnly', 'handle', 'errorKeyPrefix'] + include: ['config', 'isReorderable', 'isReadOnly', 'handle', 'fieldPathPrefix'] }, watch: { @@ -169,7 +169,7 @@ export default { removed(index) { if (! confirm(__('Are you sure?'))) return; - + this.update([ ...this.value.slice(0, index), ...this.value.slice(index + 1) diff --git a/resources/js/components/fieldtypes/grid/Row.vue b/resources/js/components/fieldtypes/grid/Row.vue index d7ca65ced1..f70acdc52e 100644 --- a/resources/js/components/fieldtypes/grid/Row.vue +++ b/resources/js/components/fieldtypes/grid/Row.vue @@ -4,7 +4,7 @@ handle.startsWith(prefix)); }, diff --git a/resources/js/components/fieldtypes/replicator/Replicator.vue b/resources/js/components/fieldtypes/replicator/Replicator.vue index 9c917d089e..c1fd58de12 100644 --- a/resources/js/components/fieldtypes/replicator/Replicator.vue +++ b/resources/js/components/fieldtypes/replicator/Replicator.vue @@ -30,7 +30,7 @@ :sortable-handle-class="sortableHandleClass" :is-read-only="isReadOnly" :collapsed="collapsed.includes(set._id)" - :error-key-prefix="errorKeyPrefix || handle" + :field-path-prefix="fieldPathPrefix || handle" :has-error="setHasError(index)" :previews="previews[set._id]" @collapsed="collapseSet(set._id)" @@ -90,7 +90,7 @@ export default { }, computed: { - + previews() { return this.meta.previews; }, @@ -204,7 +204,7 @@ export default { }, setHasError(index) { - const prefix = `${this.errorKeyPrefix || this.handle}.${index}.`; + const prefix = `${this.fieldPathPrefix || this.handle}.${index}.`; return Object.keys(this.storeState.errors ?? []).some(handle => handle.startsWith(prefix)); }, diff --git a/resources/js/components/fieldtypes/replicator/Set.vue b/resources/js/components/fieldtypes/replicator/Set.vue index 68f759a89b..6f6871123d 100644 --- a/resources/js/components/fieldtypes/replicator/Set.vue +++ b/resources/js/components/fieldtypes/replicator/Set.vue @@ -35,14 +35,14 @@
handle.startsWith(prefix)); }, From 9a0705c68217a9b68d332210b366017dc269ad6f Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 13 Apr 2022 20:38:19 +0000 Subject: [PATCH 21/24] Apply fixes from StyleCI --- src/Fieldtypes/Grid.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Grid.php b/src/Fieldtypes/Grid.php index a1baa35615..9a08f86ecc 100644 --- a/src/Fieldtypes/Grid.php +++ b/src/Fieldtypes/Grid.php @@ -163,7 +163,7 @@ public function extraValidationAttributes(): array $attributes = $this->fields()->validator()->attributes(); return collect($this->field->value())->map(function ($row, $index) use ($attributes) { - return collect($attributes)->except('_id')->mapWithKeys(function ($attribute, $handle) use ($attributes, $index) { + return collect($attributes)->except('_id')->mapWithKeys(function ($attribute, $handle) use ($index) { return [$this->rowRuleFieldPrefix($index).'.'.$handle => $attribute]; }); })->reduce(function ($carry, $rules) { From 4423f5ca374f876cf3f3a2d1dd1a9e04683e9d62 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 14 Apr 2022 13:42:40 -0400 Subject: [PATCH 22/24] Fix `$filled` array check when no values are filled. --- src/Fields/Fields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php index 558d7d0aa1..7607960ecd 100644 --- a/src/Fields/Fields.php +++ b/src/Fields/Fields.php @@ -15,7 +15,7 @@ class Fields protected $fields; protected $parent; protected $parentField; - protected $filled; + protected $filled = []; protected $withValidatableValues = false; public function __construct($items = [], $parent = null, $parentField = null) From 6ec2a95e2e040936c889638d8c061d63a5bfba02 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 14 Apr 2022 13:43:53 -0400 Subject: [PATCH 23/24] Add test coverage for recursive `preProcessValidatables()` handling. --- tests/Fields/FieldsTest.php | 70 +++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index 793233c731..622b2a6981 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -536,27 +536,67 @@ public function it_adds_values_to_fields() } /** @test */ - public function adding_values_sets_filled_status_on_fields_for_validation() + public function preprocessing_validatables_removes_unfilled_values() { - FieldRepository::shouldReceive('find')->with('one')->andReturnUsing(function () { - return new Field('one', []); - }); - - FieldRepository::shouldReceive('find')->with('two')->andReturnUsing(function () { - return new Field('two', []); - }); - $fields = new Fields([ - ['handle' => 'one', 'field' => 'one'], - ['handle' => 'two', 'field' => 'two'], + ['handle' => 'title', 'field' => ['type' => 'text']], + ['handle' => 'one', 'field' => ['type' => 'text']], + ['handle' => 'two', 'field' => ['type' => 'text']], + ['handle' => 'reppy', 'field' => ['type' => 'replicator', 'sets' => ['replicator_set' => ['fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['handle' => 'one', 'field' => ['type' => 'text']], + ['handle' => 'two', 'field' => ['type' => 'text']], + ['handle' => 'griddy_in_reppy', 'field' => ['type' => 'grid', 'fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['handle' => 'one', 'field' => ['type' => 'text']], + ['handle' => 'two', 'field' => ['type' => 'text']], + ['handle' => 'bardo_in_griddy_in_reppy', 'field' => ['type' => 'bard', 'sets' => ['bard_set' => ['fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['handle' => 'one', 'field' => ['type' => 'text']], + ['handle' => 'two', 'field' => ['type' => 'text']], + ['handle' => 'bardo_in_bardo_in_griddy_in_reppy', 'field' => ['type' => 'bard', 'sets' => ['bard_set_set' => ['fields' => [ + ['handle' => 'title', 'field' => ['type' => 'text']], + ['handle' => 'one', 'field' => ['type' => 'text']], + ['handle' => 'two', 'field' => ['type' => 'text']], + ]]]]], + ]]]]], + ]]], + ]]]]], ]); - $this->assertEquals(['one' => null, 'two' => null], $fields->values()->all()); + $this->assertEquals(['title' => null, 'one' => null, 'two' => null, 'reppy' => null], $fields->values()->all()); + $this->assertEquals([], $fields->preProcessValidatables()->values()->all()); + + $values = $expected = [ + 'title' => 'recursion madness', + 'one' => 'foo', + 'reppy' => [ + ['type' => 'replicator_set', 'two' => 'foo'], + ['type' => 'replicator_set', 'griddy_in_reppy' => [ + ['one' => 'foo'], + ['bardo_in_griddy_in_reppy' => json_encode($bardValues = [ + ['type' => 'set', 'attrs' => ['values' => ['type' => 'bard_set', 'two' => 'foo']]], + ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'foo']]], + ['type' => 'set', 'attrs' => ['type' => 'bard_set', 'values' => ['type' => 'bard_set', 'bardo_in_bardo_in_griddy_in_reppy' => json_encode($doubleNestedBardValues = [ + ['type' => 'set', 'attrs' => ['values' => ['type' => 'bard_set', 'two' => 'foo']]], + ['type' => 'paragraph', 'content' => [['type' => 'text', 'text' => 'foo']]], + ])]]], + ])], + ]], + ], + ]; + + // Calling `addValues()` should track which fields are filled at each level of nesting. + // When we call `preProcessValidatables()`, unfilled values should get removed, in + // order to ensure rules like `sometimes` and `required_if` work at all levels. + $validatableValues = $fields->addValues($values)->preProcessValidatables()->values()->all(); - $fields = $fields->addValues(['one' => 'foo']); + // Bard fields submit JSON values, so we'll replace them with their corresponding PHP array + // values here, since `preProcessValidatables()` will return JSON decoded decoded values. + $expected['reppy'][1]['griddy_in_reppy'][1]['bardo_in_griddy_in_reppy'] = $bardValues; + $expected['reppy'][1]['griddy_in_reppy'][1]['bardo_in_griddy_in_reppy'][2]['attrs']['values']['bardo_in_bardo_in_griddy_in_reppy'] = $doubleNestedBardValues; - $this->assertEquals(['one' => 'foo', 'two' => null], $fields->values()->all()); - $this->assertEquals(['one' => 'foo'], $fields->preProcessValidatables()->values()->all()); + $this->assertEquals($expected, $validatableValues); } /** @test */ From ce1914670a7745fe2f683b35887588c24b0726cf Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 14 Apr 2022 15:13:02 -0400 Subject: [PATCH 24/24] Elaborate on why we json strings in bard --- resources/js/components/fieldtypes/bard/BardFieldtype.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/js/components/fieldtypes/bard/BardFieldtype.vue b/resources/js/components/fieldtypes/bard/BardFieldtype.vue index c2b6914dbd..ba190b6210 100644 --- a/resources/js/components/fieldtypes/bard/BardFieldtype.vue +++ b/resources/js/components/fieldtypes/bard/BardFieldtype.vue @@ -299,7 +299,12 @@ export default { json(json) { if (!this.mounted) return; - // Use a json string otherwise Laravel's TrimStrings middleware will remove spaces where we need them. + // Prosemirror's JSON will include spaces between tags. + // For example (this is not the actual json)... + // "

One two three

" becomes ['OneSPACE', 'two', 'SPACEthree'] + // But, Laravel's TrimStrings middleware would remove them. + // Those spaces need to be there, otherwise it would be rendered as

Onetwothree

+ // To combat this, we submit the JSON string instead of an object. this.updateDebounced(JSON.stringify(json)); },