You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2026/05/17 05:32:48 UTC
(causeway) 01/01: CAUSEWAY-4000: PoC for advanced StandaloneValuePanel rendering
This is an automated email from the ASF dual-hosted git repository.
andi-huber pushed a commit to branch 4000-wkt.standalone.val
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 7fa86cf4bea7560919fe963cff05c85ed7a11795
Author: andi-huber <ah...@apache.org>
AuthorDate: Sun May 17 07:32:35 2026 +0200
CAUSEWAY-4000: PoC for advanced StandaloneValuePanel rendering
---
.../causeway/core/metamodel/util/Facets.java | 7 +-
.../valuesemantics/URLValueSemantics.java | 11 +-
.../wicket/model/models/PseudoAttributeModel.java | 172 +++++++++++++++++++++
.../viewer/wicket/model/models/UiAttributeWkt.java | 12 +-
.../model/models/coll/CollectionModelAbstract.java | 15 +-
.../ui/components/value/StandaloneValuePanel.java | 14 +-
.../StandaloneCollectionPage.java | 29 +---
7 files changed, 217 insertions(+), 43 deletions(-)
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/Facets.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/Facets.java
index d9dc855057d..d79bc43d309 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/Facets.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/Facets.java
@@ -317,12 +317,13 @@ public ManagedObject projected(final ManagedObject objectAdapter) {
.orElse(objectAdapter);
}
- public Optional<PromptStyle> promptStyle(final ObjectFeature feature) {
- return feature.lookupFacet(PromptStyleFacet.class)
+ public Optional<PromptStyle> promptStyle(final @Nullable ObjectFeature feature) {
+ return Optional.ofNullable(feature)
+ .flatMap(f->f.lookupFacet(PromptStyleFacet.class))
.map(PromptStyleFacet::value);
}
- public PromptStyle promptStyleOrElse(final ObjectFeature feature, final PromptStyle fallback) {
+ public PromptStyle promptStyleOrElse(final @Nullable ObjectFeature feature, final PromptStyle fallback) {
return Facets.promptStyle(feature)
.map(promptStyle->
promptStyle == PromptStyle.AS_CONFIGURED
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/URLValueSemantics.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/URLValueSemantics.java
index 5fe9eda063c..e28c3054d10 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/URLValueSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/URLValueSemantics.java
@@ -81,19 +81,18 @@ private java.net.URL parseUrlElseNull(final String url) {
@Override
public String titlePresentation(final ValueSemanticsProvider.Context context, final java.net.URL value) {
- return renderTitle(value, v->v.toString());
+ return renderTitle(value, URL::toString);
}
@Override
public String htmlPresentation(final ValueSemanticsProvider.Context context, final java.net.URL value) {
- return renderHtml(value, v->toHtmlLink(v));
+ return renderHtml(value, this::toHtmlLink);
}
private String toHtmlLink(final java.net.URL url) {
- return String.format("<a "
- + "target=\"_blank\" "
- + "class=\"no-click-bubbling\" "
- + "href=\"%s\">%s</a>", url.toString(), url.toString());
+ return """
+ <a target="_blank" class="no-click-bubbling" href="%s">%s</a>"""
+ .formatted(url.toString(), url.toString());
}
// -- PARSER
diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/PseudoAttributeModel.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/PseudoAttributeModel.java
new file mode 100644
index 00000000000..254c8ffe813
--- /dev/null
+++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/PseudoAttributeModel.java
@@ -0,0 +1,172 @@
+/*
+ * 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.model.models;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import org.jspecify.annotations.NonNull;
+
+import org.apache.causeway.commons.binding.Bindable;
+import org.apache.causeway.commons.binding.Observable;
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.binding._Bindables;
+import org.apache.causeway.commons.internal.binding._Observables;
+import org.apache.causeway.core.metamodel.commons.ViewOrEditMode;
+import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
+import org.apache.causeway.core.metamodel.facetapi.FeatureType;
+import org.apache.causeway.core.metamodel.interactions.managed.InteractionVeto;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedValue;
+import org.apache.causeway.core.metamodel.object.ManagedObject;
+import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
+import org.apache.causeway.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.causeway.viewer.commons.model.hints.RenderingHint;
+
+/**
+ * Wraps a {@link ValueModel}. Used for rendering standalone values.
+ */
+class PseudoAttributeModel extends UiAttributeWkt {
+
+ private static final long serialVersionUID = 1L;
+
+ final ValueModel valueModel;
+
+ //TODO don't serialize the value object twice
+ protected PseudoAttributeModel(final ValueModel valueModel) {
+ super(UiObjectWkt.ofAdapter(valueModel.getObject()), ViewOrEditMode.VIEWING, RenderingHint.STANDALONE_PROPERTY_COLUMN);
+ this.valueModel = valueModel;
+ }
+
+ record PseudoFeature(ValueModel valueModel) implements ObjectFeature {
+ @Override public FeatureType getFeatureType() {
+ return FeatureType.OBJECT;
+ }
+ @Override public FacetHolder getFacetHolder() {
+ return valueModel.elementType();
+ }
+ @Override public String getId() {
+ return "$value";
+ }
+ @Override public String getFriendlyName(final Supplier<ManagedObject> domainObjectProvider) {
+ return "value";
+ }
+ @Override public Optional<String> getStaticFriendlyName() {
+ return Optional.of("value");
+ }
+ @Override public String getCanonicalFriendlyName() {
+ return "value";
+ }
+ @Override public Optional<String> getDescription(final Supplier<ManagedObject> domainObjectProvider) {
+ return Optional.empty();
+ }
+ @Override public Optional<String> getStaticDescription() {
+ return Optional.empty();
+ }
+ @Override public Optional<String> getCanonicalDescription() {
+ return Optional.empty();
+ }
+ @Override public ObjectSpecification getElementType() {
+ return valueModel.elementType();
+ }
+ @Override public String asciiId() {
+ return "$value";
+ }
+ }
+
+ record PseudoManagedValue(ValueModel valueModel) implements ManagedValue {
+
+ @Override public ObjectSpecification getElementType() {
+ return valueModel.elementType();
+ }
+ @Override public Bindable<ManagedObject> getValue() {
+ return _Bindables.forValue(valueModel.getObject());
+ }
+ @Override public Observable<String> getValueAsTitle() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ @Override public Observable<String> getValueAsHtml() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ @Override public boolean isValueAsParsableTextSupported() {
+ return false;
+ }
+ @Override
+ public Bindable<String> getValueAsParsableText() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ @Override public Observable<String> getValidationMessage() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ @Override public Bindable<String> getSearchArgument() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ @Override public Observable<Can<ManagedObject>> getChoices() {
+ return _Observables.lazy(Can::empty);
+ }
+ }
+
+ @Override
+ public ObjectFeature getMetaModel() {
+ return new PseudoFeature(valueModel);
+ }
+
+ @Override
+ public ManagedObject getOwner() {
+ return valueModel.getObject();
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "$value";
+ }
+
+ @Override
+ public ManagedValue proposedValue() {
+ return new PseudoManagedValue(valueModel);
+ }
+
+ @Override
+ protected String toStringOf() {
+ return "%s[%s]".formatted(getClass().getName(), getIdentifier());
+ }
+
+ @Override public boolean isSingular() { return true; }
+ @Override public boolean isPlural() { return false; }
+ @Override public boolean isProperty() { return false; }
+ @Override public boolean isParameter() { return false; }
+
+ @Override public String getCssClass() { return null; }
+ @Override public boolean whetherHidden() { return false; }
+ @Override public Optional<InteractionVeto> disabledReason() { return Optional.empty(); }
+ @Override public int getAutoCompleteMinLength() { return 0; }
+ @Override public ManagedObject getDefault() { return valueModel.getObject(); }
+ @Override public boolean hasChoices() { return false; }
+ @Override public boolean hasAutoComplete() { return false; }
+ @Override public Can<ManagedObject> getChoices() { return Can.empty(); }
+ @Override public Can<ManagedObject> getAutoComplete(final String searchArg) { return Can.empty(); }
+ @Override public String validate(@NonNull final ManagedObject proposedAdapter) { return null; }
+ @Override protected Can<ObjectAction> calcAssociatedActions() { return Can.empty(); }
+
+}
diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiAttributeWkt.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiAttributeWkt.java
index 90c1d8319e4..56857af14bc 100644
--- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiAttributeWkt.java
+++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/UiAttributeWkt.java
@@ -67,6 +67,13 @@ public abstract class UiAttributeWkt
private static final long serialVersionUID = 1L;
+ /**
+ * Returns a readonly variant, for standalone value rendering.
+ */
+ public static UiAttributeWkt pseudo(final ValueModel valueModel) {
+ return new PseudoAttributeModel(valueModel);
+ }
+
private final UiObjectWkt parentEntityModel;
@Getter(onMethod_={@Override})
@@ -215,11 +222,10 @@ public InlinePromptContext getInlinePromptContext() {
}
public void setInlinePromptContext(final InlinePromptContext inlinePromptContext) {
- if (this.inlinePromptContext != null) {
- // otherwise the components created for an property edit inline prompt will overwrite the original
+ if (this.inlinePromptContext != null)
+ // otherwise the components created for an property edit inline prompt will overwrite the original
// components on the underlying page (which we go back to if the prompt is cancelled).
return;
- }
this.inlinePromptContext = inlinePromptContext;
}
diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/coll/CollectionModelAbstract.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/coll/CollectionModelAbstract.java
index 6222bb9cb42..8287d651d40 100644
--- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/coll/CollectionModelAbstract.java
+++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/models/coll/CollectionModelAbstract.java
@@ -19,10 +19,13 @@
package org.apache.causeway.viewer.wicket.model.models.coll;
import java.util.List;
+import java.util.Optional;
import org.apache.wicket.model.LoadableDetachableModel;
+import org.jspecify.annotations.NonNull;
import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.internal.collections._Lists;
import org.apache.causeway.core.metamodel.object.ManagedObject;
@@ -32,8 +35,6 @@
import org.apache.causeway.viewer.wicket.model.models.ActionModel;
import org.apache.causeway.viewer.wicket.model.models.interaction.BookmarkedObjectWkt;
-import org.jspecify.annotations.NonNull;
-
/**
* Represents a collection (a member) of a domain object.
*
@@ -118,6 +119,16 @@ public final Variant getVariant() {
return variant;
}
+ // -- BOOKMARKS
+
+ public List<Bookmark> bookmarks() {
+ return getDataTableModel().dataElementsObservable().getValue().stream()
+ .map(ManagedObject::getBookmark)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .toList();
+ }
+
// -- LINKS PROVIDER
/**
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanel.java
index 77febf2f2c2..eddf7dd6c1b 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanel.java
@@ -23,18 +23,19 @@
import java.util.UUID;
import org.apache.causeway.applib.services.bookmark.idstringifiers.PredefinedSerializables;
+import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.LocalResourcePath;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.object.MmValueUtils;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
+import org.apache.causeway.viewer.wicket.model.models.UiAttributeWkt;
import org.apache.causeway.viewer.wicket.model.models.ValueModel;
-import org.apache.causeway.viewer.wicket.ui.components.attributes.AttributePanel;
+import org.apache.causeway.viewer.wicket.ui.components.attributes.blobclob.BlobAttributePanelFactory;
import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
import org.apache.causeway.viewer.wicket.ui.util.Wkt;
/**
- * Panel for rendering any value types that do not have their own custom
- * {@link AttributePanel panel} to render them.
+ * Panel for rendering value types.
*/
class StandaloneValuePanel
extends PanelAbstract<ManagedObject, ValueModel> {
@@ -45,6 +46,13 @@ class StandaloneValuePanel
public StandaloneValuePanel(final String id, final ValueModel valueModel) {
super(id, valueModel);
+ //TODO do this properly using ComponentFactory lookup
+ if(valueModel.elementType().getCorrespondingClass().equals(Blob.class)) {
+ Wkt.add(this, new BlobAttributePanelFactory()
+ .createComponent(ID_STANDALONE_VALUE, UiAttributeWkt.pseudo(valueModel)));
+ return;
+ }
+
//XXX StandaloneValuePanel has its limitations compared to the ScalarPanel infrastructure,
// which has better rendering support
// (we probably need to remove StandaloneValuePanel and utilize the AttributePanel for standalone values instead)
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/standalonecollection/StandaloneCollectionPage.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/standalonecollection/StandaloneCollectionPage.java
index 3df0ec9a864..0d875255ceb 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/standalonecollection/StandaloneCollectionPage.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/pages/standalonecollection/StandaloneCollectionPage.java
@@ -19,10 +19,8 @@
package org.apache.causeway.viewer.wicket.ui.pages.standalonecollection;
import java.util.List;
-import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import org.apache.wicket.Component;
import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
@@ -30,9 +28,8 @@
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.publishing.spi.PageRenderSubscriber;
import org.apache.causeway.applib.services.user.UserMemento;
-import org.apache.causeway.commons.binding.Observable;
import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
+import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.viewer.commons.model.components.UiComponentType;
import org.apache.causeway.viewer.wicket.model.models.coll.CollectionModelStandalone;
import org.apache.causeway.viewer.wicket.model.util.PageParameterUtils;
@@ -81,29 +78,9 @@ private void onRenderingOrRendered(
final Can<PageRenderSubscriber> objectRenderSubscribers,
final BiFunction<PageRenderSubscriber, Supplier<List<Bookmark>>, Void> handler) {
- if(objectRenderSubscribers.isEmpty()) {
- return;
- }
-
- Supplier<List<Bookmark>> listSupplier = () -> {
-
- final var dataTableInteractive = collectionModel.getObject();
- Observable<Can<ManagedObject>> dataElements = dataTableInteractive.dataElementsObservable();
- Can<ManagedObject> managedObjects = dataElements.getValue();
-
- final var bookmarks =
- managedObjects.stream()
- .map(ManagedObject::getBookmark)
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(Collectors.toUnmodifiableList());
- return bookmarks;
- };
-
+ var bookmarksLazy = _Lazy.threadSafe(collectionModel::bookmarks);
objectRenderSubscribers
- .forEach(subscriber -> {
- handler.apply(subscriber, listSupplier);
- });
+ .forEach(subscriber -> handler.apply(subscriber, bookmarksLazy::get));
}
}