Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,22 @@
fieldType="number-input"
name="numberinput1674624783704"/>
</panelcontainer1>
<panelcontainerFieldset
jcr:primaryType="nt:unstructured"
jcr:title="Fieldset Panel"
sling:resourceType="forms-components-examples/components/form/panelcontainer"
enabled="{Boolean}true"
fieldType="panel"
name="panelcontainerFieldset"
useFieldset="{Boolean}true"
visible="{Boolean}true">
<textinputFieldset
jcr:primaryType="nt:unstructured"
jcr:title="Text Input in Fieldset"
sling:resourceType="forms-components-examples/components/form/textinput"
fieldType="text-input"
name="textinputFieldset"/>
</panelcontainerFieldset>
</guideContainer>
</jcr:content>
</jcr:root>
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The following properties are written to JCR for this Panel Container component a
6. `./enabled` - defines initial state of panel if its enabled or not
7. `./tooltip` - defines tooltip on panel title
8. `./description` - defines a help message that can be rendered in the field as a hint for the user
9. `./useFieldset` - if set to `true`, the panel will be rendered as a `<fieldset>` element with the label as a `<legend>` for improved semantics and accessibility. When enabled, the title becomes mandatory and the "Hide Title" option is disabled. If title is not provided, the component will fallback to the panel name.

#### Style Properties
1. `./backgroundImageReference` - defines the Panel Container background image.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@
sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
name="./wrapData@TypeHint"
value="Boolean"/>
<useFieldset
granite:class="cmp-adaptiveform-panel__useFieldset"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldLabel="Use Fieldset/Legend"
fieldDescription="Render panel as a fieldset with legend for better semantics and accessibility. When enabled, title becomes mandatory."
name="./useFieldset"
text="Enable Fieldset/Legend"
uncheckedValue="false"
value="true">
<granite:data
jcr:primaryType="nt:unstructured"
cmp-adaptiveform-panel-useFieldset="true"/>
</useFieldset>
<useFieldset-typehint
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/hidden"
name="./useFieldset@TypeHint"
value="Boolean"/>
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
} else if (dialogContent.querySelector("[data-cmp-container-v1-dialog-policy-hook]")) {
handlePolicyDialog(dialogContent);
}

// Always handle useFieldset behavior for panel dialogs
handleUseFieldsetBehavior(dialogContent);
}

if($dialog[0]) {
Expand Down Expand Up @@ -67,6 +70,73 @@
}
}

/**
* Handles the interaction between useFieldset checkbox and hideTitle checkbox.
* When useFieldset is enabled:
* - hideTitle should be disabled and unchecked (legend must be visible)
* - Title is recommended for accessibility, but code falls back to name if not provided
*
* @param {HTMLElement} containerEditor The dialog wrapper
*/
function handleUseFieldsetBehavior(containerEditor) {
var useFieldsetCheckbox = containerEditor.querySelector('[data-cmp-adaptiveform-panel-usefieldset]');
var hideTitleCheckbox = containerEditor.querySelector('coral-checkbox[name="./hideTitle"]');
var titleField = containerEditor.querySelector('input[name="./jcr:title"]');

if (!useFieldsetCheckbox) {
return;
}

// Function to update hideTitle state based on useFieldset
var updateHideTitleState = function() {
var isFieldsetEnabled = useFieldsetCheckbox.checked;

if (isFieldsetEnabled) {
// Disable and uncheck hideTitle when fieldset is enabled
if (hideTitleCheckbox) {
hideTitleCheckbox.disabled = true;
hideTitleCheckbox.checked = false;
}

// Add visual indicator that title is recommended (but not strictly required since we fall back to name in the HTL template)
if (titleField) {
var titleFieldWrapper = titleField.closest('.coral-Form-fieldwrapper');
if (titleFieldWrapper) {
var labelElement = titleFieldWrapper.querySelector('label.coral-Form-fieldlabel');
if (labelElement && !labelElement.dataset.originalText) {
// Store original text and append asterisk (indicating that title is recommended)
labelElement.dataset.originalText = labelElement.textContent;
labelElement.textContent = labelElement.textContent + ' *';
}
}
}
} else {
// Re-enable hideTitle when fieldset is disabled
if (hideTitleCheckbox) {
hideTitleCheckbox.disabled = false;
}

// Remove title recommendation indicator (restore original text)
if (titleField) {
var titleFieldWrapper = titleField.closest('.coral-Form-fieldwrapper');
if (titleFieldWrapper) {
var labelElement = titleFieldWrapper.querySelector('label.coral-Form-fieldlabel');
if (labelElement && labelElement.dataset.originalText) {
labelElement.textContent = labelElement.dataset.originalText;
delete labelElement.dataset.originalText;
}
}
}
}
};

// Initialize state on dialog load
Coral.commons.ready(useFieldsetCheckbox, function() {
updateHideTitleState();
useFieldsetCheckbox.addEventListener('change', updateHideTitleState);
});
}

