diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java index b95917e3f3..9cc711f3e3 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/ReservedProperties.java @@ -87,6 +87,8 @@ private ReservedProperties() { public static final String PN_MINIMUM = "minimum"; public static final String PN_EXCLUSIVE_MINIMUM = "exclusiveMinimum"; public static final String PN_EXCLUSIVE_MAXIMUM = "exclusiveMaximum"; + public static final String PN_EXCLUDE_MINIMUM = "excludeMinimum"; + public static final String PN_EXCLUDE_MAXIMUM = "excludeMaximum"; public static final String PN_EXCLUDE_MINIMUM_CHECK = "excludeMinimumCheck"; public static final String PN_EXCLUDE_MAXIMUM_CHECK = "excludeMaximumCheck"; public static final String PN_ENFORCE_ENUM = "enforceEnum"; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java index b97416f5b8..8dfa4d4bd1 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImpl.java @@ -22,14 +22,18 @@ import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.SlingObject; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import org.jetbrains.annotations.NotNull; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.adobe.cq.forms.core.components.internal.form.ReservedProperties; import com.adobe.cq.forms.core.components.models.form.ConstraintType; import com.adobe.cq.forms.core.components.models.form.DatePicker; import com.adobe.cq.forms.core.components.util.AbstractFieldImpl; @@ -46,6 +50,16 @@ public class DatePickerImpl extends AbstractFieldImpl implements DatePicker { @SlingObject private Resource resource; + /*** Not to be changed, kept for backward compatibility **/ + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_EXCLUDE_MINIMUM) + @Default(booleanValues = false) + protected boolean excludeMinimum; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_EXCLUDE_MAXIMUM) + @Default(booleanValues = false) + protected boolean excludeMaximum; + /*** end of Not to be changed **/ + private Date exclusiveMinimumVaue; private Date exclusiveMaximumValue; @@ -84,8 +98,8 @@ public Date getExclusiveMinimumDate() { @PostConstruct private void initDatePicker() { - exclusiveMaximumValue = ComponentUtils.getExclusiveValue(exclusiveMaximum, maximumDate, null); - exclusiveMinimumVaue = ComponentUtils.getExclusiveValue(exclusiveMinimum, minimumDate, null); + exclusiveMaximumValue = ComponentUtils.getExclusiveValue(exclusiveMaximum, maximumDate, excludeMaximum); + exclusiveMinimumVaue = ComponentUtils.getExclusiveValue(exclusiveMinimum, minimumDate, excludeMinimum); // in json either, exclusiveMaximum or maximum should be present if (exclusiveMaximumValue != null) { maximumDate = null; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java index 9a7f3c40fd..a1c69a051f 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/ComponentUtils.java @@ -167,6 +167,9 @@ public static T getExclusiveValue(Object exclusiveValue, T value, Object exc } else { return null; } + } else if (Boolean.TRUE.equals(exclusiveValueCheck) && value != null) { // backward compatibility case // not to be changed + // If so, return the value + return (T) value; } else { // Handle other cases or return null if desired return null; diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImplTest.java index dabcbb1e4b..d7ff283a62 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/DatePickerImplTest.java @@ -47,6 +47,7 @@ public class DatePickerImplTest { private static final String PATH_DATEPICKER = CONTENT_ROOT + "/datepicker"; private static final String PATH_DATEPICKER_DATALAYER = CONTENT_ROOT + "/datepicker-datalayer"; private static final String PATH_DATEPICKER_DISPLAY_VALUE_EXPRESSION = CONTENT_ROOT + "/datepicker-displayValueExpression"; + private static final String PATH_DATEPICKER_BACKWARD_COMPATIBLE = CONTENT_ROOT + "/datepicker-backwardcompatible"; private final AemContext context = FormsCoreComponentTestContext.newAemContext(); @@ -241,6 +242,12 @@ void testJSONExport() throws Exception { Utils.testJSONExport(datePicker, Utils.getTestExporterJSONPath(BASE, PATH_DATEPICKER)); } + @Test + void testJSONExportBackwardCompatibility() throws Exception { + DatePicker datePicker = Utils.getComponentUnderTest(PATH_DATEPICKER_BACKWARD_COMPATIBLE, DatePicker.class, context); + Utils.testJSONExport(datePicker, Utils.getTestExporterJSONPath(BASE, PATH_DATEPICKER_BACKWARD_COMPATIBLE)); + } + @Test void testJSONExportForCustomized() throws Exception { DatePicker datePicker = Utils.getComponentUnderTest(PATH_DATEPICKER_CUSTOMIZED, DatePicker.class, context); diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/NumberInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/NumberInputImplTest.java index 2f6a56d0b7..a69b567eaf 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/NumberInputImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/NumberInputImplTest.java @@ -51,6 +51,7 @@ public class NumberInputImplTest { private static final String PATH_NUMBER_INPUT_INTEGER_TYPE = CONTENT_ROOT + "/numberinput-integer-type"; private static final String PATH_NUMBER_INPUT_CONSTRAINTS = CONTENT_ROOT + "/numberinput-exclusive"; private static final String PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE = CONTENT_ROOT + "/numberinput-backwardcompatible"; + private static final String PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE_2 = CONTENT_ROOT + "/numberinput-backwardcompatible-2"; private static final String PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE_STRING = CONTENT_ROOT + "/numberinput-backwardcompatible-string"; private static final String PATH_NUMBER_INPUT = CONTENT_ROOT + "/numberinput"; private static final String PATH_NUMBER_INPUT_DATALAYER = CONTENT_ROOT + "/numberinput-datalayer"; @@ -267,6 +268,12 @@ void testJSONExportBackwardCompatible() throws Exception { Utils.testJSONExport(numberInput, Utils.getTestExporterJSONPath(BASE, PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE)); } + @Test + void testJSONExportBackwardCompatible2() throws Exception { + NumberInput numberInput = Utils.getComponentUnderTest(PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE_2, NumberInput.class, context); + Utils.testJSONExport(numberInput, Utils.getTestExporterJSONPath(BASE, PATH_NUMBER_INPUT_BACKWARD_COMPATIBLE_2)); + } + @Test void testGetProperties() throws Exception { NumberInput numberInput = Utils.getComponentUnderTest(PATH_NUMBER_INPUT_CUSTOMIZED, NumberInput.class, context); diff --git a/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json new file mode 100644 index 0000000000..1c51515690 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/datepicker/exporter-datepicker-backwardcompatible.json @@ -0,0 +1,25 @@ +{ + "id": "datepicker-0ac1fed1fe", + "fieldType": "date-input", + "name": "DoB", + "description": "Date of Birth", + "type": "string", + "label": { + "value": "DoB" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/datepicker-backwardcompatible" + }, + "format": "date", + "exclusiveMaximum": "2024-07-23", + "exclusiveMinimum": "2024-07-10", + ":type": "core/fd/components/form/datepicker/v1/datepicker" +} \ No newline at end of file diff --git a/bundles/af-core/src/test/resources/form/datepicker/test-content.json b/bundles/af-core/src/test/resources/form/datepicker/test-content.json index 649610aa94..67aef568ce 100644 --- a/bundles/af-core/src/test/resources/form/datepicker/test-content.json +++ b/bundles/af-core/src/test/resources/form/datepicker/test-content.json @@ -89,6 +89,33 @@ "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/datepicker" } }, + "datepicker-backwardcompatible": { + "id": "datepicker-0ac1fed1fe", + "sling:resourceType": "core/fd/components/form/datepicker/v1/datepicker", + "fieldType": "date-input", + "excludeMaximum" : true, + "excludeMinimum" : true, + "minimumDate": "Wed Jul 10 2024 00:00:00 GMT+0000", + "maximumDate": "Tue Jul 23 2024 00:00:00 GMT+0000", + "description": "Date of Birth", + "jcr:title" : "DoB", + "name": "DoB", + "type": "string", + "label": { + "value": "DoB" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/datepicker" + } + }, "datepicker-displayValueExpression" : { "jcr:primaryType": "nt:unstructured", "sling:resourceType" : "core/fd/components/form/datepicker/v1/datepicker", diff --git a/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json new file mode 100644 index 0000000000..d605eb9bf0 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/numberinput/exporter-numberinput-backwardcompatible-2.json @@ -0,0 +1,24 @@ +{ + ":type": "core/fd/components/form/numberinput/v1/numberinput", + "description": "Enter your phone number.", + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "exclusiveMaximum": 2000002, + "exclusiveMinimum": 10002, + "fieldType": "number-input", + "id": "numberinput-57e5de6073", + "label": { + "value": "Phone Number" + }, + "name": "Phone Number", + "properties": { + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/numberinput-backwardcompatible-2" + }, + "type": "number" +} diff --git a/bundles/af-core/src/test/resources/form/numberinput/test-content.json b/bundles/af-core/src/test/resources/form/numberinput/test-content.json index b4804526d2..3211baa765 100644 --- a/bundles/af-core/src/test/resources/form/numberinput/test-content.json +++ b/bundles/af-core/src/test/resources/form/numberinput/test-content.json @@ -133,6 +133,33 @@ "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/accordion/item_2/numberinput" } }, + "numberinput-backwardcompatible-2": { + "id": "numberinput-57e5de6073", + "sling:resourceType": "core/fd/components/form/numberinput/v1/numberinput", + "fieldType": "number-input", + "description": "Enter your phone number.", + "name": "Phone Number", + "jcr:title" : "Phone Number", + "type": "string", + "label": { + "value": "Phone Number" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "excludeMinimumCheck" : true, + "excludeMaximumCheck" : true, + "minimum" : 10002, + "maximum" : 2000002, + "properties": { + "fd:dor": { + "dorExclusion": false + }, + "fd:path": "/content/forms/af/af2/jcr:content/guideContainer/accordion/item_2/numberinput" + } + }, "numberinput-backwardcompatible-string": { "sling:resourceType": "core/fd/components/form/numberinput/v1/numberinput", "fieldType": "number-input", diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/termsandconditions/_cq_template/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/termsandconditions/_cq_template/.content.xml index 95a10387e4..6b2bdabe8f 100644 --- a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/termsandconditions/_cq_template/.content.xml +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/termsandconditions/_cq_template/.content.xml @@ -19,6 +19,7 @@ jcr:title="I agree to the terms & conditions" sling:resourceType="core/fd/components/form/checkbox/v1/checkbox" enabled="false" + required="true" checkedValue="true" fieldType="checkbox"> diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-function/js/functions.js b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-function/js/functions.js index 0fbb91de2c..86bdb7fa43 100644 --- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-function/js/functions.js +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/clientlibs/clientlib-it-custom-function/js/functions.js @@ -208,4 +208,17 @@ function testRemoveInstance(globals) { var repeatablePanel = globals.form.panelcontainer2; globals.functions.dispatchEvent(repeatablePanel, 'removeInstance'); -} \ No newline at end of file +} + +/** + * clearValueCustomFunction + * @name clearValueCustomFunction + * @param {object} field field whose value to be cleared + * @param {scope} globals + **/ +function clearValueCustomFunction(field, globals) { + // only clear data if change was done by user from the UI. + if (globals.event.payload.eventSource == "ui") { + globals.functions.setProperty(field, {value: null}); + } +} diff --git a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/textinput/v1/textinput/clientlibs/site/js/textinputview.js b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/textinput/v1/textinput/clientlibs/site/js/textinputview.js index 44588fd79c..83ce63257d 100644 --- a/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/textinput/v1/textinput/clientlibs/site/js/textinputview.js +++ b/it/apps/src/main/content/jcr_root/apps/forms-core-components-it/components/textinput/v1/textinput/clientlibs/site/js/textinputview.js @@ -68,10 +68,10 @@ setModel(model) { super.setModel(model); if (this.widget.value !== '') { - this._model.value = this.widget.value; + this.setModelValue(this.widget.value) } this.widget.addEventListener('blur', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); this.setWidgetValueToDisplayValue(); this.setInactive(); }); diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/actions/submit/email/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/actions/submit/email/.content.xml new file mode 100755 index 0000000000..2893fd709a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/actions/submit/email/.content.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/uichange/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/uichange/.content.xml new file mode 100644 index 0000000000..233c74dbd1 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/ruleeditor/uichange/.content.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/actions/submit/email/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/actions/submit/email/.content.xml new file mode 100755 index 0000000000..910ec1fb2a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/actions/submit/email/.content.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/datepicker/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/datepicker/basic/.content.xml index 9759c11c97..40ef5f82a5 100755 --- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/datepicker/basic/.content.xml +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/datepicker/basic/.content.xml @@ -187,6 +187,23 @@ textIsRich="[true,true,true]" unboundFormElement="{Boolean}false" visible="{Boolean}true"/> + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/numberinput/basic/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/numberinput/basic/.content.xml index 84b51cab1b..a21ad13cfa 100755 --- a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/numberinput/basic/.content.xml +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/numberinput/basic/.content.xml @@ -139,12 +139,27 @@ displayValueExpression="formatCreditCardNumber($field)" fieldType="number-input" name="numberinput1710483567726"> - - + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/uichange/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/uichange/.content.xml new file mode 100644 index 0000000000..5b006ea93e --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/ruleeditor/uichange/.content.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/MockMailServiceImpl.java b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/MockMailServiceImpl.java new file mode 100644 index 0000000000..2cda3cc7cb --- /dev/null +++ b/it/core/src/main/java/com/adobe/cq/forms/core/components/it/service/MockMailServiceImpl.java @@ -0,0 +1,102 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2024 Adobe + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package com.adobe.cq.forms.core.components.it.service; + +import com.day.cq.mailer.MailService; +import com.day.cq.mailer.MailingException; +import org.apache.commons.mail.Email; +import org.apache.commons.mail.EmailException; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class serves as a mock implementation of {@link MailService} for testing purposes. + * It uses a {@link DataManager} to store and retrieve email objects based on their subject. + */ +@Component( + service = MailService.class, + immediate = true +) +public class MockMailServiceImpl implements MailService { + + @Reference + private DataManager dataManager; + + private static final Logger logger = LoggerFactory.getLogger(MockMailServiceImpl.class); + + /** + * Stores the email object in the data manager. + * + * @param email an instance of {@link Email} + * @throws EmailException if the email cannot be stored + */ + @Override + public void sendEmail(Email email) throws EmailException { + if (dataManager == null) { + logger.error("DataManager is not available."); + throw new EmailException("DataManager is not available."); + } + if (email != null) { + logger.info("Storing email with subject: {}", email.getSubject()); + dataManager.put(email.getSubject(), email); + } + } + + /** + * Determines if this service can handle the specified email class. + * + * @param aClass the email class to check + * @return true if the service can handle the specified class, false otherwise + */ + @Override + public boolean handles(Class aClass) { + // Example: return aClass.equals(MySpecificEmail.class); + return false; // Modify based on actual handling logic + } + + /** + * Stores the email object in the data manager and handles exceptions. + * + * @param email an instance of {@link Email} + * @throws MailingException if the email cannot be sent + */ + @Override + public void send(Email email) throws MailingException { + try { + sendEmail(email); + } catch (EmailException e) { + logger.error("Failed to send email", e); + throw new MailingException(e); + } + } + + /** + * Fetches the mail for the given subject from the data manager. + * + * @param subject of the email. + * @return instance of {@link Email} for the given subject, or null if not found. + */ + public Email getEmail(String subject) { + if (dataManager == null) { + logger.error("DataManager is not available."); + return null; + } + logger.info("Retrieving email with subject: {}", subject); + return (Email) dataManager.get(subject); + } +} \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkbox/v1/checkbox/checkbox.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkbox/v1/checkbox/checkbox.html index f5316ce0dc..5de9e1ea2a 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkbox/v1/checkbox/checkbox.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkbox/v1/checkbox/checkbox.html @@ -30,7 +30,7 @@ data-cmp-enabled="${checkbox.enabled ? 'true' : 'false'}" data-cmp-required="${checkbox.required ? 'true': 'false'}" data-cmp-data-layer="${checkbox.data.json}" - data-sly-test.widgetId="${'{0}-{1}' @ format=[component.id, '_widget']}" + data-sly-test.widgetId="${'{0}-{1}' @ format=[checkbox.id, 'widget']}" data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}">
@@ -38,7 +38,7 @@
widget.setAttribute(FormView.Constants.ARIA_INVALID, !valid)); - } + } updateValue(modelValue) { modelValue = [].concat(modelValue); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/_cq_dialog/.content.xml index 91372443fb..4886b1c14d 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/_cq_dialog/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/_cq_dialog/.content.xml @@ -95,7 +95,7 @@ { - this._model.value = this.widgetObject.getValue(); + this.setModelValue(this.widgetObject.getValue()) //setDisplayValue is required for cases where value remains same while focussing in and out. this.widgetObject.setDisplayValue(this._model.value); + this.widgetObject.setCalendarWidgetValue(this._model.value); this.setInactive(); }, this.getWidget()); this.widgetObject.addEventListener('focus', (e) => { @@ -106,15 +107,15 @@ this.widgetObject.addEventListener('input', (e) => { if( e.target.value === '') { // clear the value if user manually empties the value in date input box - this._model.value = ""; + this.setModelValue(""); } }, this.getWidget()); } else { if (this.widget.value !== '') { - this._model.value = this.widget.value; + this.setModelValue(this.widget.value); } this.widget.addEventListener('blur', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); this.setInactive(); }); this.widget.addEventListener('focus', (e) => { diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js index 0fb545d3e6..db1ec69279 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerwidget.js @@ -1031,7 +1031,7 @@ if (typeof window.DatePickerWidget === 'undefined') { } #clearDate(view) { - this.#model.value = ""; + this.#model.dispatch(new FormView.Actions.UIChange({'value': ''})); let existingSelectedItem = this['$' + view.toLowerCase()].getElementsByClassName("dp-selected")[0]; if (existingSelectedItem) { existingSelectedItem.classList.remove("dp-selected"); @@ -1068,7 +1068,7 @@ if (typeof window.DatePickerWidget === 'undefined') { this.selectedYear = this.currentYear; this.selectedDay = val; this.setValue(this.toString()); - this.#model.value = this.toString(); // updating model value to the latest when calender is changed + this.#model.dispatch(new FormView.Actions.UIChange({'value': this.toString()})); // updating model value to the latest when calender is changed this.#curInstance.$field.focus(); let existingSelectedElement = this['$' + this.view.toLowerCase()].getElementsByClassName("dp-selected")[0]; @@ -1158,6 +1158,16 @@ if (typeof window.DatePickerWidget === 'undefined') { } } + setCalendarWidgetValue(value) { + if (this.#curInstance === null && this.#widget != null) { + this.#curInstance = window.afCache.get(this.#widget, "datetimepicker"); + } + if (this.#curInstance != null) { + // also change the date of the calendar widget + this.#curInstance.selectedDate = value; + } + } + /** * Sets the formatted edit value on the widget * @param value diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js index 310d26dc24..3deb8ba4a1 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js @@ -223,10 +223,10 @@ } }); if (valueArray.length !== 0 || this._model.value != null) { - this._model.value = valueArray; + this.setModelValue(valueArray); } } else { - this._model.value = widget.value; + this.setModelValue(widget.value); } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js index 81282127e8..41662f809c 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js @@ -68,10 +68,10 @@ setModel(model) { super.setModel(model); if (this.widget.value !== '') { - this._model.value = this.widget.value; + this.setModelValue(this.widget.value); } this.widget.addEventListener('blur', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); this.setWidgetValueToDisplayValue(); this.setInactive(); }); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/hcaptcha/v1/hcaptcha/clientlibs/site/js/hcaptchawidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/hcaptcha/v1/hcaptcha/clientlibs/site/js/hcaptchawidget.js index 8b983e2d6b..bd25fd2ae3 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/hcaptcha/v1/hcaptcha/clientlibs/site/js/hcaptchawidget.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/hcaptcha/v1/hcaptcha/clientlibs/site/js/hcaptchawidget.js @@ -86,7 +86,7 @@ if (typeof window.HCaptchaWidget === 'undefined') { } setCaptchaModel = function(response) { - this.#model.value = (response); + this.#model.dispatch(new FormView.Actions.UIChange({'value': response})); } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/_cq_dialog/.content.xml index 6e0db092eb..7c0ddd0c85 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/_cq_dialog/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/_cq_dialog/.content.xml @@ -102,6 +102,7 @@ fieldDescription="Error message shown when entered value is less than minimum." fieldLabel="Minimum error message" name="./minimumMessage"/> + + { - this._model.value = e.target.value; + this.setModelValue(e.target.value); if(this.element) { this.setInactive(); } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputwidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputwidget.js index 286f275b78..882928bb97 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputwidget.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputwidget.js @@ -101,7 +101,7 @@ this.#handleCut(e); }); widget.addEventListener('blur', (e) => { - this.#model.value = this.getValue(e.target.value); + this.#model.dispatch(new FormView.Actions.UIChange({'value': this.getValue(e.target.value)})); this.#widget.value = this.#model.displayValue; }); // IME specific handling, to handle japanese languages max limit diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js index 3c39967ea7..3d7cceb25f 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/radiobutton/v1/radiobutton/clientlibs/site/js/radiobuttonview.js @@ -82,7 +82,7 @@ let widgets = this.widget; widgets.forEach(widget => { widget.addEventListener('change', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); }); widget.addEventListener('focus', (e) => { this.setActive(); @@ -116,7 +116,7 @@ if (readonly === true) { widget.setAttribute(FormView.Constants.HTML_ATTRS.DISABLED, "disabled"); } else { - widget.removeAttribute(FormView.Constants.HTML_ATTRS.DISABLED); + widget.removeAttribute(FormView.Constants.HTML_ATTRS.DISABLED); } }); } @@ -126,7 +126,7 @@ let widgets = this.widget; this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, valid); widgets.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !valid)); - } + } updateValue(modelValue) { this.widget.forEach(widget => { diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchaview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchaview.js index 275a277bf6..56fae2ea52 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchaview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchaview.js @@ -72,7 +72,7 @@ this.initializeWidget(); } else { if (this.widget.value !== '') { - this._model.value = this.widget.value; + this._model.dispatch(new FormView.Actions.UIChange({'value': this.widget.value})); } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchawidget.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchawidget.js index b0db1e1e8f..7e9e67e149 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchawidget.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/recaptcha/v1/recaptcha/clientlibs/site/js/recaptchawidget.js @@ -103,7 +103,7 @@ if (typeof window.RecaptchaWidget === 'undefined') { } setCaptchaModel = function(response) { - this.#model.value = (response); + this.#model.dispatch(new FormView.Actions.UIChange({'value': response})); } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js index d7d2142a60..7fe8a2d048 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js @@ -68,10 +68,10 @@ setModel(model) { super.setModel(model); if (this.widget.value !== '') { - this._model.value = this.widget.value; + this.setModelValue(this.widget.value); } this.widget.addEventListener('blur', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); this.setWidgetValueToDisplayValue(); this.setInactive(); }); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/termsandconditions/v1/termsandconditions/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/termsandconditions/v1/termsandconditions/_cq_template.xml index 95a10387e4..856d620fbc 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/termsandconditions/v1/termsandconditions/_cq_template.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/termsandconditions/v1/termsandconditions/_cq_template.xml @@ -18,6 +18,7 @@ jcr:primaryType="nt:unstructured" jcr:title="I agree to the terms & conditions" sling:resourceType="core/fd/components/form/checkbox/v1/checkbox" + required="true" enabled="false" checkedValue="true" fieldType="checkbox"> diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/README.md index b76b834337..115c38d857 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/README.md +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/README.md @@ -42,6 +42,16 @@ The following properties are written to JCR for this Form Text component and are 8. `./readOnly` - if set to `true`, the filed will be read only 9. `./maxLength` - defines the maximum length of input allowed for the field. 10. `./minLength` - defines the minimum length of input allowed for the field. +11. `./maxLengthMessage` - defines the maximum length error message for the field. +12. `./minLengthMessage` - defines the minimum length error message for the field. + + +### Behavior of `maxLength` and `maxLengthMessage` + +The `maxLength` property in HTML5 compliant fields, such as those used in the Adaptive Form Text Input component, restricts users from typing more characters than specified. This restriction aligns with HTML5 standards to ensure the input length does not exceed the defined maximum. However, there are scenarios, particularly in custom widgets or headless implementations, where it might be necessary to allow input beyond the `maxLength`. + +To accommodate such use-cases, while still adhering to HTML5 compliance within the core component, the `maxLengthMessage` property is provided. This property allows developers to define a custom error message that is displayed when the input exceeds the `maxLength`, offering a way to guide users even when custom logic permits them to enter more characters than the standard limit. + ## Client Libraries The component provides a `core.forms.components.textinput.v1.runtime` client library category that contains the Javascript runtime for the component. diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js index 183b2fe4db..e2119a07df 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js @@ -68,10 +68,10 @@ setModel(model) { super.setModel(model); if (this.widget.value !== '') { - this._model.value = this.widget.value; + this.setModelValue(this.widget.value); } this.widget.addEventListener('blur', (e) => { - this._model.value = e.target.value; + this.setModelValue(e.target.value); this.setWidgetValueToDisplayValue(); this.setInactive(); }); diff --git a/ui.apps/pom.xml b/ui.apps/pom.xml index 75f4cb5006..55ff64f9e2 100644 --- a/ui.apps/pom.xml +++ b/ui.apps/pom.xml @@ -82,9 +82,22 @@ - /apps/core/fd/components + /apps/core/fd/components/aemform + + + /apps/core/fd/components/commons + + + /apps/core/fd/components/formsportal + + + + /apps/core/fd/components + + + application ${vault.package.group} @@ -298,10 +311,13 @@ - /libs/core/fd/components + /libs/core/fd/components/aemform - /libs/core/fd/clientlibs + /libs/core/fd/components/commons + + + /libs/core/fd/components/formsportal cloud diff --git a/ui.frontend/package-lock.json b/ui.frontend/package-lock.json index 491eb26839..2649b80b16 100644 --- a/ui.frontend/package-lock.json +++ b/ui.frontend/package-lock.json @@ -9,9 +9,9 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@aemforms/af-core": "^0.22.92", - "@aemforms/af-custom-functions": "1.0.8", - "@aemforms/af-formatters": "^0.22.92" + "@aemforms/af-core": "^0.22.96", + "@aemforms/af-custom-functions": "1.0.9", + "@aemforms/af-formatters": "^0.22.96" }, "devDependencies": { "@babel/preset-env": "^7.18.2", @@ -61,12 +61,12 @@ } }, "node_modules/@aemforms/af-core": { - "version": "0.22.92", - "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.92.tgz", - "integrity": "sha512-Or7g38Gnx9d04Zhg50SIyJWhcNVio8J4ArrbcSomEdrzVfFU+dzrD9KOsytIdKm/J6Rg9+Z+2rR1ZMVLyOfvKw==", + "version": "0.22.96", + "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.96.tgz", + "integrity": "sha512-t6JLaGZXPWTjBsI59V9khpHIz2fLG9rMY09U7OALmVrPPnVWDUT6pXQfhbCmL1qDB7FCrso0DrjxjTIl/HBrQA==", "dependencies": { "@adobe/json-formula": "0.1.50", - "@aemforms/af-formatters": "^0.22.92" + "@aemforms/af-formatters": "^0.22.96" } }, "node_modules/@aemforms/af-custom-functions": { @@ -75,9 +75,9 @@ "integrity": "sha512-R7+fKX4A5DaNoHvukm9eM762En8XA/ZVE3F5a/2lUuDmgi4f5INWE+i5+PIDZtHD66R4ZGaNAI4LoLtaXxgOGA==" }, "node_modules/@aemforms/af-formatters": { - "version": "0.22.92", - "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.92.tgz", - "integrity": "sha512-NLL7sCMeUIy6mIxsQM1tMdOd1JNmyd5s3UZut9HO83uJjpDEI7r0QPPQRenpnPBf+OxmPaakzE/uCdGsf0awQw==" + "version": "0.22.96", + "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.96.tgz", + "integrity": "sha512-pKYF1IBi8ihBRowxuJyaSu7OjKDUGU4OGuN/G+1GBXFxqEgiLM97gECJYIGchY9zfeVSsQNGUjXXuvj6q7s0NA==" }, "node_modules/@ampproject/remapping": { "version": "2.2.1", @@ -11076,12 +11076,12 @@ "integrity": "sha512-dmlLYfbty8NPVIdxvI9cJ+ZdXsrRCFrCdmL1+aR2auEzXJ86rD0bm1qu+S4NOpFiZLKIyx0zvUTykms40vNjsA==" }, "@aemforms/af-core": { - "version": "0.22.92", - "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.92.tgz", - "integrity": "sha512-Or7g38Gnx9d04Zhg50SIyJWhcNVio8J4ArrbcSomEdrzVfFU+dzrD9KOsytIdKm/J6Rg9+Z+2rR1ZMVLyOfvKw==", + "version": "0.22.96", + "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.96.tgz", + "integrity": "sha512-t6JLaGZXPWTjBsI59V9khpHIz2fLG9rMY09U7OALmVrPPnVWDUT6pXQfhbCmL1qDB7FCrso0DrjxjTIl/HBrQA==", "requires": { "@adobe/json-formula": "0.1.50", - "@aemforms/af-formatters": "^0.22.92" + "@aemforms/af-formatters": "^0.22.96" } }, "@aemforms/af-custom-functions": { @@ -11090,9 +11090,9 @@ "integrity": "sha512-R7+fKX4A5DaNoHvukm9eM762En8XA/ZVE3F5a/2lUuDmgi4f5INWE+i5+PIDZtHD66R4ZGaNAI4LoLtaXxgOGA==" }, "@aemforms/af-formatters": { - "version": "0.22.92", - "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.92.tgz", - "integrity": "sha512-NLL7sCMeUIy6mIxsQM1tMdOd1JNmyd5s3UZut9HO83uJjpDEI7r0QPPQRenpnPBf+OxmPaakzE/uCdGsf0awQw==" + "version": "0.22.96", + "resolved": "https://registry.npmjs.org/@aemforms/af-formatters/-/af-formatters-0.22.96.tgz", + "integrity": "sha512-pKYF1IBi8ihBRowxuJyaSu7OjKDUGU4OGuN/G+1GBXFxqEgiLM97gECJYIGchY9zfeVSsQNGUjXXuvj6q7s0NA==" }, "@ampproject/remapping": { "version": "2.2.1", diff --git a/ui.frontend/package.json b/ui.frontend/package.json index cd2296c237..3c5b67bfc4 100644 --- a/ui.frontend/package.json +++ b/ui.frontend/package.json @@ -23,8 +23,8 @@ "webpack-merge": "^5.8.0" }, "dependencies": { - "@aemforms/af-core": "^0.22.92", - "@aemforms/af-formatters": "^0.22.92", - "@aemforms/af-custom-functions": "1.0.8" + "@aemforms/af-core": "^0.22.96", + "@aemforms/af-formatters": "^0.22.96", + "@aemforms/af-custom-functions": "1.0.9" } } diff --git a/ui.frontend/src/index.js b/ui.frontend/src/index.js index df3ffa9136..f9bc61b0f8 100644 --- a/ui.frontend/src/index.js +++ b/ui.frontend/src/index.js @@ -15,7 +15,7 @@ ******************************************************************************/ import Utils from "./utils.js"; import LanguageUtils from "./LanguageUtils.js"; -import {createFormInstance, FileObject, extractFileInfo, Click, Change, Submit, Blur, AddItem, RemoveItem, CustomEvent} from "@aemforms/af-core"; +import {createFormInstance, FileObject, extractFileInfo, Click, Change, Submit, Blur, AddItem, RemoveItem, CustomEvent, UIChange} from "@aemforms/af-core"; import {FormField, FormContainer, FormFieldBase, FormPanel, FormTabs, FormFileInput, FormOptionFieldBase, FormCheckBox, FormFileInputWidgetBase, FormFileInputWidget} from "./view/index.js"; import {Constants} from "./constants.js"; import GuideBridge from "./GuideBridge.js"; @@ -48,7 +48,7 @@ window.guideBridge = new GuideBridge(); * @property {string} RemoveItem - The action for removing an item. */ const Actions = { - Click, Change, Submit, Blur, AddItem, RemoveItem, CustomEvent + Click, Change, Submit, Blur, AddItem, RemoveItem, UIChange, CustomEvent } /** diff --git a/ui.frontend/src/view/FormCheckBox.js b/ui.frontend/src/view/FormCheckBox.js index bfcff396eb..acf5fc4075 100644 --- a/ui.frontend/src/view/FormCheckBox.js +++ b/ui.frontend/src/view/FormCheckBox.js @@ -45,11 +45,8 @@ class FormCheckBox extends FormFieldBase { this._onValue = this._model._jsonModel.enum[0]; this._offValue = this._model._jsonModel.enum[1]; this.widget.addEventListener('change', (e) => { - if (this.widget.checked) { - this._model.value = this._onValue; - } else { - this._model.value = this._offValue; - } + const value = this.widget.checked ? this._onValue : this._offValue; + this._model.dispatch(new FormView.Actions.UIChange({'value': value})); }) } diff --git a/ui.frontend/src/view/FormField.js b/ui.frontend/src/view/FormField.js index b272bc16da..271d5451d9 100644 --- a/ui.frontend/src/view/FormField.js +++ b/ui.frontend/src/view/FormField.js @@ -130,6 +130,10 @@ class FormField { } } + setModelValue(value) { + this._model.dispatch(new FormView.Actions.UIChange({'value': value})); + } + /** * Updates the HTML with the respective model. * This is for the markup that is generated on the client-side (e.g., repeatable panel). diff --git a/ui.tests/test-module/package-lock.json b/ui.tests/test-module/package-lock.json index 5c54673beb..11fbffd3e0 100644 --- a/ui.tests/test-module/package-lock.json +++ b/ui.tests/test-module/package-lock.json @@ -14913,7 +14913,8 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", - "dev": true + "dev": true, + "requires": {} }, "backo2": { "version": "1.0.2", @@ -15926,7 +15927,8 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz", "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==", - "dev": true + "dev": true, + "requires": {} }, "cypress-iframe": { "version": "1.0.1", @@ -20786,7 +20788,8 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true + "dev": true, + "requires": {} }, "xhr": { "version": "2.6.0", diff --git a/ui.tests/test-module/specs/actions/submit/submit.runtime.spec.js b/ui.tests/test-module/specs/actions/submit/submit.runtime.spec.js index bbbf04c07a..c71280ac35 100644 --- a/ui.tests/test-module/specs/actions/submit/submit.runtime.spec.js +++ b/ui.tests/test-module/specs/actions/submit/submit.runtime.spec.js @@ -20,6 +20,7 @@ describe("Form with Submit Button", () => { const customSubmitPagePathRequestParameters = "content/forms/af/core-components-it/samples/actions/submit/customsubmit/basicwithrequestparameters.html" const externalPagePathSubmit = "content/forms/af/core-components-it/samples/actions/submit/external.html" const submitSuccessRulePagePath = "content/forms/af/core-components-it/samples/actions/submit/submitsuccessrule.html" + const emailPagePath = "content/forms/af/core-components-it/samples/actions/submit/email.html" const bemBlock = 'cmp-button' const IS = "adaptiveFormButton" const selectors = { @@ -81,6 +82,23 @@ describe("Form with Submit Button", () => { }) }); + // actual email won't trigger, but in case of any error, the test case would fail + it("Clicking the button should trigger email submission of the form", () => { + cy.previewForm(emailPagePath); + cy.intercept({ + method: 'POST', + url: '**/adobe/forms/af/submit/*', + }).as('afSubmission') + + cy.get(`.cmp-adaptiveform-button__widget`).click() + cy.wait('@afSubmission').then(({ response}) => { + expect(response.statusCode).to.equal(200); + expect(response.body).to.be.not.null; + expect(response.body.thankYouMessage).to.be.not.null; + expect(response.body.thankYouMessage).to.equal("Thank you for submitting the form."); + }) + }); + it("Form submit should show validation errors", () => { cy.previewForm(pagePath); diff --git a/ui.tests/test-module/specs/aemEmbedContainer/aemEmbedContainer.runtime.spec.js b/ui.tests/test-module/specs/aemEmbedContainer/aemEmbedContainer.runtime.spec.js index 6775e68564..f9b4e87dbb 100644 --- a/ui.tests/test-module/specs/aemEmbedContainer/aemEmbedContainer.runtime.spec.js +++ b/ui.tests/test-module/specs/aemEmbedContainer/aemEmbedContainer.runtime.spec.js @@ -41,7 +41,7 @@ describe("Sites with Aem Embed Container", () => { it("test for aemembedcontainer presence inside iframe", () => { getIframeBody().find('.cmp-adaptiveform-container').should('have.length', 1); - getIframeBody().find('.cmp-adaptiveform-container').find('.cmp-adaptiveform-numberinput__widget').should('have.length', 8); + getIframeBody().find('.cmp-adaptiveform-container').find('.cmp-adaptiveform-numberinput__widget').its('length').should('be.gt', 8); }) it("test for form presence in nonIframe mode", () => { diff --git a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js index fcf671f6f4..8a16fbe67c 100644 --- a/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js +++ b/ui.tests/test-module/specs/datepicker/datepicker.runtime.spec.js @@ -224,6 +224,14 @@ describe("Form Runtime with Date Picker", () => { }); + date = '15/8/2020'; + cy.get(`#${datePicker7}`).find("input").clear().type(date).then(() => { + cy.get(`#${datePicker7}`).find(".cmp-adaptiveform-datepicker__calendar-icon").should("be.visible").click({force: true}).then(() => { + cy.get("#li-day-16").should("be.visible").should("have.class", "dp-selected"); // first check for the original date selection + cy.get(".dp-caption").should("be.visible").should("contain.text", "2020"); + }); + }); + // check clear option cy.get(`#${datePicker7}`).find(".cmp-adaptiveform-datepicker__calendar-icon").should("be.visible").click({force: true}).then(() => { cy.get(".dp-clear").click(); @@ -332,4 +340,19 @@ describe("Form Runtime with Date Picker", () => { cy.get(".dp-caption").should('include.text', todayYear); }); }); + + it("check minimum, maximum, exclusiveMinimum and exclusiveMaximum constraints ", () => { + const [dateInput10, dateInput10FieldView] = Object.entries(formContainer._fields)[10]; + const input = "2024-07-10"; + let model = dateInput10FieldView.getModel(); + cy.get(`#${dateInput10}`).find("input").clear().type(input).blur().then(x => { + cy.get(`#${dateInput10}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',"Value must be greater than 2024-07-10."); + cy.get(`#${dateInput10}`).find("input").clear().type("2024-07-12").blur().then(x => { + cy.get(`#${dateInput10}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',""); + cy.get(`#${dateInput10}`).find("input").clear().type("2024-07-26").blur().then(x => { + cy.get(`#${dateInput10}`).find(".cmp-adaptiveform-datepicker__errormessage").should('have.text',"Value must be less than 2024-07-23."); + }) + }) + }) + }); }) diff --git a/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js b/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js index 41cbb05462..1f1f1fcf23 100644 --- a/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js +++ b/ui.tests/test-module/specs/numberinput/numberinput.runtime.spec.js @@ -249,6 +249,18 @@ describe("Form with Number Input", () => { }) } }); + + it("check minimum, maximum, exclusiveMinimum and exclusiveMaximum constraints ", () => { + const [numberInput9, numberInput9FieldView] = Object.entries(formContainer._fields)[8]; + const input = "1212"; + let model = numberInput9FieldView.getModel(); + cy.get(`#${numberInput9}`).find("input").clear().type(input).blur().then(x => { + cy.get(`#${numberInput9}`).find(".cmp-adaptiveform-numberinput__errormessage").should('have.text',"Value must be less than or equal to 100."); + cy.get(`#${numberInput9}`).find("input").clear().type("10").blur().then(x => { + cy.get(`#${numberInput9}`).find(".cmp-adaptiveform-numberinput__errormessage").should('have.text',"Minimum value should be 10"); + }) + }) + }); }) describe("Form with Number Input Required Validation", () => { diff --git a/ui.tests/test-module/specs/ruleeditor/uichange.runtime.spec.js b/ui.tests/test-module/specs/ruleeditor/uichange.runtime.spec.js new file mode 100644 index 0000000000..b72d34b369 --- /dev/null +++ b/ui.tests/test-module/specs/ruleeditor/uichange.runtime.spec.js @@ -0,0 +1,61 @@ +/* + * + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2024 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + */ + +describe('Form with custom functions containing ui change action configured in client lib', () => { + const formPath = "content/forms/af/core-components-it/samples/ruleeditor/uichange.html"; + let formContainer = null; + + /** + * initialization of form container before every test + * */ + beforeEach(() => { + cy.previewForm(formPath).then(p => { + formContainer = p; + }); + }); + + it("ui change action should reset field model based on custom function", () => { + // Rule on textBox1: set value of textBox1 to "test", which is output of custom function testFunction1() + expect(formContainer, "formcontainer is initialized").to.not.be.null; + const [numberInput1, fieldView1] = Object.entries(formContainer._fields)[0] + const [numberInput7, fieldView2] = Object.entries(formContainer._fields)[6] + const numberInput1Model = formContainer._model.getElement(numberInput1) + const numberInput7Model = formContainer._model.getElement(numberInput7) + numberInput1Model.value = 123; + cy.get(`#${numberInput1}`).find("input").should('have.value',"123") + // let's trigger ui change action by doing changes via the dom API's + cy.get(`#${numberInput7}`).find("input").clear().type("93").blur().then(x => { + cy.get(`#${numberInput1}`).find("input").should('have.value',"") + }) + }) + + + it("setting value programmatically should not reset the model value", () => { + // Rule on textBox1: set value of textBox1 to "test", which is output of custom function testFunction1() + expect(formContainer, "formcontainer is initialized").to.not.be.null; + const [numberInput1, fieldView1] = Object.entries(formContainer._fields)[0] + const [numberInput7, fieldView2] = Object.entries(formContainer._fields)[6] + const numberInput1Model = formContainer._model.getElement(numberInput1) + const numberInput7Model = formContainer._model.getElement(numberInput7) + numberInput1Model.value = 123; + cy.get(`#${numberInput1}`).find("input").should('have.value',"123") + // let's trigger ui change action by doing changes via the dom API's + numberInput7Model.value = 93; + cy.get(`#${numberInput1}`).find("input").should('have.value',"123"); + }) +}) diff --git a/ui.tests/test-module/specs/termsandconditions/tnc.runtime.spec.js b/ui.tests/test-module/specs/termsandconditions/tnc.runtime.spec.js index d2a12800fa..6858432dfd 100644 --- a/ui.tests/test-module/specs/termsandconditions/tnc.runtime.spec.js +++ b/ui.tests/test-module/specs/termsandconditions/tnc.runtime.spec.js @@ -110,5 +110,25 @@ describe("Form Runtime with Terms and Conditions", () => { }) }) }); -}) + it('checkbox should be required by default', () => { + const tncWithLinksID = formContainer._model.items[1].id; + const model = formContainer._model.getElement(tncWithLinksID) + expect(model.getState().items[0].enabled).to.equal(false); + cy.get(`#${tncWithLinksID}`).get('a').click() + .then(() => { + expect(model.getState().items[0].enabled).to.equal(true); + cy.get(`#${tncWithLinksID} .cmp-adaptiveform-checkbox`).invoke('attr', 'data-cmp-enabled') + .should('eq', 'true'); + cy.get(`#${tncWithLinksID} .cmp-adaptiveform-checkbox`).click() + .then(() => { + cy.get(`#${tncWithLinksID} .cmp-adaptiveform-checkbox__errormessage`).should('be.empty'); + }) + cy.get(`#${tncWithLinksID} .cmp-adaptiveform-checkbox`).dblclick().should('not.be.checked') + .then(() => { + cy.get(`#${tncWithLinksID} .cmp-adaptiveform-checkbox__errormessage`).should('not.be.null'); + }) + }) + }) + + })