This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/master by this push:
new 4f5d33e4f8 CAUSEWAY-3415: improved table view select dropdown rendering
4f5d33e4f8 is described below
commit 4f5d33e4f8e4f27c3e43239c2e5d051cb86cc143
Author: Andi Huber <[email protected]>
AuthorDate: Wed Jun 21 13:58:06 2023 +0200
CAUSEWAY-3415: improved table view select dropdown rendering
- adding sections
---
.../wicket/ui/CollectionContentsAsFactory.java | 7 +-
.../CollectionPresentationSelectorPanel.html | 5 +-
.../CollectionPresentationSelectorPanel.java | 149 +++++++++++++--------
.../ui/components/collection/selector/_Util.java | 90 +++++++++++++
.../ComponentFactoryRegistryDefaultTest.java | 83 ------------
5 files changed, 189 insertions(+), 145 deletions(-)
diff --git
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CollectionContentsAsFactory.java
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CollectionContentsAsFactory.java
index 0f418efe47..0bd981d8a5 100644
---
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CollectionContentsAsFactory.java
+++
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/CollectionContentsAsFactory.java
@@ -50,8 +50,11 @@ public interface CollectionContentsAsFactory {
IModel<String> getCssClass();
/**
- * An ordinal, that governs the order of appearance in the UI dropdown,
- * that will associate with this factory.
+ * An ordinal, that governs the order of appearance in the UI dropdown.
+ * <ul>
+ * <li>{@literal 1000..1999} reserved for different table
presentations</li>
+ * <li>{@literal 2000..2999} reserved for different table exports</li>
+ * </ul>
* <p>
* Lowest comes first.
*/
diff --git
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.html
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.html
index ce4d68aebf..eafbaef32d 100644
---
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.html
+++
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.html
@@ -28,7 +28,10 @@
<span class="caret"></span>
</button>
<ul wicket:id="viewList" class="dropdown-menu dropdown-menu-right"
role="menu">
- <li wicket:id="viewItem" class="viewItem">
+
+ <li wicket:id="viewItem">
+ <span wicket:id="sectionLabel"
class="sectionLabel">[sectionLabel]</span>
+
<a href="#" wicket:id="viewLink" class="dropdown-item">
<span wicket:id="viewItemIcon"
class="ViewLinkItem"></span>
<span wicket:id="viewItemTitle"
class="ViewLinkItemTitle">[link title]</span>
diff --git
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
index b3c8b31080..9a2a6b1b28 100644
---
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
+++
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
@@ -18,19 +18,22 @@
*/
package org.apache.causeway.viewer.wicket.ui.components.collection.selector;
+import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
+import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.DownloadLink;
+import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.commons.internal.base._Strings;
import
org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
import org.apache.causeway.viewer.commons.model.components.UiComponentType;
import org.apache.causeway.viewer.wicket.model.hints.CausewaySelectorEvent;
@@ -38,11 +41,13 @@ import
org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import
org.apache.causeway.viewer.wicket.ui.components.widgets.links.AjaxLinkNoPropagate;
import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
import org.apache.causeway.viewer.wicket.ui.util.Wkt;
import org.apache.causeway.viewer.wicket.ui.util.WktComponents;
import org.apache.causeway.viewer.wicket.ui.util.WktLinks;
+import lombok.NonNull;
import lombok.val;
/**
@@ -65,6 +70,8 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
// private static final String ID_VIEW_BUTTON_TITLE = "viewButtonTitle";
private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon";
+ private static final String ID_SECTION_LABEL = "sectionLabel";
+
private final CollectionPresentationSelectorHelper selectorHelper;
private final ComponentHintKey componentHintKey;
@@ -124,7 +131,19 @@ extends PanelAbstract<DataTableModel,
EntityCollectionModel> {
final Label viewButtonIcon = Wkt.labelAdd(views, ID_VIEW_BUTTON_ICON,
"");
Wkt.listViewAdd(container, ID_VIEW_ITEM, sorted(componentFactories),
item->{
- final ComponentFactory componentFactory = item.getModelObject();
+ val eitherComponentFactoryOrSectionLabel = item.getModelObject();
+
+ if(eitherComponentFactoryOrSectionLabel.isSectionLabel()) {
+ Wkt.cssAppend(item, "list-section-label");
+ Wkt.labelAdd(item, ID_SECTION_LABEL,
eitherComponentFactoryOrSectionLabel.getSectionLabel());
+ WktComponents.permanentlyHide(item, ID_VIEW_LINK);
+ return;
+ }
+
+ Wkt.cssAppend(item, "viewItem");
+ WktComponents.permanentlyHide(item, ID_SECTION_LABEL);
+
+ final ComponentFactory componentFactory =
eitherComponentFactoryOrSectionLabel.getComponentFactory();
// add direct download link instead of a panel
if(componentFactory.getComponentType() ==
UiComponentType.COLLECTION_CONTENTS_EXPORT) {
@@ -135,10 +154,7 @@ extends PanelAbstract<DataTableModel,
EntityCollectionModel> {
item.addOrReplace(downloadLink);
// add title and icon to the link
- WktLinks.listItemAsDropdownLink(item, downloadLink,
- ID_VIEW_ITEM_TITLE,
CollectionPresentationSelectorPanel::nameFor,
- ID_VIEW_ITEM_ICON, null,
- CollectionPresentationSelectorPanel::cssClassFor);
+
EitherComponentFactoryOrSectionLabel.addLinkWithIconAndTitle(item,
downloadLink);
return;
}
@@ -156,18 +172,11 @@ extends PanelAbstract<DataTableModel,
EntityCollectionModel> {
});
// add title and icon to the link
- WktLinks.listItemAsDropdownLink(item, link,
- ID_VIEW_ITEM_TITLE,
CollectionPresentationSelectorPanel::nameFor,
- ID_VIEW_ITEM_ICON, null,
- CollectionPresentationSelectorPanel::cssClassFor);
-
- // hide the selected item
- val isSelected = componentFactory ==
CollectionPresentationSelectorPanel.this.selectedComponentFactory;
- if (isSelected) {
- //viewButtonTitle.setDefaultModel(nameFor(componentFactory));
- final IModel<String> cssClass = cssClassFor(componentFactory,
viewButtonIcon);
- Wkt.cssReplace(viewButtonIcon, "ViewLinkItem " +
cssClass.getObject());
- Wkt.cssAppend(link, "active");
+ EitherComponentFactoryOrSectionLabel.addLinkWithIconAndTitle(item,
link);
+
+ // mark the selected item as active
+ if (eitherComponentFactoryOrSectionLabel.isSelectedIn(this)) {
+
eitherComponentFactoryOrSectionLabel.markAsActive(viewButtonIcon, link);
}
});
@@ -178,52 +187,35 @@ extends PanelAbstract<DataTableModel,
EntityCollectionModel> {
/**
* Sorts given CollectionContentsAsFactory(s) by their
orderOfAppearanceInUiDropdown,
* in order of discovery otherwise.
+ * @param filter
* @see CollectionContentsAsFactory#orderOfAppearanceInUiDropdown()
*/
- private List<ComponentFactory> sorted(final Can<ComponentFactory>
componentFactories) {
- return componentFactories.stream()
- .sorted((a, b)->Integer.compare(
- orderOfAppearanceInUiDropdown(a),
- orderOfAppearanceInUiDropdown(b)))
- .collect(Collectors.toList());
- }
-
- private static int orderOfAppearanceInUiDropdown(final ComponentFactory
componentFactory) {
- return componentFactory instanceof CollectionContentsAsFactory
- ? ((CollectionContentsAsFactory)
componentFactory).orderOfAppearanceInUiDropdown()
- : Integer.MAX_VALUE;
- }
-
- private static IModel<String> cssClassFor(final ComponentFactory
componentFactory, final Label viewIcon) {
- IModel<String> cssClass = null;
- if (componentFactory instanceof CollectionContentsAsFactory) {
- val collectionContentsAsFactory = (CollectionContentsAsFactory)
componentFactory;
- cssClass = collectionContentsAsFactory.getCssClass();
- viewIcon.setDefaultModelObject("");
- viewIcon.setEscapeModelStrings(true);
+ private List<EitherComponentFactoryOrSectionLabel> sorted(final
Can<ComponentFactory> componentFactories) {
+ val presentations = sorted(componentFactories,
_Util.filterTablePresentations());
+ val exports = sorted(componentFactories, _Util.filterTableExports());
+ val sortedWithSeparators = new
ArrayList<EitherComponentFactoryOrSectionLabel>();
+
+ if(!presentations.isEmpty()) {
+
sortedWithSeparators.add(EitherComponentFactoryOrSectionLabel.of("Presentations"));
+ sortedWithSeparators.addAll(presentations);
}
- if (cssClass == null) {
- String name = componentFactory.getName();
- cssClass = Model.of(_Strings.asLowerDashed.apply(name));
- // Small hack: if there is no specific CSS class then we assume
that background-image is used
- // the span.ViewItemLink should have some content to show it
- // FIX: find a way to do this with CSS (width and height don't
seems to help)
- viewIcon.setDefaultModelObject("     ");
- viewIcon.setEscapeModelStrings(false);
+ if(!exports.isEmpty()) {
+
sortedWithSeparators.add(EitherComponentFactoryOrSectionLabel.of("Exports"));
+ sortedWithSeparators.addAll(exports);
}
- return cssClass;
+
+ return sortedWithSeparators;
}
- private static IModel<String> nameFor(final ComponentFactory
componentFactory) {
- IModel<String> name = null;
- if (componentFactory instanceof CollectionContentsAsFactory) {
- val collectionContentsAsFactory = (CollectionContentsAsFactory)
componentFactory;
- name = collectionContentsAsFactory.getTitleLabel();
- }
- if (name == null) {
- name = Model.of(componentFactory.getName());
- }
- return name;
+ private List<EitherComponentFactoryOrSectionLabel> sorted(
+ final Can<ComponentFactory> componentFactories,
+ final Predicate<? super ComponentFactory> filter) {
+ final List<EitherComponentFactoryOrSectionLabel> sorted =
componentFactories.stream()
+ .filter(filter)
+ .sorted(_Util.orderByOrderOfAppearanceInUiDropdown())
+ .map((final ComponentFactory
factory)->EitherComponentFactoryOrSectionLabel.of(factory))
+ .collect(Collectors.toList());
+ return sorted;
}
protected void setViewHintAndBroadcast(final String viewName, final
AjaxRequestTarget target) {
@@ -232,6 +224,45 @@ extends PanelAbstract<DataTableModel,
EntityCollectionModel> {
new CausewaySelectorEvent(component,
CollectionPresentationSelectorHelper.UIHINT_EVENT_VIEW_KEY, viewName, target));
}
+ @lombok.Value
+ static class EitherComponentFactoryOrSectionLabel implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public static EitherComponentFactoryOrSectionLabel of(final @NonNull
ComponentFactory componentFactory) {
+ return new EitherComponentFactoryOrSectionLabel(componentFactory,
null);
+ }
+ public static EitherComponentFactoryOrSectionLabel of(final @NonNull
String sectionLabel) {
+ return new EitherComponentFactoryOrSectionLabel(null,
sectionLabel);
+ }
+ final ComponentFactory componentFactory;
+ final String sectionLabel;
+ boolean isComponentFactory() { return componentFactory!=null; }
+ boolean isSectionLabel() { return sectionLabel!=null; }
+ boolean isSelectedIn(final CollectionPresentationSelectorPanel panel) {
+ return componentFactory == panel.selectedComponentFactory;
+ }
+ void markAsActive(final Label viewButtonIcon, final
AjaxLinkNoPropagate link) {
+ final IModel<String> cssClass =
_Util.cssClassFor(componentFactory, viewButtonIcon);
+ Wkt.cssReplace(viewButtonIcon, "ViewLinkItem " +
cssClass.getObject());
+ Wkt.cssAppend(link, "active");
+ }
+ // -- UTILITY
+ static void addLinkWithIconAndTitle(
+ final @NonNull ListItem<EitherComponentFactoryOrSectionLabel>
item,
+ final @NonNull MarkupContainer link) {
+ WktLinks.listItemAsDropdownLink(item, link,
+ ID_VIEW_ITEM_TITLE,
EitherComponentFactoryOrSectionLabel::nameFor,
+ ID_VIEW_ITEM_ICON, null,
+ EitherComponentFactoryOrSectionLabel::cssClassFor);
+ }
+ // -- HELPER
+ private static IModel<String> nameFor(final
EitherComponentFactoryOrSectionLabel either) {
+ return _Util.nameFor(either.getComponentFactory());
+ }
+ private static IModel<String> cssClassFor(final
EitherComponentFactoryOrSectionLabel either, final Label viewIcon) {
+ return _Util.cssClassFor(either.getComponentFactory(), viewIcon);
+ }
+ }
+
}
diff --git
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
new file mode 100644
index 0000000000..be36c613f3
--- /dev/null
+++
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
@@ -0,0 +1,90 @@
+/*
+ * 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.viewer.wicket.ui.components.collection.selector;
+
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.viewer.commons.model.components.UiComponentType;
+import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
+import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+
+import lombok.val;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+class _Util {
+
+ IModel<String> nameFor(final ComponentFactory componentFactory) {
+ IModel<String> name = null;
+ if (componentFactory instanceof CollectionContentsAsFactory) {
+ val collectionContentsAsFactory = (CollectionContentsAsFactory)
componentFactory;
+ name = collectionContentsAsFactory.getTitleLabel();
+ }
+ if (name == null) {
+ name = Model.of(componentFactory.getName());
+ }
+ return name;
+ }
+
+ IModel<String> cssClassFor(final ComponentFactory componentFactory, final
Label viewIcon) {
+ IModel<String> cssClass = null;
+ if (componentFactory instanceof CollectionContentsAsFactory) {
+ val collectionContentsAsFactory = (CollectionContentsAsFactory)
componentFactory;
+ cssClass = collectionContentsAsFactory.getCssClass();
+ viewIcon.setDefaultModelObject("");
+ viewIcon.setEscapeModelStrings(true);
+ }
+ if (cssClass == null) {
+ String name = componentFactory.getName();
+ cssClass = Model.of(_Strings.asLowerDashed.apply(name));
+ // Small hack: if there is no specific CSS class then we assume
that background-image is used
+ // the span.ViewItemLink should have some content to show it
+ // FIX: find a way to do this with CSS (width and height don't
seems to help)
+ viewIcon.setDefaultModelObject("     ");
+ viewIcon.setEscapeModelStrings(false);
+ }
+ return cssClass;
+ }
+
+ int orderOfAppearanceInUiDropdownFor(final ComponentFactory
componentFactory) {
+ return componentFactory instanceof CollectionContentsAsFactory
+ ? ((CollectionContentsAsFactory)
componentFactory).orderOfAppearanceInUiDropdown()
+ : Integer.MAX_VALUE;
+ }
+
+ Predicate<? super ComponentFactory> filterTablePresentations() {
+ return f->f.getComponentType() == UiComponentType.COLLECTION_CONTENTS;
+ }
+ Predicate<? super ComponentFactory> filterTableExports() {
+ return f->f.getComponentType() ==
UiComponentType.COLLECTION_CONTENTS_EXPORT;
+ }
+
+ Comparator<? super ComponentFactory>
orderByOrderOfAppearanceInUiDropdown() {
+ return (a, b)->Integer.compare(
+ _Util.orderOfAppearanceInUiDropdownFor(a),
+ _Util.orderOfAppearanceInUiDropdownFor(b));
+ }
+
+}
diff --git
a/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java
b/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java
deleted file mode 100644
index 71d3c7248d..0000000000
---
a/viewers/wicket/viewer/src/test/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.viewer.wicket.viewer.registries.components;
-
-import java.util.List;
-
-import org.apache.wicket.model.IModel;
-import org.hamcrest.Matchers;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.apache.causeway.viewer.commons.model.components.UiComponentType;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory.ApplicationAdvice;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
-import
org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorHelper;
-import
org.apache.causeway.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
-
-import lombok.val;
-
-class ComponentFactoryRegistryDefaultTest {
-
- private ComponentFactory one;
- private ComponentFactory two;
- private ComponentFactory ajaxTableComponentFactory;
-
- @BeforeEach
- void setUp() throws Exception {
-
- ajaxTableComponentFactory = new
CollectionContentsAsAjaxTablePanelFactory() {
- private static final long serialVersionUID = 1L;
- @Override public ApplicationAdvice appliesTo(final IModel<?>
model) {
- return ApplicationAdvice.APPLIES;
- }
- };
-
- one = Mockito.mock(ComponentFactoryAbstract.class);
-
Mockito.when(one.getComponentType()).thenReturn(UiComponentType.COLLECTION_CONTENTS);
- Mockito.when(one.appliesTo(UiComponentType.COLLECTION_CONTENTS,
null)).thenReturn(ApplicationAdvice.APPLIES);
-
- two = Mockito.mock(ComponentFactoryAbstract.class);
-
Mockito.when(two.getComponentType()).thenReturn(UiComponentType.COLLECTION_CONTENTS);
- Mockito.when(two.appliesTo(UiComponentType.COLLECTION_CONTENTS,
null)).thenReturn(ApplicationAdvice.APPLIES);
- }
-
- @Test
- void testOrderAjaxTableToEnd() {
-
- val compRegistry = ComponentFactoryRegistryDefault.forTesting(List.of(
- one,
- ajaxTableComponentFactory,
- two));
-
- val orderAjaxTableToEnd = new
CollectionPresentationSelectorHelper(null, compRegistry)
- .getComponentFactories();
-
- assertThat(orderAjaxTableToEnd, Matchers.contains(
- one,
- two,
- ajaxTableComponentFactory));
-
- }
-
-}