/**
* Binds policy dialog handling
*
Expand Down Expand Up @@ -154,4 +224,4 @@
}
}

})(jQuery);
})(jQuery);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,32 @@
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
<template data-sly-template.responsiveGrid="${ @ container, panel, label, shortDescription, longDescription, questionMark}">
<div data-sly-use.formstructparser="com.adobe.cq.forms.core.components.models.form.FormStructureParser"
<sly data-sly-set.useFieldset="${properties.useFieldset}"
data-sly-set.legendText="${panel.label.value ? panel.label.value : (panel.name ? panel.name : panel.id)}"></sly>

<fieldset data-sly-test="${useFieldset}"
data-sly-use.formstructparser="com.adobe.cq.forms.core.components.models.form.FormStructureParser"
id="${panel.id}"
class="cmp-container"
data-cmp-is="adaptiveFormPanel"
data-cmp-data-layer="${panel.data.json}"
data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}"
data-cmp-visible="${panel.visible ? 'true' : 'false'}"
data-cmp-enabled="${panel.enabled ? 'true' : 'false'}"
data-cmp-readonly="${panel.readOnly ? 'true' : 'false'}"
title="${panel.tooltipVisible ? '' : panel.tooltipText}"
style="${container.backgroundStyle @ context='styleString'}">
<legend class="cmp-container__label-container">
<div data-sly-call="${label.label @componentId=panel.id, labelValue=legendText, labelVisible=true, labelRichText=panel.label.richText, bemBlock='cmp-container'}" data-sly-unwrap></div>
<div data-sly-call="${questionMark.questionMark @componentId=panel.id, longDescription=panel.description, bemBlock='cmp-container'}" data-sly-unwrap></div>
</legend>
<div data-sly-call="${shortDescription.shortDescription @componentId=panel.id, shortDescriptionVisible=panel.tooltipVisible, shortDescription=panel.tooltip, bemBlock='cmp-container'}" data-sly-unwrap></div>
<div data-sly-call="${longDescription.longDescription @componentId=panel.id, longDescription=panel.description, bemBlock='cmp-container'}" data-sly-unwrap></div>
<sly data-sly-resource="${resource @ resourceType='wcm/foundation/components/responsivegrid'}"></sly>
</fieldset>

<div data-sly-test="${!useFieldset}"
data-sly-use.formstructparser="com.adobe.cq.forms.core.components.models.form.FormStructureParser"
id="${panel.id}"
class="cmp-container"
data-cmp-is="adaptiveFormPanel"
Expand All @@ -27,7 +52,7 @@
role="${container.roleAttribute}"
style="${container.backgroundStyle @ context='styleString'}">
<div class="cmp-container__label-container" role="heading">
<div data-sly-call="${label.label @componentId=panel.id, labelValue=panel.label.value, labelVisible=panel.label.visible, labelRichText=panel.label.richText, bemBlock='cmp-container'}" data-sly-unwrap></div>
<div data-sly-call="${label.label @componentId=panel.id, labelValue=panel.label.value, labelVisible=panel.label.visible, labelRichText=panel.label.richText, bemBlock='cmp-container', isHeading=true}" data-sly-unwrap></div>
<div data-sly-call="${questionMark.questionMark @componentId=panel.id, longDescription=panel.description, bemBlock='cmp-container'}" data-sly-unwrap></div>
</div>
<div data-sly-call="${shortDescription.shortDescription @componentId=panel.id, shortDescriptionVisible=panel.tooltipVisible, shortDescription=panel.tooltip, bemBlock='cmp-container'}" data-sly-unwrap></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,40 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
<template data-sly-template.simple="${ @ container, panel, label, shortDescription, longDescription, questionMark}">
<sly data-sly-test="${wcmmode.edit}" data-sly-use.allowed="com.day.cq.wcm.foundation.AllowedComponentList"></sly>
<div data-sly-use.templates="core/wcm/components/commons/v1/templates.html"
<sly data-sly-use.templates="core/wcm/components/commons/v1/templates.html"
data-sly-set.useFieldset="${properties.useFieldset}"
data-sly-set.legendText="${panel.label.value ? panel.label.value : (panel.name ? panel.name : panel.id)}"></sly>

<!-- Fieldset variant -->
<fieldset data-sly-test="${useFieldset}"
data-sly-use.formstructparser="com.adobe.cq.forms.core.components.models.form.FormStructureParser"
id="${panel.id}"
data-cmp-is="adaptiveFormPanel"
data-cmp-data-layer="${panel.data.json}"
data-cmp-adaptiveformcontainer-path="${formstructparser.formContainerPath}"
data-cmp-visible="${panel.visible ? 'true' : 'false'}"
data-cmp-enabled="${panel.enabled ? 'true' : 'false'}"
data-cmp-readonly="${panel.readOnly ? 'true' : 'false'}"
class="cmp-container${wcmmode.edit ? ' {0}': '' @ format=[allowed.cssClass]}"
title="${panel.tooltipVisible ? '' : panel.tooltipText}"
style="${container.backgroundStyle @ context='styleString'}">
<legend class="cmp-container__label-container">
<div data-sly-call="${label.label @componentId=panel.id, labelValue=legendText, labelVisible=true, labelRichText=panel.label.richText, bemBlock='cmp-container'}" data-sly-unwrap></div>
<div data-sly-call="${questionMark.questionMark @componentId=panel.id, longDescription=panel.description, bemBlock='cmp-container'}" data-sly-unwrap></div>
</legend>
<div data-sly-call="${shortDescription.shortDescription @componentId=panel.id, shortDescriptionVisible=panel.tooltipVisible, shortDescription=panel.tooltip, bemBlock='cmp-container'}" data-sly-unwrap></div>
<div data-sly-call="${longDescription.longDescription @componentId=panel.id, longDescription=panel.description, bemBlock='cmp-container'}" data-sly-unwrap></div>
<sly data-sly-test.isAllowedApplicable="${allowed.isApplicable}"
data-sly-use.allowedTemplate="allowedcomponents.html"
data-sly-call="${allowedTemplate.allowedcomponents @ title=allowed.title, components=allowed.components}"></sly>
<sly data-sly-test="${!isAllowedApplicable}"
data-sly-repeat="${container.items}" data-sly-resource="${item.path @ decoration=true}"></sly>
<sly data-sly-test="${!isAllowedApplicable && !wcmmode.disabled}"
data-sly-resource="${resource.path @ resourceType='core/wcm/components/container/v1/container/new', appendPath='/*', decorationTagName='div', cssClassName='new section'}" />
</fieldset>

<!-- Legacy div variant -->
<div data-sly-test="${!useFieldset}"
data-sly-use.formstructparser="com.adobe.cq.forms.core.components.models.form.FormStructureParser"
id="${panel.id}"
data-cmp-is="adaptiveFormPanel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ describe('Page - Authoring', function () {
.should("exist");
cy.get("[name='./jcr:title']")
.should("exist");
cy.get("[name='./useFieldset']")
.should("exist");

if (!isSites) {
cy.get("[name='./layout']")
.should("not.exist");
Expand Down Expand Up @@ -166,6 +169,51 @@ describe('Page - Authoring', function () {
cy.deleteComponentByPath(panelEditPath);
});

it('check fieldset option exists and behavior', function(){
dropPanelInContainer();
cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + panelContainerPathSelector);
cy.invokeEditableAction("[data-action='CONFIGURE']");

// Verify useFieldset checkbox exists
cy.get("[name='./useFieldset']")
.first()
.should("exist")
.should("be.visible");

// Get the title field label and verify initial state
cy.get("[name='./jcr:title']").should("exist");

// Check if useFieldset checkbox is checked and verify title becomes required
cy.get("[name='./useFieldset']").first().then(($checkbox) => {
const isChecked = $checkbox.prop('checked');
// Find the label for jcr:title field by navigating from the input to its parent wrapper
cy.get("[name='./jcr:title']").parent().find('label').then(($label) => {
if (isChecked) {
// When fieldset is checked, title should be required
expect($label.text()).to.include('*');
}
});
});

// Toggle the checkbox and verify the label changes accordingly
cy.get("[name='./useFieldset']").first().click();
cy.get("[name='./useFieldset']").first().then(($checkbox) => {
const isChecked = $checkbox.prop('checked');
cy.get("[name='./jcr:title']").parent().find('label').then(($label) => {
if (isChecked) {
// When fieldset is checked, title should be required
expect($label.text()).to.include('*');
} else {
// When fieldset is unchecked, title should not be required
expect($label.text()).to.not.include('*');
}
});
});

cy.get('.cq-dialog-cancel').click();
cy.deleteComponentByPath(panelEditPath);
});

if (cy.af.isLatestAddon()) {
it('Save panel as fragment via toolbar', { retries: 3}, function () {
cy.cleanTest(panelEditPath).then(function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,35 @@ describe( "Form Runtime with Panel Container - Basic Tests", () => {
cy.get(`#${textInputOfPanelId}`).find('.cmp-adaptiveform-textinput__widget').should('have.attr', 'readonly');
});
});

it("panel with useFieldset enabled should render as fieldset with legend", () => {
// panelcontainerFieldset is at index 7 (after panelcontainer1 which is at index 6)
const fieldsetPanelId = formContainer._model.items[7].id;

// Verify the panel renders as a <fieldset> element
cy.get(`#${fieldsetPanelId}`).then($el => {
expect($el.prop('tagName')).to.eq('FIELDSET');
});

// Verify the panel has a <legend> element for accessibility
cy.get(`#${fieldsetPanelId}`).find('legend').should('exist');

// Verify the legend contains the panel title
cy.get(`#${fieldsetPanelId}`).find('legend').should('contain.text', 'Fieldset Panel');
});

it("panel without useFieldset should NOT render as fieldset", () => {
// panelcontainer2 (DisabledPanel) at index 1 does not have useFieldset
const regularPanelId = formContainer._model.items[1].id;

// Verify the panel does NOT render as a <fieldset> element
cy.get(`#${regularPanelId}`).then($el => {
expect($el.prop('tagName')).to.not.eq('FIELDSET');
});

// Verify the panel does NOT have a <legend> element
cy.get(`#${regularPanelId}`).find('legend').should('not.exist');
});
})

describe( "Form Runtime with Panel Container - Repeatability Tests", () => {
Expand Down