You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/03/20 19:28:33 UTC

[isis] branch master updated: ISIS-2877: ScalarPanels: add layer of abstraction

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 98bf017  ISIS-2877: ScalarPanels: add layer of abstraction
98bf017 is described below

commit 98bf017ed32142083189be5ea7f5e88b6218fbf8
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sun Mar 20 20:28:27 2022 +0100

    ISIS-2877: ScalarPanels: add layer of abstraction
    
    - to factor out input/output format switching
---
 .../ui/components/scalars/ScalarPanelAbstract.java | 342 ++++++---------------
 .../components/scalars/ScalarPanelAbstract2.java   | 233 ++++++++++++++
 .../scalars/ScalarPanelFormFieldAbstract.java      |   2 +-
 .../scalars/ScalarPanelSelectAbstract.java         |   2 +-
 .../ui/components/tree/ParentedTreePanel.java      |   5 +-
 5 files changed, 334 insertions(+), 250 deletions(-)

diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
index 26e8c2e..f082515 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract.java
@@ -34,11 +34,9 @@ import org.apache.wicket.model.IModel;
 import org.springframework.lang.Nullable;
 
 import org.apache.isis.applib.annotation.ActionLayout;
-import org.apache.isis.applib.annotation.PromptStyle;
 import org.apache.isis.applib.services.metamodel.BeanSort;
 import org.apache.isis.applib.services.metamodel.MetaModelService;
 import org.apache.isis.commons.collections.Can;
-import org.apache.isis.commons.internal.base._Refs;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.debug._Probe;
@@ -54,20 +52,13 @@ import org.apache.isis.viewer.common.model.feature.ParameterUiModel;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.models.ActionPrompt;
 import org.apache.isis.viewer.wicket.model.models.ActionPromptProvider;
-import org.apache.isis.viewer.wicket.model.models.InlinePromptContext;
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
 import org.apache.isis.viewer.wicket.model.models.ScalarPropertyModel;
 import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.AdditionalLinksPanel;
 import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.LinkAndLabelFactory;
-import org.apache.isis.viewer.wicket.ui.components.property.PropertyEditFormPanel;
 import org.apache.isis.viewer.wicket.ui.components.property.PropertyEditPanel;
 import org.apache.isis.viewer.wicket.ui.components.propertyheader.PropertyEditPromptHeaderPanel;
 import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarFragmentFactory.FrameFragment;
-import org.apache.isis.viewer.wicket.ui.components.scalars.blobclob.IsisBlobOrClobPanelAbstract;
-import org.apache.isis.viewer.wicket.ui.components.scalars.primitive.BooleanPanel;
-import org.apache.isis.viewer.wicket.ui.components.scalars.reference.ReferencePanel;
-import org.apache.isis.viewer.wicket.ui.components.scalars.valuechoices.ValueChoicesSelect2Panel;
-import org.apache.isis.viewer.wicket.ui.components.widgets.linkandlabel.ActionLink;
 import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.isis.viewer.wicket.ui.util.Wkt;
 import org.apache.isis.viewer.wicket.ui.util.Wkt.EventTopic;
@@ -147,62 +138,6 @@ implements ScalarModelSubscriber {
         }
     }
 
-    /**
-     *
-     * @param paramModel - the action being invoked
-     * @param target - in case there's more to be repainted...
-     *
-     * @return - true if changed as a result of these pending arguments.
-     */
-    public Repaint updateIfNecessary(
-            final @NonNull ParameterUiModel paramModel,
-            final @NonNull Optional<AjaxRequestTarget> target) {
-
-        // visibility
-        val visibilityConsent = paramModel.getParameterNegotiationModel().getVisibilityConsent(paramModel.getParameterIndex());
-        val visibilityBefore = isVisible();
-        val visibilityAfter = visibilityConsent.isAllowed();
-        setVisible(visibilityAfter);
-
-        // usability
-        val usabilityConsent = paramModel.getParameterNegotiationModel().getUsabilityConsent(paramModel.getParameterIndex());
-        val usabilityBefore = isEnabled();
-        val usabilityAfter = usabilityConsent.isAllowed();
-        if(usabilityAfter) {
-            onEditable(target);
-        } else {
-            onNotEditable(usabilityConsent.getReason(), target);
-        }
-
-        val paramValue = paramModel.getValue();
-        val valueChanged = !Objects.equals(scalarModel.getObject(), paramValue);
-
-        if(valueChanged) {
-            if(ManagedObjects.isNullOrUnspecifiedOrEmpty(paramValue)) {
-                scalarModel.setObject(null);
-            } else {
-                scalarModel.setObject(paramValue);
-            }
-            scalarModel.clearPending();
-        }
-
-
-        // repaint the entire form if visibility has changed
-        if (!visibilityBefore || !visibilityAfter) {
-            return Repaint.ENTIRE_FORM;
-        }
-
-        // repaint the param if usability has changed
-        if (!usabilityAfter || !usabilityBefore) {
-            return Repaint.PARAM_ONLY;
-        }
-
-        // also repaint the param if its pending arg has changed.
-        return valueChanged
-                ? Repaint.PARAM_ONLY
-                : Repaint.NOTHING;
-    }
-
     // -- CONSTRUCTION
 
     /**
@@ -221,7 +156,7 @@ implements ScalarModelSubscriber {
      */
     protected abstract Component createCompactFrame();
 
