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

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


The following commit(s) were added to refs/heads/v4 by this push:
     new f41101af4e2 CAUSEWAY-3904: select2 with object icons (embedded + fa)
f41101af4e2 is described below

commit f41101af4e2107fa83dff0d65b233cb91e878b7a
Author: Andi Huber <[email protected]>
AuthorDate: Thu Sep 18 22:08:47 2025 +0200

    CAUSEWAY-3904: select2 with object icons (embedded + fa)
    
    - resource based icons not yet supported
---
 .../causeway/applib/annotation/ObjectSupport.java  |   3 +-
 .../objectmanager/memento/ObjectDisplayDto.java    |  57 ++++++++++
 .../objectmanager/memento/ObjectMemento.java       | 122 ++++++++++++++-------
 .../objectmanager/memento/ObjectMementoEmpty.java  |  16 +--
 .../memento/ObjectMementoSingular.java             |  37 ++-----
 .../select2/ChoiceProviderForReferencesTest.java   |   3 +-
 .../select2/ChoiceProviderForValuesTest.java       |   2 +-
 .../select2/ChoiceProviderTestAbstract.java        |   1 +
 .../object/icontitle/ObjectIconAndTitlePanel.html  |   2 +-
 .../components/widgets/select2/ChoiceProvider.java |  51 ++-------
 .../ui/components/widgets/select2/MultiChoice.java |   2 -
 .../widgets/select2/OnSelectBehavior.java          |   6 +-
 .../ui/components/widgets/select2/Select2.java     |  35 ++++--
 .../components/widgets/select2/SingleChoice.java   |   2 -
 .../ConverterForObjectAdapterMemento.java          |  69 ------------
 .../wicketapp/CausewayWicketApplication.java       |   3 -
 16 files changed, 195 insertions(+), 216 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java
index 12099207260..345ea50ff95 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/annotation/ObjectSupport.java
@@ -18,6 +18,7 @@
  */
 package org.apache.causeway.applib.annotation;
 
+import java.io.Serializable;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
@@ -60,7 +61,7 @@ public enum IconWhere {
         TABLE_ROW //TODO also TREE_NODE and SELECT_DROPDOWN
     }
 
-    public sealed interface IconResource
+    public sealed interface IconResource extends Serializable
     permits ClassPathIconResource, FontAwesomeIconResource, 
EmbeddedIconResource {
 
     }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectDisplayDto.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectDisplayDto.java
