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

ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/main by this push:
     new 6f88c309670 CAUSEWAY-2297: removes empty tabs from the grid
6f88c309670 is described below

commit 6f88c309670a5b7e9c014bdb2503a837b3660a4b
Author: Andi Huber <[email protected]>
AuthorDate: Thu Oct 23 15:02:15 2025 +0200

    CAUSEWAY-2297: removes empty tabs from the grid
---
 .../applib/layout/grid/bootstrap/BSTab.java        | 98 ++++++++++++----------
 .../grid/bootstrap/CollapseIfOneTabProcessor.java  |  2 +-
 .../grid/bootstrap/EmptyTabRemovalProcessor.java   | 77 +++++++++++++++++
 .../grid/bootstrap/GridSystemServiceBootstrap.java |  1 +
 .../testdomain/conf/Configuration_usingWicket.java |  6 +-
 5 files changed, 135 insertions(+), 49 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
index 8a00ac082ce..617ba73d9f0 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSTab.java
@@ -100,62 +100,59 @@ public static Predicate<BSTab> notEmpty() {
             final AtomicBoolean visitingTheNode = new AtomicBoolean(false);
             final AtomicBoolean foundContent = new AtomicBoolean(false);
 
-            return new Predicate<BSTab>() {
-                @Override
-                public boolean test(final BSTab thisBsTab) {
-                    final BSGrid owningGrid = thisBsTab.getGrid();
-                    owningGrid.visit(new BSGrid.VisitorAdapter() {
-
-                        /**
-                         * if found the tab, then reset 'foundContent' to 
false, and then use 'visitingTheNode' as
-                         * a marker to indicate that the visitor is now being 
passed to the nodes underneath the tab.
-                         * In those children, if visited (with the 
'visitingTheNode' flag enabled), then simply set the
-                         * 'foundContent' flag.
-                         */
-                        @Override
-                        public void preVisit(final BSTab bsTab) {
-                            if(bsTab == thisBsTab) {
-                                foundContent.set(false);
-                                visitingTheNode.set(true);
-                            }
+            return thisBsTab -> {
+                final BSGrid owningGrid = thisBsTab.getGrid();
+                owningGrid.visit(new BSGrid.VisitorAdapter() {
+
+                    /**
+                     * if found the tab, then reset 'foundContent' to false, 
and then use 'visitingTheNode' as
+                     * a marker to indicate that the visitor is now being 
passed to the nodes underneath the tab.
+                     * In those children, if visited (with the 
'visitingTheNode' flag enabled), then simply set the
+                     * 'foundContent' flag.
+                     */
+                    @Override
+                    public void preVisit(final BSTab bsTab) {
+                        if(bsTab == thisBsTab) {
+                            foundContent.set(false);
+                            visitingTheNode.set(true);
                         }
+                    }
 
-                        @Override public void postVisit(final BSTab bsTab) {
-                            if(bsTab == thisBsTab) {
-                                visitingTheNode.set(false);
-                            }
+                    @Override public void postVisit(final BSTab bsTab) {
+                        if(bsTab == thisBsTab) {
+                            visitingTheNode.set(false);
                         }
+                    }
 
-                        @Override
-                        public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
-                            if(visitingTheNode.get()) {
-                                foundContent.set(true);
-                            }
+                    @Override
+                    public void visit(final DomainObjectLayoutData 
domainObjectLayoutData) {
+                        if(visitingTheNode.get()) {
+                            foundContent.set(true);
                         }
+                    }
 
-                        @Override
-                        public void visit(final ActionLayoutData 
actionLayoutData) {
-                            if(visitingTheNode.get()) {
-                                foundContent.set(true);
-                            }
+                    @Override
+                    public void visit(final ActionLayoutData actionLayoutData) 
{
+                        if(visitingTheNode.get()) {
+                            foundContent.set(true);
                         }
+                    }
 
-                        @Override
-                        public void visit(final PropertyLayoutData 
propertyLayoutData) {
-                            if(visitingTheNode.get()) {
-                                foundContent.set(true);
-                            }
+                    @Override
+                    public void visit(final PropertyLayoutData 
propertyLayoutData) {
+                        if(visitingTheNode.get()) {
+                            foundContent.set(true);
                         }
+                    }
 
-                        @Override
-                        public void visit(final CollectionLayoutData 
collectionLayoutData) {
-                            if(visitingTheNode.get()) {
-                                foundContent.set(true);
-                            }
+                    @Override
+                    public void visit(final CollectionLayoutData 
collectionLayoutData) {
+                        if(visitingTheNode.get()) {
+                            foundContent.set(true);
                         }
-                    });
-                    return foundContent.get();
-                }
+                    }
+                });
+                return foundContent.get();
             };
         }
     }
@@ -167,6 +164,17 @@ public BSGrid getGrid() {
         return getOwner().getGrid();
     }
 
+    /**
+     * removes this tab from its tab-group
+     */
+    @Programmatic
+    public void remove() {
+        if(owner!=null) {
+            owner.getTabs().remove(this);
+            owner = null;
+        }
+    }
+
     @Override public String toString() {
         return "BSTab{" +
                 "name='" + name + '\'' +
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
index 0d64e53cf35..f7b0e4bb594 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/CollapseIfOneTabProcessor.java
@@ -35,7 +35,7 @@ public void run() {
         bsGrid.visit(new BSGrid.VisitorAdapter() {
             @Override
             public void visit(BSTabGroup bsTabGroup) {
-                if(bsTabGroup.getTabs().size()!=1) return; // when has not 
tabs is also a no-op
+                if(bsTabGroup.getTabs().size()!=1) return; // when has no tabs 
is also a no-op
 
                 var isCollapseIfOne = 
Optional.ofNullable(bsTabGroup.isCollapseIfOne())
                     .map(Boolean::booleanValue)
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
new file mode 100644
index 00000000000..45ae40cdd1e
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/EmptyTabRemovalProcessor.java
@@ -0,0 +1,77 @@
+/*
+ *  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.
+ */
+package org.apache.causeway.core.metamodel.services.grid.bootstrap;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+import org.apache.causeway.applib.layout.component.ActionLayoutData;
+import org.apache.causeway.applib.layout.component.CollectionLayoutData;
+import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
+import org.apache.causeway.applib.layout.component.PropertyLayoutData;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
+import org.apache.causeway.applib.layout.grid.bootstrap.BSTab;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Removes empty tabs from tab groups.
+ */
+record EmptyTabRemovalProcessor(BSGrid bsGrid) {
+
+    @RequiredArgsConstructor
+    static final class Wrapper {
+        final BSTab tab;
+        boolean keep = false;
+    }
+
+    public void run() {
+
+        var emptyTabs = new ArrayList<BSTab>();
+
+        bsGrid.visit(new BSGrid.VisitorAdapter() {
+
+            final Stack<Wrapper> stack = new Stack<Wrapper>();
+
+            @Override public void visit(ActionLayoutData actionLayoutData) { 
keep(); }
+            @Override public void visit(DomainObjectLayoutData 
domainObjectLayoutData) { keep(); }
+            @Override public void visit(PropertyLayoutData propertyLayoutData) 
{ keep(); }
+            @Override public void visit(CollectionLayoutData 
collectionLayoutData) { keep(); }
+
+            @Override public void visit(BSTab bsTab) {
+                stack.push(new Wrapper(bsTab));
+            }
+            @Override public void postVisit(BSTab bsTab) {
+                var wrapper = stack.pop();
+                if(!wrapper.keep) {
+                    // collecting into list, so we don't risk a 
ConcurrentModificationException,
+                    // when racing with the underlying iterator
+                    emptyTabs.add(bsTab);
+                }
+            }
+            private void keep() {
+                if(stack.isEmpty()) return;
+                stack.peek().keep = true;
+            }
+        });
+
+        emptyTabs.forEach(tab->tab.remove());
+
+    }
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
index 293c889efa0..aafb34fd5ab 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java
@@ -518,6 +518,7 @@ protected boolean validateAndNormalize(
                     });
         }
 
+        new EmptyTabRemovalProcessor(bsGrid).run();
         new CollapseIfOneTabProcessor(bsGrid).run();
         return true;
     }
diff --git 
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java
 
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java
index d4847d312c0..da0d10ee6ed 100644
--- 
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java
+++ 
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java
@@ -113,8 +113,8 @@ public static class DomainObjectPageTester
 
         public static final String BOOK_DELETE_ACTION_JPA = 
"theme:domainObjectContainer:domainObject:rows:2"
                 + ":rowContents:1"
-                + ":col:rows:1:rowContents:1:col:tabGroups:1"
-                + ":1:rowContents:1"
+                + ":col:rows:1:rowContents:1:col:rows:1"
+                + ":rowContents:1"
                 + ":col:fieldSets:1:memberGroup"
                 + ":panelHeading:associatedActionLinksPanel"
                 + ":additionalLinkList:additionalLinkItem:0:actionLink";
@@ -152,7 +152,7 @@ public static enum SimulatedProperties implements 
SimulatedProperty {
                     + ":property"),
             JPA_BOOK_ISBN("theme:domainObjectContainer:domainObject:rows:2"
                     + ":rowContents:1:col:rows:1:rowContents:1"
-                    + ":col:tabGroups:1:1"
+                    + ":col:rows:1"
                     + ":rowContents:1:col"
                     + ":fieldSets:1"
                     + ":memberGroup:properties:4"

Reply via email to