From d8e1e765ad2dacc254d5264ae79253dc522fa084 Mon Sep 17 00:00:00 2001 From: Shravani Urala Date: Tue, 1 Sep 2026 12:19:49 +0530 Subject: [PATCH 1/6] Add character count in text area --- .../internal/form/ReservedProperties.java | 1 + .../models/v1/form/TextInputImpl.java | 10 ++++++++ .../components/models/form/TextInput.java | 14 +++++++++++ .../models/v1/form/TextInputImplTest.java | 23 +++++++++++++++++++ .../exporter-textinput-charactercount.json | 20 ++++++++++++++++ .../form/textinput/test-content.json | 10 ++++++++ .../schema/0.15.2/adaptive-form.schema.json | 1 + .../components/adaptive-form/textinput.less | 5 ++++ .../form/textinput/v1/textinput/README.md | 2 ++ .../v1/textinput/_cq_dialog/.content.xml | 7 ++++++ .../v1/textinput/_cq_styleConfig/.content.xml | 8 +++++++ .../clientlibs/site/css/textinputview.css | 4 ++++ .../clientlibs/site/js/textinputview.js | 11 ++++++++- .../textinput/v1/textinput/textinput.html | 4 ++++ .../specs/textinput/textinput.authoring.cy.js | 2 ++ 15 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 bundles/af-core/src/test/resources/form/textinput/exporter-textinput-charactercount.json 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 bd9ee8f4b7..98e6a68ebd 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 @@ -152,6 +152,7 @@ private ReservedProperties() { public static final String PN_SHOW_AS_POPUP = "showAsPopup"; public static final String PN_TEXT_IS_RICH = "textIsRich"; public static final String PN_MULTILINE = "multiLine"; + public static final String PN_SHOW_CHARACTER_COUNT = "showCharacterCount"; public static final String PN_DESIGN_DEFAULT_TYPE = Title.PN_DESIGN_DEFAULT_TYPE; public static final String PN_TITLE_LINK_DISABLED = Title.PN_TITLE_LINK_DISABLED; public static final String PN_DRAG_DROP_TEXT = "dragDropText"; diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java index 150f6bc9fc..50b86365db 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java @@ -53,6 +53,10 @@ public class TextInputImpl extends AbstractFieldImpl implements TextInput { @Default(booleanValues = false) protected boolean multiLine; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_SHOW_CHARACTER_COUNT) + @Nullable + protected Boolean showCharacterCount; + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_FORMAT) @Nullable protected String format; @@ -75,6 +79,12 @@ public boolean isMultiLine() { return multiLine; } + @Override + @Nullable + public Boolean isShowCharacterCount() { + return showCharacterCount; + } + @Override public String getFieldType() { if (isMultiLine()) { diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java index 4415ceabde..a836cebfe9 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java @@ -19,6 +19,7 @@ import org.osgi.annotation.versioning.ConsumerType; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -43,6 +44,19 @@ default boolean isMultiLine() { return false; } + /** + * Returns {@code true} if a live character count should be shown below the field, {@code false} if it should not, or + * {@code null} if not configured. + * + * @return {@code true} if the character count should be shown, {@code false} if not, or {@code null} if not configured + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @Nullable + @JsonInclude(JsonInclude.Include.NON_NULL) + default Boolean isShowCharacterCount() { + return null; + } + /** * Returns {@code "off"} if autocomplete if disabled, otherwise {@code "on"} or values listed @see * here diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java index 1542522422..119a9d64e9 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java @@ -57,6 +57,7 @@ public class TextInputImplTest { private static final String PATH_TEXTINPUT_DATALAYER = CONTENT_ROOT + "/textinput-datalayer"; private static final String PATH_TEXTINPUT_CUSTOMIZED = CONTENT_ROOT + "/textinput-customized"; private static final String PATH_TEXTINPUT_2 = CONTENT_ROOT + "/multiline-textinput"; + private static final String PATH_TEXTINPUT_CHARACTERCOUNT = CONTENT_ROOT + "/textinput-charactercount"; private static final String PATH_NUMBER_TEXTINPUT = CONTENT_ROOT + "/number-textinput"; private static final String PATH_NUMBER_TEXTINPUT_EXCLUSIVE = CONTENT_ROOT + "/number-textinput-exclusive"; private static final String PATH_FORMAT_TEXTINPUT = CONTENT_ROOT + "/textinput-format"; @@ -259,6 +260,28 @@ void testIsMultiLine() { assertEquals(false, textInputMock.isMultiLine()); } + @Test + void testIsShowCharacterCount() { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CUSTOMIZED, TextInput.class, context); + assertEquals(null, textInput.isShowCharacterCount()); + TextInput textInputMock = Mockito.mock(TextInput.class); + Mockito.when(textInputMock.isShowCharacterCount()).thenCallRealMethod(); + assertEquals(null, textInputMock.isShowCharacterCount()); + } + + @Test + void testIsShowCharacterCountWhenSet() { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CHARACTERCOUNT, TextInput.class, context); + assertEquals(true, textInput.isShowCharacterCount()); + assertEquals(100, textInput.getMaxLength().intValue()); + } + + @Test + void testCharacterCountJSONExport() throws Exception { + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CHARACTERCOUNT, TextInput.class, context); + Utils.testJSONExport(textInput, Utils.getTestExporterJSONPath(BASE, PATH_TEXTINPUT_CHARACTERCOUNT)); + } + @Test void testGetPlaceHolder() { TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CUSTOMIZED, TextInput.class, context); diff --git a/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-charactercount.json b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-charactercount.json new file mode 100644 index 0000000000..1726475117 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/textinput/exporter-textinput-charactercount.json @@ -0,0 +1,20 @@ +{ + "id": "textinput-357c2ee34c", + "fieldType": "multiline-input", + "name": "abc", + "type": "string", + "maxLength": 100, + "showCharacterCount": true, + "label": { + "value": "def" + }, + "events": { + "custom:setProperty": [ + "$event.payload" + ] + }, + "properties": { + "fd:path": "/content/textinput-charactercount" + }, + ":type": "core/fd/components/form/textinput/v1/textinput" +} diff --git a/bundles/af-core/src/test/resources/form/textinput/test-content.json b/bundles/af-core/src/test/resources/form/textinput/test-content.json index 9fad69379c..a4f3e5334c 100644 --- a/bundles/af-core/src/test/resources/form/textinput/test-content.json +++ b/bundles/af-core/src/test/resources/form/textinput/test-content.json @@ -234,5 +234,15 @@ "sling:resourceType" : "core/fd/components/form/textinput/v1/textinput", "name" : "abc", "jcr:title" : "def" + }, + "textinput-charactercount" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/textinput/v1/textinput", + "name" : "abc", + "jcr:title" : "def", + "multiLine": true, + "fieldType": "multiline-input", + "showCharacterCount": true, + "maxLength": 100 } } diff --git a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json index ed6cb8dd94..2367cfa6db 100644 --- a/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json +++ b/bundles/af-core/src/test/resources/schema/0.15.2/adaptive-form.schema.json @@ -96,6 +96,7 @@ "required", "rules", "screenReaderText", + "showCharacterCount", "type", "tooltip", "validationExpression", diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/textinput.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/textinput.less index c76f76b169..2f19652f8a 100644 --- a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/textinput.less +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/textinput.less @@ -3,4 +3,9 @@ } textarea.cmp-adaptiveform-textinput__widget { height: @textarea-height; +} +.cmp-adaptiveform-textinput__charcount { + .shortdescriptionMixin(); + color: @light-gray; + text-align: right; } \ No newline at end of file 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 7aaa849870..e0809decd8 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 @@ -46,6 +46,7 @@ The following properties are written to JCR for this Form Text component and are 12. `./minLengthMessage` - defines the minimum length error message for the field. 13. `./displayPatternType` - defines the display pattern type for formatting the input value (e.g., 'phonenumber', 'socialsecuritynumber', 'email-alphanumeric', 'zipcode') 14. `./displayValueExpression` - defines the expression used to format the display value (e.g., 'formatInput($field.$value, 'phonenumber')') +15. `./showCharacterCount` - if set to `true`, a live character count is displayed below the field. Combine with `maxLength` to show a running total out of the limit (e.g. `12 / 100`). ### Behavior of `maxLength` and `maxLengthMessage` @@ -80,6 +81,7 @@ BLOCK cmp-adaptiveform-textinput ELEMENT cmp-adaptiveform-textinput__shortdescription ELEMENT cmp-adaptiveform-textinput__longdescription ELEMENT cmp-adaptiveform-textinput__errormessage + ELEMENT cmp-adaptiveform-textinput__charcount ``` ### Note diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml index 109b3998e7..3627724592 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml @@ -57,6 +57,13 @@ name="./multiLine" text="Allow multiple lines" value="true"/> + + + { + charCountCurrent.textContent = this.widget.value ? this.widget.value.length : 0; + }; + updateCharCount(); + this.widget.addEventListener('input', updateCharCount); + } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/textinput.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/textinput.html index ebf4835dad..0d269659a4 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/textinput.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/textinput.html @@ -66,6 +66,10 @@ minlength="${text.minLength}" maxlength="${text.maxLength}" dir="auto"/> +
0 / ${text.maxLength}
diff --git a/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js b/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js index 682cdd4751..508e1fd125 100644 --- a/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js +++ b/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js @@ -50,6 +50,8 @@ describe('Page - Authoring', function () { // Check If Dialog Options Are Visible cy.get("[name='./multiLine']") .should("exist"); + cy.get("[name='./showCharacterCount']") + .should("exist"); cy.get("[name='./autocomplete']") .should("exist"); From 298edbd8af2f925e1a2595457440619d3b0975d1 Mon Sep 17 00:00:00 2001 From: Shravani Urala Date: Tue, 15 Sep 2026 11:03:30 +0530 Subject: [PATCH 2/6] Address comments --- .../models/v1/form/TextInputImpl.java | 3 ++- .../components/models/form/TextInput.java | 6 +++-- .../models/v1/form/TextInputImplTest.java | 10 ++++++++ .../form/textinput/test-content.json | 9 +++++++ .../form/textinput/v1/textinput/README.md | 2 +- .../v1/textinput/_cq_dialog/.content.xml | 4 ++- .../clientlibs/editor/js/editDialog.js | 25 ++++++++++++++++++- .../specs/textinput/textinput.authoring.cy.js | 4 +++ 8 files changed, 57 insertions(+), 6 deletions(-) diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java index 50b86365db..3a52b3ec57 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImpl.java @@ -82,7 +82,8 @@ public boolean isMultiLine() { @Override @Nullable public Boolean isShowCharacterCount() { - return showCharacterCount; + // the character count is only applicable to multi line (textarea) fields + return isMultiLine() ? showCharacterCount : null; } @Override diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java index a836cebfe9..6a2f3ce8bc 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/TextInput.java @@ -46,9 +46,11 @@ default boolean isMultiLine() { /** * Returns {@code true} if a live character count should be shown below the field, {@code false} if it should not, or - * {@code null} if not configured. + * {@code null} if not configured. Only applicable when {@link #isMultiLine()} is {@code true}; {@code null} is returned + * for single line fields regardless of how the underlying property was set. * - * @return {@code true} if the character count should be shown, {@code false} if not, or {@code null} if not configured + * @return {@code true} if the character count should be shown, {@code false} if not, or {@code null} if not configured or + * not applicable * @since com.adobe.cq.forms.core.components.models.form 5.13.0 */ @Nullable diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java index 119a9d64e9..fdd641b253 100644 --- a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/TextInputImplTest.java @@ -58,6 +58,7 @@ public class TextInputImplTest { private static final String PATH_TEXTINPUT_CUSTOMIZED = CONTENT_ROOT + "/textinput-customized"; private static final String PATH_TEXTINPUT_2 = CONTENT_ROOT + "/multiline-textinput"; private static final String PATH_TEXTINPUT_CHARACTERCOUNT = CONTENT_ROOT + "/textinput-charactercount"; + private static final String PATH_TEXTINPUT_CHARACTERCOUNT_SINGLELINE = CONTENT_ROOT + "/textinput-charactercount-singleline"; private static final String PATH_NUMBER_TEXTINPUT = CONTENT_ROOT + "/number-textinput"; private static final String PATH_NUMBER_TEXTINPUT_EXCLUSIVE = CONTENT_ROOT + "/number-textinput-exclusive"; private static final String PATH_FORMAT_TEXTINPUT = CONTENT_ROOT + "/textinput-format"; @@ -282,6 +283,15 @@ void testCharacterCountJSONExport() throws Exception { Utils.testJSONExport(textInput, Utils.getTestExporterJSONPath(BASE, PATH_TEXTINPUT_CHARACTERCOUNT)); } + @Test + void testIsShowCharacterCountIgnoredForSingleLine() { + // showCharacterCount is only applicable to multi line fields; a single line field must report null + // regardless of what was authored, since the feature has no effect there. + TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CHARACTERCOUNT_SINGLELINE, TextInput.class, context); + assertEquals(false, textInput.isMultiLine()); + assertEquals(null, textInput.isShowCharacterCount()); + } + @Test void testGetPlaceHolder() { TextInput textInput = Utils.getComponentUnderTest(PATH_TEXTINPUT_CUSTOMIZED, TextInput.class, context); diff --git a/bundles/af-core/src/test/resources/form/textinput/test-content.json b/bundles/af-core/src/test/resources/form/textinput/test-content.json index a4f3e5334c..d6c32cb1ed 100644 --- a/bundles/af-core/src/test/resources/form/textinput/test-content.json +++ b/bundles/af-core/src/test/resources/form/textinput/test-content.json @@ -244,5 +244,14 @@ "fieldType": "multiline-input", "showCharacterCount": true, "maxLength": 100 + }, + "textinput-charactercount-singleline" : { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType" : "core/fd/components/form/textinput/v1/textinput", + "name" : "abc", + "jcr:title" : "def", + "fieldType": "text-input", + "showCharacterCount": true, + "maxLength": 100 } } 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 e0809decd8..cd2fdcb6d2 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 @@ -46,7 +46,7 @@ The following properties are written to JCR for this Form Text component and are 12. `./minLengthMessage` - defines the minimum length error message for the field. 13. `./displayPatternType` - defines the display pattern type for formatting the input value (e.g., 'phonenumber', 'socialsecuritynumber', 'email-alphanumeric', 'zipcode') 14. `./displayValueExpression` - defines the expression used to format the display value (e.g., 'formatInput($field.$value, 'phonenumber')') -15. `./showCharacterCount` - if set to `true`, a live character count is displayed below the field. Combine with `maxLength` to show a running total out of the limit (e.g. `12 / 100`). +15. `./showCharacterCount` - if set to `true`, a live character count is displayed below the field. Only applicable when `./multiLine` is `true` (the dialog checkbox is hidden otherwise, and the Sling Model reports `null` for single line fields regardless of the stored value). Combine with `maxLength` to show a running total out of the limit (e.g. `12 / 100`). ### Behavior of `maxLength` and `maxLengthMessage` diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml index 3627724592..20642e9b12 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/_cq_dialog/.content.xml @@ -53,16 +53,18 @@ name="./default"/>