new file mode 100644
index 00000000000..569fd231eef
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectDisplayDto.java
@@ -0,0 +1,57 @@
+/*
+ *  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.objectmanager.memento;
+
+import java.io.Serializable;
+import org.jspecify.annotations.Nullable;
+
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.commons.io.JsonUtils;
+
+/**
+ * Provides a summary of the domain object for rendering,
+ * having (translated) title and icon.
+ *
+ * @implSpec works hand in hand with select2 (third-party) java-script
+ *  and 
org.apache.causeway.viewer.wicket.ui.components.widgets.select2.Select2 
template configuration.
+ */
+public record ObjectDisplayDto(
+    Class<?> correspondingClass,
+    String bookmark,
+    String title,
+    @Nullable String iconHtml) implements Serializable {
+
+    public static ObjectDisplayDto fromJson(String json) {
+        return JsonUtils.tryRead(ObjectDisplayDto.class, json)
+            .valueAsNonNullElseFail();
+    }
+
+    public static ObjectDisplayDto fromJsonBase64(String base64EncodedJson) {
+        return fromJson(_Strings.base64UrlDecode(base64EncodedJson));
+    }
+
+    public String toJson() {
+        return JsonUtils.toStringUtf8(this);
+    }
+
+    public String toJsonBase64() {
+        return _Strings.base64UrlEncode(toJson());
+    }
+
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMemento.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMemento.java
index b23f5d5a818..d53678f7578 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMemento.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMemento.java
@@ -19,22 +19,31 @@
 package org.apache.causeway.core.metamodel.objectmanager.memento;
 
 import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Optional;
 
 import org.jspecify.annotations.Nullable;
 
+import org.springframework.util.StringUtils;
+
+import org.apache.causeway.applib.annotation.ObjectSupport.IconWhere;
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.commons.internal.base._Bytes;
-import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.applib.services.i18n.TranslationContext;
+import 
org.apache.causeway.applib.services.placeholder.PlaceholderRenderService;
+import 
org.apache.causeway.applib.services.placeholder.PlaceholderRenderService.PlaceholderLiteral;
+import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.internal.collections._Lists;
-import org.apache.causeway.commons.internal.resources._Serializables;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon;
+import 
org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconEmbedded;
+import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconFa;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.object.MmAssertionUtils;
+import org.apache.causeway.core.metamodel.object.MmHintUtils;
+import org.apache.causeway.core.metamodel.object.MmTitleUtils;
 
 /**
  * @since 2.0
@@ -43,41 +52,59 @@ public sealed interface ObjectMemento
 extends Serializable
 permits ObjectMementoEmpty, ObjectMementoSingular, ObjectMementoPacked {
 
-    /** arbitrary/random string */
-    static final String NULL_ID = "VGN6r6zKTiLhUsA0WkdQ17LvMU1IYdb0";
-
     LogicalType logicalType();
     Bookmark bookmark();
 
     /**
      * The object's title for rendering (before translation).
      * Corresponds to {@link ManagedObject#getTitle()}.
-     * <p>
-     * Directly support choice rendering, without the need to (re-)fetch 
entire object graphs.
-     * (TODO translated or not?)
+     *
+     * <p>Directly support choice rendering, without the need to (re-)fetch 
entire object graphs.
+     * (pre-translated)
      */
     String title();
 
     // -- FACTORIES
 
     static ObjectMemento empty(final LogicalType logicalType) {
-        return new ObjectMementoEmpty(logicalType);
+        return new ObjectMementoEmpty(
+            logicalType,
+            
PlaceholderRenderService.fallback().asText(PlaceholderLiteral.NULL_REPRESENTATION));
     }
-    static Optional<ObjectMemento> singular(final @Nullable ManagedObject 
adapter) {
-        return ManagedObjects.isNullOrUnspecifiedOrEmpty(adapter)
-                ? Optional.empty()
-                : Optional.of(ObjectMementoSingular.create(adapter));
+
+    static Optional<ObjectMemento> singular(
+            final @Nullable ManagedObject adapter) {
+        if(ManagedObjects.isNullOrUnspecifiedOrEmpty(adapter))
+            return Optional.empty();
+
+        var spec = adapter.objSpec();
+
+        _Assert.assertTrue(spec.isIdentifiable()
+                || spec.isParented()
+                || spec.isValue(), ()->"Don't know how to create an 
ObjectMemento for a type "
+                        + "with ObjectSpecification %s. "
+                        + "All other strategies failed. Type is neither "
+                        + "identifiable (isManagedBean() || isViewModel() || 
isEntity()), "
+                        + "nor is a 'parented' Collection, "
+                        + "nor has 'encodable' semantics, nor is (Serializable 
|| Externalizable)"
+                        .formatted(spec));
+
+        return Optional.ofNullable(new ObjectMementoSingular(
+                adapter.logicalType(),
+                MmHintUtils.bookmarkElseFail(adapter),
+                
adapter.getTranslationService().translate(TranslationContext.empty(), 
MmTitleUtils.titleOf(adapter)),
+                iconToHtml(adapter.getIcon(IconWhere.TABLE_ROW))));
     }
     /**
      * returns null for null
      */
