diff --git a/resources/js/components/field-conditions/ValidatorMixin.js b/resources/js/components/field-conditions/ValidatorMixin.js index 4d3c1ee154b..e4082473790 100644 --- a/resources/js/components/field-conditions/ValidatorMixin.js +++ b/resources/js/components/field-conditions/ValidatorMixin.js @@ -28,6 +28,17 @@ export default { let validator = new Validator(field, this.values, this.$store, this.storeName); let passes = validator.passesConditions(); + // If the field is configured to always save, never omit value. + if (field.always_save === true) { + this.$store.commit(`publish/${this.storeName}/setHiddenField`, { + dottedKey: dottedFieldPath, + hidden: ! passes, + omitValue: false, + }); + + return passes; + } + // Ensure DOM is updated to ensure all revealers are properly loaded and tracked before committing to store. this.$nextTick(() => { this.$store.commit(`publish/${this.storeName}/setHiddenField`, { diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 322e6a8d1a4..285b453c60a 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -478,6 +478,40 @@ test('it can externally force hide a field before validator conditions are evalu expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(false); }); +test('it never omits fields with always_save config', async () => { + Fields.setValues({ + is_online_event: false, + venue: false, + }); + + await Fields.setHiddenFieldsState([ + {handle: 'is_online_event'}, + {handle: 'venue', if: {is_online_event: true}, always_save: true}, + ]); + + expect(Store.state.publish.base.hiddenFields['is_online_event'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['is_online_event'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['venue'].omitValue).toBe(false); +}); + +test('it never omits nested fields with always_save config', async () => { + Fields.setValues({ + is_online_event: false, + venue: false, + }, 'nested'); + + await Fields.setHiddenFieldsState([ + {handle: 'is_online_event'}, + {handle: 'venue', if: {is_online_event: true}, always_save: true}, + ], 'nested'); + + expect(Store.state.publish.base.hiddenFields['nested.is_online_event'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.is_online_event'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.venue'].omitValue).toBe(false); +}); + test('it force hides fields with hidden visibility config', async () => { await Fields.setHiddenFieldsState([ {handle: 'first_name'}, diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 5b914150c62..004f434d0c4 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -73,6 +73,7 @@ 'fields_instructions_position_instructions' => 'Show instructions above or below the field.', 'fields_listable_instructions' => 'Control the listing column visibility.', 'fields_visibility_instructions' => 'Control field visibility on publish forms.', + 'fields_always_save_instructions' => 'Always save field value, regardless of how field conditions are evaluated.', 'fieldset_import_fieldset_instructions' => 'The fieldset to be imported.', 'fieldset_import_prefix_instructions' => 'The prefix that should be applied to each field when they are imported. eg. hero_', 'fieldset_intro' => 'Fieldsets are an optional companion to blueprints, acting as reusable partials that can be used within blueprints.', diff --git a/src/Fields/Field.php b/src/Fields/Field.php index a566be5c140..3253da4b41c 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -101,6 +101,11 @@ public function visibility() return $visibility ?? 'visible'; } + public function alwaysSave() + { + return Arr::get($this->config, 'always_save', false); + } + public function rules() { $rules = [$this->handle => $this->addNullableRule(array_merge( @@ -231,6 +236,7 @@ public function toPublishArray() 'required' => $this->isRequired(), 'visibility' => $this->visibility(), 'read_only' => $this->visibility() === 'read_only', // Deprecated: Addon fieldtypes should now reference new `visibility` state. + 'always_save' => $this->alwaysSave(), ]); } diff --git a/src/Http/Controllers/CP/Fields/FieldsController.php b/src/Http/Controllers/CP/Fields/FieldsController.php index 46a08ee4995..663dc97724c 100644 --- a/src/Http/Controllers/CP/Fields/FieldsController.php +++ b/src/Http/Controllers/CP/Fields/FieldsController.php @@ -140,6 +140,13 @@ protected function blueprint($blueprint) 'type' => 'select', 'width' => 33, ], + 'always_save' => [ + 'display' => __('Always Save'), + 'instructions' => __('statamic::messages.fields_always_save_instructions'), + 'type' => 'toggle', + 'validate' => 'boolean', + 'default' => false, + ], ]); foreach ($prepends->reverse() as $handle => $prepend) { diff --git a/tests/Fields/BlueprintTest.php b/tests/Fields/BlueprintTest.php index 106ac0987b1..833f74f1746 100644 --- a/tests/Fields/BlueprintTest.php +++ b/tests/Fields/BlueprintTest.php @@ -365,6 +365,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], ], @@ -388,6 +389,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], ], @@ -464,6 +466,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], [ 'handle' => 'nested_deeper_two', @@ -482,6 +485,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], ], diff --git a/tests/Fields/FieldTest.php b/tests/Fields/FieldTest.php index 87aefe0cb57..dfbd7726eac 100644 --- a/tests/Fields/FieldTest.php +++ b/tests/Fields/FieldTest.php @@ -326,6 +326,7 @@ public function preProcess($data) 'validate' => 'required', 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, 'component' => 'example', 'a_config_field_with_pre_processing' => 'foo preprocessed', 'a_config_field_without_pre_processing' => 'foo', diff --git a/tests/Fields/FieldsTest.php b/tests/Fields/FieldsTest.php index 8a8ec0d31bd..d542d2e7fc3 100644 --- a/tests/Fields/FieldsTest.php +++ b/tests/Fields/FieldsTest.php @@ -424,6 +424,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], [ 'handle' => 'two', @@ -440,6 +441,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], $fields->toPublishArray()); } @@ -497,6 +499,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], [ 'handle' => 'nested_deeper_two', @@ -515,6 +518,7 @@ public function converts_to_array_suitable_for_rendering_prefixed_conditional_fi 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], $fields->toPublishArray()); } diff --git a/tests/Fields/SectionTest.php b/tests/Fields/SectionTest.php index ed11886efcb..4ab4aac0ef6 100644 --- a/tests/Fields/SectionTest.php +++ b/tests/Fields/SectionTest.php @@ -150,6 +150,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], [ 'handle' => 'two', @@ -166,6 +167,7 @@ public function converts_to_array_suitable_for_rendering_fields_in_publish_compo 'default' => null, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], ], $section->toPublishArray()); diff --git a/tests/Fieldtypes/NestedFieldsTest.php b/tests/Fieldtypes/NestedFieldsTest.php index 01a1c83fe2a..c718ed67a9a 100644 --- a/tests/Fieldtypes/NestedFieldsTest.php +++ b/tests/Fieldtypes/NestedFieldsTest.php @@ -84,6 +84,7 @@ public function preProcess($data) 'required' => true, 'visibility' => 'visible', 'read_only' => false, // deprecated + 'always_save' => false, ], ], $actual); }