diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java index bb441d8366..43b48cddcc 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/form/FormConstants.java @@ -161,4 +161,6 @@ private FormConstants() { /** The resource type for date time input field v1 */ public static final String RT_FD_FORM_DATETIME_V1 = RT_FD_FORM_PREFIX + "datetime/v1/datetime"; + /** The resource type for image choice v1 */ + public static final String RT_FD_FORM_IMAGE_CHOICE_V1 = RT_FD_FORM_PREFIX + "imagechoice/v1/imagechoice"; } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java new file mode 100644 index 0000000000..98951b6707 --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImpl.java @@ -0,0 +1,117 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.internal.models.v1.form; + +import java.util.Map; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +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.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.FieldType; +import com.adobe.cq.forms.core.components.models.form.ImageChoice; +import com.adobe.cq.forms.core.components.util.AbstractOptionsFieldImpl; +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Model( + adaptables = { SlingHttpServletRequest.class, Resource.class }, + adapters = { ImageChoice.class, + ComponentExporter.class }, + resourceType = { FormConstants.RT_FD_FORM_IMAGE_CHOICE_V1 }) +@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) +public class ImageChoiceImpl extends AbstractOptionsFieldImpl implements ImageChoice { + + private static final String PN_SELECTION_TYPE = "selectionType"; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_ORIENTATION) + @Nullable + protected String orientationJcr; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = PN_SELECTION_TYPE) + @Nullable + protected String selectionTypeJcr; + + @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = ReservedProperties.PN_IMAGE_SRC) + @Nullable + protected String[] imageSrcArray; + + private Orientation orientation; + private SelectionType selectionType; + + @PostConstruct + private void initImageChoiceModel() { + orientation = Orientation.fromString(orientationJcr); + selectionType = SelectionType.fromString(selectionTypeJcr); + } + + @Override + public @NotNull Map getCustomLayoutProperties() { + Map customLayoutProperties = super.getCustomLayoutProperties(); + if (orientation != null) { + customLayoutProperties.put(ReservedProperties.PN_ORIENTATION, orientation.getValue()); + } + if (selectionType != null) { + customLayoutProperties.put(PN_SELECTION_TYPE, selectionType.getValue()); + } + return customLayoutProperties; + } + + @Override + @JsonIgnore + public Orientation getOrientation() { + return orientation; + } + + @Override + @JsonIgnore + public SelectionType getSelectionType() { + return selectionType; + } + + @Override + public String[] getImageSrc() { + return imageSrcArray != null ? imageSrcArray.clone() : null; + } + + @Override + public Integer getMinItems() { + return minItems; + } + + @Override + public Integer getMaxItems() { + return maxItems; + } + + @Override + public String getFieldType() { + if (selectionType == SelectionType.MULTI) { + return FieldType.CHECKBOX_GROUP.getValue(); + } + return FieldType.RADIO_GROUP.getValue(); + } +} diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java new file mode 100644 index 0000000000..72f034963a --- /dev/null +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/models/form/ImageChoice.java @@ -0,0 +1,146 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.models.form; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; +import org.osgi.annotation.versioning.ConsumerType; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Defines the form {@code ImageChoice} Sling Model used for the + * {@code /apps/core/fd/components/form/imagechoice/v1/imagechoice} component. + * + *

+ * The ImageChoice component allows users to select one or more options + * represented by images. It supports both single-select (radio button behavior) + * and multi-select (checkbox group behavior) modes. + *

+ * + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ +@ConsumerType +public interface ImageChoice extends Field, OptionsConstraint, ContainerConstraint { + + /** + * Defines the selection type for image choice. + * Possible values: {@code single}, {@code multi} + * + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + enum SelectionType { + SINGLE("single"), + MULTI("multi"); + + private String value; + + SelectionType(String value) { + this.value = value; + } + + public static SelectionType fromString(String value) { + for (SelectionType type : SelectionType.values()) { + if (StringUtils.equals(value, type.value)) { + return type; + } + } + return SINGLE; + } + + public String getValue() { + return value; + } + + @Override + @JsonValue + public String toString() { + return value; + } + } + + /** + * Defines the orientation for image choice layout. + * Possible values: {@code horizontal}, {@code vertical} + * + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + enum Orientation { + HORIZONTAL("horizontal"), + VERTICAL("vertical"); + + private String value; + + Orientation(String value) { + this.value = value; + } + + public static Orientation fromString(String value) { + for (Orientation type : Orientation.values()) { + if (StringUtils.equals(value, type.value)) { + return type; + } + } + return HORIZONTAL; + } + + public String getValue() { + return value; + } + + @Override + @JsonValue + public String toString() { + return value; + } + } + + /** + * Returns the orientation of the image choice component. + * + * @return {@link Orientation} + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @JsonIgnore + default Orientation getOrientation() { + return Orientation.HORIZONTAL; + } + + /** + * Returns the selection type of the image choice component. + * {@code single} behaves like radio buttons (one selection), + * {@code multi} behaves like checkbox group (multiple selections). + * + * @return {@link SelectionType} + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @JsonIgnore + default SelectionType getSelectionType() { + return SelectionType.SINGLE; + } + + /** + * Returns the array of image source paths corresponding to each enum option. + * The array is parallel to the enums array - imageSrc[i] is the image for enum[i]. + * + * @return array of image source paths, or null if not set + * @since com.adobe.cq.forms.core.components.models.form 5.13.0 + */ + @Nullable + String[] getImageSrc(); +} diff --git a/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java new file mode 100644 index 0000000000..1e3bfa6588 --- /dev/null +++ b/bundles/af-core/src/test/java/com/adobe/cq/forms/core/components/internal/models/v1/form/ImageChoiceImplTest.java @@ -0,0 +1,278 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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.internal.models.v1.form; + +import java.util.Map; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; + +import com.adobe.cq.forms.core.Utils; +import com.adobe.cq.forms.core.components.datalayer.FormComponentData; +import com.adobe.cq.forms.core.components.internal.form.FormConstants; +import com.adobe.cq.forms.core.components.models.form.*; +import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +import static org.junit.Assert.*; + +@ExtendWith(AemContextExtension.class) +public class ImageChoiceImplTest { + private static final String BASE = "/form/imagechoice"; + private static final String CONTENT_ROOT = "/content"; + private static final String PATH_IMAGECHOICE = CONTENT_ROOT + "/imagechoice"; + private static final String PATH_IMAGECHOICE_CUSTOMIZED = CONTENT_ROOT + "/imagechoice-customized"; + private static final String PATH_IMAGECHOICE_MULTI = CONTENT_ROOT + "/imagechoice-multi"; + private static final String PATH_IMAGECHOICE_NO_IMAGES = CONTENT_ROOT + "/imagechoice-no-images"; + private static final String PATH_IMAGECHOICE_DATALAYER = CONTENT_ROOT + "/imagechoice-datalayer"; + private static final String PATH_IMAGECHOICE_OPTION_SCREEN_READER_LABEL = CONTENT_ROOT + "/imagechoice-option-screenreader-label"; + + private final AemContext context = FormsCoreComponentTestContext.newAemContext(); + + @BeforeEach + void setUp() { + context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT); + } + + @Test + void testExportedType() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(FormConstants.RT_FD_FORM_IMAGE_CHOICE_V1, imageChoice.getExportedType()); + } + + @Test + void testFieldType_singleSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(FieldType.RADIO_GROUP.getValue(), imageChoice.getFieldType()); + } + + @Test + void testFieldType_multiSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertEquals(FieldType.CHECKBOX_GROUP.getValue(), imageChoice.getFieldType()); + } + + @Test + void testGetLabel() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("Favorite Animal", imageChoice.getLabel().getValue()); + } + + @Test + void testGetName() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("imagechoice_12345", imageChoice.getName()); + } + + @Test + void testGetDataRef() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("a.b", imageChoice.getDataRef()); + } + + @Test + void testGetDescription() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("Select your favorite animal", imageChoice.getDescription()); + } + + @Test + void testGetScreenReaderText() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals("custom screen reader text", imageChoice.getScreenReaderText()); + } + + @Test + void testIsVisible() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isVisible()); + } + + @Test + void testIsVisibleForCustomized() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(false, imageChoice.isVisible()); + } + + @Test + void testIsEnabled() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isEnabled()); + } + + @Test + void testIsReadOnly() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(false, imageChoice.isReadOnly()); + } + + @Test + void testIsReadOnlyForCustomized() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(true, imageChoice.isReadOnly()); + } + + @Test + void testGetEnum() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "cat", "dog", "bird" }, imageChoice.getEnums()); + } + + @Test + void testGetEnumNames() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "Cat", "Dog", "Bird" }, imageChoice.getEnumNames()); + } + + @Test + void testGetDefault_singleSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { "cat" }, imageChoice.getDefault()); + } + + @Test + void testGetDefault_multiSelect() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertArrayEquals(new String[] { "red", "blue" }, imageChoice.getDefault()); + } + + @Test + void testGetType() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(BaseConstraint.Type.STRING, imageChoice.getType()); + } + + @Test + void testGetOrientation_horizontal() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(ImageChoice.Orientation.HORIZONTAL, imageChoice.getOrientation()); + } + + @Test + void testGetOrientation_vertical() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(ImageChoice.Orientation.VERTICAL, imageChoice.getOrientation()); + } + + @Test + void testGetSelectionType_single() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(ImageChoice.SelectionType.SINGLE, imageChoice.getSelectionType()); + } + + @Test + void testGetSelectionType_multi() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_MULTI); + assertEquals(ImageChoice.SelectionType.MULTI, imageChoice.getSelectionType()); + } + + @Test + void testGetImageSrc() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertArrayEquals(new String[] { + "/content/dam/animals/cat.png", + "/content/dam/animals/dog.png", + "/content/dam/animals/bird.png" + }, imageChoice.getImageSrc()); + } + + @Test + void testGetImageSrc_null() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_NO_IMAGES); + assertNull(imageChoice.getImageSrc()); + } + + @Test + void testGetProperties() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + Map properties = imageChoice.getProperties(); + assertFalse(properties.isEmpty()); + Map customProperties = (Map) properties.get(Base.CUSTOM_PROPERTY_WRAPPER); + assertTrue((boolean) customProperties.get("tooltipVisible")); + assertEquals("vertical", customProperties.get("orientation")); + assertEquals("single", customProperties.get("selectionType")); + } + + @Test + void testDorProperties() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_CUSTOMIZED); + assertEquals(true, imageChoice.getDorProperties().get("dorExclusion")); + assertEquals("4", imageChoice.getDorProperties().get("dorColspan")); + } + + @Test + void testEnforceEnum() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE); + assertEquals(true, imageChoice.isEnforceEnum()); + } + + @Test + void testGetOptionScreenReaderLabels() { + ImageChoice imageChoice = getImageChoiceUnderTest(PATH_IMAGECHOICE_OPTION_SCREEN_READER_LABEL); + String[] screenReaderLabels = imageChoice.getOptionScreenReaderLabels(); + assertEquals("Favorite Pet: Cat", screenReaderLabels[0]); + assertEquals("Favorite Pet: Dog", screenReaderLabels[1]); + } + + @Test + void testDataLayerProperties() throws IllegalAccessException { + ImageChoice imageChoice = Utils.getComponentUnderTest(PATH_IMAGECHOICE_DATALAYER, ImageChoice.class, context); + FieldUtils.writeField(imageChoice, "dataLayerEnabled", true, true); + FormComponentData dataObject = (FormComponentData) imageChoice.getData(); + assert (dataObject != null); + assert (dataObject.getId()).equals("imagechoice-bafbf1a102"); + assert (dataObject.getType()).equals("core/fd/components/form/imagechoice/v1/imagechoice"); + assert (dataObject.getTitle()).equals("Favorite Color"); + assert (dataObject.getFieldType()).equals("radio-group"); + assert (dataObject.getDescription()).equals("Choose your favorite color"); + } + + @Test + void testDefaultInterface() { + ImageChoice imageChoiceMock = Mockito.mock(ImageChoice.class); + Mockito.when(imageChoiceMock.getOrientation()).thenCallRealMethod(); + assertEquals(ImageChoice.Orientation.HORIZONTAL, imageChoiceMock.getOrientation()); + Mockito.when(imageChoiceMock.getSelectionType()).thenCallRealMethod(); + assertEquals(ImageChoice.SelectionType.SINGLE, imageChoiceMock.getSelectionType()); + } + + @Test + void testSelectionTypeFromString() { + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString("single")); + assertEquals(ImageChoice.SelectionType.MULTI, ImageChoice.SelectionType.fromString("multi")); + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString("invalid")); + assertEquals(ImageChoice.SelectionType.SINGLE, ImageChoice.SelectionType.fromString(null)); + } + + @Test + void testOrientationFromString() { + assertEquals(ImageChoice.Orientation.HORIZONTAL, ImageChoice.Orientation.fromString("horizontal")); + assertEquals(ImageChoice.Orientation.VERTICAL, ImageChoice.Orientation.fromString("vertical")); + assertEquals(ImageChoice.Orientation.HORIZONTAL, ImageChoice.Orientation.fromString("invalid")); + assertEquals(ImageChoice.Orientation.HORIZONTAL, ImageChoice.Orientation.fromString(null)); + } + + private ImageChoice getImageChoiceUnderTest(String resourcePath) { + context.currentResource(resourcePath); + MockSlingHttpServletRequest request = context.request(); + return request.adaptTo(ImageChoice.class); + } +} diff --git a/bundles/af-core/src/test/resources/form/imagechoice/test-content.json b/bundles/af-core/src/test/resources/form/imagechoice/test-content.json new file mode 100644 index 0000000000..14bd29dc48 --- /dev/null +++ b/bundles/af-core/src/test/resources/form/imagechoice/test-content.json @@ -0,0 +1,156 @@ +{ + "imagechoice": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_12345", + "jcr:title": "Image Choice", + "orientation": "horizontal", + "selectionType": "single", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "Option 1", + "Option 2" + ], + "imageSrc": [ + "/content/dam/image1.png", + "/content/dam/image2.png" + ], + "fieldType": "radio-group", + "type": "string" + }, + "imagechoice-customized": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_12345", + "jcr:title": "Favorite Animal", + "description": "Select your favorite animal", + "enabled": true, + "orientation": "vertical", + "selectionType": "single", + "dataRef": "a.b", + "custom": "custom screen reader text", + "tooltip": "Pick one animal", + "required": "true", + "dorExclusion": true, + "dorColspan": "4", + "assistPriority": "custom", + "enum": [ + "cat", + "dog", + "bird" + ], + "type": "string", + "enumNames": [ + "Cat", + "Dog", + "Bird" + ], + "imageSrc": [ + "/content/dam/animals/cat.png", + "/content/dam/animals/dog.png", + "/content/dam/animals/bird.png" + ], + "visible": false, + "readOnly": true, + "fieldType": "radio-group", + "mandatoryMessage": "This is a required field", + "default": "cat", + "tooltipVisible": "true" + }, + "imagechoice-multi": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_multi", + "jcr:title": "Select Colors", + "orientation": "horizontal", + "selectionType": "multi", + "enum": [ + "red", + "green", + "blue" + ], + "type": "string[]", + "enumNames": [ + "Red", + "Green", + "Blue" + ], + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/green.png", + "/content/dam/colors/blue.png" + ], + "fieldType": "checkbox-group", + "default": ["red", "blue"] + }, + "imagechoice-no-images": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice_noimages", + "jcr:title": "No Images", + "orientation": "horizontal", + "selectionType": "single", + "enum": [ + "a", + "b" + ], + "enumNames": [ + "Option A", + "Option B" + ], + "fieldType": "radio-group", + "type": "string" + }, + "imagechoice-datalayer": { + "id": "imagechoice-bafbf1a102", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "fieldType": "radio-group", + "name": "FavoriteColor", + "jcr:title": "Favorite Color", + "description": "Choose your favorite color", + "visible": true, + "type": "string", + "enabled": true, + "readOnly": false, + "selectionType": "single", + "enforceEnum": true, + "enumNames": [ + "Red", + "Blue" + ], + "enum": [ + "red", + "blue" + ], + "imageSrc": [ + "/content/dam/colors/red.png", + "/content/dam/colors/blue.png" + ] + }, + "imagechoice-option-screenreader-label": { + "id": "imagechoice-1076f3bd737", + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "core/fd/components/form/imagechoice/v1/imagechoice", + "name": "imagechoice", + "isTitleRichText": "true", + "jcr:title": "Favorite Pet", + "orientation": "vertical", + "selectionType": "single", + "enum": [ + "0", + "1" + ], + "enumNames": [ + "

Cat

", + "

Dog