-    @Nullable static ObjectMemento singularOrEmpty(final @Nullable 
ManagedObject adapter) {
+    @Nullable static ObjectMemento singularOrEmpty(
+            final @Nullable ManagedObject adapter) {
         MmAssertionUtils.assertPojoIsScalar(adapter);
-        return !ManagedObjects.isNullOrUnspecifiedOrEmpty(adapter)
-                ? ObjectMementoSingular.create(adapter)
-                : ManagedObjects.isSpecified(adapter)
+        return singular(adapter)
+            .orElseGet(()->ManagedObjects.isSpecified(adapter)
                     ? ObjectMemento.empty(adapter.logicalType())
-                    : null;
+                    : null);
     }
     static ObjectMemento packed(
             final LogicalType logicalType,
@@ -93,36 +120,51 @@ static ObjectMemento packed(
                 ? orig
                 : _Lists.newArrayList(container);
         return new ObjectMementoPacked(logicalType, arrayList);
-
     }
 
     // -- UTILITY
 
-    @Nullable
-    static String enstringToUrlBase64(final @Nullable ObjectMemento memento) {
-        var base64UrlEncodedMemento = memento!=null
-                ? _Strings.ofBytes(
-                    _Bytes.asUrlBase64.apply(
-                            _Serializables.write(memento)),
-                    StandardCharsets.US_ASCII)
-                : null;
-        return base64UrlEncodedMemento;
+    private static String iconToHtml(@Nullable ObjectIcon objectIcon) {
+        //if(true) return "<i class=\"%s\"></i>".formatted("fa-solid 
fa-thumbs-up");
+
+        //TODO not supported yet as requires resource caching from wicket
+//        if(objectIcon instanceof ObjectIconUrlBased urlBased)
+//            return "<img src=\"" + urlBased.url().toExternalForm() + "\"/>";
+        if(objectIcon instanceof ObjectIconEmbedded embedded)
+            return "<img src=\"" + embedded.dataUri().toExternalForm() + 
"\"/>";
+        if(objectIcon instanceof ObjectIconFa fa)
+            return fa.fontAwesomeLayers().toHtml();
+
+        return null;
     }
 
-    @Nullable
-    static ObjectMemento destringFromUrlBase64(final @Nullable String 
base64UrlEncodedMemento) {
+    static ObjectMemento fromDto(final ObjectDisplayDto dto) {
+        var bookmark = Bookmark.parse(dto.bookmark()).orElseThrow();
+        var logicalType = new LogicalType(bookmark.logicalTypeName(), 
dto.correspondingClass());
+        return bookmark.isEmpty()
+            ? new ObjectMementoEmpty(logicalType, dto.title())
+            : new ObjectMementoSingular(logicalType, bookmark, dto.title(), 
dto.iconHtml());
+    }
+
+    static String enstringToBase64(final ObjectMemento memento) {
+        if(memento instanceof ObjectMementoEmpty objectMementoEmpty)
+            return objectMementoEmpty.toDto().toJsonBase64();
+        if(memento instanceof ObjectMementoSingular objectMementoSingular)
+            return objectMementoSingular.toDto().toJsonBase64();
+
+        throw _Exceptions.unexpectedCodeReach();
+    }
+
+    static ObjectMemento destringFromBase64(final String base64EncodedDto) {
+        if(!StringUtils.hasLength(base64EncodedDto))
+            throw _Exceptions.unexpectedCodeReach();
+
         try {
-            return _Strings.isNotEmpty(base64UrlEncodedMemento)
-                    && !NULL_ID.equals(base64UrlEncodedMemento)
-                    ? _Serializables.read(
-                            ObjectMemento.class,
-                            _Bytes.ofUrlBase64.apply(
-                                    
base64UrlEncodedMemento.getBytes(StandardCharsets.US_ASCII)))
-                    : null;
+            return fromDto(ObjectDisplayDto.fromJsonBase64(base64EncodedDto));
         } catch (Exception e) {
+            e.printStackTrace();
             return null; // map to null if anything goes wrong
         }
-
     }
 
     default boolean isEmpty() {
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoEmpty.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoEmpty.java
index 7d4953d98fc..967f346b545 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoEmpty.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoEmpty.java
@@ -20,23 +20,19 @@
 
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
-import 
org.apache.causeway.applib.services.placeholder.PlaceholderRenderService;
-import 
org.apache.causeway.applib.services.placeholder.PlaceholderRenderService.PlaceholderLiteral;
-
-import org.jspecify.annotations.NonNull;
 
 record ObjectMementoEmpty(
-        @NonNull LogicalType logicalType)
+        LogicalType logicalType,
+        String title)
 implements ObjectMemento {
 
-    @Override
-    public String title() {
-        return 
PlaceholderRenderService.fallback().asText(PlaceholderLiteral.NULL_REPRESENTATION);
-    }
-
     @Override
     public Bookmark bookmark() {
         return Bookmark.empty(logicalType);
     }
 
+    public ObjectDisplayDto toDto() {
+        return new ObjectDisplayDto(logicalType.correspondingClass(), 
bookmark().stringify(), title, null);
+    }
+
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoSingular.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoSingular.java
index 8cf9f03b4a7..7f3980bbf47 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoSingular.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/memento/ObjectMementoSingular.java
@@ -18,43 +18,24 @@
  */
 package org.apache.causeway.core.metamodel.objectmanager.memento;
 
+import org.jspecify.annotations.Nullable;
+
 import org.apache.causeway.applib.id.LogicalType;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.commons.internal.assertions._Assert;
-import org.apache.causeway.core.metamodel.object.MmHintUtils;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.object.MmTitleUtils;
-
-import org.jspecify.annotations.NonNull;
 
 record ObjectMementoSingular(
-        @NonNull LogicalType logicalType,
-        @NonNull Bookmark bookmark,
-        String title)
+        LogicalType logicalType,
+        Bookmark bookmark,
+        String title,
+        @Nullable String iconHtml)
 implements ObjectMemento {
 
-    // -- FACTORIES
-
-    static ObjectMementoSingular create(final @NonNull ManagedObject adapter) {
-        var spec = adapter.objSpec();
-
-        _Assert.assertTrue(spec.isIdentifiable()
-                || spec.isParented()
-                || spec.isValue(), ()->"Don't know how to create an 
ObjectMemento for a type "
-                        + "with ObjectSpecification %s. "
-                        + "All other strategies failed. Type is neither "
-                        + "identifiable (isManagedBean() || isViewModel() || 
isEntity()), "
-                        + "nor is a 'parented' Collection, "
-                        + "nor has 'encodable' semantics, nor is (Serializable 
|| Externalizable)"
-                        .formatted(spec));
-
-        return new ObjectMementoSingular(
-                adapter.logicalType(),
-                MmHintUtils.bookmarkElseFail(adapter),
-                MmTitleUtils.titleOf(adapter));
+    public ObjectDisplayDto toDto() {
+        return new ObjectDisplayDto(logicalType.correspondingClass(), 
bookmark.stringify(), title, iconHtml);
     }
 
     @Override public int hashCode() { return bookmark.hashCode(); }
+
     @Override public boolean equals(final Object o) {
         return (o instanceof ObjectMementoSingular other)
                 ? this.bookmark.equals(other.bookmark)
diff --git 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForReferencesTest.java
 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForReferencesTest.java
index 625c5b31f49..51f1859e7ca 100644
--- 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForReferencesTest.java
+++ 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForReferencesTest.java
@@ -65,7 +65,8 @@ void roundtrip() {
             System.err.printf("displayValue: %s%n", 
choiceProvider.getDisplayValue(memento));
         });*/
 
-        var asIds = mementos.map(choiceProvider::getIdValue);
+
+        var asIds = 
mementos.map(choiceProvider.toSelect2ChoiceProvider()::getIdValue);
 
         var recoveredMementos = 
Can.ofCollection(choiceProvider.toChoices(asIds.toList()));
 
diff --git 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForValuesTest.java
 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForValuesTest.java
index aa215979379..ec58bdb82f0 100644
--- 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForValuesTest.java
+++ 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderForValuesTest.java
@@ -68,7 +68,7 @@ void roundtrip() {
 //            System.err.printf("displayValue: %s%n", 
choiceProvider.getDisplayValue(memento));
 //        });
 
-        var asIds = mementos.map(choiceProvider::getIdValue);
+        var asIds = 
mementos.map(choiceProvider.toSelect2ChoiceProvider()::getIdValue);
 
         var recoveredMementos = 
Can.ofCollection(choiceProvider.toChoices(asIds.toList()));
 
diff --git 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderTestAbstract.java
 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderTestAbstract.java
index ff32c17d8f4..995a36b8575 100644
--- 
a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderTestAbstract.java
+++ 
b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProviderTestAbstract.java
@@ -38,6 +38,7 @@ abstract class ChoiceProviderTestAbstract {
     protected void setUp() {
         mmc = MetaModelContext_forTesting.builder()
                 .memberExecutor(mock(MemberExecutorService.class))
+                .objectIconService((managedObject, iconWhere) -> null)
                 .build()
                 .withValueSemantics(new BigDecimalValueSemantics())
                 .withValueSemantics(new IntValueSemantics())
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.html
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.html
index 63fb3a1d88e..8eb166fb6a6 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.html
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/object/icontitle/ObjectIconAndTitlePanel.html
@@ -20,7 +20,7 @@
 <html xmlns:wicket="http://wicket.apache.org";>
     <body>
         <wicket:panel>
-             <span wicket:id="objectLinkWrapper" 
class="objectIconAndTitlePanel objectIconAndTitleComponentType">
+             <span wicket:id="objectLinkWrapper" 
class="objectIconAndTitlePanel">
                  <a href="#" wicket:id="objectLink" class="objectUrlSource">
                      <span wicket:id="objectIconFaLeft"></span>
                      <img wicket:id="objectIcon"/>
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProvider.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProvider.java
index 36b912e9e2f..4b57528c2a6 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProvider.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/ChoiceProvider.java
@@ -26,8 +26,6 @@
 import org.jspecify.annotations.Nullable;
 import org.wicketstuff.select2.Response;
 
-import org.apache.causeway.applib.services.i18n.TranslationContext;
-import 
org.apache.causeway.applib.services.placeholder.PlaceholderRenderService.PlaceholderLiteral;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
@@ -48,31 +46,6 @@ public ChoiceProvider(
         this(attributeModel, 
UiAttribute.ChoiceProviderSort.valueOf(attributeModel));
     }
 
-    /**
-     * Get the value for displaying to an end user.
-     */
-    public String getDisplayValue(final ObjectMemento choiceMemento) {
-        if (choiceMemento == null
-                || choiceMemento.isEmpty()) {
-            return 
getPlaceholderRenderService().asText(PlaceholderLiteral.NULL_REPRESENTATION);
-        }
-        return translate(choiceMemento.title());
-    }
-
-    /**
-     * This method is called to get the id value of an object (used as the 
value attribute of a
-     * choice element) The id can be extracted from the object like a primary 
key, or if the list is
-     * stable you could just return a toString of the index.
-     * <p>
-     * Note that the given index can be {@code -1} if the object in question 
is not contained in the
-     * available choices.
-     */
-    public String getIdValue(final ObjectMemento choiceMemento) {
-        if (choiceMemento == null) return ObjectMemento.NULL_ID;
-
-        return ObjectMemento.enstringToUrlBase64(choiceMemento);
-    }
-
     /**
      * Queries application for choices that match the search {@code term} and 
adds them to the
      * {@code response}
@@ -91,7 +64,7 @@ public void query(
 
         // else, if not mandatory, prepend null
         var mementosIncludingNull = mementosFiltered.toArrayList();
-        mementosIncludingNull.add(0, null);
+        mementosIncludingNull.add(0, 
ObjectMemento.empty(attributeModel.getElementType().logicalType()));
 
         response.addAll(mementosIncludingNull);
     }
@@ -103,7 +76,7 @@ public void query(
      */
     public Collection<ObjectMemento> toChoices(final Collection<String> ids) {
         return _NullSafe.stream(ids)
-                .map(this::mementoFromIdWithNullHandling)
+                .map(this::mementoFromId)
                 .collect(Collectors.toList());
     }
 
@@ -122,10 +95,10 @@ org.wicketstuff.select2.ChoiceProvider<ObjectMemento> 
toSelect2ChoiceProvider()
                 delegate.query(term, page, response);
             }
             @Override public String getIdValue(final ObjectMemento object) {
-                return delegate.getIdValue(object);
+                return ObjectMemento.enstringToBase64(object);
             }
             @Override public String getDisplayValue(final ObjectMemento 
object) {
-                return delegate.getDisplayValue(object);
+                return null; // not needed, already encoded into the 'id'
             }
         };
     }
@@ -177,12 +150,6 @@ private Can<ManagedObject> reconstructPendingArgs(
        return pendingArgsList;
     }
 
-    private @Nullable ObjectMemento mementoFromIdWithNullHandling(final String 
id) {
-        if(ObjectMemento.NULL_ID.equals(id)) return null;
-
-        return mementoFromId(id);
-    }
-
     /**
      * Whether to not prepend <code>null</code> as choice candidate.
      */
@@ -216,18 +183,14 @@ private Can<ObjectMemento> filter(
 
         if (Strings.isEmpty(term)) return choiceMementos;
 
-        var translationContext = TranslationContext.empty();
-        var translator = getTranslationService();
         var termLower = term.toLowerCase();
 
-        return choiceMementos.filter((final ObjectMemento candidateMemento)->{
-            var title = translator.translate(translationContext, 
candidateMemento.title());
-            return title.toLowerCase().contains(termLower);
-        });
+        return choiceMementos.filter((final ObjectMemento candidateMemento)->
+            candidateMemento.title().toLowerCase().contains(termLower));
     }
 
     private @Nullable ObjectMemento mementoFromId(final @Nullable String id) {
-        return ObjectMemento.destringFromUrlBase64(id);
+        return ObjectMemento.destringFromBase64(id);
     }
 
 }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/MultiChoice.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/MultiChoice.java
index 007d96dbba6..d5ae9b4a437 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/MultiChoice.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/MultiChoice.java
@@ -63,8 +63,6 @@ record MultiChoice(
                 // --
 
             });
-        
setDelay(Math.toIntExact(getConfiguration().viewer().wicket().select2AjaxDelay().toMillis()));
-        component().setRequired(attributeModel.isRequired());
     }
 
     @Override
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/OnSelectBehavior.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/OnSelectBehavior.java
index 134d0fac404..0d048685459 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/OnSelectBehavior.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/OnSelectBehavior.java
@@ -111,7 +111,7 @@ private void updatePendingModels() {
             Event.valueOf(pair)
             .ifPresent(event->{
                 if(getComponent() instanceof Select2MultiChoice 
select2MultiChoice) {
-                    var objectMementoFromEvent = 
ObjectMemento.destringFromUrlBase64(pair.getValue());
+                    var objectMementoFromEvent = 
ObjectMemento.destringFromBase64(pair.getValue());
                     if(objectMementoFromEvent==null) return; // add or remove 
nothing is a no-op
 
                     var component = 
_Casts.<Select2MultiChoice<ObjectMemento>>uncheckedCast(select2MultiChoice);
@@ -141,7 +141,7 @@ private void updatePendingModels() {
                     var component = 
_Casts.<Select2Choice<ObjectMemento>>uncheckedCast(select2Choice);
                     switch(event) {
                     case SELECT:
-                        var objectMementoFromEvent = 
ObjectMemento.destringFromUrlBase64(pair.getValue());
+                        var objectMementoFromEvent = 
ObjectMemento.destringFromBase64(pair.getValue());
                         if(objectMementoFromEvent==null) {
                             // select nothing is rather a CLEAR operation
                             component.clearInput();
@@ -161,7 +161,7 @@ private void updatePendingModels() {
                 } else return;
 
                 if(XrayUi.isXrayEnabled()) {
-                    var objectMementoFromEvent = 
ObjectMemento.destringFromUrlBase64(pair.getValue());
+                    var objectMementoFromEvent = 
ObjectMemento.destringFromBase64(pair.getValue());
                     if(objectMementoFromEvent!=null) {
                         _XrayEvent.event("Select2 event: %s %s", event, 
objectMementoFromEvent.bookmark());
                     } else {
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/Select2.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/Select2.java
index b33e1de6a20..e049d9e7698 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/Select2.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/Select2.java
@@ -34,7 +34,8 @@
 import org.apache.causeway.viewer.wicket.model.models.UiAttributeWkt;
 import 
org.apache.causeway.viewer.wicket.ui.components.attributes.AttributeModelChangeDispatcher;
 
-public interface Select2 extends Serializable {
+public sealed interface Select2 extends Serializable
+permits SingleChoice, MultiChoice {
 
     static Select2 create(
         final String id,
@@ -56,6 +57,23 @@ static Select2 create(
         settings.setDropdownAutoWidth(true);
         settings.setWidth("100%");
         settings.setPlaceholder(attributeModel.getFriendlyName());
+        // the id string is url-safe base64 encoded JSON coming from 
ObjectDisplayDto
+        var template = """
+            function(opt) {
+                if(!opt) return "undefined";
+                if(!opt.id) return "undefined";
+                var base64 = opt.id.replace(/-/g, '+').replace(/_/g, '/')
+                var dto = JSON.parse(atob(base64));
+                if(!dto) return "undefined";
+                if(!dto.title) return "undefined";
+                if(dto.iconHtml) {
+                    return $('<span>' + dto.iconHtml + ' ' + dto.title + 
'</span>');
+                }
+                return dto.title;
+            }""";
+
+        settings.setTemplateResult(template);
+        settings.setTemplateSelection(template);
 
         switch(attributeModel.getChoiceProviderSort()) {
             case 
AUTO_COMPLETE->settings.setMinimumInputLength(attributeModel.getAutoCompleteMinLength());
@@ -64,6 +82,11 @@ static Select2 create(
             case CHOICES, NO_CHOICES->{}
         }
 
+        component.setRequired(attributeModel.isRequired());
+        // time to wait for the user to stop typing before issuing the ajax 
request.
+        component.getSettings().getAjax(true)
+            
.setDelay(Math.toIntExact(attributeModel.getConfiguration().viewer().wicket().select2AjaxDelay().toMillis()));
+
         component.setOutputMarkupPlaceholderTag(true);
         component.setLabel(Model.of(attributeModel.getFriendlyName()));
 
@@ -100,14 +123,4 @@ default void setMutable(final boolean mutability) {
         component().setEnabled(mutability);
     }
 
-    /**
-     * The number of milliseconds to wait for the user to stop typing before
-     * issuing the ajax request.
-     * @param millis
-     */
-    default void setDelay(final int millis) {
-        var ajaxSettings = component().getSettings().getAjax(true);
-        ajaxSettings.setDelay(millis);
-    }
-
 }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/SingleChoice.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/SingleChoice.java
index 30f15d6b71b..c84408c06c7 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/SingleChoice.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/select2/SingleChoice.java
@@ -35,8 +35,6 @@ record SingleChoice(Select2Choice<ObjectMemento> component)
             final UiAttributeWkt attributeModel,
             final ChoiceProvider choiceProvider) {
         this(new Select2Choice<>(id, model, 
choiceProvider.toSelect2ChoiceProvider()));
-        
setDelay(Math.toIntExact(getConfiguration().viewer().wicket().select2AjaxDelay().toMillis()));
-        component().setRequired(attributeModel.isRequired());
     }
 
     @Override
diff --git 
a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/ConverterForObjectAdapterMemento.java
 
b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/ConverterForObjectAdapterMemento.java
deleted file mode 100644
index 880ae61a7ad..00000000000
--- 
a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/ConverterForObjectAdapterMemento.java
+++ /dev/null
@@ -1,69 +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.integration;
-
-import java.util.Locale;
-
-import org.apache.wicket.util.convert.IConverter;
-
-import org.apache.causeway.applib.services.bookmark.Bookmark;
-import org.apache.causeway.applib.services.bookmark.Oid;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMemento;
-
-import lombok.RequiredArgsConstructor;
-
-/**
- * Implementation of a Wicket {@link IConverter} for
- * {@link ObjectMemento}s, converting to-and-from their stringified {@link 
Bookmark}s.
- */
-@RequiredArgsConstructor
-public class ConverterForObjectAdapterMemento implements 
IConverter<ObjectMemento> {
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * Converts string representation of {@link Oid} to
-     * {@link ObjectMemento}.
-     */
-    @Override
-    public ObjectMemento convertToObject(
-            final String base64UrlEncodedMemento, final Locale locale) {
-        var obj = ObjectMemento.destringFromUrlBase64(base64UrlEncodedMemento);
-
-        //XXX ever used ?
-        System.err.printf("ConverterForObjectAdapterMemento: convertTo 
ObjectMemento %s->%s%n", base64UrlEncodedMemento, obj);
-
-        return obj;
-    }
-
-    /**
-     * Converts {@link ObjectMemento} to string representation of
-     * {@link Bookmark}.
-     */
-    @Override
-    public String convertToString(final ObjectMemento memento, final Locale 
locale) {
-        var string = ObjectMemento.enstringToUrlBase64(memento);
-
-        //XXX ever used ?
-        System.err.printf("ConverterForObjectAdapterMemento: convertFrom 
ObjectMemento %s->%s%n", memento.bookmark(), string);
-
-        return string;
-    }
-
-}
diff --git 
a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
 
b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
index 59e90a558ac..6dbfe9c7d07 100644
--- 
a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
+++ 
b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java
@@ -56,7 +56,6 @@
 import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMemento;
 import 
org.apache.causeway.viewer.wicket.model.causeway.WicketApplicationInitializer;
 import org.apache.causeway.viewer.wicket.model.models.PageType;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
@@ -69,7 +68,6 @@
 import 
org.apache.causeway.viewer.wicket.viewer.integration.AuthenticatedWebSessionForCauseway;
 import 
org.apache.causeway.viewer.wicket.viewer.integration.CausewayResourceSettings;
 import 
org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapter;
-import 
org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapterMemento;
 import 
org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway;
 
 import lombok.Getter;
@@ -373,7 +371,6 @@ protected Class<? extends AuthenticatedWebSession> 
getWebSessionClass() {
     protected IConverterLocator newConverterLocator() {
         final ConverterLocator converterLocator = new ConverterLocator();
         converterLocator.set(ManagedObject.class, new 
ConverterForObjectAdapter());
-        converterLocator.set(ObjectMemento.class, new 
ConverterForObjectAdapterMemento());
         return converterLocator;
     }
 


Reply via email to