You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2017/01/19 11:09:29 UTC

[23/24] isis git commit: ISIS-785: wrapped Select2Choice with a Select2 wrapper class, idea being to encapsulate whether use Select2Choice or Select2MultiChoice.

ISIS-785: wrapped Select2Choice with a Select2 wrapper class, idea being to encapsulate whether use Select2Choice or Select2MultiChoice.

Added ScalarModel#isCollection() - true if for an action parameter with featureType of ACTION_PARAMETER_COLL , and treat its ObjectAdapterMemento as a memento for a adapter of a transient java.util.ArrayList that contains (persistent) pojos.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/e114e6cc
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/e114e6cc
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/e114e6cc

Branch: refs/heads/maint-1.13.3
Commit: e114e6ccf60dcc2494de099e491192f4c3d2f19a
Parents: fbc259f
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Wed Jan 18 19:13:29 2017 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Wed Jan 18 19:22:14 2017 +0000

----------------------------------------------------------------------
 .../model/mementos/ObjectAdapterMemento.java    |  22 ++-
 .../viewer/wicket/model/models/ScalarModel.java |  50 ++++++
 .../models/ScalarModelWithMultiPending.java     | 151 +++++++++++++++++++
 .../reference/EntityLinkSelect2Panel.java       |   4 +-
 .../scalars/reference/ReferencePanel.java       | 112 ++++++++------
 .../components/scalars/reference/Select2.java   | 145 ++++++++++++++++++
 .../widgets/select2/Select2ChoiceUtil.java      |  17 +++
 7 files changed, 453 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/ObjectAdapterMemento.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/ObjectAdapterMemento.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/ObjectAdapterMemento.java
index 609a047..a6ad7c5 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/ObjectAdapterMemento.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/ObjectAdapterMemento.java
@@ -22,6 +22,8 @@ package org.apache.isis.viewer.wicket.model.mementos;
 import java.io.Serializable;
 import java.util.List;
 
+import javax.annotation.Nullable;
+
 import com.google.common.base.Function;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
@@ -323,7 +325,8 @@ public class ObjectAdapterMemento implements Serializable {
      */
     public ObjectAdapter getObjectAdapter(
             final ConcurrencyChecking concurrencyChecking,
-            final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) {
+            final PersistenceSession persistenceSession,
+            final SpecificationLoader specificationLoader) {
         return type.getAdapter(this, concurrencyChecking, persistenceSession, specificationLoader);
     }
 
@@ -495,6 +498,23 @@ public class ObjectAdapterMemento implements Serializable {
             };
         }
 
+        public static Function<? super ObjectAdapterMemento, Object> toPojo(
+                final PersistenceSession persistenceSession,
+                final SpecificationLoader specificationLoader) {
+            return new Function<ObjectAdapterMemento, Object>() {
+                @Nullable @Override public Object apply(@Nullable final ObjectAdapterMemento input) {
+                    if(input == null) {
+                        return null;
+                    }
+                    final ObjectAdapter objectAdapter = input
+                            .getObjectAdapter(ConcurrencyChecking.NO_CHECK, persistenceSession, specificationLoader);
+                    if(objectAdapter == null) {
+                        return null;
+                    }
+                    return objectAdapter.getObject();
+                }
+            };
+        }
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
index fbfd78e..6d9cba9 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
@@ -20,6 +20,7 @@
 package org.apache.isis.viewer.wicket.model.models;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -289,6 +290,11 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
             public ObjectAdapter load(final ScalarModel scalarModel) {
                 return scalarModel.loadFromSuper();
             }
+
+            @Override
+            public boolean isCollection(final ScalarModel scalarModel) {
+                return false;
+            }
         },
         PARAMETER {
             @Override
@@ -506,6 +512,7 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
                 }
 
                 // return an empty collection
+                // TODO: this should probably move down into OneToManyActionParameter impl
                 final OneToManyActionParameter otmap = (OneToManyActionParameter) actionParameter;
                 final CollectionSemantics collectionSemantics = otmap.getCollectionSemantics();
                 final TypeOfFacet typeOfFacet = actionParameter.getFacet(TypeOfFacet.class);
@@ -513,6 +520,13 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
                 final Object emptyCollection = collectionSemantics.emptyCollectionOf(elementType);
                 return scalarModel.getCurrentSession().getPersistenceSession().adapterFor(emptyCollection);
             }
