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"