Skip to content

Commit

Permalink
Track and remove nested hidden fields (#5805)
Browse files Browse the repository at this point in the history
Co-authored-by: StyleCI Bot <bot@styleci.io>
Co-authored-by: Jason Varga <jason@pixelfear.com>
  • Loading branch information
3 people committed Apr 14, 2022
1 parent 5437106 commit 4b6dd3c
Show file tree
Hide file tree
Showing 25 changed files with 460 additions and 103 deletions.
15 changes: 12 additions & 3 deletions resources/js/components/data-list/HasHiddenFields.js
@@ -1,3 +1,5 @@
import HiddenValuesOmitter from '../field-conditions/Omitter.js';

export default {

computed: {
Expand All @@ -6,10 +8,17 @@ export default {
return this.$store.state.publish[this.publishContainer].hiddenFields;
},

jsonSubmittingFields() {
return this.$store.state.publish[this.publishContainer].jsonSubmittingFields;
},

visibleValues() {
return _.omit(this.values, (_, handle) => {
return this.hiddenFields[handle];
});
let hiddenFields = _.chain(this.hiddenFields)
.pick(hidden => hidden)
.keys()
.value();

return new HiddenValuesOmitter(this.values, this.jsonSubmittingFields).omit(hiddenFields);
},

}
Expand Down
91 changes: 91 additions & 0 deletions resources/js/components/field-conditions/Omitter.js
@@ -0,0 +1,91 @@
import { clone } from '../../bootstrap/globals.js'

export default class {
constructor(values, jsonFields) {
this.values = clone(values);

this.jsonFields = clone(jsonFields || [])
.filter((field, index) => jsonFields.indexOf(field) === index)
.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].');
}

missingValue(dottedKey) {
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 jsPath = this.dottedKeyToJsPath(dottedKey);
let fieldValue = eval('values.' + jsPath);
let decodedFieldValue = JSON.parse(fieldValue);

eval('values.' + jsPath + ' = decodedFieldValue');

this.values = values;
}

jsonEncodeValue(dottedKey) {
if (this.missingValue(dottedKey)) return;

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) {
if (this.missingValue(dottedKey)) return;

let values = clone(this.values);
let jsPath = this.dottedKeyToJsPath(dottedKey);

eval('delete values.' + jsPath);

this.values = values;
}
}
4 changes: 2 additions & 2 deletions resources/js/components/field-conditions/ValidatorMixin.js
Expand Up @@ -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,
});

Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/Fieldtype.vue
Expand Up @@ -22,7 +22,7 @@ export default {
default: false
},
namePrefix: String,
errorKeyPrefix: String,
fieldPathPrefix: String,
},
methods: {
Expand Down
11 changes: 9 additions & 2 deletions resources/js/components/fieldtypes/bard/BardFieldtype.vue
Expand Up @@ -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));
})
Expand Down Expand Up @@ -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.fieldPathPrefix || this.handle);
},
beforeDestroy() {
Expand All @@ -297,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)...
// "<p>One <b>two</b> three</p>" becomes ['OneSPACE', '<b>two</b>', 'SPACEthree']
// But, Laravel's TrimStrings middleware would remove them.
// Those spaces need to be there, otherwise it would be rendered as <p>One<b>two</b>three</p>
// To combat this, we submit the JSON string instead of an object.
this.updateDebounced(JSON.stringify(json));
},
Expand Down
10 changes: 5 additions & 5 deletions resources/js/components/fieldtypes/bard/Set.vue
Expand Up @@ -36,14 +36,14 @@
<div class="replicator-set-body" v-if="!collapsed && index !== undefined">
<set-field
v-for="field in fields"
v-show="showField(field)"
v-show="showField(field, fieldPath(field))"
:key="field.handle"
:field="field"
:value="values[field.handle]"
:meta="meta[field.handle]"
:parent-name="parentName"
:set-index="index"
:error-key="errorKey(field)"
:field-path="fieldPath(field)"
:read-only="isReadOnly"
@updated="updated(field.handle, $event)"
@meta-updated="metaUpdated(field.handle, $event)"
Expand Down Expand Up @@ -191,10 +191,10 @@ export default {
this.options.bard.expandSet(this.node.attrs.id);
},
errorKey(field) {
let prefix = this.options.bard.errorKeyPrefix || this.options.bard.handle;
fieldPath(field) {
let prefix = this.options.bard.fieldPathPrefix || this.options.bard.handle;
return `${prefix}.${this.index}.attrs.values.${field.handle}`;
}
},
}
}
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/fieldtypes/grid/Cell.vue
Expand Up @@ -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)"
Expand Down Expand Up @@ -59,7 +59,7 @@ export default {
type: Array,
required: true
},
errorKey: {
fieldPath: {
type: String,
required: true
}
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/fieldtypes/grid/Grid.vue
Expand Up @@ -117,7 +117,7 @@ export default {
reactiveProvide: {
name: 'grid',
include: ['config', 'isReorderable', 'isReadOnly', 'handle', 'errorKeyPrefix']
include: ['config', 'isReorderable', 'isReadOnly', 'handle', 'fieldPathPrefix']
},
watch: {
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 8 additions & 7 deletions resources/js/components/fieldtypes/grid/Row.vue
Expand Up @@ -4,7 +4,7 @@
<td class="drag-handle" :class="sortableHandleClass" v-if="grid.isReorderable"></td>
<grid-cell
v-for="(field, i) in fields"
:show-inner="showField(field)"
:show-inner="showField(field, fieldPath(field.handle))"
:key="field.handle"
:field="field"
:value="values[field.handle]"
Expand All @@ -13,7 +13,7 @@
:row-index="index"
:grid-name="name"
:errors="errors(field.handle)"
:error-key="errorKey(field.handle)"
:field-path="fieldPath(field.handle)"
@updated="updated(field.handle, $event)"
@meta-updated="metaUpdated(field.handle, $event)"
@focus="$emit('focus')"
Expand Down Expand Up @@ -67,7 +67,7 @@ export default {
type: String,
required: true
},
errorKeyPrefix: {
fieldPathPrefix: {
type: String
},
canDelete: {
Expand Down Expand Up @@ -108,15 +108,16 @@ export default {
this.$emit('meta-updated', meta);
},
errorKey(handle) {
return `${this.errorKeyPrefix}.${this.index}.${handle}`;
fieldPath(handle) {
return `${this.fieldPathPrefix}.${this.index}.${handle}`;
},
errors(handle) {
const state = this.$store.state.publish[this.storeName];
if (! state) return [];
return state.errors[this.errorKey(handle)] || [];
}
return state.errors[this.fieldPath(handle)] || [];
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/grid/Stacked.vue
Expand Up @@ -18,7 +18,7 @@
:values="row"
:meta="meta[row._id]"
:name="name"
:error-key-prefix="errorKeyPrefix"
:field-path-prefix="fieldPathPrefix"
:can-delete="canDeleteRows"
:can-add-rows="canAddRows"
@updated="(row, value) => $emit('updated', row, value)"
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/grid/StackedRow.vue
Expand Up @@ -22,7 +22,7 @@
:parent-name="name"
:set-index="index"
:errors="errors(field.handle)"
:error-key-prefix="errorKey(field.handle)"
:field-path-prefix="fieldPath(field.handle)"
class="p-2"
:read-only="grid.isReadOnly"
@updated="updated(field.handle, $event)"
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/fieldtypes/grid/Table.vue
Expand Up @@ -30,7 +30,7 @@
:values="row"
:meta="meta[row._id]"
:name="name"
:error-key-prefix="errorKeyPrefix"
:field-path-prefix="fieldPathPrefix"
:can-delete="canDeleteRows"
:can-add-rows="canAddRows"
@updated="(row, value) => $emit('updated', row, value)"
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/fieldtypes/grid/View.vue
Expand Up @@ -15,8 +15,8 @@ export default {
return `${this.name}-drag-handle`;
},
errorKeyPrefix() {
return this.grid.errorKeyPrefix || this.grid.handle;
fieldPathPrefix() {
return this.grid.fieldPathPrefix || this.grid.handle;
}
},
Expand Down
8 changes: 4 additions & 4 deletions resources/js/components/fieldtypes/replicator/Field.vue
Expand Up @@ -20,7 +20,7 @@
:value="value"
:handle="field.handle"
:name-prefix="namePrefix"
:error-key-prefix="errorKey"
:field-path-prefix="fieldPath"
:has-error="hasError || hasNestedError"
:read-only="isReadOnly"
@input="$emit('updated', $event)"
Expand Down Expand Up @@ -65,7 +65,7 @@ export default {
type: Number,
required: true
},
errorKey: {
fieldPath: {
type: String
},
readOnly: Boolean,
Expand Down Expand Up @@ -98,15 +98,15 @@ export default {
},
errors() {
return this.storeState.errors[this.errorKey] || [];
return this.storeState.errors[this.fieldPath] || [];
},
hasError() {
return this.errors.length > 0;
},
hasNestedError() {
const prefix = `${this.errorKey}.`;
const prefix = `${this.fieldPath}.`;
return Object.keys(this.storeState.errors).some(handle => handle.startsWith(prefix));
},
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/fieldtypes/replicator/Replicator.vue
Expand Up @@ -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)"
Expand Down Expand Up @@ -90,7 +90,7 @@ export default {
},
computed: {
previews() {
return this.meta.previews;
},
Expand Down Expand Up @@ -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));
},
Expand Down

0 comments on commit 4b6dd3c

Please sign in to comment.