-    // -- INPUT FORMAT
+    // -- REGULAR FRAME
 
     private MarkupContainer frameIfRegular;
     protected final MarkupContainer getRegularFrame() { return frameIfRegular; }
@@ -231,19 +166,12 @@ implements ScalarModelSubscriber {
      */
     protected abstract MarkupContainer createRegularFrame();
 
-    // --
+    // -- FRAME CONTAINER
 
     private WebMarkupContainer scalarFrameContainer;
     protected final WebMarkupContainer getScalarFrameContainer() { return scalarFrameContainer; }
 
-    /**
-     * Used by most subclasses
-     * ({@link ScalarPanelAbstract}, {@link ReferencePanel}, {@link ValueChoicesSelect2Panel})
-     * but not all ({@link IsisBlobOrClobPanelAbstract}, {@link BooleanPanel})
-     */
-    private WebMarkupContainer scalarIfRegularInlinePromptForm;
-
-    WebMarkupContainer inlinePromptLink;
+    // -- CONSTRUCTION
 
     protected ScalarPanelAbstract(final String id, final ScalarModel scalarModel) {
         super(id, scalarModel);
@@ -255,30 +183,9 @@ implements ScalarModelSubscriber {
     @Override
     protected void onInitialize() {
         super.onInitialize();
-        buildGuiAndCallHooks();
-        setOutputMarkupId(true);
-    }
-
-    private void buildGuiAndCallHooks() {
         buildGui();
-
-        final ScalarModel scalarModel = scalarModel();
-
-        final String disableReasonIfAny = scalarModel.disableReasonIfAny();
-        final boolean mustBeEditable = scalarModel.mustBeEditable();
-        if (disableReasonIfAny != null) {
-            if(mustBeEditable) {
-                onInitializeNotEditable();
-            } else {
-                onInitializeReadonly(disableReasonIfAny);
-            }
-        } else {
-            if (scalarModel.isViewMode()) {
-                onInitializeNotEditable();
-            } else {
-                onInitializeEditable();
-            }
-        }
+        callHooks();
+        setOutputMarkupId(true);
     }
 
     /**
@@ -289,24 +196,6 @@ implements ScalarModelSubscriber {
     }
 
     /**
-     * Mandatory hook for implementations to indicate whether it supports the {@link PromptStyle#INLINE inline} or
-     * {@link PromptStyle#INLINE_AS_IF_EDIT prompt}s, and if so, how.
-     *
-     * <p>
-     *     For those that do, both {@link #createInlinePromptForm()} and
-     *     {@link #createInlinePromptLink()} must return non-null values (and their corresponding markup
-     *     must define the corresponding elements).
-     * </p>
-     *
-     * <p>
-     *     Implementations that support inline prompts are: ({@link ScalarPanelAbstract}, {@link ReferencePanel} and
-     *     {@link ValueChoicesSelect2Panel}; those that don't are {@link IsisBlobOrClobPanelAbstract} and {@link BooleanPanel}.
-     * </p>
-     *
-     */
-    protected abstract InlinePromptConfig getInlinePromptConfig();
-
-    /**
      * Builds GUI lazily prior to first render.
      *
      * <p>
@@ -327,8 +216,7 @@ implements ScalarModelSubscriber {
             frameIfCompact.setVisible(false);
             frameIfRegular.setOutputMarkupId(true); // enable as AJAX target
 
-            scalarFrameContainer.addOrReplace(frameIfCompact, frameIfRegular,
-                    scalarIfRegularInlinePromptForm = createInlinePromptForm());
+            scalarFrameContainer.addOrReplace(frameIfCompact, frameIfRegular);
 
             val associatedLinksAndLabels = associatedLinksAndLabels();
             addPositioningCssTo(frameIfRegular, associatedLinksAndLabels);
@@ -336,6 +224,8 @@ implements ScalarModelSubscriber {
 
             addFeedbackOnlyTo(frameIfRegular, getValidationFeedbackReceiver());
 
+            setupInlinePrompt();
+
             break;
         default:
             frameIfRegular = createShallowComponentForRegular();
@@ -344,64 +234,11 @@ implements ScalarModelSubscriber {
             frameIfCompact.setVisible(true);
 
             scalarFrameContainer.addOrReplace(frameIfCompact, frameIfRegular,
-                    scalarIfRegularInlinePromptForm = createInlinePromptForm());
+                    createShallowInlinePromptFormContainer());
 
             break;
         }
 
-        val inlinePromptConfig = getInlinePromptConfig();
-        if(inlinePromptConfig.isSupported()) {
-
-            frameIfRegular
-                .add(inlinePromptLink = createInlinePromptLink());
-
-            // even if this particular scalarModel (property) is not configured for inline edits,
-            // it's possible that one of the associated actions is.  Thus we set the prompt context
-            scalarModel.setInlinePromptContext(
-                    new InlinePromptContext(
-                            scalarModel,
-                            scalarFrameContainer,
-                            frameIfRegular, scalarIfRegularInlinePromptForm));
-
-            // start off assuming that neither the property nor any of the associated actions
-            // are using inline prompts
-
-            val componentToHideRef = _Refs.<Component>objectRef(inlinePromptLink);
-
-            if (scalarModel.getPromptStyle().isInline()
-                    && scalarModel.canEnterEditMode()) {
-
-                // we configure the prompt link if _this_ property is configured for inline edits...
-                Wkt.behaviorAddOnClick(inlinePromptLink, this::onPropertyInlineEditClick);
-                componentToHideRef.setValue(inlinePromptConfig.getComponentToHideIfAny());
-
-            } else {
-
-                val inlineActionIfAny =
-                        scalarModel.getAssociatedActions().getFirstAssociatedWithInlineAsIfEdit();
-
-                // not editable property, but maybe one of the actions is.
-                inlineActionIfAny
-                .map(LinkAndLabelFactory.forPropertyOrParameter(scalarModel))
-                .map(LinkAndLabel::getUiComponent)
-                .map(ActionLink.class::cast)
-                .filter(ActionLink::isVisible)
-                .filter(ActionLink::isEnabled)
-                .ifPresent(actionLinkInlineAsIfEdit->{
-                    Wkt.behaviorAddOnClick(inlinePromptLink, actionLinkInlineAsIfEdit::onClick);
-                    componentToHideRef.setValue(inlinePromptConfig.getComponentToHideIfAny());
-                });
-            }
-
-            componentToHideRef.getValue()
-            .ifPresent(componentToHide->componentToHide.setVisibilityAllowed(false));
-        }
-
-        addEditPropertyIf(
-                scalarModel.canEnterEditMode()
-                && (scalarModel.getPromptStyle().isDialog()
-                        || !inlinePromptConfig.isSupported()));
-
         // prevent from tabbing into non-editable widgets.
         if(scalarModel.isProperty()
                 && scalarModel.getMode() == ScalarRepresentation.VIEWING
@@ -417,6 +254,8 @@ implements ScalarModelSubscriber {
         addFormComponentBehaviourToUpdateSubscribers();
     }
 
+    protected abstract void setupInlinePrompt();
+
     private Can<LinkAndLabel> associatedLinksAndLabels() {
         // find associated actions for this scalar property (only properties will have any.)
         // convert those actions into UI layer widgets
@@ -444,6 +283,34 @@ implements ScalarModelSubscriber {
                 .createComponent(Wkt::container); // empty component;
     }
 
+    private WebMarkupContainer createShallowInlinePromptFormContainer() {
+        val inlinePromptFormContainer =
+                new WebMarkupContainer(ID_SCALAR_IF_REGULAR_INLINE_PROMPT_FORM);
+        inlinePromptFormContainer.setVisible(false);
+        return inlinePromptFormContainer;
+    }
+
+    private void callHooks() {
+
+        final ScalarModel scalarModel = scalarModel();
+
+        final String disableReasonIfAny = scalarModel.disableReasonIfAny();
+        final boolean mustBeEditable = scalarModel.mustBeEditable();
+        if (disableReasonIfAny != null) {
+            if(mustBeEditable) {
+                onInitializeNotEditable();
+            } else {
+                onInitializeReadonly(disableReasonIfAny);
+            }
+        } else {
+            if (scalarModel.isViewMode()) {
+                onInitializeNotEditable();
+            } else {
+                onInitializeEditable();
+            }
+        }
+    }
+
     /**
      * The widget starts off in read-only, but should be possible to activate into edit mode.
      */
@@ -594,42 +461,9 @@ implements ScalarModelSubscriber {
         return scalarNameLabel;
     }
 
-    /**
-     * Returns a container holding an empty form.  This can be switched out using {@link #switchFormForInlinePrompt(AjaxRequestTarget)}.
-     */
-    private WebMarkupContainer createInlinePromptForm() {
-
-        // (placeholder initially, create dynamically when needed - otherwise infinite loop because form references regular)
-
-        WebMarkupContainer scalarIfRegularInlinePromptForm =
-                new WebMarkupContainer( ID_SCALAR_IF_REGULAR_INLINE_PROMPT_FORM);
-        scalarIfRegularInlinePromptForm.setOutputMarkupId(true);
-        scalarIfRegularInlinePromptForm.setVisible(false);
-
-        return scalarIfRegularInlinePromptForm;
-    }
-
-    private WebMarkupContainer createInlinePromptLink() {
-        final IModel<String> inlinePromptModel = obtainInlinePromptModel();
-        if(inlinePromptModel == null) {
-            throw new IllegalStateException(this.getClass().getName()
-                    + ": obtainInlinePromptModel() returning null is not compatible "
-                    + "with supportsInlinePrompt() returning true ");
-        }
-
-        final WebMarkupContainer inlinePromptLink =
-                new WebMarkupContainer(ID_SCALAR_VALUE_INLINE_PROMPT_LINK);
-        inlinePromptLink.setOutputMarkupId(true);
-        inlinePromptLink.setOutputMarkupPlaceholderTag(true);
 
-        configureInlinePromptLink(inlinePromptLink);
 
-        final Component editInlineLinkLabel =
-                createInlinePromptComponent(ID_SCALAR_VALUE_INLINE_PROMPT_LABEL, inlinePromptModel);
-        inlinePromptLink.add(editInlineLinkLabel);
 
-        return inlinePromptLink;
-    }
 
     protected void configureInlinePromptLink(final WebMarkupContainer inlinePromptLink) {
         Wkt.cssAppend(inlinePromptLink, obtainInlinePromptLinkCssIfAny());
@@ -646,47 +480,6 @@ implements ScalarModelSubscriber {
 
     // ///////////////////////////////////////////////////////////////////
 
-    /**
-     * Components returning true for {@link #getInlinePromptConfig()}
-     * are required to override and return a non-null value.
-     */
-    protected IModel<String> obtainInlinePromptModel() {
-        return null;
-    }
-
-    private void onPropertyInlineEditClick(final AjaxRequestTarget target) {
-        scalarModel.toEditMode();
-
-        switchFormForInlinePrompt(target);
-
-        getRegularFrame().setVisible(false);
-        scalarIfRegularInlinePromptForm.setVisible(true);
-
-        target.add(scalarFrameContainer);
-
-        Wkt.focusOnMarkerAttribute(scalarIfRegularInlinePromptForm, target);
-    }
-
-    private void switchFormForInlinePrompt(final AjaxRequestTarget target) {
-        scalarIfRegularInlinePromptForm = (PropertyEditFormPanel) getComponentFactoryRegistry()
-                .addOrReplaceComponent(
-                    scalarFrameContainer,
-                    ID_SCALAR_IF_REGULAR_INLINE_PROMPT_FORM,
-                    ComponentType.PROPERTY_EDIT_FORM,
-                    scalarModel);
-
-        onSwitchFormForInlinePrompt(scalarIfRegularInlinePromptForm, target);
-    }
-
-
-    /**
-     * Optional hook.
-     */
-    protected void onSwitchFormForInlinePrompt(
-            final WebMarkupContainer inlinePromptForm,
-            final AjaxRequestTarget target) {
-    }
-
     // -- EDIT PROPERTY ICON
 
     protected WebMarkupContainer addEditPropertyIf(final boolean condition) {
@@ -809,6 +602,63 @@ implements ScalarModelSubscriber {
     public void repaint(final AjaxRequestTarget target) {
         target.add(this);
     }
+    /**
+    *
+    * @param paramModel - the action being invoked
+    * @param target - in case there's more to be repainted...
+    *
+    * @return - true if changed as a result of these pending arguments.
+    */
+
+   public Repaint updateIfNecessary(
+           final @NonNull ParameterUiModel paramModel,
+           final @NonNull Optional<AjaxRequestTarget> target) {
+
+       // visibility
+       val visibilityConsent = paramModel.getParameterNegotiationModel().getVisibilityConsent(paramModel.getParameterIndex());
+       val visibilityBefore = isVisible();
+       val visibilityAfter = visibilityConsent.isAllowed();
+       setVisible(visibilityAfter);
+
+       // usability
+       val usabilityConsent = paramModel.getParameterNegotiationModel().getUsabilityConsent(paramModel.getParameterIndex());
+       val usabilityBefore = isEnabled();
+       val usabilityAfter = usabilityConsent.isAllowed();
+       if(usabilityAfter) {
+           onEditable(target);
+       } else {
+           onNotEditable(usabilityConsent.getReason(), target);
+       }
+
+       val paramValue = paramModel.getValue();
+       val valueChanged = !Objects.equals(scalarModel.getObject(), paramValue);
+
+       if(valueChanged) {
+           if(ManagedObjects.isNullOrUnspecifiedOrEmpty(paramValue)) {
+               scalarModel.setObject(null);
+           } else {
+               scalarModel.setObject(paramValue);
+           }
+           scalarModel.clearPending();
+       }
+
+
+       // repaint the entire form if visibility has changed
+       if (!visibilityBefore || !visibilityAfter) {
+           return Repaint.ENTIRE_FORM;
+       }
+
+       // repaint the param if usability has changed
+       if (!usabilityAfter || !usabilityBefore) {
+           return Repaint.PARAM_ONLY;
+       }
+
+       // also repaint the param if its pending arg has changed.
+       return valueChanged
+               ? Repaint.PARAM_ONLY
+               : Repaint.NOTHING;
+   }
+
 
 
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract2.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract2.java
new file mode 100644
index 0000000..77500da
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelAbstract2.java
@@ -0,0 +1,233 @@
+/*
+ *  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;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.IModel;
+
+import org.apache.isis.applib.annotation.PromptStyle;
+import org.apache.isis.commons.internal.base._Refs;
+import org.apache.isis.viewer.common.model.components.ComponentType;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.models.InlinePromptContext;
+import org.apache.isis.viewer.wicket.model.models.ScalarModel;
+import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.LinkAndLabelFactory;
+import org.apache.isis.viewer.wicket.ui.components.property.PropertyEditFormPanel;
+import org.apache.isis.viewer.wicket.ui.components.scalars.blobclob.IsisBlobOrClobPanelAbstract;
+import org.apache.isis.viewer.wicket.ui.components.scalars.primitive.BooleanPanel;
+import org.apache.isis.viewer.wicket.ui.components.scalars.reference.ReferencePanel;
+import org.apache.isis.viewer.wicket.ui.components.scalars.valuechoices.ValueChoicesSelect2Panel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.linkandlabel.ActionLink;
+import org.apache.isis.viewer.wicket.ui.util.Wkt;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.val;
+
+/**
+ *  Adds inline prompt logic.
+ */
+public abstract class ScalarPanelAbstract2
+extends ScalarPanelAbstract {
+
+    private static final long serialVersionUID = 1L;
+
+    // -- INLINE PROMPT LINK
+
+    protected WebMarkupContainer inlinePromptLink;
+
+    // -- INLINE PROMPT FORM CONTAINER
+
+    /**
+     * Used by most subclasses
+     * ({@link ScalarPanelAbstract}, {@link ReferencePanel}, {@link ValueChoicesSelect2Panel})
+     * but not all ({@link IsisBlobOrClobPanelAbstract}, {@link BooleanPanel})
+     */
+    @Getter(AccessLevel.PROTECTED)
+    private WebMarkupContainer inlinePromptFormContainer;
+
+    // -- CONSTRUCTION
+
+    protected ScalarPanelAbstract2(final String id, final ScalarModel scalarModel) {
+        super(id, scalarModel);
+    }
+
+    /**
+     * Mandatory hook for implementations to indicate whether it supports the {@link PromptStyle#INLINE inline} or
+     * {@link PromptStyle#INLINE_AS_IF_EDIT prompt}s, and if so, how.
+     * <p>
+     * For those that do, both {@link #createInlinePromptFormContainer()} and
+     * {@link #createInlinePromptLink()} must return non-null values (and their corresponding markup
+     * must define the corresponding elements).
+     * <p>
+     * Implementations that support inline prompts are: ({@link ScalarPanelAbstract}, {@link ReferencePanel} and
+     * {@link ValueChoicesSelect2Panel}; those that don't are {@link IsisBlobOrClobPanelAbstract} and {@link BooleanPanel}.
+     */
+    protected abstract InlinePromptConfig getInlinePromptConfig();
+
+    @Override
+    protected final void setupInlinePrompt() {
+
+        val scalarModel = scalarModel();
+        val frameIfRegular = getRegularFrame();
+        val scalarFrameContainer = getScalarFrameContainer();
+
+        scalarFrameContainer.add(inlinePromptFormContainer = createInlinePromptFormContainer());
+
+        val inlinePromptConfig = getInlinePromptConfig();
+        if(inlinePromptConfig.isSupported()) {
+
+            frameIfRegular
+                .add(inlinePromptLink = createInlinePromptLink());
+
+            // even if this particular scalarModel (property) is not configured for inline edits,
+            // it's possible that one of the associated actions is.  Thus we set the prompt context
+            scalarModel.setInlinePromptContext(
+                    new InlinePromptContext(
+                            scalarModel,
+                            scalarFrameContainer,
+                            frameIfRegular, getInlinePromptFormContainer()));
+
+            // start off assuming that neither the property nor any of the associated actions
+            // are using inline prompts
+
+            val componentToHideRef = _Refs.<Component>objectRef(inlinePromptLink);
+
+            if (scalarModel.getPromptStyle().isInline()
+                    && scalarModel.canEnterEditMode()) {
+
+                // we configure the prompt link if _this_ property is configured for inline edits...
+                Wkt.behaviorAddOnClick(inlinePromptLink, this::onPropertyInlineEditClick);
+                componentToHideRef.setValue(inlinePromptConfig.getComponentToHideIfAny());
+
+            } else {
+
+                val inlineActionIfAny =
+                        scalarModel.getAssociatedActions().getFirstAssociatedWithInlineAsIfEdit();
+
+                // not editable property, but maybe one of the actions is.
+                inlineActionIfAny
+                .map(LinkAndLabelFactory.forPropertyOrParameter(scalarModel))
+                .map(LinkAndLabel::getUiComponent)
+                .map(ActionLink.class::cast)
+                .filter(ActionLink::isVisible)
+                .filter(ActionLink::isEnabled)
+                .ifPresent(actionLinkInlineAsIfEdit->{
+                    Wkt.behaviorAddOnClick(inlinePromptLink, actionLinkInlineAsIfEdit::onClick);
+                    componentToHideRef.setValue(inlinePromptConfig.getComponentToHideIfAny());
+                });
+            }
+
+            componentToHideRef.getValue()
+            .ifPresent(componentToHide->componentToHide.setVisibilityAllowed(false));
+        }
+
+        addEditPropertyIf(
+                scalarModel.canEnterEditMode()
+                && (scalarModel.getPromptStyle().isDialog()
+                        || !inlinePromptConfig.isSupported()));
+
+    }
+
+    /**
+     * Components returning true for {@link #getInlinePromptConfig()}
+     * are required to override and return a non-null value.
+     */
+    protected IModel<String> obtainInlinePromptModel() {
+        return null;
+    }
+
+    /**
+     * Optional hook.
+     */
+    protected void onSwitchFormForInlinePrompt(
+            final WebMarkupContainer inlinePromptForm,
+            final AjaxRequestTarget target) {
+    }
+
+    // -- HELPER
+
+    private WebMarkupContainer createInlinePromptLink() {
+        final IModel<String> inlinePromptModel = obtainInlinePromptModel();
+        if(inlinePromptModel == null) {
+            throw new IllegalStateException(this.getClass().getName()
+                    + ": obtainInlinePromptModel() returning null is not compatible "
+                    + "with supportsInlinePrompt() returning true ");
+        }
+
+        final WebMarkupContainer inlinePromptLink =
+                new WebMarkupContainer(ID_SCALAR_VALUE_INLINE_PROMPT_LINK);
+        inlinePromptLink.setOutputMarkupId(true);
+        inlinePromptLink.setOutputMarkupPlaceholderTag(true);
+
+        configureInlinePromptLink(inlinePromptLink);
+
+        final Component editInlineLinkLabel =
+                createInlinePromptComponent(ID_SCALAR_VALUE_INLINE_PROMPT_LABEL, inlinePromptModel);
+        inlinePromptLink.add(editInlineLinkLabel);
+
+        return inlinePromptLink;
+    }
+
+    /**
+     * Returns a container holding an empty form.
+     * This can be switched out using {@link #switchFormForInlinePrompt(AjaxRequestTarget)}.
+     */
+    private WebMarkupContainer createInlinePromptFormContainer() {
+
+        WebMarkupContainer inlinePromptFormContainer =
+                new WebMarkupContainer(ID_SCALAR_IF_REGULAR_INLINE_PROMPT_FORM);
+        inlinePromptFormContainer.setOutputMarkupId(true);
+        inlinePromptFormContainer.setVisible(false);
+
+        return inlinePromptFormContainer;
+    }
+
+    private void onPropertyInlineEditClick(final AjaxRequestTarget target) {
+        val scalarModel = scalarModel();
+        val scalarFrameContainer = getScalarFrameContainer();
+
+        scalarModel.toEditMode();
+
+        switchFormForInlinePrompt(target);
+
+        getRegularFrame().setVisible(false);
+        inlinePromptFormContainer.setVisible(true);
+
+        target.add(scalarFrameContainer);
+
+        Wkt.focusOnMarkerAttribute(inlinePromptFormContainer, target);
+    }
+
+    private void switchFormForInlinePrompt(final AjaxRequestTarget target) {
+        val scalarFrameContainer = getScalarFrameContainer();
+
+        inlinePromptFormContainer = (PropertyEditFormPanel) getComponentFactoryRegistry()
+                .addOrReplaceComponent(
+                    scalarFrameContainer,
+                    ID_SCALAR_IF_REGULAR_INLINE_PROMPT_FORM,
+                    ComponentType.PROPERTY_EDIT_FORM,
+                    scalarModel());
+
+        onSwitchFormForInlinePrompt(inlinePromptFormContainer, target);
+    }
+
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelFormFieldAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelFormFieldAbstract.java
index fcce6f2..1284e29 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelFormFieldAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelFormFieldAbstract.java
@@ -47,7 +47,7 @@ import org.apache.isis.viewer.wicket.ui.util.WktTooltips;
 import lombok.val;
 
 public abstract class ScalarPanelFormFieldAbstract<T>
-extends ScalarPanelAbstract {
+extends ScalarPanelAbstract2 {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelSelectAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelSelectAbstract.java
index 015d414..94643c6 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelSelectAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ScalarPanelSelectAbstract.java
@@ -45,7 +45,7 @@ import lombok.NonNull;
 import lombok.val;
 
 public abstract class ScalarPanelSelectAbstract
-extends ScalarPanelAbstract {
+extends ScalarPanelAbstract2 {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/tree/ParentedTreePanel.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/tree/ParentedTreePanel.java
index 1d6286c..0c1a03f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/tree/ParentedTreePanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/tree/ParentedTreePanel.java
@@ -23,7 +23,8 @@ import org.apache.wicket.MarkupContainer;
 
 import org.apache.isis.viewer.wicket.model.models.ScalarModel;
 import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarFragmentFactory.FrameFragment;
-import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelAbstract;
+import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelAbstract.InlinePromptConfig;
+import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelAbstract2;
 
 import lombok.val;
 
@@ -31,7 +32,7 @@ import lombok.val;
  * Immutable tree, hooks into the ScalarPanelTextField without actually using its text field.
  */
 public class ParentedTreePanel
-extends ScalarPanelAbstract {
+extends ScalarPanelAbstract2 {
 
     private static final long serialVersionUID = 1L;