AEM Dialog Show/Hide with Multiple Dropdown Values and Custom Validation — Issue with Image Field Not Validating
Hello AEM Community,
I'm working on a component dialog in AEM where a dropdown drives visibility of field containers using a custom JS-based show/hide logic that supports multiple values per target.
Goal / Functionality:
A select dropdown has 4 options: Images, Articles, Posts, and Videos
Images, Articles, and Posts share a common image upload field
Videos shows a separate video path and poster field
Both single and multifield dropdowns are supported
Validation is handled properly — hidden required fields are not submitted, and visible fields are validated correctly
Issue:
While the custom JS handles visibility and validation well for the video path field, I’ve noticed that the image field (used in the shared container) allows submission even when required and empty.
Oddly enough, the video path field is correctly validated.
Dialog XML Snippet:
xml
Copy
Edit
<tab
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Select Show Tab"
granite:class="cq-dialog-dropdown-showhide"
defaultValue="about"
name="./maintab">
<granite:data
jcr:primaryType="nt:unstructured"
cq-dialog-dropdown-showhide-target=".connect-layout-show-hide-main"/>
<items jcr:primaryType="nt:unstructured">
<images jcr:primaryType="nt:unstructured" text="Images" value="images"/>
<articles jcr:primaryType="nt:unstructured" text="Articles" value="articles"/>
<posts jcr:primaryType="nt:unstructured" text="Posts" value="posts"/>
<videos jcr:primaryType="nt:unstructured" text="Videos" value="videos"/>
</items>
</tab>
<main-video-container
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
granite:class="hide connect-layout-show-hide-main">
<granite:data
jcr:primaryType="nt:unstructured"
showhidetargetvalue="videos"
validation-ignore="{Boolean}true"/>
<items jcr:primaryType="nt:unstructured">
<videosrc
granite:class="mybrowser video"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Video Path"
name="./mainVideoSrc"
required="{Boolean}true">
<granite:data jcr:primaryType="nt:unstructured" assettype="*" mimeType="*" mode="single" rootpath="/content/dam"/>
</videosrc>
<video-poster
granite:class="mybrowser"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Video Poster"
name="./mainVideoPoster">
<granite:data jcr:primaryType="nt:unstructured" assettype="images" mimeType="image/*" mode="single" rootpath="/content/dam"/>
</video-poster>
</items>
</main-video-container>
<main-image-container
sling:resourceType="granite/ui/components/coral/foundation/container"
granite:class="hide connect-layout-show-hide-main">
<granite:data
jcr:primaryType="nt:unstructured"
showhidetargetvalue="images,articles,posts"
validation-ignore="{Boolean}true"/>
<items jcr:primaryType="nt:unstructured">
<imagepath
granite:class="assetbrowser"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Image"
name="./mainimagepath"
required="{Boolean}true">
<granite:data
jcr:primaryType="nt:unstructured"
assettype="images"
mimeType="image/*"
mode="single"
rootpath="/content/dam"/>
</imagepath>
</items>
</main-image-container>
Custom Show/Hide + Validation JS
javascript
Copy
Edit
(function(document, $) {
"use strict";
$(document).on("foundation-contentloaded", function(e) {
Coral.commons.ready(function() {
showHideHandler($(".cq-dialog-dropdown-showhide", e.target));
showHideMultifieldHandler($(".cq-dialog-dropdown-showhide-multival", e.target));
});
setTimeout(function() {
$(".cq-dialog-dropdown-showhide-multival").each(function() {
showHideMultifieldHandler($(this));
});
}, 100);
});
$(document).on("change", ".cq-dialog-dropdown-showhide", function() {
if ($(this).closest('.coral3-Multifield').length > 0) {
showHideHandler($(this));
}
});
$(document).on("selected", ".cq-dialog-dropdown-showhide-multival", function(e) {
showHideMultifieldHandler($(this));
});
$(document).on("click", ".cq-dialog-submit", function(e) {
$(".hide").find("[required]").each(function() {
var $field = $(this);
if (!$field.attr("data-temp-submission-required")) {
$field.attr("data-temp-submission-required", "true");
$field.removeAttr("required");
$field.removeAttr("aria-required");
}
});
$(".hide input, .hide coral-textfield").each(function() {
if ($(this).attr("type") !== "hidden") {
clearFieldValue($(this));
}
});
});
function showHideHandler(el) {
el.each(function(i, element) {
if ($(element).is("coral-select")) {
Coral.commons.ready(element, function(component) {
multiSelectShowHide(element);
component.on("change", function() {
multiSelectShowHide(element);
});
});
}
});
}
function multiSelectShowHide(element) {
let target = $(element).data("cqDialogDropdownShowhideTarget");
let $target = $(target);
let selectedValue = element.value;
if (target) {
$target.each(function() {
let $currentTarget = $(this);
let targetValues = $currentTarget.data('showhidetargetvalue');
if (targetValues) {
let shouldShow = targetValues.replace(/ /g, '').split(',').includes(selectedValue);
if (shouldShow) {
$currentTarget.removeClass('hide');
restoreRequiredFields($currentTarget);
} else {
$currentTarget.addClass('hide');
storeAndRemoveRequiredFields($currentTarget);
}
}
});
}
}
function showHideMultifieldHandler(el) {
el.each(function(i, element) {
if ($(element).is("coral-select")) {
Coral.commons.ready(element, function(component) {
showHideMultifield(component, element);
component.on("change", function() {
showHideMultifield(component, element);
});
});
}
});
}
function showHideMultifield(component, element) {
var target = $(element).data("cqDialogDropdownShowhideTarget");
var $target = $(target);
var elementIndex = $(element).closest('coral-multifield-item').index();
if (target) {
var value = component.value;
$target.each(function() {
var tarIndex = $(this).closest('coral-multifield-item').index();
if (elementIndex == tarIndex) {
var $currentTarget = $(this);
var targetValues = $currentTarget.data('showhidetargetvalue');
if (targetValues) {
var shouldShow = targetValues.replace(/ /g, '').split(',').includes(value);
if (shouldShow) {
$currentTarget.removeClass("hide");
restoreRequiredFields($currentTarget);
} else {
$currentTarget.addClass("hide");
storeAndRemoveRequiredFields($currentTarget);
}
}
}
});
}
}
function storeAndRemoveRequiredFields($container) {
$container.find("[required], [aria-required='true']").each(function() {
var $field = $(this);
$field.attr("data-original-required", "true");
$field.removeAttr("required").removeAttr("aria-required");
clearFieldValue($field);
clearValidationErrors($field);
});
}
function restoreRequiredFields($container) {
$container.find("[data-original-required='true']").each(function() {
var $field = $(this);
$field.attr("required", "true").attr("aria-required", "true");
$field.removeAttr("data-original-required");
});
}
function clearFieldValue($field) {
try {
if ($field.is("input[type='text'], textarea")) {
$field.val("");
} else if ($field.is("coral-textfield")) {
$field[0].value = "";
} else if ($field.is("coral-select")) {
$field[0].value = "";
} else if ($field.is("coral-checkbox, coral-radio")) {
$field[0].checked = false;
} else if ($field.is("coral-autocomplete")) {
if (typeof $field[0].clear === "function") $field[0].clear();
}
} catch (e) {
console.warn("Clear field error:", e);
}
}
function clearValidationErrors($field) {
try {
$field.removeAttr("aria-invalid").removeClass("is-invalid");
$field.closest(".coral-Form-field").removeClass("is-invalid").find(".coral-Form-fielderror").remove();
if ($field[0]) $field[0].invalid = false;
} catch (e) {
console.warn("Clear validation error:", e);
}
}
function getFieldValue($field) {
try {
return $field[0]?.value || $field.val();
} catch (e) {
return $field.val();
}
}
$(document).on("foundation-validation-valid", function(e) {
$(".hide [required]").each(function() {
$(this).removeAttr("required").removeAttr("aria-required").attr("data-temp-submission-required", "true");
});
});
if ($.validator && typeof $.validator.register === "function") {
$.validator.register({
selector: "[required]:visible:not(.hide):not(.hide *)",
validate: function(el) {
const $field = $(el);
if ($field.closest('.hide').length > 0) return null;
const value = getFieldValue($field);
if (!value || value.trim() === '') {
return Granite.I18n.get('This field is required.');
}
return null;
}
});
}
$(document).on("foundation-contenthide foundation-contentunloaded", function(e) {
$("[data-original-required='true'], [data-temp-submission-required='true']").each(function() {
$(this).attr("required", "true").attr("aria-required", "true").removeAttr("data-original-required").removeAttr("data-temp-submission-required");
});
});
})(document, Granite.$);
🙏 Looking for Suggestions
Why is the imagepath field not enforcing required validation while others like videosrc are?
Is my custom JS too heavy? Can it be made more efficient without losing functionality?
Is this overall approach still considered best-practice in AEM (6.5+) dialogs, or is there a more Coral 3/native way to handle this?
Has anyone faced this inconsistency with asset-based textfields before?
Appreciate any insights from the community!