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 2021/10/25 07:45:57 UTC

[isis] branch 2877_compound.value.types updated: ISIS-2877: simplify PromptFormAbstract by introducing layer of abstraction with OkCancelForm

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

ahuber pushed a commit to branch 2877_compound.value.types
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/2877_compound.value.types by this push:
     new 5212aef  ISIS-2877: simplify PromptFormAbstract by introducing layer of abstraction with OkCancelForm
5212aef is described below

commit 5212aef0b5e753ba57b20f1f37bad3c0d8a0ca47
Author: andi-huber <ah...@apache.org>
AuthorDate: Mon Oct 25 09:45:42 2021 +0200

    ISIS-2877: simplify PromptFormAbstract by introducing layer of
    abstraction with OkCancelForm
---
 .../viewer/wicket/model/models/FormExecutor.java   |  10 +-
 .../wicket/model/models/FormExecutorContext.java   |   2 +-
 .../ui/components/property/PropertyEditForm.java   |  33 ++-
 .../widgets/linkandlabel/ActionLink.java           |  15 +-
 .../isis/viewer/wicket/ui/panels/FormAbstract.java |  18 +-
 .../wicket/ui/panels/FormExecutorDefault.java      |  43 +++-
 .../isis/viewer/wicket/ui/panels/OkCancelForm.java | 105 ++++++++
 .../wicket/ui/panels/PromptFormAbstract.java       | 285 +++++----------------
 .../org/apache/isis/viewer/wicket/ui/util/Wkt.java | 116 ++++++++-
 9 files changed, 354 insertions(+), 273 deletions(-)

diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutor.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutor.java
index 0c9feba..b9d116c 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutor.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutor.java
@@ -38,11 +38,13 @@ public interface FormExecutor extends Serializable {
 
         public boolean isFailure() { return this == FAILURE_SO_STAY_ON_PAGE; }
         public boolean isSuccess() { return this != FAILURE_SO_STAY_ON_PAGE; }
+        public boolean isSuccessWithRedirect() { return this == SUCCESS_SO_REDIRECT_TO_RESULT_PAGE; }
+        public boolean isSuccessWithinNestedContext() { return this == SUCCESS_IN_NESTED_CONTEXT_SO_STAY_ON_PAGE; }
     }
 
     FormExecutionOutcome executeAndProcessResults(
-            final Page page,
-            final AjaxRequestTarget targetIfAny,
-            final Form<?> feedbackFormIfAny,
-            final boolean promptStyle);
+            Page page,
+            AjaxRequestTarget targetIfAny,
+            Form<?> feedbackFormIfAny,
+            FormExecutorContext formExecutorContext);
 }
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutorContext.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutorContext.java
index 1b48165..6498a36 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutorContext.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/FormExecutorContext.java
@@ -29,7 +29,7 @@ extends HasParentUiModel<EntityModel>, HasCommonContext {
 
     InlinePromptContext getInlinePromptContext();
 
-    default boolean isWithinPrompt() {
+    default boolean isWithinInlinePrompt() {
         return getPromptStyle().isInlineOrInlineAsIfEdit()
                 && getInlinePromptContext() != null;
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/property/PropertyEditForm.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/property/PropertyEditForm.java
index 810078f..544b223 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/property/PropertyEditForm.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/property/PropertyEditForm.java
@@ -24,7 +24,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.model.IModel;
 
-import org.apache.isis.applib.annotation.PromptStyle;
 import org.apache.isis.commons.internal.base._Either;
 import org.apache.isis.viewer.common.model.components.ComponentType;
 import org.apache.isis.viewer.wicket.model.hints.IsisPropertyEditCompletedEvent;
@@ -87,22 +86,22 @@ class PropertyEditForm extends PromptFormAbstract<ScalarPropertyModel> {
 
     }
 
-    // REVIEW: this overload may not be necessary, recall that the important call needed is getScalarModel().reset(),
-    // which is called in the superclass.
-    @Override
-    public void onCancelSubmitted(
-            final AjaxRequestTarget target) {
-
-        final PromptStyle promptStyle = getScalarModel().getPromptStyle();
-
-        if (promptStyle.isInlineOrInlineAsIfEdit()) {
-
-            getScalarModel().toViewMode();
-            getScalarModel().clearPending();
-        }
-
-        super.onCancelSubmitted(target);
-    }
+//    // REVIEW: this overload may not be necessary, recall that the important call needed is getScalarModel().reset(),
+//    // which is called in the superclass.
+//    @Override
+//    public void onCancelSubmitted(
+//            final AjaxRequestTarget target) {
+//
+//        final PromptStyle promptStyle = getScalarModel().getPromptStyle();
+//
+//        if (promptStyle.isInlineOrInlineAsIfEdit()) {
+//
+//            getScalarModel().toViewMode();
+//            getScalarModel().clearPending();
+//        }
+//
+//        super.onCancelSubmitted(target);
+//    }
 
     @Override
     protected _Either<ActionModel, ScalarPropertyModel> getMemberModel() {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/linkandlabel/ActionLink.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/linkandlabel/ActionLink.java
index fb93115..7f9dbc2 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/linkandlabel/ActionLink.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/linkandlabel/ActionLink.java
@@ -28,7 +28,6 @@ import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.request.cycle.RequestCycle;
 
-import org.apache.isis.commons.internal.base._Either;
 import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.commons.internal.debug._Probe.EntryPoint;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -191,10 +190,10 @@ extends IndicatingAjaxLink<ManagedObject> {
     private void startDialogWithParams(final AjaxRequestTarget target) {
 
         val actionModel = this.getActionModel();
-        val actionLink = this;
-        val promptProvider = ActionPromptProvider.getFrom(actionLink.getPage());
         val actionOwnerSpec = actionModel.getActionOwner().getSpecification();
-        val actionPrompt = promptProvider.getActionPrompt(actionModel.getPromptStyle(), actionOwnerSpec.getBeanSort());
+        val actionPrompt = ActionPromptProvider
+                .getFrom(this.getPage())
+                .getActionPrompt(actionModel.getPromptStyle(), actionOwnerSpec.getBeanSort());
 
         val actionParametersPanel = (ActionParametersPanel)
                 getComponentFactoryRegistry()
@@ -217,14 +216,14 @@ extends IndicatingAjaxLink<ManagedObject> {
 
     private void executeWithoutParams() {
         val actionModel = this.getActionModel();
-        val actionLink = this;
-        val page = actionLink.getPage();
+        val page = this.getPage();
 
         // returns true - if redirecting to new page, or repainting all components.
         // returns false - if invalid args; if concurrency exception;
 
-        val formExecutor = new FormExecutorDefault(_Either.left(actionModel));
-        val outcome = formExecutor.executeAndProcessResults(page, null, null, actionModel.isWithinPrompt());
+        val outcome = FormExecutorDefault
+                .forAction(actionModel)
+                .executeAndProcessResults(page, null, null, actionModel);
 
         if(outcome.isSuccess()) {
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormAbstract.java
index 2a056d4..e9295ec 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormAbstract.java
@@ -40,20 +40,22 @@ implements ComponentFactoryRegistryAccessor, PageClassRegistryAccessor {
     private transient PageClassRegistry pageClassRegistry;
     private transient IsisAppCommonContext commonContext;
 
-    public FormAbstract(final String id) {
+    protected FormAbstract(final String id) {
         super(id);
     }
 
-    public FormAbstract(final String id, final IModel<T> model) {
+    protected FormAbstract(final String id, final IModel<T> model) {
         super(id, model);
     }
 
-    public IsisAppCommonContext getCommonContext() {
+    // -- DEPENDENCIES
+
+    public final IsisAppCommonContext getCommonContext() {
         return commonContext = CommonContextUtils.computeIfAbsent(commonContext);
     }
 
     @Override
-    public ComponentFactoryRegistry getComponentFactoryRegistry() {
+    public final ComponentFactoryRegistry getComponentFactoryRegistry() {
         if(componentFactoryRegistry==null) {
             componentFactoryRegistry = ((ComponentFactoryRegistryAccessor) getApplication()).getComponentFactoryRegistry();
         }
@@ -61,22 +63,22 @@ implements ComponentFactoryRegistryAccessor, PageClassRegistryAccessor {
     }
 
     @Override
-    public PageClassRegistry getPageClassRegistry() {
+    public final PageClassRegistry getPageClassRegistry() {
         if(pageClassRegistry==null) {
             pageClassRegistry = ((PageClassRegistryAccessor) getApplication()).getPageClassRegistry();
         }
         return pageClassRegistry;
     }
 
-    protected SpecificationLoader getSpecificationLoader() {
+    protected final SpecificationLoader getSpecificationLoader() {
         return getCommonContext().getSpecificationLoader();
     }
 
-    protected ServiceRegistry getServiceRegistry() {
+    protected final ServiceRegistry getServiceRegistry() {
         return getCommonContext().getServiceRegistry();
     }
 
-    protected TranslationService getTranslationService() {
+    protected final TranslationService getTranslationService() {
         return getCommonContext().getTranslationService();
     }
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
index f39a403..ac285dc 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
@@ -37,6 +37,7 @@ import org.apache.isis.core.runtime.context.IsisAppCommonContext;
 import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
 import org.apache.isis.viewer.wicket.model.models.ActionModel;
 import org.apache.isis.viewer.wicket.model.models.FormExecutor;
+import org.apache.isis.viewer.wicket.model.models.FormExecutorContext;
 import org.apache.isis.viewer.wicket.model.models.ScalarPropertyModel;
 import org.apache.isis.viewer.wicket.ui.actionresponse.ActionResultResponse;
 import org.apache.isis.viewer.wicket.ui.actionresponse.ActionResultResponseType;
@@ -52,10 +53,26 @@ implements FormExecutor {
 
     private static final long serialVersionUID = 1L;
 
+    // -- FACTORIES
+
+    public static FormExecutor forAction(final ActionModel actionModel) {
+        return new FormExecutorDefault(_Either.left(actionModel));
+    }
+
+    public static FormExecutor forProperty(final ScalarPropertyModel propertyModel) {
+        return new FormExecutorDefault(_Either.right(propertyModel));
+    }
+
+    public static FormExecutor forMember(final _Either<ActionModel, ScalarPropertyModel> actionOrPropertyModel) {
+        return new FormExecutorDefault(actionOrPropertyModel);
+    }
+
+    // -- CONSTRUCTION
+
     protected final WicketViewerSettings settings;
     private final _Either<ActionModel, ScalarPropertyModel> actionOrPropertyModel;
 
-    public FormExecutorDefault(
+    private FormExecutorDefault(
             final _Either<ActionModel, ScalarPropertyModel> actionOrPropertyModel) {
         this.actionOrPropertyModel = actionOrPropertyModel;
         this.settings = getSettings();
@@ -70,7 +87,7 @@ implements FormExecutor {
             final Page page,
             final AjaxRequestTarget ajaxTarget,
             final Form<?> feedbackFormIfAny,
-            final boolean withinPrompt) {
+            final FormExecutorContext formExecutorContext) {
 
         try {
 
@@ -97,6 +114,14 @@ implements FormExecutor {
                     act->act.executeActionAndReturnResult(),
                     prop->prop.applyValueThenReturnOwner());
 
+            // if we are in a nested dialog/form, the result must be feed into the calling dialog's/form's parameter negotiation model
+            val isNestedContext = actionOrPropertyModel.fold(
+                    act->act.getActionOwner().getSpecification().isValue(), //FIXME[ISIS-2877] we know if the action owner is a value-type, the form is a nested one - however, should be extended for more generic use
+                    prop->false);
+            if(isNestedContext) {
+                return FormExecutionOutcome.SUCCESS_IN_NESTED_CONTEXT_SO_STAY_ON_PAGE;
+            }
+
             if(log.isDebugEnabled()) {
                 log.debug("about to redirect with {} after execution result {}",
                         EntityUtil.getEntityState(resultAdapter),
@@ -121,17 +146,13 @@ implements FormExecutor {
             // there's no need to set the abort cause on the transaction, it will have already been done
             // (in IsisTransactionManager#executeWithinTransaction(...)).
 
-            // if inline prompt then redirect to error page
-            if (withinPrompt) {
-                // throwing an exception will get caught by WebRequestCycleForIsis#onException(...)
-                throw ex; // redirect to the error page.
-            }
-
-            // attempt to recognize this exception using the ExceptionRecognizers
-            if(recognizeExceptionThenRaise(ex, ajaxTarget, feedbackFormIfAny).isPresent()) {
+            // attempt to recognize this exception using the ExceptionRecognizers (but only when not in inline prompt context!?)
+            if(!formExecutorContext.isWithinInlinePrompt()
+                    && recognizeExceptionThenRaise(ex, ajaxTarget, feedbackFormIfAny).isPresent()) {
                 return FormExecutionOutcome.FAILURE_SO_STAY_ON_PAGE; // invalid args, stay on page
             }
 
+            // throwing an exception will get caught by WebRequestCycleForIsis#onException(...)
             throw ex; // redirect to the error page.
         }
     }
@@ -204,4 +225,6 @@ implements FormExecutor {
         return getCommonContext().lookupServiceElseFail(WicketViewerSettings.class);
     }
 
+
+
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/OkCancelForm.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/OkCancelForm.java
new file mode 100644
index 0000000..50d1e00
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/OkCancelForm.java
@@ -0,0 +1,105 @@
+/*
+ *  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.panels;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.util.string.AppendingStringBuffer;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
+import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
+import org.apache.isis.viewer.wicket.ui.util.Wkt;
+
+import lombok.val;
+
+public abstract class OkCancelForm<T extends IModel<ManagedObject>>
+extends FormAbstract<ManagedObject>{
+
+    private static final long serialVersionUID = 1L;
+    private static final String ID_OK_BUTTON = "okButton";
+    public  static final String ID_CANCEL_BUTTON = "cancelButton";
+
+    protected final WicketViewerSettings settings;
+    protected final AjaxButton okButton;
+    protected final AjaxButton cancelButton;
+
+    protected OkCancelForm(final String id, final WicketViewerSettings settings, final IModel<ManagedObject> model) {
+        super(id, model);
+        this.settings = settings;
+        okButton = addOkButton();
+        cancelButton = addCancelButton();
+        doConfigureOkButton(okButton);
+        doConfigureCancelButton(cancelButton);
+        setOutputMarkupId(true);
+    }
+
+    protected void doConfigureOkButton(final AjaxButton okButton) {};
+    protected abstract void doConfigureCancelButton(AjaxButton cancelButton);
+    protected abstract void onOkSubmitted(AjaxButton okButton, AjaxRequestTarget target);
+    protected abstract void onCancelSubmitted(AjaxRequestTarget target);
+    protected abstract void closePromptIfAny(AjaxRequestTarget target);
+
+    protected AjaxButton addOkButton() {
+        val okButton = Wkt.buttonAddOk(this, ID_OK_BUTTON, new ResourceModel("okLabel"), settings, this::onOkSubmitted);
+        okButton.add(new JGrowlBehaviour(super.getCommonContext()));
+        setDefaultButton(okButton);
+        return okButton;
+    }
+
+    protected AjaxButton addCancelButton() {
+        val cancelButton = Wkt.buttonAdd(this, ID_CANCEL_BUTTON, new ResourceModel("cancelLabel"), (button, target)->{
+            //form.setMultiPart(true);
+            closePromptIfAny(target);
+            onCancelSubmitted(target);
+        });
+        // so can submit with invalid content (eg mandatory params missing)
+        cancelButton.setDefaultFormProcessing(false);
+        return cancelButton;
+    }
+
+    // workaround for https://issues.apache.org/jira/browse/WICKET-6364
+    @Override
+    protected final void appendDefaultButtonField() {
+        AppendingStringBuffer buffer = new AppendingStringBuffer();
+        buffer.append(
+                "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">");
+        buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>");
+        Component submittingComponent = this.defaultSubmittingComponent();
+        buffer.append("<input type=\"submit\" tabindex=\"-1\" name=\"");
+        buffer.append(this.defaultSubmittingComponent().getInputName());
+        buffer.append("\" onclick=\" var b=document.getElementById(\'");
+        buffer.append(submittingComponent.getMarkupId());
+        buffer.append("\'); if (b!=null&amp;&amp;b.onclick!=null&amp;&amp;typeof(b.onclick) != \'undefined\') {  "
+                + "var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); };  return false;\" ");
+        buffer.append(" />");
+        buffer.append("</div>");
+        this.getResponse().write(buffer);
+    }
+
+    // -- HELPER
+
+    private AjaxButton defaultSubmittingComponent() {
+        return okButton;
+    }
+
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/PromptFormAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/PromptFormAbstract.java
index 88c026f..749d942 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/PromptFormAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/PromptFormAbstract.java
@@ -23,20 +23,14 @@ import java.util.List;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.Page;
-import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.event.Broadcast;
-import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
 import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.JavaScriptContentHeaderItem;
 import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.ResourceModel;
-import org.apache.wicket.util.string.AppendingStringBuffer;
 
 import org.apache.isis.commons.internal.base._Either;
 import org.apache.isis.commons.internal.collections._Lists;
@@ -53,7 +47,6 @@ import org.apache.isis.viewer.wicket.model.models.ScalarPropertyModel;
 import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarModelSubscriber;
 import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelAbstract;
 import org.apache.isis.viewer.wicket.ui.components.widgets.formcomponent.FormFeedbackPanel;
-import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
 import org.apache.isis.viewer.wicket.ui.pages.entity.EntityPage;
 import org.apache.isis.viewer.wicket.ui.util.Components;
@@ -64,149 +57,92 @@ import lombok.val;
 public abstract class PromptFormAbstract<T extends
     FormExecutorContext
     & IModel<ManagedObject>>
-extends FormAbstract<ManagedObject>
+extends OkCancelForm<T>
 implements ScalarModelSubscriber {
 
     private static final long serialVersionUID = 1L;
 
-    private static final String ID_OK_BUTTON = "okButton";
-    public  static final String ID_CANCEL_BUTTON = "cancelButton";
     private static final String ID_FEEDBACK = "feedback";
 
     protected final List<ScalarPanelAbstract> paramPanels = _Lists.newArrayList();
 
     private final Component parentPanel;
-    private final WicketViewerSettings settings;
     private final T formExecutorContext;
 
-    private final AjaxButton okButton;
-    private final AjaxButton cancelButton;
-
     protected PromptFormAbstract(
             final String id,
             final Component parentPanel,
             final WicketViewerSettings settings,
             final T model) {
 
-        super(id, model);
+        super(id, settings, model);
         this.parentPanel = parentPanel;
-        this.settings = settings;
         this.formExecutorContext = model;
 
-        setOutputMarkupId(true); // for ajax button
         addParameters();
 
         Wkt.add(this, new FormFeedbackPanel(ID_FEEDBACK));
-
-        okButton = addOkButton();
-        cancelButton = addCancelButton();
-        doConfigureOkButton(okButton);
-        doConfigureCancelButton(cancelButton);
     }
 
+    // -- SETUP
+
+    protected abstract void addParameters();
+    protected abstract _Either<ActionModel, ScalarPropertyModel> getMemberModel();
+    protected abstract Object newCompletedEvent(AjaxRequestTarget target, Form<?> form);
+
     @Override
-    public void renderHead(final IHeaderResponse response) {
+    public final void renderHead(final IHeaderResponse response) {
         super.renderHead(response);
-
         response.render(OnDomReadyHeaderItem.forScript(
                 String.format("Wicket.Event.publish(Isis.Topic.FOCUS_FIRST_PARAMETER, '%s')", getMarkupId())));
-
     }
 
-    protected abstract void addParameters();
-
-    protected AjaxButton addOkButton() {
-
-        AjaxButton okButton = settings.isUseIndicatorForFormSubmit()
-                ? new IndicatingAjaxButton(ID_OK_BUTTON, new ResourceModel("okLabel")) {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            public void onSubmit(final AjaxRequestTarget target) {
-
-                _Probe.entryPoint(EntryPoint.USER_INTERACTION, "Wicket Ajax Request, "
-                        + "originating from User clicking OK on an inline editing form or "
-                        + "action prompt.");
-
-                onOkSubmittedOf(target, getForm(), this);
-            }
-
-            @Override
-            protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
-                if (settings.isPreventDoubleClickForFormSubmit()) {
-                    PanelUtil.disableBeforeReenableOnComplete(attributes, this);
-                }
-            }
-
-            @Override
-            protected void onError(final AjaxRequestTarget target) {
-                target.add(getForm());
-            }
+    @Override
+    protected final void doConfigureCancelButton(final AjaxButton cancelButton) {
+        if (formExecutorContext.getPromptStyle().isInlineOrInlineAsIfEdit()) {
+            Wkt.behaviorAddFireOnEscapeKey(cancelButton, this::onCancelSubmitted);
         }
-        : new AjaxButton(ID_OK_BUTTON, new ResourceModel("okLabel")) {
-            private static final long serialVersionUID = 1L;
+    }
 
-            @Override
-            public void onSubmit(final AjaxRequestTarget target) {
+    // -- BEHAVIOR
 
-                _Probe.entryPoint(EntryPoint.USER_INTERACTION, "Wicket Ajax Request, "
-                        + "originating from User clicking OK on an inline editing form or "
-                        + "action prompt.");
+    @Override
+    protected final void onOkSubmitted(
+            final AjaxButton okButton,
+            final AjaxRequestTarget target) {
 
-                onOkSubmittedOf(target, getForm(), this);
-            }
+        _Probe.entryPoint(EntryPoint.USER_INTERACTION, "Wicket Ajax Request, "
+                + "originating from User clicking OK on an inline editing form or "
+                + "action prompt.");
 
-            @Override
-            protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
-                if (settings.isPreventDoubleClickForFormSubmit()) {
-                    PanelUtil.disableBeforeReenableOnComplete(attributes, this);
-                }
-            }
+        setLastFocusHint();
 
-            @Override
-            protected void onError(final AjaxRequestTarget target) {
-                target.add(getForm());
-            }
-        };
-        okButton.add(new JGrowlBehaviour(super.getCommonContext()));
-        setDefaultButton(okButton);
-        add(okButton);
-        return okButton;
-    }
+        val form = okButton.getForm();
 
-    protected AjaxButton addCancelButton() {
-        final AjaxButton cancelButton = new AjaxButton(ID_CANCEL_BUTTON, new ResourceModel("cancelLabel")) {
-            private static final long serialVersionUID = 1L;
+        final FormExecutor formExecutor = FormExecutorDefault.forMember(getMemberModel());
 
-            @Override
-            public void onSubmit(final AjaxRequestTarget target) {
-                //form.setMultiPart(true);
-                closePromptIfAny(target);
+        val outcome = formExecutor
+                .executeAndProcessResults(target.getPage(), target, form, formExecutorContext);
 
-                onCancelSubmitted(target);
-            }
-        };
-        // so can submit with invalid content (eg mandatory params missing)
-        cancelButton.setDefaultFormProcessing(false);
+        if (outcome.isSuccessWithRedirect()) {
+            completePrompt(target);
 
-        if (formExecutorContext.getPromptStyle().isInlineOrInlineAsIfEdit()) {
-            cancelButton.add(new FireOnEscapeKey() {
-                private static final long serialVersionUID = 1L;
-
-                @Override
-                protected void respond(final AjaxRequestTarget target) {
-                    onCancelSubmitted(target);
-                }
-            });
+            okButton.send(target.getPage(), Broadcast.EXACT, newCompletedEvent(target, form));
+            Components.addToAjaxRequest(target, form);
+            return;
         }
 
-        add(cancelButton);
+        if (outcome.isSuccessWithinNestedContext()) {
+            completePrompt(target);
 
-        return cancelButton;
+            okButton.send(target.getPage(), Broadcast.EXACT, newCompletedEvent(target, form));
+            Components.addToAjaxRequest(target, form);
+            return;
+        }
     }
 
-    protected void closePromptIfAny(final AjaxRequestTarget target) {
-
+    @Override
+    protected final void closePromptIfAny(final AjaxRequestTarget target) {
         try {
             final ActionPromptProvider promptProvider = ActionPromptProvider.getFrom(parentPanel);
             if(promptProvider != null) {
@@ -218,58 +154,31 @@ implements ScalarModelSubscriber {
         }
     }
 
-    /**
-     * Optional hook
-     */
-    protected void doConfigureOkButton(final AjaxButton okButton) {
-    }
-
-    /**
-     * Optional hook
-     */
-    protected void doConfigureCancelButton(final AjaxButton cancelButton) {
-    }
-
-    private UiHintContainer getPageUiHintContainerIfAny() {
-        final Page page;
-        try {
-            page = getPage();
-        } catch(org.apache.wicket.WicketRuntimeException ex) {
-            return null;
-        }
-        if (page instanceof EntityPage) {
-            EntityPage entityPage = (EntityPage) page;
-            return entityPage.getUiHintContainerIfAny();
+    @Override
+    public final void onError(final AjaxRequestTarget target, final ScalarPanelAbstract scalarPanel) {
+        if (scalarPanel != null) {
+            // ensure that any feedback error associated with the providing component is shown.
+            target.add(scalarPanel);
         }
-        return null;
     }
 
-    private void onOkSubmittedOf(
-            final AjaxRequestTarget target,
-            final Form<?> form,
-            final AjaxButton okButton) {
-
+    @Override
+    public final void onCancelSubmitted(final AjaxRequestTarget target) {
         setLastFocusHint();
+        completePrompt(target);
+    }
 
-        final FormExecutor formExecutor = new FormExecutorDefault(getMemberModel());
-
-        val outcome = formExecutor
-                .executeAndProcessResults(target.getPage(), target, form, formExecutorContext.isWithinPrompt());
-
-        if (outcome.isSuccess()) {
-            completePrompt(target);
+    // -- HELPER
 
-            okButton.send(target.getPage(), Broadcast.EXACT, newCompletedEvent(target, form));
-            Components.addToAjaxRequest(target, form);
+    private void completePrompt(final AjaxRequestTarget target) {
+        if (formExecutorContext.isWithinInlinePrompt()) {
+            rebuildGuiAfterInlinePromptDone(target);
+        } else {
+            closePromptIfAny(target);
         }
-
     }
 
-    protected abstract _Either<ActionModel, ScalarPropertyModel> getMemberModel();
-
-
     private void setLastFocusHint() {
-
         final UiHintContainer entityModel = getPageUiHintContainerIfAny();
         if (entityModel == null) {
             return;
@@ -280,33 +189,18 @@ implements ScalarModelSubscriber {
         }
     }
 
-    protected abstract Object newCompletedEvent(
-            final AjaxRequestTarget target,
-            final Form<?> form);
-
-    @Override
-    public void onError(final AjaxRequestTarget target, final ScalarPanelAbstract scalarPanel) {
-        if (scalarPanel != null) {
-            // ensure that any feedback error associated with the providing component is shown.
-            target.add(scalarPanel);
+    private UiHintContainer getPageUiHintContainerIfAny() {
+        final Page page;
+        try {
+            page = getPage();
+        } catch(org.apache.wicket.WicketRuntimeException ex) {
+            return null;
         }
-    }
-
-    public void onCancelSubmitted(final AjaxRequestTarget target) {
-        setLastFocusHint();
-        completePrompt(target);
-    }
-
-    private void completePrompt(final AjaxRequestTarget target) {
-        if (isWithinPrompt()) {
-            rebuildGuiAfterInlinePromptDone(target);
-        } else {
-            closePromptIfAny(target);
+        if (page instanceof EntityPage) {
+            EntityPage entityPage = (EntityPage) page;
+            return entityPage.getUiHintContainerIfAny();
         }
-    }
-
-    private boolean isWithinPrompt() {
-        return this.formExecutorContext.isWithinPrompt();
+        return null;
     }
 
     private void rebuildGuiAfterInlinePromptDone(final AjaxRequestTarget target) {
@@ -336,53 +230,4 @@ implements ScalarModelSubscriber {
         target.add(parent);
     }
 
-    private AjaxButton defaultSubmittingComponent() {
-        return okButton;
-    }
-
-    // workaround for https://issues.apache.org/jira/browse/WICKET-6364
-    @Override
-    protected void appendDefaultButtonField() {
-        AppendingStringBuffer buffer = new AppendingStringBuffer();
-        buffer.append(
-                "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">");
-        buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>");
-        Component submittingComponent = this.defaultSubmittingComponent();
-        buffer.append("<input type=\"submit\" tabindex=\"-1\" name=\"");
-        buffer.append(this.defaultSubmittingComponent().getInputName());
-        buffer.append("\" onclick=\" var b=document.getElementById(\'");
-        buffer.append(submittingComponent.getMarkupId());
-        buffer.append(
-                "\'); if (b!=null&amp;&amp;b.onclick!=null&amp;&amp;typeof(b.onclick) != \'undefined\') {  var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); };  return false;\" ");
-        buffer.append(" />");
-        buffer.append("</div>");
-        this.getResponse().write(buffer);
-    }
-
-    static abstract class FireOnEscapeKey extends AbstractDefaultAjaxBehavior {
-        private static final long serialVersionUID = 1L;
-
-        private static final String PRE_JS =
-                "" + "$(document).ready( function() { \n"
-                        + "  $(document).bind('keyup', function(evt) { \n"
-                        + "    if (evt.keyCode == 27) { \n";
-        private static final String POST_JS =
-                "" + "      evt.preventDefault(); \n   "
-                        + "    } \n"
-                        + "  }); \n"
-                        + "});";
-
-        @Override
-        public void renderHead(final Component component, final IHeaderResponse response) {
-            super.renderHead(component, response);
-
-            final String javascript = PRE_JS + getCallbackScript() + POST_JS;
-            response.render(
-                    JavaScriptContentHeaderItem.forScript(javascript, null, null));
-        }
-
-        @Override
-        protected abstract void respond(final AjaxRequestTarget target);
-
-    }
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/util/Wkt.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/util/Wkt.java
index 4992799..4198f8f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/util/Wkt.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/util/Wkt.java
@@ -22,11 +22,17 @@ import java.util.List;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
 import org.apache.wicket.ajax.AjaxEventBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
 import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptContentHeaderItem;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.TextArea;
@@ -34,6 +40,7 @@ import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.model.IModel;
+import org.danekja.java.util.function.serializable.SerializableBiConsumer;
 import org.danekja.java.util.function.serializable.SerializableBooleanSupplier;
 import org.danekja.java.util.function.serializable.SerializableConsumer;
 import org.springframework.lang.Nullable;
@@ -42,12 +49,13 @@ import org.apache.isis.applib.Identifier;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.debug._Probe;
 import org.apache.isis.commons.internal.debug._Probe.EntryPoint;
-
-import lombok.val;
-import lombok.experimental.UtilityClass;
+import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
+import org.apache.isis.viewer.wicket.ui.panels.PanelUtil;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.behavior.CssClassNameAppender;
 import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
+import lombok.val;
+import lombok.experimental.UtilityClass;
 
 /**
  * Wicket common idioms, in alphabetical order.
@@ -83,10 +91,107 @@ public class Wkt {
         };
     }
 
+    public Behavior behaviorFireOnEscapeKey(final SerializableConsumer<AjaxRequestTarget> onRespond) {
+        return new AbstractDefaultAjaxBehavior() {
+            private static final long serialVersionUID = 1L;
+            private static final String PRE_JS =
+                    "" + "$(document).ready( function() { \n"
+                            + "  $(document).bind('keyup', function(evt) { \n"
+                            + "    if (evt.keyCode == 27) { \n";
+            private static final String POST_JS =
+                    "" + "      evt.preventDefault(); \n   "
+                            + "    } \n"
+                            + "  }); \n"
+                            + "});";
+            @Override public void renderHead(final Component component, final IHeaderResponse response) {
+                super.renderHead(component, response);
+                final String javascript = PRE_JS + getCallbackScript() + POST_JS;
+                response.render(
+                        JavaScriptContentHeaderItem.forScript(javascript, null, null));
+            }
+            @Override protected void respond(final AjaxRequestTarget target) {
+                onRespond.accept(target);
+            }
+        };
+    }
+
     public Behavior behaviorAddOnClick(
-            final MarkupContainer markupProvider,
+            final MarkupContainer markupContainer,
             final SerializableConsumer<AjaxRequestTarget> onClick) {
-        return add(markupProvider, behaviorOnClick(onClick));
+        return add(markupContainer, behaviorOnClick(onClick));
+    }
+
+    public Behavior behaviorAddFireOnEscapeKey(
+            final MarkupContainer markupContainer,
+            final SerializableConsumer<AjaxRequestTarget> onRespond) {
+        return add(markupContainer, behaviorFireOnEscapeKey(onRespond));
+    }
+
+    // -- BUTTON
+
+    public AjaxButton button(
+            final String id,
+            final IModel<String> labelModel,
+            final SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onClick) {
+        return new AjaxButton(id, labelModel) {
+            private static final long serialVersionUID = 1L;
+            @Override public void onSubmit(final AjaxRequestTarget target) {
+                onClick.accept(this, target);
+            }
+        };
+    }
+
+    public AjaxButton buttonOk(
+            final String id,
+            final IModel<String> labelModel,
+            final WicketViewerSettings settings,
+            final SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onClick) {
+        return settings.isUseIndicatorForFormSubmit()
+        ? new IndicatingAjaxButton(id, labelModel) {
+            private static final long serialVersionUID = 1L;
+            @Override public void onSubmit(final AjaxRequestTarget target) {
+                onClick.accept(this, target);
+            }
+            @Override protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                if (settings.isPreventDoubleClickForFormSubmit()) {
+                    PanelUtil.disableBeforeReenableOnComplete(attributes, this);
+                }
+            }
+            @Override protected void onError(final AjaxRequestTarget target) {
+                target.add(getForm());
+            }
+        }
+        : new AjaxButton(id, labelModel) {
+            private static final long serialVersionUID = 1L;
+            @Override public void onSubmit(final AjaxRequestTarget target) {
+                onClick.accept(this, target);
+            }
+            @Override protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                if (settings.isPreventDoubleClickForFormSubmit()) {
+                    PanelUtil.disableBeforeReenableOnComplete(attributes, this);
+                }
+            }
+            @Override protected void onError(final AjaxRequestTarget target) {
+                target.add(getForm());
+            }
+        };
+    }
+
+    public AjaxButton buttonAdd(
+            final MarkupContainer markupContainer,
+            final String id,
+            final IModel<String> labelModel,
+            final SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onClick) {
+        return add(markupContainer, button(id, labelModel, onClick));
+    }
+
+    public AjaxButton buttonAddOk(
+            final MarkupContainer markupContainer,
+            final String id,
+            final IModel<String> labelModel,
+            final WicketViewerSettings settings,
+            final SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onClick) {
+        return add(markupContainer, buttonOk(id, labelModel, settings, onClick));
     }
 
     // -- CONTAINER
@@ -222,6 +327,7 @@ public class Wkt {
             @Override public void onClick(final AjaxRequestTarget target) {
                 onClick.accept(target);
             }
+            @SuppressWarnings("deprecation")
             @Override protected void onComponentTag(final ComponentTag tag) {
                 super.onComponentTag(tag);
                 Buttons.fixDisabledState(this, tag);