" + ], + "imageSrc": [ + "/content/dam/pets/cat.png", + "/content/dam/pets/dog.png" + ], + "fieldType": "radio-group" + } +} diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less new file mode 100644 index 0000000000..200ef93755 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/adaptive-form/imagechoice.less @@ -0,0 +1,52 @@ +.cmp-adaptiveform-imagechoice { + .containerMixin(); + .descriptionMixin(); + .validInvalidMixin(); + border: none; + margin: 0; + padding: 0; + min-inline-size: 0; + &__widget { + .itemWidgetMixin(); + display: flex; + flex-wrap: wrap; + gap: 12px; + &.horizontal { + flex-direction: row; + } + &.vertical { + flex-direction: column; + } + } + &__option { + position: relative; + } + &__option-label { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + border: 2px solid transparent; + border-radius: 4px; + padding: 8px; + &:hover { + border-color: @gray; + } + } + &__option-image { + display: block; + border-radius: 4px; + overflow: hidden; + } + &__image { + display: block; + max-width: 150px; + max-height: 120px; + object-fit: contain; + } + &__option-text { + margin-top: 4px; + text-align: center; + font-size: 14px; + } +} diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less index 2bf20e2945..c56f07bc92 100644 --- a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/clientlibs/forms-clientlib-site/styles/components/index.less @@ -26,6 +26,7 @@ @import 'adaptive-form/fileinput.less'; @import 'adaptive-form/footer.less'; @import 'adaptive-form/image.less'; +@import 'adaptive-form/imagechoice.less'; @import 'adaptive-form/numberinput.less'; @import 'adaptive-form/pageheader.less'; @import 'adaptive-form/panelcontainer.less'; diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml new file mode 100644 index 0000000000..8ebb6827ef --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/.content.xml @@ -0,0 +1,7 @@ + + diff --git a/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml new file mode 100644 index 0000000000..6e219079f0 --- /dev/null +++ b/examples/ui.apps/src/main/content/jcr_root/apps/forms-components-examples/components/form/imagechoice/_cq_template.xml @@ -0,0 +1,10 @@ + + diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml index 65ace20958..c381b90879 100644 --- a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/.content.xml @@ -25,6 +25,7 @@ + diff --git a/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml new file mode 100644 index 0000000000..80a04a0181 --- /dev/null +++ b/examples/ui.content/src/main/content/jcr_root/content/core-components-examples/library/adaptive-form/imagechoice/.content.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml new file mode 100755 index 0000000000..c5a8153a6a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/.content.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail new file mode 100755 index 0000000000..56194b1144 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml new file mode 100755 index 0000000000..d1b2c6467e --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/_jcr_content/folderThumbnail.dir/.content.xml @@ -0,0 +1,10 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml new file mode 100755 index 0000000000..cbbe8d1d5a --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..f352235916 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..5e894ec643 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..e1e7a92de4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..ec29a9990e Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..6ef2f919ef Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml new file mode 100755 index 0000000000..4e4d647468 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..5cc5f6305e Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..5276cf9428 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..95f13f648d Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..03ba56a2d1 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..6c21cce3cc Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/a1.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml new file mode 100755 index 0000000000..51a47e9bb5 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..ad96923225 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..bb09b96ae4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..99bc97b2ff Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..66d7f9c2a5 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..04b99954d4 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/c.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml new file mode 100755 index 0000000000..8b40a93643 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/.content.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..f544c5a987 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..7723550979 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..45484a2c18 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..2c236fa421 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original new file mode 100755 index 0000000000..fe516e38d2 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..61c8312d66 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/tennis_court.jpeg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml new file mode 100755 index 0000000000..faf851c875 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/.content.xml @@ -0,0 +1,59 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png new file mode 100755 index 0000000000..39e1c18842 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.140.100.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png new file mode 100755 index 0000000000..b6d685b9ed Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.319.319.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png new file mode 100755 index 0000000000..d025e7098c Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.thumbnail.48.48.png.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg new file mode 100755 index 0000000000..c2b6691dc1 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml new file mode 100755 index 0000000000..636a3c9d86 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/cq5dam.web.1280.1280.jpeg.dir/.content.xml @@ -0,0 +1,7 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original new file mode 100755 index 0000000000..a5621768e7 Binary files /dev/null and b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original differ diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml new file mode 100755 index 0000000000..1642d40408 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/images/thumbnail.jpg/_jcr_content/renditions/original.dir/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml new file mode 100755 index 0000000000..3f764b2959 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml new file mode 100755 index 0000000000..f3dc65db15 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/imagechoice/testimagechoice/.content.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/imagechoice/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/imagechoice/.content.xml new file mode 100755 index 0000000000..3edadd6c63 --- /dev/null +++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/imagechoice/.content.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editor/js/editDialog.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editor/js/editDialog.js old mode 100644 new mode 100755 index 2991e6074f..47ea71ccbf --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editor/js/editDialog.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editor/js/editDialog.js @@ -13,7 +13,179 @@ * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ -(function($, channel, Coral) { +(function($, Granite) { "use strict"; -})(jQuery, jQuery(document), Coral); \ No newline at end of file + var EDIT_DIALOG = ".cmp-adaptiveform-container__editdialog", + DOR_TYPE_SELECTOR = EDIT_DIALOG + " .cmp-adaptiveform-container__dortypeselector", + DOR_TYPE = EDIT_DIALOG + " .cmp-adaptiveform-container__dortype", + AUTO_GENERATE_DOR_PANEL = EDIT_DIALOG + " .cmp-adaptiveform-container__dordetails", + DOR_TEMPLATE_REF_FIELD = EDIT_DIALOG + " .cmp-adaptiveform-container__dortemplateref", + Utils = window.CQ.FormsCoreComponents.Utils.v1; + + // Constants for DOR selection types + var DOR_NONE = "none", + DOR_SELECT = "select", + DOR_GENERATE = "generate", + FM_AF_ROOT = "/content/forms/af/"; + + var DEFAULT_VALUE_MAP = { + "dorPath": "/content/dam/formsanddocuments", + "dorName": "auto-generated-dor", + "dorTitle": "Auto-generated Document of Record" + }; + + function manageDeleteFields(dialog, filedArray,shouldDelete) { + for (var fieldName of filedArray) { + var field = dialog.find('[name="./' + fieldName + '"]'); + var foundationField = null; + if (field.length !== 0) { + foundationField = field.adaptTo("foundation-field"); + } + var deleteField = dialog.find('input[name="./' + fieldName + '@Delete"]'); + if (shouldDelete) { + if (foundationField) { + foundationField.setValue(''); + foundationField.setRequired(false); + foundationField.setInvalid(false); + } + if (deleteField.length === 0) { + // Create the @Delete field if it doesn't exist + var deleteInput = $('', { + type: 'hidden', + name: './' + fieldName + '@Delete', + value: '' + }); + dialog.find('form').append(deleteInput); + } + } else { + deleteField.remove(); + foundationField.setRequired(true); + if (DEFAULT_VALUE_MAP[fieldName]) { + foundationField.setValue(DEFAULT_VALUE_MAP[fieldName]); + } + } + } + } + + function manageElementVisibility(dialog, elements, shouldShow) { + for (var element of elements) { + if (shouldShow) { + element.removeAttr('hidden'); + } else { + element.attr('hidden', true); + } + } + } + + function manageFDViewPayload(dialog, shouldDelete) { + var form = dialog.find('form'); + if (shouldDelete) { + form.find('input[name="./fd:view/print/jcr:created"]').remove(); + form.find('input[name="./fd:view/print/jcr:lastModified"]').remove(); + form.find('input[name="./fd:view/print/sling:resourceType"]').remove(); + form.find('input[name="./fd:view/print/metaTemplateRef"]').remove(); + } else { + form.append($('', { + type: 'hidden', + name: './fd:view/print/jcr:created', + value: '' + })); + form.append($('', { + type: 'hidden', + name: './fd:view/print/jcr:lastModified', + value: '' + })); + form.append($('', { + type: 'hidden', + name: './fd:view/print/sling:resourceType', + value: 'fd/af/authoring/components/dor/dorProperties' + })); + form.append($('', { + type: 'hidden', + name: './fd:view/print/metaTemplateRef', + value: '/libs/fd/af/dor/templates/defaultTemplate.xdp' + })); + } + } + + function isFormEditor(){ + return Granite.author.page.path.startsWith(FM_AF_ROOT); + } + + function handleDorTypeSelection(dialog) { + if (isFormEditor()) { + return; + } + var dorTypeSelector = dialog.find(DOR_TYPE_SELECTOR)[0]; + var dorTemplateRefField = dialog.find(DOR_TEMPLATE_REF_FIELD); + var autoGenerateDorPanel = dialog.find(AUTO_GENERATE_DOR_PANEL); + var dorType = dialog.find(DOR_TYPE)[0]; + + if (!dorTypeSelector && !dorType) { + return; + } + + // Initialize DOR type from stored value, default to "none" if not set + dorType.value = dorType.value ? dorType.value : DOR_NONE; + + + // Check the appropriate radio button + var radioButton = dorTypeSelector.querySelector('input[value="' + dorType.value + '"]'); + if (radioButton) { + radioButton.checked = true; + } + + var updateDorUIBasedOnSelection = function(event) { + if (isFormEditor()) { + return; + } + var selectedValue = event.target.value; + dorType.value = selectedValue; + var deleteFields = [], + persistFields = [], + showElements = [], + hideElements = []; + if (selectedValue === DOR_SELECT) { + showElements.push(dorTemplateRefField); + hideElements.push(autoGenerateDorPanel); + persistFields.push('dorTemplateRef'); + deleteFields.push('dorName', 'dorTitle', 'dorPath'); + manageFDViewPayload(dialog, true); + } else if (selectedValue === DOR_GENERATE) { + hideElements.push(dorTemplateRefField); + showElements.push(autoGenerateDorPanel); + persistFields.push('dorName', 'dorTitle', 'dorPath'); + deleteFields.push('dorTemplateRef'); + manageFDViewPayload(dialog, false); + } else { + // DOR_NONE or DOR_GENERATE in form editor + hideElements.push(dorTemplateRefField, autoGenerateDorPanel); + deleteFields.push('dorName', 'dorTitle', 'dorPath', 'dorTemplateRef'); + manageFDViewPayload(dialog, true); + } + + manageElementVisibility(dialog, showElements, true); + manageElementVisibility(dialog, hideElements, false); + manageDeleteFields(dialog, deleteFields, true); + manageDeleteFields(dialog, persistFields, false); + + setTimeout(function() { + var selectedTab = dialog.find('coral-tab.is-selected'); + if (selectedTab.length > 0) { + selectedTab[0].invalid = false; + } + }); + } + + // Handle DOR type selection change + dorTypeSelector.addEventListener("change", updateDorUIBasedOnSelection); + + // Initialize the UI based on the current selection + updateDorUIBasedOnSelection({target: {value: dorType.value}}); + + } + + Utils.initializeEditDialog(EDIT_DIALOG)(handleDorTypeSelection); + +})(jQuery, Granite); \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/.content.xml new file mode 100644 index 0000000000..c5fbe03577 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/.content.xml @@ -0,0 +1,18 @@ + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/.content.xml new file mode 100644 index 0000000000..645f734ab5 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/.content.xml @@ -0,0 +1,19 @@ + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/.content.xml new file mode 100644 index 0000000000..d8d2014c14 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/.content.xml @@ -0,0 +1,23 @@ + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/README.md b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/README.md new file mode 100644 index 0000000000..1cdb3354cd --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/README.md @@ -0,0 +1,105 @@ + +Adaptive Form Image Choice (v1) +==== +Adaptive Form Image Choice component written in HTL. Allows users to select one or more options represented as images. + +## Features + +* Provides image-based selection in the form of: + * radio buttons (single select) + * checkboxes (multi select) +* Orientation of options (`horizontal` or `vertical`) +* Custom constraint messages for the above types +* Styles + +### Use Object +The Form Image Choice component uses the `com.adobe.cq.forms.core.components.models.form.ImageChoice` Sling Model for its Use-object. + +### Edit Dialog Properties +The following properties are written to JCR for this Image Choice component and are expected to be available as `Resource` properties: + +1. `./jcr:title` - defines the label to use for this field +2. `./hideTitle` - if set to `true`, the label of this field will be hidden +3. `./name` - defines the name of the field, which will be submitted with the form data +4. `./default` - defines the default value of the field +5. `./description` - defines a help message that can be rendered in the field as a hint for the user +6. `./required` - if set to `true`, this field will be marked as required, not allowing the form to be submitted until the field has a value +7. `./requiredMessage` - defines the message displayed as tooltip when submitting the form if the value is left empty +8. `./readOnly` - if set to `true`, the field will be read only +9. `./type` - defines the data type of the value +10. `./enum` - defines the set of possible values for this field +11. `./enumNames` - defines the user-friendly text to display for the possible options of the field +12. `./imageSrc` - defines the image source paths for each option +13. `./fieldType` - defines the type of the component (`checkbox-group` for multi select, `radio-group` for single select) +14. `./orientation` - defines how options should be displayed, horizontally or vertically + +## Client Libraries +The component provides a `core.forms.components.imagechoice.v1.runtime` client library category that contains the Javascript runtime for the component. +It should be added to a relevant site client library using the `embed` property. + +It also provides a `core.forms.components.imagechoice.v1.editor` editor client library category that includes +JavaScript handling for dialog interaction. It is already included by its edit dialog. + +## BEM Description +``` +BLOCK cmp-adaptiveform-imagechoice + ELEMENT cmp-adaptiveform-imagechoice__label + ELEMENT cmp-adaptiveform-imagechoice__label-container + ELEMENT cmp-adaptiveform-imagechoice__widget + MODIFIER cmp-adaptiveform-imagechoice__widget.VERTICAL + ELEMENT cmp-adaptiveform-imagechoice__option + ELEMENT cmp-adaptiveform-imagechoice__option-label + ELEMENT cmp-adaptiveform-imagechoice__option__widget + ELEMENT cmp-adaptiveform-imagechoice__option-image + ELEMENT cmp-adaptiveform-imagechoice__image + ELEMENT cmp-adaptiveform-imagechoice__option-text + ELEMENT cmp-adaptiveform-imagechoice__questionmark + ELEMENT cmp-adaptiveform-imagechoice__shortdescription + ELEMENT cmp-adaptiveform-imagechoice__longdescription + ELEMENT cmp-adaptiveform-imagechoice__errormessage +``` + +### Note +By placing the class names `cmp-adaptiveform-imagechoice__label` and `cmp-adaptiveform-imagechoice__questionmark` within the `cmp-adaptiveform-imagechoice__label-container` class, you create a logical grouping of the label and question mark elements. This approach simplifies the process of maintaining a consistent styling for both elements. + +## JavaScript Data Attribute Bindings + +The following attributes must be added for the initialization of the image-choice component in the form view: +1. `data-cmp-is="adaptiveFormImageChoice"` +2. `data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}"` + +The following are optional attributes that can be added to the component in the form view: +1. `data-cmp-valid` having a boolean value to indicate whether the field is currently valid or not +2. `data-cmp-required` having a boolean value to indicate whether the field is currently required or not +3. `data-cmp-readonly` having a boolean value to indicate whether the field is currently readonly or not +4. `data-cmp-active` having a boolean value to indicate whether the field is currently active or not +5. `data-cmp-visible` having a boolean value to indicate whether the field is currently visible or not +6. `data-cmp-enabled` having a boolean value to indicate whether the field is currently enabled or not +7. `data-cmp-selection-type` having a value of `multi` or `single` to indicate the selection mode + +## Replace feature: +We support replace feature that allows replacing Image Choice component to any of the below components: + +* Check Box Group +* Radio Button +* Drop Down + +## Information +* **Vendor**: Adobe +* **Version**: v1 +* **Compatibility**: Cloud +* **Status**: production-ready diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_dialog/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_dialog/.content.xml new file mode 100644 index 0000000000..6ed857e2de --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_dialog/.content.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_styleConfig/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_styleConfig/.content.xml new file mode 100644 index 0000000000..ba5b51aa5c --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_styleConfig/.content.xml @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_template.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_template.xml new file mode 100644 index 0000000000..6e219079f0 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/_cq_template.xml @@ -0,0 +1,10 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/.content.xml new file mode 100644 index 0000000000..564d04a5c1 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/.content.xml @@ -0,0 +1,3 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/.content.xml new file mode 100644 index 0000000000..a16accb3d1 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/.content.xml @@ -0,0 +1,5 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js.txt new file mode 100644 index 0000000000..c00d18a634 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2026 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. +############################################################################### + +#base=js +editDialog.js diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js/editDialog.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js/editDialog.js new file mode 100644 index 0000000000..80c684bd93 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/editor/js/editDialog.js @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright 2026 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. + ******************************************************************************/ +(function($) { + "use strict"; + + var EDIT_DIALOG = ".cmp-adaptiveform-imagechoice__editdialog", + IMAGECHOICE_ASSISTPRIORITY = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__assistprioritycustom", + IMAGECHOICE_CUSTOMTEXT = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__customtext", + IMAGECHOICE_TYPE = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__type", + IMAGECHOICE_TYPE_HIDDEN = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__typehidden", + IMAGECHOICE_DEFAULTVALUE = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__value", + IMAGECHOICE_ENUM = EDIT_DIALOG + " .cmp-adaptiveform-base__enum", + IMAGECHOICE_SELECTION_TYPE = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__selectiontype", + IMAGECHOICE_FIELD_TYPE = EDIT_DIALOG + " .cmp-adaptiveform-imagechoice__fieldtype", + Utils = window.CQ.FormsCoreComponents.Utils.v1; + + /** + * Syncs fieldType and the hidden type field based on selectionType and the visible type dropdown. + * + * selectionType=single + type select "String" -> fieldType=radio-group, type=string + * selectionType=multi + type select "String" -> fieldType=checkbox-group, type=string[] + * selectionType=single + type select "Number" -> fieldType=radio-group, type=number + * selectionType=multi + type select "Number" -> fieldType=checkbox-group, type=number[] + */ + function syncSelectionDependentFields(dialog) { + var selectionTypeSelect = dialog.find(IMAGECHOICE_SELECTION_TYPE); + var fieldTypeHidden = dialog.find(IMAGECHOICE_FIELD_TYPE); + var typeSelect = dialog.find(IMAGECHOICE_TYPE); + var typeHidden = dialog.find(IMAGECHOICE_TYPE_HIDDEN); + + function getSelectionValue() { + if (selectionTypeSelect.length > 0 && selectionTypeSelect[0].selectedItem) { + return selectionTypeSelect[0].selectedItem.value; + } + return 'single'; + } + + function getBaseType() { + var val = ''; + if (typeSelect.length > 0 && typeSelect[0].selectedItem) { + val = typeSelect[0].selectedItem.value; + } + // always return base type without [] + return val.replace('[]', '') || 'string'; + } + + // On dialog open, set the visible type select to match the saved type (strip []) + function initTypeSelect() { + var savedType = typeHidden.val() || 'string'; + var baseType = savedType.replace('[]', ''); + if (typeSelect.length > 0) { + typeSelect[0].value = baseType; + } + } + + function update() { + var isMulti = getSelectionValue() === 'multi'; + var baseType = getBaseType(); + + // Update fieldType hidden + fieldTypeHidden.val(isMulti ? 'checkbox-group' : 'radio-group'); + + // Update type hidden: append [] for multi + typeHidden.val(isMulti ? baseType + '[]' : baseType); + } + + initTypeSelect(); + update(); + + dialog.on("change", IMAGECHOICE_SELECTION_TYPE, function() { + update(); + }); + dialog.on("change", IMAGECHOICE_TYPE, function() { + update(); + }); + } + + /** + * Shows custom text box depending on the value of assist priority + */ + function handleAssistPriorityChange(dialog) { + var assistpriority = dialog.find(IMAGECHOICE_ASSISTPRIORITY); + var customtext = dialog.find(IMAGECHOICE_CUSTOMTEXT); + var hideAndShowElements = function() { + if(assistpriority[0].value === "custom"){ + customtext.show(); + } else { + customtext.hide(); + } + }; + hideAndShowElements(); + dialog.on("change", assistpriority, function() { + hideAndShowElements(); + }); + } + + var registerDialogValidator = Utils.registerDialogDataTypeValidators( + IMAGECHOICE_DEFAULTVALUE, + IMAGECHOICE_ENUM, + function (dialog) { + var selectedValue = ''; + var imagechoiceSaveValue = dialog.find(IMAGECHOICE_TYPE); + if (imagechoiceSaveValue && imagechoiceSaveValue.length > 0) { + selectedValue = imagechoiceSaveValue[0].selectedItem ? imagechoiceSaveValue[0].selectedItem.value : ''; + } + return selectedValue.toLowerCase(); + } + ); + + Utils.initializeEditDialog(EDIT_DIALOG)(syncSelectionDependentFields, handleAssistPriorityChange, registerDialogValidator); + +})(jQuery); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/.content.xml new file mode 100644 index 0000000000..02865473c9 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css.txt new file mode 100644 index 0000000000..70e1f1d1d4 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2026 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. +############################################################################### + +#base=css +imagechoiceview.css diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css/imagechoiceview.css b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css/imagechoiceview.css new file mode 100644 index 0000000000..bf0be1d6f8 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/css/imagechoiceview.css @@ -0,0 +1,139 @@ +/* Reset fieldset defaults for semantic grouping */ +.cmp-adaptiveform-imagechoice { + border: none; + margin: 0; + padding: 0; + min-inline-size: 0; +} + +.cmp-adaptiveform-imagechoice__label-container { + padding: 0; +} + +.cmp-adaptiveform-imagechoice__label { +} + +.cmp-adaptiveform-imagechoice__widget { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.cmp-adaptiveform-imagechoice__widget.vertical { + flex-direction: column; +} + +.cmp-adaptiveform-imagechoice__option { + position: relative; +} + +.cmp-adaptiveform-imagechoice__option-label { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + border: 2px solid transparent; + border-radius: 4px; + padding: 8px; + transition: border-color 0.2s ease; +} + +@media (prefers-reduced-motion: reduce) { + .cmp-adaptiveform-imagechoice__option-label { + transition: none; + } +} + +.cmp-adaptiveform-imagechoice__option-label:hover { + border-color: #b3b3b3; +} + +/* Visually hidden but accessible to screen readers and keyboard navigation */ +.cmp-adaptiveform-imagechoice__option__widget { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* Selected state - highlight image and text */ +.cmp-adaptiveform-imagechoice__option__widget:checked + .cmp-adaptiveform-imagechoice__option-image { + outline: 3px solid #1473e6; + outline-offset: 2px; + border-radius: 4px; +} + +.cmp-adaptiveform-imagechoice__option__widget:checked ~ .cmp-adaptiveform-imagechoice__option-text { + color: #1473e6; + font-weight: 600; +} + +/* Focus visible - keyboard focus indicator */ +.cmp-adaptiveform-imagechoice__option__widget:focus-visible + .cmp-adaptiveform-imagechoice__option-image { + outline: 2px solid #1473e6; + outline-offset: 2px; + border-radius: 4px; +} + +/* Fallback for browsers without :focus-visible support */ +.cmp-adaptiveform-imagechoice__option__widget:focus:not(:focus-visible) + .cmp-adaptiveform-imagechoice__option-image { + outline: none; +} + +.cmp-adaptiveform-imagechoice__option__widget:focus + .cmp-adaptiveform-imagechoice__option-image { + outline: 2px solid #1473e6; + outline-offset: 2px; + border-radius: 4px; +} + +/* Combined checked + focus state */ +.cmp-adaptiveform-imagechoice__option__widget:checked:focus-visible + .cmp-adaptiveform-imagechoice__option-image { + outline: 3px solid #0d66d0; + outline-offset: 2px; + border-radius: 4px; +} + +.cmp-adaptiveform-imagechoice__option-image { + display: block; + border-radius: 4px; + overflow: hidden; +} + +.cmp-adaptiveform-imagechoice__image { + display: block; + max-width: 150px; + max-height: 120px; + object-fit: contain; +} + +.cmp-adaptiveform-imagechoice__option-text { + margin-top: 4px; + text-align: center; + font-size: 14px; +} + +/* Disabled state */ +.cmp-adaptiveform-imagechoice__option__widget:disabled + .cmp-adaptiveform-imagechoice__option-image { + opacity: 0.5; +} + +.cmp-adaptiveform-imagechoice__option__widget:disabled ~ .cmp-adaptiveform-imagechoice__option-text { + opacity: 0.5; +} + +.cmp-adaptiveform-imagechoice__longdescription { +} + +.cmp-adaptiveform-imagechoice__shortdescription { +} + +.cmp-adaptiveform-imagechoice__questionmark { +} + +.cmp-adaptiveform-imagechoice__errormessage { +} diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js.txt new file mode 100644 index 0000000000..ded54e9986 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2026 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. +############################################################################### + +#base=js +imagechoiceview.js diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js/imagechoiceview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js/imagechoiceview.js new file mode 100644 index 0000000000..63be7c472d --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/clientlibs/site/js/imagechoiceview.js @@ -0,0 +1,291 @@ +/******************************************************************************* + * Copyright 2026 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. + ******************************************************************************/ +(function() { + + "use strict"; + class ImageChoice extends FormView.FormOptionFieldBase { + + static NS = FormView.Constants.NS; + static IS = "adaptiveFormImageChoice"; + static bemBlock = 'cmp-adaptiveform-imagechoice'; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + widgets: `.${ImageChoice.bemBlock}__widget`, + widget: `.${ImageChoice.bemBlock}__option__widget`, + label: `.${ImageChoice.bemBlock}__label`, + description: `.${ImageChoice.bemBlock}__longdescription`, + qm: `.${ImageChoice.bemBlock}__questionmark`, + errorDiv: `.${ImageChoice.bemBlock}__errormessage`, + tooltipDiv: `.${ImageChoice.bemBlock}__shortdescription`, + option: `.${ImageChoice.bemBlock}__option`, + optionLabel: `.${ImageChoice.bemBlock}__option-label`, + optionImage: `.${ImageChoice.bemBlock}__option-image`, + optionText: `.${ImageChoice.bemBlock}__option-text` + }; + + constructor(params) { + super(params); + this.qm = this.element.querySelector(ImageChoice.selectors.qm); + this._selectionType = this.element.getAttribute('data-cmp-selection-type') || 'single'; + } + + get isMultiSelect() { + return this._selectionType === 'multi'; + } + + getWidgets() { + return this.element.querySelector(ImageChoice.selectors.widgets); + } + + getWidget() { + return this.element.querySelectorAll(ImageChoice.selectors.widget); + } + + getDescription() { + return this.element.querySelector(ImageChoice.selectors.description); + } + + getLabel() { + return this.element.querySelector(ImageChoice.selectors.label); + } + + getQuestionMarkDiv() { + return this.element.querySelector(ImageChoice.selectors.qm); + } + + getTooltipDiv() { + return this.element.querySelector(ImageChoice.selectors.tooltipDiv); + } + + getErrorDiv() { + return this.element.querySelector(ImageChoice.selectors.errorDiv); + } + + getOptions() { + return this.element.querySelectorAll(ImageChoice.selectors.option); + } + + #addWidgetListeners(optionWidget) { + optionWidget.addEventListener('change', (e) => { + if (this.isMultiSelect) { + this.#updateMultiValue(); + } else { + this.setModelValue(e.target.value); + } + }); + optionWidget.addEventListener('focus', (e) => { + this.setActive(); + }); + optionWidget.addEventListener('blur', (e) => { + this.setInactive(); + }); + } + + setModel(model) { + super.setModel(model); + this.widget.forEach(optionWidget => { + this.#addWidgetListeners(optionWidget); + }); + if (this.isMultiSelect) { + this.#updateMultiValue(); + } + } + + #updateMultiValue() { + let value = []; + this.widget.forEach(widget => { + if (widget.checked) { + value.push(widget.value); + } + }, this); + if (value.length !== 0 || this._model.value != null) { + this.setModelValue(value); + } + } + + updateEnabled(enabled, state) { + this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_ENABLED, enabled); + this.widget.forEach(widget => { + if (enabled === false) { + if (state.readOnly === false) { + widget.setAttribute(FormView.Constants.HTML_ATTRS.DISABLED, "disabled"); + } + } else if (state.readOnly === false) { + widget.removeAttribute(FormView.Constants.HTML_ATTRS.DISABLED); + } + }); + } + + updateReadOnly(readonly) { + this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_READONLY, readonly); + this.widget.forEach(widget => { + if (readonly === true) { + widget.setAttribute(FormView.Constants.HTML_ATTRS.DISABLED, "disabled"); + widget.setAttribute("aria-readonly", true); + } else { + widget.removeAttribute(FormView.Constants.HTML_ATTRS.DISABLED); + widget.removeAttribute("aria-readonly"); + } + }); + } + + updateValidity(validity) { + if (validity.valid === undefined) { + this.element.removeAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID); + this.widget.forEach(widget => widget.removeAttribute(FormView.Constants.ARIA_INVALID)); + } else { + this.element.setAttribute(FormView.Constants.DATA_ATTRIBUTE_VALID, validity.valid); + this.widget.forEach(widget => widget.setAttribute(FormView.Constants.ARIA_INVALID, !validity.valid)); + } + } + + updateValue(modelValue) { + if (this.isMultiSelect) { + modelValue = [].concat(modelValue); + let selectedWidgetValues = modelValue.map(String); + this.widget.forEach(widget => { + if (selectedWidgetValues.includes(widget.value)) { + widget.checked = true; + widget.setAttribute(FormView.Constants.HTML_ATTRS.CHECKED, FormView.Constants.HTML_ATTRS.CHECKED); + } else { + widget.checked = false; + widget.removeAttribute(FormView.Constants.HTML_ATTRS.CHECKED); + } + }, this); + } else { + this.widget.forEach(widget => { + if (modelValue != null && widget.value != null && (modelValue.toString() == widget.value.toString())) { + widget.checked = true; + widget.setAttribute(FormView.Constants.HTML_ATTRS.CHECKED, FormView.Constants.HTML_ATTRS.CHECKED); + } else { + widget.checked = false; + widget.removeAttribute(FormView.Constants.HTML_ATTRS.CHECKED); + } + }, this); + } + super.updateEmptyStatus(); + } + + #createOption(value, itemLabel) { + let inputType = this.isMultiSelect ? 'checkbox' : 'radio'; + let richScreenReaderText = `${this._model.label.value}: ${itemLabel}`; + let plainScreenReaderText = window.DOMPurify ? window.DOMPurify.sanitize(richScreenReaderText, { ALLOWED_TAGS: [] }) : richScreenReaderText; + + // Look up image by finding the index of this value in the model's enum array + let imgHtml = ''; + let imageSrcArray = this._model.imageSrc; + let enumArray = this._model.enum; + if (imageSrcArray && enumArray) { + let idx = enumArray.indexOf(value); + if (idx === -1) { + // try string comparison + idx = enumArray.findIndex(e => String(e) === String(value)); + } + if (idx >= 0 && imageSrcArray[idx]) { + let altText = window.DOMPurify ? window.DOMPurify.sanitize(itemLabel, { ALLOWED_TAGS: [] }) : itemLabel; + imgHtml = `${altText}`; + } + } + + const optionTemplate = ` +
+ +
`; + + const container = document.createElement('div'); + container.innerHTML = optionTemplate; + let addedOptionWidget = container.querySelector(ImageChoice.selectors.widget); + if (this._model.readOnly === true || this._model.enabled === false) { + addedOptionWidget.setAttribute("disabled", true); + if (this._model.readOnly === true) { + addedOptionWidget.setAttribute("aria-readonly", true); + } + } + this.#addWidgetListeners(addedOptionWidget); + return container.firstElementChild; + } + + updateEnum(newEnums) { + super.updateEnumForRadioButtonAndCheckbox(newEnums, this.#createOption); + this.widget = this.getWidget(); + } + + updateEnumNames(newEnumNames) { + // Override parent behavior: parent's updateEnumNamesForRadioButtonAndCheckbox + // does option.querySelector('span') which grabs the __option-image span (first span) + // and overwrites its innerHTML with text, destroying the . + // Instead, target the __option-text span specifically. + let options = [...this.getOptions()]; + if (options.length === 0) { + newEnumNames.forEach((value) => { + this.getWidgets().appendChild(this.#createOption(value, value)); + }); + } else { + options.forEach((option, index) => { + let textSpan = option.querySelector(ImageChoice.selectors.optionText); + let input = option.querySelector(ImageChoice.selectors.widget); + if (index < newEnumNames.length) { + let purifiedValue = window.DOMPurify ? window.DOMPurify.sanitize(newEnumNames[index]) : newEnumNames[index]; + if (textSpan) { + textSpan.innerHTML = purifiedValue; + } + if (input && input.hasAttribute("aria-label")) { + let richScreenReaderText = `${this._model.label.value}: ${purifiedValue}`; + let plainScreenReaderText = window.DOMPurify ? window.DOMPurify.sanitize(richScreenReaderText, { ALLOWED_TAGS: [] }) : richScreenReaderText; + input.setAttribute("aria-label", plainScreenReaderText); + } + } + }); + } + this.widget = this.getWidget(); + } + + updateRequired(required, state) { + if (this.widget) { + this.element.toggleAttribute("required", required); + this.element.setAttribute("data-cmp-required", required); + this.element.setAttribute("aria-required", required); + let widgetsContainer = this.getWidgets(); + if (widgetsContainer) { + widgetsContainer.setAttribute("aria-required", required); + } + } + } + + syncMarkupWithModel() { + // Do NOT call super.syncMarkupWithModel() which rebuilds options + // from enum/enumNames and destroys the server-rendered images. + // Only sync widget names with the model. + this.#syncWidgetName(); + } + + #syncWidgetName() { + const name = this.getModel()?.name; + this.widget.forEach(widget => { + widget.setAttribute("name", `${this.id}_${name}`); + }); + } + } + + FormView.Utils.setupField(({element, formContainer}) => { + return new ImageChoice({element, formContainer}); + }, ImageChoice.selectors.self); + +})(); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.html new file mode 100644 index 0000000000..9229a125ba --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.html @@ -0,0 +1,48 @@ + + + +
+ + + + +
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.js new file mode 100644 index 0000000000..555dccfece --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/imagechoice.js @@ -0,0 +1,35 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2026 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. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +use(function() { + + var clientlibsArr = [ 'core.forms.components.base.v1.editor' ]; + var labelPath = 'core/fd/components/af-commons/v1/fieldTemplates/label.html'; + var shortDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/shortDescription.html"; + var longDescriptionPath = "core/fd/components/af-commons/v1/fieldTemplates/longDescription.html"; + var questionMarkPath = "core/fd/components/af-commons/v1/fieldTemplates/questionMark.html" + var errorMessagePath = "core/fd/components/af-commons/v1/fieldTemplates/errorMessage.html"; + var legendPath = "core/fd/components/af-commons/v1/fieldTemplates/legend.html"; + return { + labelPath : labelPath, + shortDescriptionPath : shortDescriptionPath, + longDescriptionPath : longDescriptionPath, + questionMarkPath : questionMarkPath, + errorMessagePath : errorMessagePath, + legendPath : legendPath, + clientlibs : clientlibsArr + } +}); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/widget.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/widget.html new file mode 100644 index 0000000000..84ed64de45 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/imagechoice/v1/imagechoice/widget.html @@ -0,0 +1,45 @@ + + diff --git a/ui.tests/test-module/libs/commons/formsConstants.js b/ui.tests/test-module/libs/commons/formsConstants.js index e32138ba4a..0948d73fa5 100644 --- a/ui.tests/test-module/libs/commons/formsConstants.js +++ b/ui.tests/test-module/libs/commons/formsConstants.js @@ -40,6 +40,7 @@ var formsConstants = { "formdropdown": "/apps/forms-components-examples/components/form/dropdown", "formbutton": "/apps/forms-components-examples/components/form/button", "formimage": "/apps/forms-components-examples/components/form/image", + "formimagechoice": "/apps/forms-components-examples/components/form/imagechoice", "formradiobutton": "/apps/forms-components-examples/components/form/radiobutton", "formfileinput": "/apps/forms-components-examples/components/form/fileinput", "wizard": "/apps/forms-components-examples/components/form/wizard", diff --git a/ui.tests/test-module/specs/imagechoice/imagechoice.authoring.cy.js b/ui.tests/test-module/specs/imagechoice/imagechoice.authoring.cy.js new file mode 100644 index 0000000000..76bb69ae93 --- /dev/null +++ b/ui.tests/test-module/specs/imagechoice/imagechoice.authoring.cy.js @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright 2026 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. + ******************************************************************************/ + +const sitesSelectors = require('../../libs/commons/sitesSelectors'), + afConstants = require('../../libs/commons/formsConstants'); + +/** + * Testing ImageChoice with Sites Editor + */ +describe('Page - Authoring', function () { + + const dropImageChoiceInContainer = function() { + const dataPath = "/content/forms/af/core-components-it/blank/jcr:content/guideContainer/*", + responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']"; + cy.selectLayer("Edit"); + cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Image Choice", afConstants.components.forms.resourceType.formimagechoice); + cy.get('body').click(0, 0); + } + + const dropImageChoiceInSites = function() { + const dataPath = "/content/core-components-examples/library/adaptive-form/imagechoice/jcr:content/root/responsivegrid/demo/component/guideContainer/*", + responsiveGridDropZoneSelector = sitesSelectors.overlays.overlay.component + "[data-path='" + dataPath + "']"; + cy.selectLayer("Edit"); + cy.insertComponent(responsiveGridDropZoneSelector, "Adaptive Form Image Choice", afConstants.components.forms.resourceType.formimagechoice); + cy.get('body').click(0, 0); + } + + const getPreviewIframeBody = () => { + return cy + .get('iframe#ContentFrame') + .its('0.contentDocument.body').should('not.be.empty') + .then(cy.wrap) + } + + const testImageChoiceBehaviour = function(imageChoiceEditPathSelector, imageChoiceDrop, isSites) { + if (isSites) { + dropImageChoiceInSites(); + } else { + dropImageChoiceInContainer(); + } + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + imageChoiceEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + + // Check If Dialog Options Are Visible + cy.get("[name='./selectionType']") + .should("exist"); + cy.get("[name='./type']") + .should("exist"); + cy.get("[name='./enum']") + .should("exist"); + cy.get("[name='./enumNames']") + .should("exist"); + + // Image source multifield should exist + cy.get(".cmp-adaptiveform-imagechoice__imagesrc").should("exist"); + + // Orientation options should exist + cy.get('input[name="./orientation"][value="horizontal"]').should("exist"); + cy.get('input[name="./orientation"][value="vertical"]').should("exist"); + + // Checking some dynamic behaviours + cy.get("[data-granite-coral-multifield-name='./enum'] coral-button-label:contains('Add')").should("exist"); + + // verifying prefill data is working for enum and enumNames + cy.get('input[name="./enum"]').should('have.length', 2); + cy.get('input[name="./enum"]').first().should('have.value', 0); + cy.get('input[name="./enumNames"]').should('have.length', 2); + cy.get('input[name="./enumNames"]').first().should('have.value', "Option 1"); + + // selecting number as data type + cy.get('.cmp-adaptiveform-imagechoice__type').should("exist").click(); + cy.get('coral-selectlist-item[value="number"]').should("exist").click(); + + // selecting vertical alignment as orientation + cy.get('input[name="./orientation"][value="vertical"]').should("exist").click(); + + // saving the dialog with changes + cy.get('.cq-dialog-submit').click(); + + // verifying alignment change in preview editor + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice__widget.vertical').should('have.length', 1); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice__option').should('have.length', 2); + + cy.deleteComponentByPath(imageChoiceDrop); + } + + context('Open Forms Editor', function() { + const pagePath = "/content/forms/af/core-components-it/blank", + imageChoicePath = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/imagechoice", + imageChoiceEditPathSelector = "[data-path='" + imageChoicePath + "']", + imageChoiceDrop = pagePath + afConstants.FORM_EDITOR_FORM_CONTAINER_SUFFIX + "/" + afConstants.components.forms.resourceType.formimagechoice.split("/").pop(); + + beforeEach(function () { + cy.openAuthoring(pagePath); + }); + + it('insert ImageChoice in form container', function () { + cy.cleanTest(imageChoiceDrop).then(function() { + dropImageChoiceInContainer(); + cy.deleteComponentByPath(imageChoiceDrop); + }); + }); + + it('open edit dialog of ImageChoice', { retries: 3 }, function () { + cy.cleanTest(imageChoiceDrop).then(function() { + testImageChoiceBehaviour(imageChoiceEditPathSelector, imageChoiceDrop); + }); + }); + + it('verify selection type toggle between single and multi', { retries: 3 }, function () { + cy.cleanTest(imageChoiceDrop).then(function() { + dropImageChoiceInContainer(); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + imageChoiceEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + + // default should be single select + cy.get('.cmp-adaptiveform-imagechoice__selectiontype').should("exist"); + + // switch to multi select + cy.get('.cmp-adaptiveform-imagechoice__selectiontype').click(); + cy.get('coral-selectlist-item[value="multi"]').click(); + + cy.get('.cq-dialog-submit').click(); + + // verify multi select renders checkboxes + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice').should('exist'); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice') + .invoke('attr', 'data-cmp-selection-type').should('eq', 'multi'); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice input[type="checkbox"]').should('have.length', 2); + + cy.deleteComponentByPath(imageChoiceDrop); + }); + }); + + it('verify image source multifield in dialog', { retries: 3 }, function () { + cy.cleanTest(imageChoiceDrop).then(function() { + dropImageChoiceInContainer(); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + imageChoiceEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + + // image source multifield should be present + cy.get(".cmp-adaptiveform-imagechoice__imagesrc").should("exist"); + // add an image source entry + cy.get(".cmp-adaptiveform-imagechoice__imagesrc coral-button-label:contains('Add')").should("exist").click({ force: true }); + cy.get('.cmp-adaptiveform-imagechoice__imagesrc input[name="./imageSrc"]').should('have.length.at.least', 1); + + cy.get('.cq-dialog-cancel').click(); + cy.deleteComponentByPath(imageChoiceDrop); + }); + }); + + it('check for duplicate enum values', { retries: 3 }, function () { + cy.cleanTest(imageChoiceDrop).then(function() { + dropImageChoiceInContainer(); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + imageChoiceEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + + cy.get("[data-granite-coral-multifield-name='./enum'] coral-button-label:contains('Add')").should("exist").click({ force: true }); + cy.get('input[name="./enum"]').last().invoke('val', '0'); + cy.get('input[name="./enumNames"]').last().invoke('val', 'Item 3'); + cy.get('.cq-dialog-submit').click().then(() => { + cy.get('.cq-dialog-submit').should('not.exist') + }); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice__option').should('have.length', 2); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice').parent().parent().contains('Item 3'); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice').parent().parent().contains('Option 2'); + getPreviewIframeBody().find('.cmp-adaptiveform-imagechoice').parent().parent().contains('Option 1').should('not.exist'); + cy.deleteComponentByPath(imageChoiceDrop); + }); + }); + }); + + context('Open Sites Editor', function () { + const pagePath = "/content/core-components-examples/library/adaptive-form/imagechoice", + imageChoiceEditPath = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + "/guideContainer/imagechoice", + imageChoiceEditPathSelector = "[data-path='" + imageChoiceEditPath + "']", + imageChoiceDrop = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + '/guideContainer/' + afConstants.components.forms.resourceType.formimagechoice.split("/").pop(); + + beforeEach(function () { + cy.openAuthoring(pagePath); + }); + + it('insert aem forms ImageChoice', function () { + dropImageChoiceInSites(); + cy.deleteComponentByPath(imageChoiceDrop); + }); + + }); +}); diff --git a/ui.tests/test-module/specs/imagechoice/imagechoice.runtime.cy.js b/ui.tests/test-module/specs/imagechoice/imagechoice.runtime.cy.js new file mode 100644 index 0000000000..b0763e17fd --- /dev/null +++ b/ui.tests/test-module/specs/imagechoice/imagechoice.runtime.cy.js @@ -0,0 +1,265 @@ +/******************************************************************************* + * Copyright 2026 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. + ******************************************************************************/ +describe("Form Runtime with ImageChoice", () => { + + const pagePath = "content/forms/af/core-components-it/samples/imagechoice/testimagechoice.html" + const bemBlock = 'cmp-adaptiveform-imagechoice' + const IS = "adaptiveFormImageChoice" + const selectors = { + imagechoice: `[data-cmp-is="${IS}"]` + } + + let formContainer = null + + /** + * Helper to get field by model children index (guarantees model order). + * Object.entries(formContainer._fields) does NOT guarantee insertion order + * matches model item order, so we use _model._children instead. + */ + const getField = (index) => { + const child = formContainer._model._children[index]; + return [child.id, formContainer._fields[child.id]]; + }; + + beforeEach(() => { + cy.previewForm(pagePath).then(p => { + formContainer = p; + }) + }); + + const checkHTML = (id, state, inputCount) => { + const visible = state.visible; + const passVisibleCheck = `${visible === true ? "" : "not."}be.visible`; + const passDisabledAttributeCheck = `${state.enabled === false || state.readOnly === true ? "" : "not."}have.attr`; + cy.get(`#${id}`) + .should(passVisibleCheck) + .invoke('attr', 'data-cmp-visible') + .should('eq', visible.toString()); + cy.get(`#${id}`) + .invoke('attr', 'data-cmp-enabled') + .should('eq', state.enabled.toString()); + return cy.get(`#${id}`).within(() => { + cy.get('*').should(passVisibleCheck) + cy.get('input') + .should('have.length', inputCount) + cy.get('input') + .should(passDisabledAttributeCheck, 'disabled'); + }) + } + + it("should get model and view initialized properly", () => { + expect(formContainer, "formcontainer is initialized").to.not.be.null; + expect(formContainer._model.items.length, "model and view elements match").to.equal(Object.keys(formContainer._fields).length); + Object.entries(formContainer._fields).forEach(([id, field]) => { + expect(field.getId()).to.equal(id) + expect(formContainer._model.getElement(id), `model and view are in sync`).to.equal(field.getModel()) + }); + }) + + it("multi-select imagechoice should render with horizontal orientation and checkbox inputs", () => { + const [id, fieldView] = getField(0) + cy.get(`#${id}`).find(`.${bemBlock}__widget`).should('have.class', 'horizontal') + cy.get(`#${id}`).invoke('attr', 'data-cmp-selection-type').should('eq', 'multi') + cy.get(`#${id}`).find('input[type="checkbox"]').should('have.length', 3) + }) + + it("single-select imagechoice should render with vertical orientation and radio inputs", () => { + const [id, fieldView] = getField(1) + cy.get(`#${id}`).find(`.${bemBlock}__widget`).should('have.class', 'vertical') + cy.get(`#${id}`).invoke('attr', 'data-cmp-selection-type').should('eq', 'single') + cy.get(`#${id}`).find('input[type="radio"]').should('have.length', 2) + }) + + it("should render images for each option", () => { + const [id, fieldView] = getField(0) + cy.get(`#${id}`).find(`.${bemBlock}__image`).should('have.length', 3) + cy.get(`#${id}`).find(`.${bemBlock}__image`).each(($img) => { + cy.wrap($img).should('have.attr', 'src').and('not.be.empty') + }) + }) + + it("should render option text for each option", () => { + const [id, fieldView] = getField(0) + cy.get(`#${id}`).find(`.${bemBlock}__option-text`).should('have.length', 3) + cy.get(`#${id}`).find(`.${bemBlock}__option-text`).eq(0).should('contain.text', 'a') + cy.get(`#${id}`).find(`.${bemBlock}__option-text`).eq(1).should('contain.text', 'b') + cy.get(`#${id}`).find(`.${bemBlock}__option-text`).eq(2).should('contain.text', 'c') + }) + + it("model's changes are reflected in the html for multi-select", () => { + const [id, fieldView] = getField(0) + const model = formContainer._model.getElement(id) + + model.value = 1 + checkHTML(model.id, model.getState(), 3).then(() => { + cy.get(`#${id}`).find('input').eq(0).should('be.checked') + cy.get(`#${id}`).find('input').eq(1).should('not.be.checked') + cy.get(`#${id}`).find('input').eq(2).should('not.be.checked') + }).then(() => { + model.visible = false + return checkHTML(model.id, model.getState(), 3) + }).then(() => { + model.enable = false + return checkHTML(model.id, model.getState(), 3) + }) + }) + + it("model's changes are reflected in the html for single-select", () => { + const [id, fieldView] = getField(1) + const model = formContainer._model.getElement(id) + + model.value = 'a' + checkHTML(model.id, model.getState(), 2).then(() => { + cy.get(`#${id}`).find('input').eq(0).should('be.checked') + cy.get(`#${id}`).find('input').eq(1).should('not.be.checked') + }).then(() => { + model.value = 'b' + cy.get(`#${id}`).find('input').eq(0).should('not.be.checked') + cy.get(`#${id}`).find('input').eq(1).should('be.checked') + }) + }) + + it("html changes are reflected in model for multi-select", () => { + const [id, fieldView] = getField(0) + const model = formContainer._model.getElement(id) + + cy.get(`#${id}`).find("input").eq(0).click({force: true}).then(() => { + expect(model.getState().value).to.deep.include(1); + }) + + cy.get(`#${id}`).find("input").eq(2).click({force: true}).then(() => { + const val = model.getState().value; + expect(val).to.deep.include(1); + expect(val).to.deep.include(3); + }) + + cy.get(`#${id}`).find("input").eq(0).click({force: true}).then(() => { + const val = model.getState().value; + expect(val).to.not.deep.include(1); + expect(val).to.deep.include(3); + }) + }) + + it("html changes are reflected in model for single-select", () => { + const [id, fieldView] = getField(1) + const model = formContainer._model.getElement(id) + + cy.get(`#${id}`).find("input").eq(0).click({force: true}).then(() => { + expect(model.getState().value).to.equal('a'); + }) + + cy.get(`#${id}`).find("input").eq(1).click({force: true}).then(() => { + expect(model.getState().value).to.equal('b'); + }) + }) + + it("should toggle enabled/disabled state via model", () => { + const [id, fieldView] = getField(0) + const model = formContainer._model.getElement(id) + + model.enabled = false + cy.get(`#${id}`).invoke('attr', 'data-cmp-enabled').should('eq', 'false') + cy.get(`#${id}`).find('input').should('have.attr', 'disabled') + + cy.wrap(null).then(() => { + model.enabled = true + }) + cy.get(`#${id}`).invoke('attr', 'data-cmp-enabled').should('eq', 'true') + cy.get(`#${id}`).find('input').should('not.have.attr', 'disabled') + }) + + it("should toggle readOnly state via model", () => { + const [id, fieldView] = getField(0) + const model = formContainer._model.getElement(id) + + model.readOnly = true + cy.get(`#${id}`).invoke('attr', 'data-cmp-readonly').should('eq', 'true') + cy.get(`#${id}`).find('input').should('have.attr', 'disabled') + + cy.wrap(null).then(() => { + model.readOnly = false + }) + cy.get(`#${id}`).invoke('attr', 'data-cmp-readonly').should('eq', 'false') + cy.get(`#${id}`).find('input').should('not.have.attr', 'disabled') + }) + + it("should toggle visibility via model", () => { + const [id, fieldView] = getField(0) + const model = formContainer._model.getElement(id) + + model.visible = false + cy.get(`#${id}`).should('not.be.visible') + cy.get(`#${id}`).invoke('attr', 'data-cmp-visible').should('eq', 'false') + + cy.wrap(null).then(() => { + model.visible = true + }) + cy.get(`#${id}`).should('be.visible') + cy.get(`#${id}`).invoke('attr', 'data-cmp-visible').should('eq', 'true') + }) + + it("should add filled/empty class at container div for multi-select", () => { + const [id, fieldView] = getField(0) + + cy.get(`#${id}`).should('have.class', `${bemBlock}--empty`) + cy.get(`#${id}`).find("input").eq(0).click({force: true}).then(() => { + cy.get(`#${id}`).should('have.class', `${bemBlock}--filled`) + }) + }) + + it("should add filled/empty class at container div for single-select", () => { + const [id, fieldView] = getField(1) + + cy.get(`#${id}`).should('have.class', `${bemBlock}--empty`) + cy.get(`#${id}`).find("input").eq(0).click({force: true}).then(() => { + cy.get(`#${id}`).should('have.class', `${bemBlock}--filled`) + }) + }) + + it("reset button should clear multi-select imagechoice values", () => { + const [multiId, multiFieldView] = getField(0) + const [resetId, resetFieldView] = getField(2) + + cy.get(`#${multiId}`).find("input").eq(0).click({force: true}) + cy.get(`#${multiId}`).find("input").eq(1).click({force: true}) + cy.get(`#${multiId}`).find("input").eq(0).should('be.checked') + cy.get(`#${multiId}`).find("input").eq(1).should('be.checked') + + cy.get(`#${resetId} button`).click().then(() => { + cy.get(`#${multiId}`).find("input").should('not.be.checked') + }) + }) + + it("reset button should clear single-select imagechoice value", () => { + const [singleId, singleFieldView] = getField(1) + const [resetId, resetFieldView] = getField(2) + + cy.get(`#${singleId}`).find("input").eq(0).click({force: true}) + cy.get(`#${singleId}`).find("input").eq(0).should('be.checked') + + cy.get(`#${resetId} button`).click().then(() => { + cy.get(`#${singleId}`).find("input").should('not.be.checked') + }) + }) + + it("decoration element should not have same class name", () => { + expect(formContainer, "formcontainer is initialized").to.not.be.null; + cy.wrap().then(() => { + const id = formContainer._model._children[0].id; + cy.get(`#${id}`).parent().should("not.have.class", bemBlock); + }) + }) +})