diff --git a/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml
new file mode 100644
index 0000000000..1507a3f6cc
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/dam/formsanddocuments/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml
new file mode 100644
index 0000000000..92cbdbac87
--- /dev/null
+++ b/it/content/src/main/content/jcr_root/content/forms/af/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop/.content.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
index 0b9df2d561..c624dea2a6 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js
@@ -509,13 +509,17 @@
result.parentElement = this.element;
}
} else {
- var previousInstanceElement = instanceManager.children[instanceIndex - 1].element;
- var previousInstanceItemDiv = this.#getItemById(previousInstanceElement.id + Accordion.idSuffixes.item);
- result.beforeViewElement = previousInstanceItemDiv;
+ let previousInstanceElement = this.#getRepeatableElementAt(instanceManager, instanceIndex - 1);
+ result.beforeViewElement = previousInstanceElement;
}
return result;
}
+ #getRepeatableElementAt(instanceManager, index) {
+ let childId = instanceManager._model.items.find((model) => model.index === index)?.id;
+ return this.element.querySelector(`#${childId}${Accordion.idSuffixes.item}`);
+ }
+
#prepareAccordionMarkupToBeAdded(instanceManager, addedModel, htmlElement) {
var accordionItemDivToBeRepeated = this._templateHTML[instanceManager.getId()]['accordionItemDiv'].cloneNode(true);
var itemDivId = accordionItemDivToBeRepeated.id;
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
index 0b7cf39f98..ffe9db7d51 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/checkboxgroup/v1/checkboxgroup/clientlibs/site/js/checkboxgroupview.js
@@ -191,6 +191,12 @@
this.element.setAttribute("data-cmp-required", required);
}
}
+
+ syncMarkupWithModel() {
+ super.syncMarkupWithModel();
+ this.updateEnum(this._model.enum);
+ this.updateEnumNames(this._model.enumNames);
+ }
}
FormView.Utils.setupField(({element, formContainer}) => {
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/clientlibs/site/js/panelcontainerview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/clientlibs/site/js/panelcontainerview.js
index e2922ef795..8e2f5016a5 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/clientlibs/site/js/panelcontainerview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/clientlibs/site/js/panelcontainerview.js
@@ -173,8 +173,7 @@
result.parentElement = this._templateHTML['divToAppendChild'];
}
} else {
- var previousInstanceElement = instanceManager.children[instanceIndex - 1].element;
- var previousInstanceItemDiv = this.#getCachedElementById(previousInstanceElement.id).parentElement;
+ let previousInstanceItemDiv = instanceManager.getElementAt(instanceIndex - 1);
result.beforeViewElement = previousInstanceItemDiv;
}
return result;
diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
index c86095258a..815bd476fa 100644
--- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
+++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js
@@ -256,18 +256,20 @@
const tabs = this.#getCachedTabs();
if (wizardPanels) {
for (let i = 0; i < wizardPanels.length; i++) {
- if (i === parseInt(this.#_active)) {
- wizardPanels[i].classList.add(Wizard.selectors.active.wizardpanel);
- wizardPanels[i].removeAttribute(FormView.Constants.ARIA_HIDDEN);
- tabs[i].classList.add(Wizard.selectors.active.tab);
- tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, true);
- tabs[i].setAttribute(FormView.Constants.TABINDEX, "0");
- } else {
- wizardPanels[i].classList.remove(Wizard.selectors.active.wizardpanel);
- wizardPanels[i].setAttribute(FormView.Constants.ARIA_HIDDEN, true);
- tabs[i].classList.remove(Wizard.selectors.active.tab);
- tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, false);
- tabs[i].setAttribute(FormView.Constants.TABINDEX, "-1");
+ if(tabs[i]) {
+ if (i === parseInt(this.#_active)) {
+ wizardPanels[i].classList.add(Wizard.selectors.active.wizardpanel);
+ wizardPanels[i].removeAttribute(FormView.Constants.ARIA_HIDDEN);
+ tabs[i].classList.add(Wizard.selectors.active.tab);
+ tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, true);
+ tabs[i].setAttribute(FormView.Constants.TABINDEX, "0");
+ } else {
+ wizardPanels[i].classList.remove(Wizard.selectors.active.wizardpanel);
+ wizardPanels[i].setAttribute(FormView.Constants.ARIA_HIDDEN, true);
+ tabs[i].classList.remove(Wizard.selectors.active.tab);
+ tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, false);
+ tabs[i].setAttribute(FormView.Constants.TABINDEX, "-1");
+ }
}
}
}
@@ -617,13 +619,17 @@
result.beforeViewElement = this.getPreviousButtonDiv();
}
} else {
- let previousInstanceElement = instanceManager.children[instanceIndex - 1].element;
- let previousInstanceWizardPanelIndex = this.#getTabIndexById(previousInstanceElement.id + Wizard.#tabIdSuffix);
- result.beforeViewElement = this.#getCachedWizardPanels()[previousInstanceWizardPanelIndex];
+ let previousInstanceElement = this.#getRepeatableElementAt(instanceManager, instanceIndex - 1)
+ result.beforeViewElement = previousInstanceElement;
}
return result;
}
+ #getRepeatableElementAt(instanceManager, index) {
+ let childId = instanceManager._model.items.find((model) => model.index === index)?.id;
+ return this.element.querySelector(`#${childId}${Wizard.#wizardPanelIdSuffix}`);
+ }
+
updateChildVisibility(visible, state) {
this.updateVisibilityOfNavigationElement(this.#getTabNavElementById(state.id + Wizard.#tabIdSuffix), visible);
let activeTabNavElement = this.#getCachedTabs()[this.#_active];
diff --git a/ui.frontend/src/view/FormTabs.js b/ui.frontend/src/view/FormTabs.js
index 67dacdc808..e432c30ca4 100644
--- a/ui.frontend/src/view/FormTabs.js
+++ b/ui.frontend/src/view/FormTabs.js
@@ -106,20 +106,23 @@ class FormTabs extends FormPanel {
var tabs = this.#getCachedTabs();
if (tabpanels) {
for (var i = 0; i < tabpanels.length; i++) {
- if (tabs[i].id === this.#_active) {
- tabpanels[i].classList.add(this.#_selectors.active.tabpanel);
- tabpanels[i].removeAttribute(Constants.ARIA_HIDDEN);
- tabs[i].classList.add(this.#_selectors.active.tab);
- tabs[i].setAttribute(Constants.ARIA_SELECTED, true);
- tabs[i].setAttribute(Constants.TABINDEX, "0");
- tabs[i].setAttribute(Constants.ARIA_CURRENT, "true");
- } else {
- tabpanels[i].classList.remove(this.#_selectors.active.tabpanel);
- tabpanels[i].setAttribute(Constants.ARIA_HIDDEN, true);
- tabs[i].classList.remove(this.#_selectors.active.tab);
- tabs[i].setAttribute(Constants.ARIA_SELECTED, false);
- tabs[i].setAttribute(Constants.TABINDEX, "-1");
- tabs[i].setAttribute(Constants.ARIA_CURRENT, "false");
+ // In case of repeatability ( adding instance via loop) tabs and tabpanels may be out of sync, it will sync in future
+ if(tabs[i]) {
+ if (tabs[i].id === this.#_active) {
+ tabpanels[i].classList.add(this.#_selectors.active.tabpanel);
+ tabpanels[i].removeAttribute(Constants.ARIA_HIDDEN);
+ tabs[i].classList.add(this.#_selectors.active.tab);
+ tabs[i].setAttribute(Constants.ARIA_SELECTED, true);
+ tabs[i].setAttribute(Constants.TABINDEX, "0");
+ tabs[i].setAttribute(Constants.ARIA_CURRENT, "true");
+ } else {
+ tabpanels[i].classList.remove(this.#_selectors.active.tabpanel);
+ tabpanels[i].setAttribute(Constants.ARIA_HIDDEN, true);
+ tabs[i].classList.remove(this.#_selectors.active.tab);
+ tabs[i].setAttribute(Constants.ARIA_SELECTED, false);
+ tabs[i].setAttribute(Constants.TABINDEX, "-1");
+ tabs[i].setAttribute(Constants.ARIA_CURRENT, "false");
+ }
}
}
}
@@ -544,13 +547,17 @@ class FormTabs extends FormPanel {
result.beforeViewElement = this.#getTabListElement();
}
} else {
- var previousInstanceElement = instanceManager.children[instanceIndex - 1].element;
- var previousInstanceTabPanelIndex = this.#getTabIndexById(previousInstanceElement.id + this.#tabIdSuffix);
- result.beforeViewElement = this.#getCachedTabPanels()[previousInstanceTabPanelIndex];
+ let previousInstanceElement = this.#getRepeatableElementAt(instanceManager, instanceIndex - 1);
+ result.beforeViewElement = previousInstanceElement;
}
return result;
}
+ #getRepeatableElementAt(instanceManager, index) {
+ let childId = instanceManager._model.items.find((model) => model.index === index)?.id;
+ return this.element.querySelector(`#${childId}${this.#tabPanelIdSuffix}`);
+ }
+
/**
* Gets the child view at the specified index.
* @param {number} index - The index of the child view.
diff --git a/ui.frontend/src/view/InstanceManager.js b/ui.frontend/src/view/InstanceManager.js
index b65df09bc9..28a69fe11e 100644
--- a/ui.frontend/src/view/InstanceManager.js
+++ b/ui.frontend/src/view/InstanceManager.js
@@ -282,13 +282,29 @@ class InstanceManager {
let afterElement = this.children[0].element.parentElement;
this.parentElement.insertBefore(htmlElement, afterElement);
} else {
- let beforeViewElement = (beforeElement != null) ? beforeElement : this.children[instanceIndex - 1].element.parentElement;
+ let beforeViewElement = this.getElementAt(instanceIndex - 1);
beforeViewElement.after(htmlElement);
}
}
return htmlElement;
}
+ /**
+ * Gets the HTML element at the given index
+ * @param index {number} The index of the element
+ * @returns {Element}
+ */
+ getElementAt(index) {
+ let childModel = this._model.items.find((model) => model.index === index);
+ let viewElement = this.parentElement.querySelector(`#${childModel.id}`);
+ if (viewElement) {
+ while (viewElement.parentElement !== this.parentElement) {
+ viewElement = viewElement.parentElement;
+ }
+ }
+ return viewElement;
+ }
+
/**
* Removes HTML for the removed instance
* @param removedInstanceView {object} The view of the removed instance
diff --git a/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.spec.js b/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.spec.js
index 5dbc6c925f..2b0f4993fc 100644
--- a/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.spec.js
+++ b/ui.tests/test-module/specs/accordion/accordion.repeatability.runtime.spec.js
@@ -196,6 +196,22 @@ describe("Form with Accordion Container with repeatable elements", () => {
})
cy.expectNoConsoleErrors();
})
+
+ it('test adding panel programmatically', () => {
+ getItemDivs().should('have.length', 5);
+ getAccordionPanels().should('have.length', 5);
+ getAccordionButtons().should('have.length', 5);
+ cy.wrap(null).then(() => {
+ const instanceManager = formContainer._model.items[0].items[0];
+ for(let i=0; i<4; i++){
+ instanceManager.dispatch({type: "addItem"})
+ }
+ }).then(() => {
+ getItemDivs().should('have.length', 8);
+ getAccordionPanels().should('have.length', 8);
+ getAccordionButtons().should('have.length', 8);
+ })
+ });
})
describe("Form with Accordion Container with nested repeatable elements", () => {
diff --git a/ui.tests/test-module/specs/panelcontainer/panelcontainer.repeatability.addinstancevialoop.spec.js b/ui.tests/test-module/specs/panelcontainer/panelcontainer.repeatability.addinstancevialoop.spec.js
new file mode 100644
index 0000000000..61d1b24fea
--- /dev/null
+++ b/ui.tests/test-module/specs/panelcontainer/panelcontainer.repeatability.addinstancevialoop.spec.js
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright 2024 Adobe
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+describe("Repeatability Tests in Panel Container", () => {
+ const pagePath = "content/forms/af/core-components-it/samples/panelcontainer/repeatability-tests/add-instances-via-loop.html";
+
+ let formContainer = null;
+
+ beforeEach(() => {
+ cy.previewFormWithPanel(pagePath).then(p => {
+ formContainer = p;
+ });
+ });
+
+ it("add instance using loop and change checkbox enums", () => {
+ const instanceManager = formContainer._model.items[0];
+ const initialNoOfChild = instanceManager.items.length;
+ expect(initialNoOfChild).to.equal(1);
+ cy.get(".cmp-adaptiveform-container__wrapper")
+ .children()
+ .should("have.length", 1);
+
+ cy.wrap(null).then(() => {
+ for(let i=0; i < 4; i++) {
+ if(i !== 0) {
+ instanceManager.dispatch({type: "addItem"});
+ }
+ const chbx = instanceManager.items[i].items[0];
+ chbx.enum = [ "item1" + i, "item2" + i];
+ chbx.enumNames = [ "Item1" + i, "Item2" + i];
+ }
+ })
+
+ cy.get(".cmp-adaptiveform-container__wrapper > div")
+ .should("have.length", 4);
+
+ cy.wrap(null).then(() => {
+ for(let i=0; i < 4; i++) {
+ const chbx = instanceManager.items[i].items[0];
+ expect(chbx.enum).to.deep.equal([ "item1" + i, "item2" + i]);
+ expect(chbx.enumNames).to.deep.equal([ "Item1" + i, "Item2" + i]);
+ cy.get("#" + chbx.id + " [value='item1" + i + "']")
+ .should("be.visible");
+ cy.get("#" + chbx.id + " [value='item2" + i + "']")
+ .should("be.visible");
+ }
+ })
+ })
+})
\ No newline at end of file
diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.spec.js b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.spec.js
index 3c440ae07e..68b9332bfa 100644
--- a/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.spec.js
+++ b/ui.tests/test-module/specs/tabsontop/tabsontop.runtime.repeatability.spec.js
@@ -131,5 +131,18 @@ describe("Form with TabsOnTop Container", () => {
})
})
+ it('test adding panel programmatically', () => {
+ getTabs().should('have.length', 4);
+ getTabPanels().should('have.length', 4);
+ cy.wrap(null).then(() => {
+ const instanceManager = formContainer._model.items[0].items[0];
+ for(let i=0; i<5; i++){
+ instanceManager.dispatch({type: "addItem"})
+ }
+ }).then(() => {
+ getTabs().should('have.length', 7);
+ getTabPanels().should('have.length', 7);
+ })
+ });
})
diff --git a/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.spec.js b/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.spec.js
index 68d0250ef2..f749a1829b 100644
--- a/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.spec.js
+++ b/ui.tests/test-module/specs/wizard/wizard.runtime.repeatability.spec.js
@@ -138,7 +138,19 @@ describe("Form with Wizard Container", () => {
cy.expectNoConsoleErrors();
})
-
+ it('test adding panel programmatically', () => {
+ getTabs().should('have.length', 4);
+ getWizardPanels().should('have.length', 4);
+ cy.wrap(null).then(() => {
+ const instanceManager = formContainer._model.items[0].items[0];
+ for(let i=0; i<5; i++){
+ instanceManager.dispatch({type: "addItem"})
+ }
+ }).then(() => {
+ getTabs().should('have.length', 6);
+ getWizardPanels().should('have.length', 6);
+ })
+ });
})
describe('visibility of navigation buttons', function () {