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 () {