+
+            @Override
+            public boolean isCollection(final ScalarModel scalarModel) {
+                final ActionParameterMemento parameterMemento = scalarModel.getParameterMemento();
+                final ObjectActionParameter actionParameter = parameterMemento.getActionParameter(scalarModel.getSpecificationLoader());
+                return actionParameter.getFeatureType() == FeatureType.ACTION_PARAMETER_COLLECTION;
+            }
         };
 
         private static List<ObjectAdapter> choicesAsList(final ObjectAdapter[] choices) {
@@ -576,6 +590,8 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
         public abstract void reset(ScalarModel scalarModel);
 
         public abstract ObjectAdapter load(final ScalarModel scalarModel);
+
+        public abstract boolean isCollection(final ScalarModel scalarModel);
     }
 
     private final Kind kind;
@@ -651,6 +667,10 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
         setObject(associatedAdapter);
     }
 
+    public boolean isCollection() {
+        return kind.isCollection(this);
+    }
+
     /**
      * Whether the scalar represents a {@link Kind#PROPERTY property} or a
      * {@link Kind#PARAMETER}.
@@ -842,6 +862,36 @@ public class ScalarModel extends EntityModel implements LinksProvider,HasExecuti
         };
     }
 
+    /**
+     * @return
+     */
+    public ScalarModelWithMultiPending asScalarModelWithMultiPending() {
+        return new ScalarModelWithMultiPending(){
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public ArrayList<ObjectAdapterMemento> getPending() {
+                final ObjectAdapterMemento pending = ScalarModel.this.getPending();
+                return Util.asMementoList(pending, getPersistenceSession(), getSpecificationLoader());
+            }
+
+            @Override
+            public void setPending(final ArrayList<ObjectAdapterMemento> pending) {
+                final ObjectAdapter list = Util.toAdapter(pending, getPersistenceSession(), getSpecificationLoader());
+                final ObjectAdapterMemento adapterMemento = ObjectAdapterMemento.createOrNull(list);
+                ScalarModel.this.setPending(adapterMemento);
+            }
+
+            @Override
+            public ScalarModel getScalarModel() {
+                return ScalarModel.this;
+            }
+        };
+    }
+
+
+
 
     public String getReasonInvalidIfAny() {
         final OneToOneAssociation property = getPropertyMemento().getProperty(getSpecificationLoader());

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModelWithMultiPending.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModelWithMultiPending.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModelWithMultiPending.java
new file mode 100644
index 0000000..e57494b
--- /dev/null
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModelWithMultiPending.java
@@ -0,0 +1,151 @@
+/**
+ *  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.isis.viewer.wicket.model.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
+
+import org.apache.wicket.model.Model;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+
+/**
+ * For widgets that use a <tt>org.wicketstuff.select2.Select2MultiChoice</tt>;
+ * synchronizes the {@link Model} of the <tt>Select2MultiChoice</tt>
+ * with the parent {@link ScalarModel}, allowing also for pending values.
+ */
+public interface ScalarModelWithMultiPending extends Serializable {
+
+    public ArrayList<ObjectAdapterMemento> getPending();
+    public void setPending(ArrayList<ObjectAdapterMemento> pending);
+
+    public ScalarModel getScalarModel();
+
+    static class Util {
+
+        private static final Logger LOG = LoggerFactory.getLogger(ScalarModelWithMultiPending.Util.class);
+        
+        public static Model<ArrayList<ObjectAdapterMemento>> createModel(final ScalarModelWithMultiPending owner) {
+            return new Model<ArrayList<ObjectAdapterMemento>>() {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public ArrayList<ObjectAdapterMemento> getObject() {
+                    if (owner.getPending() != null) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("pending not null: " + owner.getPending().toString());
+                        }
+                        return owner.getPending();
+                    }
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("pending is null");
+                    }
+
+                    final ScalarModel scalarModel = owner.getScalarModel();
+                    final ObjectAdapterMemento objectAdapterMemento = scalarModel.getObjectAdapterMemento();
+                    ArrayList<ObjectAdapterMemento> mementos = asMementoList(objectAdapterMemento,
+                            scalarModel.getPersistenceSession(), scalarModel.getSpecificationLoader());
+
+                    owner.setPending(mementos);
+                    return mementos;
+                }
+
+                @Override
+                public void setObject(final ArrayList<ObjectAdapterMemento> adapterMemento) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(String.format("setting to: %s", adapterMemento != null ? adapterMemento.toString() : null));
+                    }
+                    owner.setPending(adapterMemento);
+
+                    final ScalarModel ownerScalarModel = owner.getScalarModel();
+                    final PersistenceSession persistenceSession = ownerScalarModel.getPersistenceSession();
+                    final SpecificationLoader specificationLoader = ownerScalarModel.getSpecificationLoader();
+
+                    if(adapterMemento == null) {
+                        ownerScalarModel.setObject(null);
+                    } else {
+                        final ArrayList<ObjectAdapterMemento> ownerPending = owner.getPending();
+                        if (ownerPending != null) {
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug(String.format("setting to pending: %s", ownerPending.toString()));
+                            }
+                            final ObjectAdapter objectAdapterOfListOfPojos =
+                                    toAdapter(ownerPending, persistenceSession, specificationLoader);
+                            ownerScalarModel.setObject(objectAdapterOfListOfPojos);
+                        }
+                    }
+                }
+            };
+        }
+
+        public static ObjectAdapter toAdapter(
+                final ArrayList<ObjectAdapterMemento> ownerPending,
+                final PersistenceSession persistenceSession,
+                final SpecificationLoader specificationLoader) {
+            final ArrayList<Object> listOfPojos = Lists
+                    .newArrayList(FluentIterable.from(ownerPending).transform(
+                            ObjectAdapterMemento.Functions
+                                    .toPojo(persistenceSession, specificationLoader))
+                            .toList());
+            return persistenceSession.adapterFor(listOfPojos);
+        }
+
+        public static ArrayList<ObjectAdapterMemento> asMementoList(
+                final ObjectAdapterMemento objectAdapterMemento,
+                final PersistenceSession persistenceSession,
+                final SpecificationLoader specificationLoader) {
+
+            if(objectAdapterMemento == null) {
+                return Lists.newArrayList();
+            }
+
+            final ObjectAdapter objectAdapter = objectAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK,
+                    persistenceSession, specificationLoader);
+            if(objectAdapter == null) {
+                return Lists.newArrayList();
+            }
+
+            final List<ObjectAdapter> objectAdapters = CollectionFacet.Utils.convertToAdapterList(objectAdapter);
+
+            return Lists.newArrayList(
+                FluentIterable.from(objectAdapters)
+                              .transform(ObjectAdapterMemento.Functions.fromAdapter())
+                              .toList());
+        }
+
+        public static ObjectAdapterMemento toAdapterMemento(
+                final Collection<ObjectAdapterMemento> modelObject,
+                final PersistenceSession persistenceSession, final SpecificationLoader specificationLoader) {
+            final ObjectAdapter objectAdapter = ScalarModelWithMultiPending.Util
+                    .toAdapter(Lists.newArrayList(modelObject), persistenceSession, specificationLoader);
+            return ObjectAdapterMemento.createOrNull(objectAdapter);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/EntityLinkSelect2Panel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/EntityLinkSelect2Panel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/EntityLinkSelect2Panel.java
index 8ac459d..a4035ac 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/EntityLinkSelect2Panel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/EntityLinkSelect2Panel.java
@@ -44,8 +44,8 @@ class EntityLinkSelect2Panel extends FormComponentPanelAbstract<ObjectAdapter> i
      */
     @Override
     public boolean checkRequired() {
-        if(owningPanel.select2Field != null) {
-            return owningPanel.select2Field.checkRequired();
+        if(owningPanel.select2 != null) {
+            return owningPanel.select2.checkRequired();
         }
         return super.checkRequired();
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/ReferencePanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/ReferencePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/ReferencePanel.java
index 0039bf6..1d4c814 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/ReferencePanel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/ReferencePanel.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.viewer.wicket.ui.components.scalars.reference;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import com.google.common.collect.Lists;
@@ -35,7 +36,6 @@ import org.apache.wicket.validation.IValidatable;
 import org.apache.wicket.validation.IValidator;
 import org.apache.wicket.validation.ValidationError;
 import org.wicketstuff.select2.ChoiceProvider;
-import org.wicketstuff.select2.Select2Choice;
 import org.wicketstuff.select2.Settings;
 
 import org.apache.isis.core.commons.config.IsisConfiguration;
@@ -50,7 +50,8 @@ import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
-import org.apache.isis.viewer.wicket.model.models.ScalarModelWithPending.Util;
+import org.apache.isis.viewer.wicket.model.models.ScalarModelWithMultiPending;
+import org.apache.isis.viewer.wicket.model.models.ScalarModelWithPending;
 import org.apache.isis.viewer.wicket.ui.ComponentFactory;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
 import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.EntityActionUtil;
@@ -79,7 +80,8 @@ public class ReferencePanel extends ScalarPanelAbstract {
     private static final String KEY_DISABLE_DEPENDENT_CHOICE_AUTO_SELECTION = "isis.viewer.wicket.disableDependentChoiceAutoSelection";
 
     private EntityLinkSelect2Panel entityLink;
-    Select2Choice<ObjectAdapterMemento> select2Field;
+
+    Select2 select2;
 
     private EntityLinkSimplePanel entitySimpleLink;
 
@@ -182,8 +184,8 @@ public class ReferencePanel extends ScalarPanelAbstract {
     // called from buildGui
     @Override
     protected void addFormComponentBehavior(Behavior behavior) {
-        if(select2Field != null) {
-            select2Field.add(behavior);
+        if(select2 != null) {
+            select2.add(behavior);
         }
     }
 
@@ -259,14 +261,24 @@ public class ReferencePanel extends ScalarPanelAbstract {
 
         // syncLinkWithInputIfAutoCompleteOrChoices
         if(isEditableWithEitherAutoCompleteOrChoices()) {
-            final IModel<ObjectAdapterMemento> model = Util.createModel(getModel().asScalarModelWithPending());       
-            
-            if(select2Field == null) {
+
+            if(select2 == null) {
                 entityLink.setRequired(getModel().isRequired());
-                select2Field = Select2ChoiceUtil.newSelect2Choice(ID_AUTO_COMPLETE, model, getModel());
-                setProviderAndCurrAndPending(select2Field, getModel().getActionArgsHint());
 
-                final Settings settings = select2Field.getSettings();
+                if(getModel().isCollection()) {
+                    final IModel<ArrayList<ObjectAdapterMemento>> modelWithMultiPending =
+                            ScalarModelWithMultiPending.Util.createModel(getModel().asScalarModelWithMultiPending());
+                    select2 = Select2.with(Select2ChoiceUtil.newSelect2MultiChoice(ID_AUTO_COMPLETE, modelWithMultiPending,
+                            getModel()));
+                } else {
+                    final IModel<ObjectAdapterMemento> modelWithPending =
+                            ScalarModelWithPending.Util.createModel(getModel().asScalarModelWithPending());
+                    select2 = Select2.with(Select2ChoiceUtil.newSelect2Choice(ID_AUTO_COMPLETE, modelWithPending,
+                            getModel()));
+                }
+                setProviderAndCurrAndPending(select2, getModel().getActionArgsHint());
+
+                final Settings settings = select2.getSettings();
 
                 // one of these three case should be true
                 // (as per the isEditableWithEitherAutoCompleteOrChoices() guard above)
@@ -287,26 +299,26 @@ public class ReferencePanel extends ScalarPanelAbstract {
                     settings.setMinimumInputLength(minLength);
                 }
 
-                entityLink.addOrReplace(select2Field);
+                entityLink.addOrReplace(select2.component());
 
             } else {
                 //
-                // the select2Field already exists, so the widget has been rendered before.  If it is
+                // the select2Choice already exists, so the widget has been rendered before.  If it is
                 // being re-rendered now, it may be because some other property/parameter was invalid.
                 // when the form was submitted, the selected object (its oid as a string) would have
                 // been saved as rawInput.  If the property/parameter had been valid, then this rawInput
-                // would be correctly converted and processed by the select2Field's choiceProvider.  However,
+                // would be correctly converted and processed by the select2Choice's choiceProvider.  However,
                 // an invalid property/parameter means that the webpage is re-rendered in another request,
                 // and the rawInput can no longer be interpreted.  The net result is that the field appears
                 // with no input.
                 //
-                // The fix is therefore (I think) simply to clear any rawInput, so that the select2Field
+                // The fix is therefore (I think) simply to clear any rawInput, so that the select2Choice
                 // renders its state from its model.
                 //
                 // see: FormComponent#getInputAsArray()
                 // see: Select2Choice#renderInitializationScript()
                 //
-                select2Field.clearInput();
+                select2.clearInput();
             }
 
             if(getComponentForRegular() != null) {
@@ -317,9 +329,9 @@ public class ReferencePanel extends ScalarPanelAbstract {
 
 
             // syncUsability
-            if(select2Field != null) {
+            if(select2 != null) {
                 final boolean mutability = entityLink.isEnableAllowed() && !getModel().isViewMode();
-                select2Field.setEnabled(mutability);
+                select2.setEnabled(mutability);
             }
 
             Components.permanentlyHide(entityLink, "entityLinkIfNull");
@@ -327,7 +339,7 @@ public class ReferencePanel extends ScalarPanelAbstract {
             // this is horrid; adds a label to the id
             // should instead be a 'temporary hide'
             Components.permanentlyHide(entityLink, ID_AUTO_COMPLETE);
-            select2Field = null; // this forces recreation next time around
+            select2 = null; // this forces recreation next time around
         }
         
     }
@@ -338,23 +350,23 @@ public class ReferencePanel extends ScalarPanelAbstract {
     
     // called by syncWithInput, updateChoices
     private void setProviderAndCurrAndPending(
-            final Select2Choice<ObjectAdapterMemento> select2Field, 
+            final Select2 select2,
             final ObjectAdapter[] argsIfAvailable) {
         if (getModel().hasChoices()) {
             
             final List<ObjectAdapterMemento> choiceMementos = obtainChoiceMementos(argsIfAvailable);
             ObjectAdapterMementoProviderAbstract providerForChoices = providerForChoices(choiceMementos);
 
-            select2Field.setProvider(providerForChoices);
+            select2.setProvider(providerForChoices);
             getModel().clearPending();
             
-            resetIfCurrentNotInChoices(select2Field, choiceMementos);
+            resetIfCurrentNotInChoices(select2, choiceMementos);
             
         } else if(getModel().hasAutoComplete()) {
-            select2Field.setProvider(providerForParamOrPropertyAutoComplete());
+            select2.setProvider(providerForParamOrPropertyAutoComplete());
             getModel().clearPending();
         } else {
-            select2Field.setProvider(providerForObjectAutoComplete());
+            select2.setProvider(providerForObjectAutoComplete());
             getModel().clearPending();
         }
     }
@@ -370,24 +382,33 @@ public class ReferencePanel extends ScalarPanelAbstract {
     }
 
     // called by setProviderAndCurrAndPending
-    private void resetIfCurrentNotInChoices(final Select2Choice<ObjectAdapterMemento> select2Field, final List<ObjectAdapterMemento> choiceMementos) {
-        final ObjectAdapterMemento curr = select2Field.getModelObject();
-        if(curr == null) {
-            select2Field.getModel().setObject(null);
-            getModel().setObject(null);
-            return;
-        }
-        
-        if(!curr.containedIn(choiceMementos, getPersistenceSession(), getSpecificationLoader())) {
-            if(!choiceMementos.isEmpty() && autoSelect()) {
-                final ObjectAdapterMemento newAdapterMemento = choiceMementos.get(0);
-                select2Field.getModel().setObject(newAdapterMemento);
-                getModel().setObject(newAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK,
-                        getPersistenceSession(), getSpecificationLoader()));
-            } else {
-                select2Field.getModel().setObject(null);
+    private void resetIfCurrentNotInChoices(final Select2 select2, final List<ObjectAdapterMemento> choiceMementos) {
+        final ObjectAdapterMemento curr = select2.getModelObject(getPersistenceSession(), getSpecificationLoader());
+
+        if(!getModel().isCollection()) {
+
+            if(curr == null) {
+                select2.getModel(getPersistenceSession(), getSpecificationLoader()).setObject(null);
                 getModel().setObject(null);
+                return;
+            }
+
+            if(!curr.containedIn(choiceMementos, getPersistenceSession(), getSpecificationLoader())) {
+                if(!choiceMementos.isEmpty() && autoSelect()) {
+                    final ObjectAdapterMemento newAdapterMemento = choiceMementos.get(0);
+                    select2.getModel(getPersistenceSession(), getSpecificationLoader()).setObject(newAdapterMemento);
+                    getModel().setObject(newAdapterMemento.getObjectAdapter(ConcurrencyChecking.NO_CHECK,
+                            getPersistenceSession(), getSpecificationLoader()));
+                } else {
+                    select2.getModel(getPersistenceSession(), getSpecificationLoader()).setObject(null);
+                    getModel().setObject(null);
+                }
             }
+
+        } else {
+
+            // TODO
+
         }
     }
 
@@ -468,11 +489,12 @@ public class ReferencePanel extends ScalarPanelAbstract {
         if(isEditableWithEitherAutoCompleteOrChoices()) {
 
             // flush changes to pending
-            ObjectAdapterMemento convertedInput = select2Field.getConvertedInput();
+            ObjectAdapterMemento convertedInput =
+                    select2.getConvertedInput(getPersistenceSession(), getSpecificationLoader());
             
             getModel().setPending(convertedInput);
-            if(select2Field != null) {
-                select2Field.getModel().setObject(convertedInput);
+            if(select2 != null) {
+                select2.getModel(getPersistenceSession(), getSpecificationLoader()).setObject(convertedInput);
             }
             
             final ObjectAdapter adapter = convertedInput!=null?convertedInput.getObjectAdapter(ConcurrencyChecking.NO_CHECK,
@@ -496,8 +518,8 @@ public class ReferencePanel extends ScalarPanelAbstract {
      */
     @Override
     public boolean updateChoices(ObjectAdapter[] argsIfAvailable) {
-        if(select2Field != null) {
-            setProviderAndCurrAndPending(select2Field, argsIfAvailable);
+        if(select2 != null) {
+            setProviderAndCurrAndPending(select2, argsIfAvailable);
             return true;
         } else {
             return false;

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/Select2.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/Select2.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/Select2.java
new file mode 100644
index 0000000..e650d3b
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/reference/Select2.java
@@ -0,0 +1,145 @@
+/*
+ *  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.isis.viewer.wicket.ui.components.scalars.reference;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.html.form.HiddenField;
+import org.apache.wicket.model.IModel;
+import org.wicketstuff.select2.ChoiceProvider;
+import org.wicketstuff.select2.Select2Choice;
+import org.wicketstuff.select2.Select2MultiChoice;
+import org.wicketstuff.select2.Settings;
+
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.ScalarModelWithMultiPending;
+
+public class Select2 implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    final Select2Choice<ObjectAdapterMemento> select2Choice;
+    final Select2MultiChoice<ObjectAdapterMemento> select2MultiChoice;
+
+    public static Select2 with(final Select2Choice<ObjectAdapterMemento> select2Choice) {
+        return new Select2(select2Choice, null);
+    }
+
+    public static Select2 with(final Select2MultiChoice<ObjectAdapterMemento> select2MultiChoice) {
+        return new Select2(null, select2MultiChoice);
+    }
+
+    private Select2(
+            final Select2Choice<ObjectAdapterMemento> select2Choice,
+            final Select2MultiChoice<ObjectAdapterMemento> select2MultiChoice) {
+        this.select2Choice = select2Choice;
+        this.select2MultiChoice = select2MultiChoice;
+    }
+
+    public void add(final Behavior behavior) {
+        component().add(behavior);
+    }
+
+    HiddenField<?> component() {
+        return select2Choice != null
+                ? (HiddenField<ObjectAdapterMemento>) select2Choice
+                : (HiddenField<Collection<ObjectAdapterMemento>>) select2MultiChoice;
+    }
+
+    public Settings getSettings() {
+        return select2Choice != null ? select2Choice.getSettings() : select2MultiChoice.getSettings();
+    }
+
+    public void clearInput() {
+        component().clearInput();
+    }
+
+    public void setEnabled(final boolean mutability) {
+        component().setEnabled(mutability);
+    }
+
+    public boolean checkRequired() {
+        return component().checkRequired();
+    }
+
+    public void setProvider(final ChoiceProvider<ObjectAdapterMemento> providerForChoices) {
+        if (select2Choice != null)
+            select2Choice.setProvider(providerForChoices);
+        else
+            select2MultiChoice.setProvider(providerForChoices);
+    }
+
+    public ObjectAdapterMemento getModelObject(
+            final PersistenceSession persistenceSession,
+            final SpecificationLoader specificationLoader) {
+        if (select2Choice != null) {
+            return select2Choice.getModelObject();
+        } else {
+            final Collection<ObjectAdapterMemento> modelObject = select2MultiChoice.getModelObject();
+            return ScalarModelWithMultiPending.Util
+                    .toAdapterMemento(modelObject, persistenceSession, specificationLoader);
+        }
+    }
+
+    public IModel<ObjectAdapterMemento> getModel(
+            final PersistenceSession persistenceSession,
+            final SpecificationLoader specificationLoader) {
+        if (select2Choice != null) {
+            return select2Choice.getModel();
+        } else {
+            final IModel<Collection<ObjectAdapterMemento>> model = select2MultiChoice.getModel();
+            final Collection<ObjectAdapterMemento> modelObject = model.getObject();
+            final ObjectAdapterMemento memento = ScalarModelWithMultiPending.Util
+                    .toAdapterMemento(modelObject, persistenceSession, specificationLoader);
+            return new IModel<ObjectAdapterMemento>() {
+                @Override
+                public ObjectAdapterMemento getObject() {
+                    return memento;
+                }
+
+                @Override
+                public void setObject(final ObjectAdapterMemento memento) {
+                    final ArrayList<ObjectAdapterMemento> mementos = ScalarModelWithMultiPending.Util
+                            .asMementoList(memento, persistenceSession, specificationLoader);
+                    model.setObject(mementos);
+                }
+
+                @Override public void detach() {
+
+                }
+            };
+        }
+    }
+
+    public ObjectAdapterMemento getConvertedInput(
+            final PersistenceSession persistenceSession,
+            final SpecificationLoader specificationLoader) {
+        if (select2Choice != null) {
+            return select2Choice.getConvertedInput();
+        } else {
+            final Collection<ObjectAdapterMemento> convertedInput = select2MultiChoice.getConvertedInput();
+            return ScalarModelWithMultiPending.Util.toAdapterMemento(convertedInput, persistenceSession, specificationLoader);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/e114e6cc/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/select2/Select2ChoiceUtil.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/select2/Select2ChoiceUtil.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/select2/Select2ChoiceUtil.java
index 476286b..08a00c6 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/select2/Select2ChoiceUtil.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/select2/Select2ChoiceUtil.java
@@ -16,6 +16,7 @@
  */
 package org.apache.isis.viewer.wicket.ui.components.widgets.select2;
 
+import java.util.ArrayList;
 import java.util.Collection;
 
 import org.apache.wicket.ajax.json.JSONException;
@@ -24,6 +25,7 @@ import org.apache.wicket.model.IModel;
 import org.wicketstuff.select2.ChoiceProvider;
 import org.wicketstuff.select2.Response;
 import org.wicketstuff.select2.Select2Choice;
+import org.wicketstuff.select2.Select2MultiChoice;
 
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
@@ -38,6 +40,19 @@ public final class Select2ChoiceUtil  {
         return select2Choice;
     }
 
+    public static Select2MultiChoice<ObjectAdapterMemento> newSelect2MultiChoice(
+            final String id,
+            final IModel<ArrayList<ObjectAdapterMemento>> modelObject,
+            final ScalarModel scalarModel) {
+
+        // TODO: naughty..
+        final IModel<Collection<ObjectAdapterMemento>> modelObjectColl = (IModel)modelObject;
+
+        Select2MultiChoice<ObjectAdapterMemento> select2Choice = new Select2MultiChoice<>(id, modelObjectColl, EmptyChoiceProvider.INSTANCE);
+        select2Choice.setRequired(scalarModel.isRequired());
+        return select2Choice;
+    }
+
     private static class EmptyChoiceProvider extends ChoiceProvider<ObjectAdapterMemento> {
 
         private static final EmptyChoiceProvider INSTANCE = new EmptyChoiceProvider();
@@ -57,4 +72,6 @@ public final class Select2ChoiceUtil  {
             return null;
         }
     }
+
+
 }