You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2026/02/04 10:26:17 UTC

(myfaces-tobago) branch main updated: fix(columnSelector): immediate in composite component

This is an automated email from the ASF dual-hosted git repository.

hnoeth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/main by this push:
     new e37e6da51e fix(columnSelector): immediate in composite component
e37e6da51e is described below

commit e37e6da51e23cfeea868bb8c8d9f2777d2d24c53
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Mon Jan 26 15:02:39 2026 +0100

    fix(columnSelector): immediate in composite component
    
    Test for columnSelector immediate=true in a composite component with a facet.
    State is now written in time.
    
    Issue: TOBAGO-2465
---
 .../tobago/internal/component/AbstractUISheet.java |   4 -
 .../internal/renderkit/renderer/SheetRenderer.java |   3 +
 .../immediate/immediate_compositeComponent.xhtml   |  45 +++++
 .../demo-components/columnSelectorImmediate.xhtml  |  68 ++++++++
 .../src/test/typescript/columnSelector.test.ts     | 184 +++++++++++++++++++++
 5 files changed, 300 insertions(+), 4 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
index 956457a70e..75b1cad529 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUISheet.java
@@ -554,10 +554,6 @@ public void broadcast(final FacesEvent facesEvent) throws AbortProcessingExcepti
       getSheetState(getFacesContext()).updateSortState(((SortActionEvent) facesEvent).getColumn().getId());
       sort(getFacesContext(), (SortActionEvent) facesEvent);
     } else if (facesEvent instanceof SheetRowSelectionChangeEvent sheetRowSelectionChangeEvent) {
-      final SheetState sheetState = getSheetState(sheetRowSelectionChangeEvent.getFacesContext());
-      if (sheetState != null) {
-        sheetState.setSelectedRows(sheetRowSelectionChangeEvent.getNewSelectedRows());
-      }
       if (getClientId().equals(sheetRowSelectionChangeEvent.getSourceId())) {
         final ActionListener defaultActionListener = getFacesContext().getApplication().getActionListener();
         if (defaultActionListener != null) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
index 928d742c33..0cb38ed82c 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
@@ -168,6 +168,9 @@ public void decodeInternal(final FacesContext facesContext, final T component) {
         final String sourceId = requestParameterMap.get(ClientBehaviorContext.BEHAVIOR_SOURCE_PARAM_NAME);
         List<Integer> oldSelectedRows = component.getSheetState(facesContext).getSelectedRows();
         if (!selectedRows.equals(oldSelectedRows)) {
+          if (state != null) {
+            state.setSelectedRows(selectedRows);
+          }
           component.queueEvent(new SheetRowSelectionChangeEvent(component, sourceId, oldSelectedRows, selectedRows));
         }
       } else {
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/sheet/columnSelector/immediate/immediate_compositeComponent.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/sheet/columnSelector/immediate/immediate_compositeComponent.xhtml
new file mode 100644
index 0000000000..6b7ebf8e29
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/sheet/columnSelector/immediate/immediate_compositeComponent.xhtml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  - Licensed to the Apache Software Foundation (ASF) under one
+  - or more contributor license agreements.  See the NOTICE file
+  - distributed with this work for additional information
+  - regarding copyright ownership.  The ASF licenses this file
+  - to you 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.
+  -->
+
+<ui:composition template="/main.xhtml"
+                xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:f="jakarta.faces.core"
+                xmlns:dc="jakarta.faces.composite/demo-components"
+                xmlns:tc="http://myfaces.apache.org/tobago/component"
+                xmlns:ui="jakarta.faces.facelets">
+  <dc:columnSelectorImmediate id="compComp" columnSelectorAjaxController="#{columnSelectorAjaxController}">
+    <f:facet name="buttons">
+      <tc:out id="selectedCount" label="Selected count"
+              value="#{columnSelectorAjaxController.sheetState.selectedRows.size()}"/>
+      <tc:button id="submit" label="Submit"/>
+
+      <tc:separator/>
+      <tc:button id="saveSelectedRowsAjax" label="Save selected rows (Ajax)" immediate="true"
+                 action="#{columnSelectorAjaxController.saveSelectedRows()}">
+        <f:ajax execute="sheet" render="sheet selectedCount selectedCountByAction"/>
+      </tc:button>
+      <tc:button id="saveSelectedRowsSubmit" label="Save selected rows (Submit)" immediate="true"
+                 action="#{columnSelectorAjaxController.saveSelectedRows()}"/>
+      <tc:out id="selectedCountByAction" label="Selected count by action"
+              value="#{columnSelectorAjaxController.savedSelectedRows}"/>
+    </f:facet>
+  </dc:columnSelectorImmediate>
+</ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/columnSelectorImmediate.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/columnSelectorImmediate.xhtml
new file mode 100644
index 0000000000..51a186e318
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/resources/demo-components/columnSelectorImmediate.xhtml
@@ -0,0 +1,68 @@
+<?xml version = "1.0" encoding = "UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+-->
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns:cc="jakarta.faces.composite"
+      xmlns:f="jakarta.faces.core"
+      xmlns:tc="http://myfaces.apache.org/tobago/component">
+<cc:interface>
+  <cc:attribute name="columnSelectorAjaxController"/>
+</cc:interface>
+<cc:implementation>
+  <tc:button id="resetImmediateTrueAjaxEnabled" label="Reset immediate=true ajax=enabled" immediate="true"
+             action="#{cc.attrs.columnSelectorAjaxController.reset(true, false)}"/>
+  <tc:button id="resetImmediateTrueAjaxDisabled" label="Reset immediate=true ajax=disabled" immediate="true"
+             action="#{cc.attrs.columnSelectorAjaxController.reset(true, true)}"/>
+  <tc:button id="resetImmediateFalseAjaxEnabled" label="Reset immediate=false ajax=enabled" immediate="true"
+             action="#{cc.attrs.columnSelectorAjaxController.reset(false, false)}"/>
+  <tc:button id="resetImmediateFalseAjaxDisabled" label="Reset immediate=false ajax=disabled" immediate="true"
+             action="#{cc.attrs.columnSelectorAjaxController.reset(false, true)}"/>
+
+  <tc:out label="immediate" value="#{cc.attrs.columnSelectorAjaxController.columnSelectorImmediate}"/>
+  <tc:out label="f:ajax"
+          value="#{cc.attrs.columnSelectorAjaxController.columnSelectorAjaxDisabled ? 'disabled' : 'enabled'}"/>
+
+  <tc:sheet id="sheet" value="#{cc.attrs.columnSelectorAjaxController.solarList}" var="object"
+            rows="5"
+            state="#{cc.attrs.columnSelectorAjaxController.sheetState}" selectable="multi">
+    <tc:columnSelector id="colSelect" immediate="#{cc.attrs.columnSelectorAjaxController.columnSelectorImmediate}">
+      <f:ajax render="sheet ::selectedCount ::::messages"
+              disabled="#{cc.attrs.columnSelectorAjaxController.columnSelectorAjaxDisabled}"/>
+    </tc:columnSelector>
+    <tc:column label="Name">
+      <tc:in value="#{object.name}" autoSpacing="false"/>
+    </tc:column>
+    <tc:column label="Orbit">
+      <tc:out value="#{object.orbit}" autoSpacing="false"/>
+    </tc:column>
+    <tc:column label="Period (Days)">
+      <tc:out value="#{object.period}" autoSpacing="false"/>
+    </tc:column>
+    <tc:column label="Discoverer">
+      <tc:out value="#{object.discoverer}" autoSpacing="false"/>
+    </tc:column>
+    <tc:column label="Required">
+      <tc:in required="true">
+        <tc:event event="click" stopPropagation="true" omit="true"/>
+      </tc:in>
+    </tc:column>
+  </tc:sheet>
+  <cc:renderFacet name="buttons"/>
+</cc:implementation>
+</html>
diff --git a/tobago-example/tobago-example-demo/src/test/typescript/columnSelector.test.ts b/tobago-example/tobago-example-demo/src/test/typescript/columnSelector.test.ts
index 4543b264e8..1dbfa60568 100644
--- a/tobago-example/tobago-example-demo/src/test/typescript/columnSelector.test.ts
+++ b/tobago-example/tobago-example-demo/src/test/typescript/columnSelector.test.ts
@@ -541,3 +541,187 @@ test.describe("sheet/columnSelector/immediate/immediate.xhtml", () => {
     await expect(alert).not.toBeVisible();
   });
 });
+
+test.describe("sheet/columnSelector/immediate/immediate_compositeComponent.xhtml", () => {
+
+  test.beforeEach(async ({page}, testInfo) => {
+    await page.goto("/content/900-test/sheet/columnSelector/immediate/immediate_compositeComponent.xhtml");
+  });
+
+  test("immediate=true, ajax=enabled", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateTrueAjaxEnabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const toggleAll = sheet.locator("th[id='page:mainForm:compComp:sheet:colSelect'] input");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row4Orbit = sheet.locator("tr[row-index='4'] td:nth-child(3) tobago-out");
+    const selectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCount'] span.form-control-plaintext");
+
+    await reset.click();
+
+    await expect(sheet).toBeVisible();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await expect(selectedCount).toHaveText("1");
+    await expect(alert).not.toBeVisible();
+
+    await row4Orbit.click();
+    await expect(selectedCount).toHaveText("2");
+    await expect(alert).not.toBeVisible();
+
+    await toggleAll.click();
+    await expect(selectedCount).toHaveText("5");
+    await expect(alert).not.toBeVisible();
+
+    await toggleAll.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+  });
+
+  test("immediate=true, ajax=disabled", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateTrueAjaxDisabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const toggleAll = sheet.locator("th[id='page:mainForm:compComp:sheet:colSelect'] input");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row4Orbit = sheet.locator("tr[row-index='4'] td:nth-child(3) tobago-out");
+    const selectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCount'] span.form-control-plaintext");
+    const submit = page.locator("button[id='page:mainForm:compComp:submit']");
+
+    await reset.click();
+    await expect(sheet).toBeVisible();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await expect(selectedCount).toHaveText("0");
+    await submit.click();
+    await expect(selectedCount).toHaveText("1");
+    await expect(alert).toBeVisible();
+
+    await row4Orbit.click();
+    await expect(selectedCount).toHaveText("1");
+    await submit.click();
+    await expect(selectedCount).toHaveText("2");
+    await expect(alert).toBeVisible();
+
+    await toggleAll.click();
+    await expect(selectedCount).toHaveText("2");
+    await submit.click();
+    await expect(selectedCount).toHaveText("5");
+    await expect(alert).toBeVisible();
+
+    await toggleAll.click();
+    await expect(selectedCount).toHaveText("5");
+    await submit.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+  });
+
+  test("immediate=false, ajax=enabled", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateFalseAjaxEnabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const toggleAll = sheet.locator("th[id='page:mainForm:compComp:sheet:colSelect'] input");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row4Orbit = sheet.locator("tr[row-index='4'] td:nth-child(3) tobago-out");
+    const selectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCount'] span.form-control-plaintext");
+
+    await reset.click();
+
+    await expect(sheet).toBeVisible();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+
+    await row4Orbit.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+
+    await toggleAll.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+  });
+
+  test("immediate=false, ajax=disabled", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateFalseAjaxDisabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const toggleAll = sheet.locator("th[id='page:mainForm:compComp:sheet:colSelect'] input");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row4Orbit = sheet.locator("tr[row-index='4'] td:nth-child(3) tobago-out");
+    const selectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCount'] span.form-control-plaintext");
+    const submit = page.locator("button[id='page:mainForm:compComp:submit']");
+
+    await reset.click();
+    await expect(sheet).toBeVisible();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await submit.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+
+    await row4Orbit.click();
+    await submit.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+
+    await toggleAll.click();
+    await submit.click();
+    await expect(selectedCount).toHaveText("0");
+    await expect(alert).toBeVisible();
+  });
+
+  test("Save selected rows (Ajax)", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateTrueAjaxDisabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row1Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_1']");
+    const row2Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_2']");
+    const savedSelectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCountByAction'] span.form-control-plaintext");
+    const saveSelectedRowsButton = page.locator(".tobago-button[id='page:mainForm:compComp:saveSelectedRowsAjax']");
+
+    await reset.click();
+    await expect(sheet).toBeVisible();
+    await expect(savedSelectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await row1Checkbox.click();
+    await row2Checkbox.click();
+    await saveSelectedRowsButton.click();
+
+    await expect(savedSelectedCount).toHaveText("3");
+    await expect(alert).not.toBeVisible();
+  });
+
+  test("Save selected rows (Submit)", async ({page}) => {
+    const alert = page.locator("tobago-messages[id='page:messages'] .alert");
+    const reset = page.locator(".tobago-button[id='page:mainForm:compComp:resetImmediateTrueAjaxDisabled']");
+    const sheet = page.locator("tobago-sheet[id='page:mainForm:compComp:sheet']");
+    const row0Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_0']");
+    const row2Checkbox = sheet.locator("input[name='page:mainForm:compComp:sheet_data_row_selector_2']");
+    const savedSelectedCount = page.locator("tobago-out[id='page:mainForm:compComp:selectedCountByAction'] span.form-control-plaintext");
+    const saveSelectedRowsButton = page.locator(".tobago-button[id='page:mainForm:compComp:saveSelectedRowsSubmit']");
+
+    await reset.click();
+    await expect(sheet).toBeVisible();
+    await expect(savedSelectedCount).toHaveText("0");
+    await expect(alert).not.toBeVisible();
+
+    await row0Checkbox.click();
+    await row2Checkbox.click();
+    await saveSelectedRowsButton.click();
+
+    await expect(savedSelectedCount).toHaveText("2");
+    await expect(alert).not.toBeVisible();
+  });
+});