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 2020/04/07 13:52:26 UTC

[isis] branch master updated (32fdcc6 -> 42cbb4a)

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

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


    from 32fdcc6  ISIS-2340: unify sub menu item model creation via MenuItemUiModel
     new 8e184d1  ISIS-2340: Vaadin: make MenuActionFactory a top-level class
     new 42cbb4a  ISIS-2340: unify menu builders into common MenuModelFactory

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 ...enuActionVaa.java => MenuActionFactoryVaa.java} |  35 ++++---
 ...ctionVaa.java => MenuActionLinkFactoryVaa.java} |  34 ++++---
 .../viewer/vaadin/model/menu/MenuItemVaa.java      |  19 +---
 .../vaadin/ui/components/links/ActionLink.java     |  23 -----
 .../links/ActionLinkFactoryForVaadin.java          |  26 -----
 .../viewer/vaadin/ui/pages/main/MenuUtil.java      | 110 ++++-----------------
 .../common/model/action/MenuActionFactory.java     |  16 +++
 .../viewer/common/model/menu/MenuModelFactory.java |  86 ++++++++++++++++
 .../common/model/menuitem/MenuItemUiModel.java     |   9 +-
 .../actionmenu/serviceactions/CssMenuItem.java     |   4 -
 .../serviceactions/ServiceActionUtil.java          |  98 ++++++++----------
 .../serviceactions/ServiceActionsPanelFactory.java |  13 ++-
 .../serviceactions/TertiaryMenuPanelFactory.java   |  10 +-
 13 files changed, 225 insertions(+), 258 deletions(-)
 copy incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/{MenuActionVaa.java => MenuActionFactoryVaa.java} (51%)
 copy incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/{MenuActionVaa.java => MenuActionLinkFactoryVaa.java} (53%)
 delete mode 100644 incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLink.java
 delete mode 100644 incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLinkFactoryForVaadin.java
 create mode 100644 viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/MenuActionFactory.java
 create mode 100644 viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuModelFactory.java


[isis] 01/02: ISIS-2340: Vaadin: make MenuActionFactory a top-level class

Posted by ah...@apache.org.
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

commit 8e184d150ede6b6c18f561b563f73f2a7eb9f6b7
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Apr 7 13:11:12 2020 +0200

    ISIS-2340: Vaadin: make MenuActionFactory a top-level class
---
 .../actionlink/MenuActionLinkFactory.java          | 50 ++++++++++++++++++++++
 .../vaadin/ui/components/links/ActionLink.java     | 23 ----------
 .../links/ActionLinkFactoryForVaadin.java          | 26 -----------
 .../viewer/vaadin/ui/pages/main/MenuUtil.java      | 26 +----------
 4 files changed, 51 insertions(+), 74 deletions(-)

diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
new file mode 100644
index 0000000..1c0f716
--- /dev/null
+++ b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
@@ -0,0 +1,50 @@
+/*
+ *  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.incubator.viewer.vaadin.ui.components.actionlink;
+
+import com.vaadin.flow.component.Component;
+import com.vaadin.flow.component.html.Label;
+
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.incubator.viewer.vaadin.model.entity.ObjectVaa;
+import org.apache.isis.viewer.common.model.link.ActionLinkFactory;
+import org.apache.isis.viewer.common.model.link.LinkAndLabelUiModel;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@RequiredArgsConstructor
+public class MenuActionLinkFactory implements ActionLinkFactory<Component> {
+
+    /** model of the service that holds the menu item's action */
+    private final ObjectVaa serviceModel; 
+    
+    @Override
+    public LinkAndLabelUiModel<Component> newLink(final ObjectAction objectAction) {
+
+        val objectAdapter = serviceModel.getManagedObject();
+        val linkComponent = new Label(objectAction.getName());
+        val whetherReturnsBlobOrClob = ObjectAction.Util.returnsBlobOrClob(objectAction);
+        
+        //linkComponent.addClassName(className);
+        
+        return LinkAndLabelUiModel.newLinkAndLabel(linkComponent, objectAdapter, objectAction, whetherReturnsBlobOrClob);
+        
+    }
+}
\ No newline at end of file
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLink.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLink.java
deleted file mode 100644
index 2adc57f..0000000
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLink.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.incubator.viewer.vaadin.ui.components.links;
-
-public class ActionLink {
-
-}
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLinkFactoryForVaadin.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLinkFactoryForVaadin.java
deleted file mode 100644
index 093d427..0000000
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/links/ActionLinkFactoryForVaadin.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.apache.isis.incubator.viewer.vaadin.ui.components.links;
-
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.incubator.viewer.vaadin.model.entity.ObjectVaa;
-import org.apache.isis.viewer.common.model.link.LinkAndLabelUiModel;
-
-import lombok.RequiredArgsConstructor;
-
-/**
- * Responsible for creating an action's link and label.
- * @since Apr 5, 2020
- * @implNote corresponds to Wicket
- * org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions.ServiceActionLinkFactory
- *
- */
-@RequiredArgsConstructor(staticName = "of")
-public class ActionLinkFactoryForVaadin {
-
-    final ObjectVaa entityUiModel;
-
-    public LinkAndLabelUiModel<ActionLink> newLink(ObjectAction objectAction) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-    
-}
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
index e75bff6..9c6098d 100644
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
+++ b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
@@ -8,23 +8,19 @@ import com.vaadin.flow.component.Text;
 import com.vaadin.flow.component.html.Div;
 import com.vaadin.flow.component.html.Hr;
 import com.vaadin.flow.component.html.Image;
-import com.vaadin.flow.component.html.Label;
 import com.vaadin.flow.component.menubar.MenuBar;
 import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment;
 import com.vaadin.flow.component.orderedlayout.FlexLayout;
 
 import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
 import org.apache.isis.applib.services.menu.MenuBarsService.Type;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.runtimeservices.menubars.bootstrap3.MenuBarsServiceBS3;
 import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
 import org.apache.isis.incubator.viewer.vaadin.model.action.MenuActionVaa;
 import org.apache.isis.incubator.viewer.vaadin.model.entity.ObjectVaa;
 import org.apache.isis.incubator.viewer.vaadin.model.menu.MenuItemVaa;
-import org.apache.isis.viewer.common.model.link.ActionLinkFactory;
-import org.apache.isis.viewer.common.model.link.LinkAndLabelUiModel;
+import org.apache.isis.incubator.viewer.vaadin.ui.components.actionlink.MenuActionLinkFactory;
 
-import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.experimental.UtilityClass;
 import lombok.extern.log4j.Log4j2;
@@ -175,25 +171,5 @@ final class MenuUtil {
         }
     }
     
-    @RequiredArgsConstructor
-    private static class MenuActionLinkFactory implements ActionLinkFactory<Component> {
-
-        /** model of the service that holds the menu item's action */
-        private final ObjectVaa serviceModel; 
-        
-        @Override
-        public LinkAndLabelUiModel<Component> newLink(final ObjectAction objectAction) {
-
-            val objectAdapter = serviceModel.getManagedObject();
-            val linkComponent = new Label(objectAction.getName());
-            val whetherReturnsBlobOrClob = ObjectAction.Util.returnsBlobOrClob(objectAction);
-            
-            //linkComponent.addClassName(className);
-            
-            return LinkAndLabelUiModel.newLinkAndLabel(linkComponent, objectAdapter, objectAction, whetherReturnsBlobOrClob);
-            
-        }
-    }
-    
     
 }


[isis] 02/02: ISIS-2340: unify menu builders into common MenuModelFactory

Posted by ah...@apache.org.
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

commit 42cbb4ac4ede674cec8ea1c305e2dbe80b1b91df
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Apr 7 15:52:13 2020 +0200

    ISIS-2340: unify menu builders into common MenuModelFactory
---
 .../vaadin/model/action/MenuActionFactoryVaa.java} | 39 ++++-----
 .../model/action/MenuActionLinkFactoryVaa.java}    |  4 +-
 .../viewer/vaadin/model/menu/MenuItemVaa.java      | 19 +----
 .../viewer/vaadin/ui/pages/main/MenuUtil.java      | 86 +++++--------------
 .../common/model/action/MenuActionFactory.java     | 16 ++++
 .../viewer/common/model/menu/MenuModelFactory.java | 86 +++++++++++++++++++
 .../common/model/menuitem/MenuItemUiModel.java     |  9 +-
 .../actionmenu/serviceactions/CssMenuItem.java     |  4 -
 .../serviceactions/ServiceActionUtil.java          | 98 ++++++++++------------
 .../serviceactions/ServiceActionsPanelFactory.java | 13 ++-
 .../serviceactions/TertiaryMenuPanelFactory.java   | 10 +--
 11 files changed, 202 insertions(+), 182 deletions(-)

diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionFactoryVaa.java
similarity index 51%
copy from incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
copy to incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionFactoryVaa.java
index 1c0f716..1340d2a 100644
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
+++ b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionFactoryVaa.java
@@ -16,35 +16,36 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.incubator.viewer.vaadin.ui.components.actionlink;
+package org.apache.isis.incubator.viewer.vaadin.model.action;
 
 import com.vaadin.flow.component.Component;
-import com.vaadin.flow.component.html.Label;
 
+import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
 import org.apache.isis.incubator.viewer.vaadin.model.entity.ObjectVaa;
-import org.apache.isis.viewer.common.model.link.ActionLinkFactory;
-import org.apache.isis.viewer.common.model.link.LinkAndLabelUiModel;
+import org.apache.isis.viewer.common.model.action.MenuActionFactory;
 
-import lombok.RequiredArgsConstructor;
 import lombok.val;
 
-@RequiredArgsConstructor
-public class MenuActionLinkFactory implements ActionLinkFactory<Component> {
+public class MenuActionFactoryVaa implements MenuActionFactory<Component> {
 
-    /** model of the service that holds the menu item's action */
-    private final ObjectVaa serviceModel; 
-    
     @Override
-    public LinkAndLabelUiModel<Component> newLink(final ObjectAction objectAction) {
-
-        val objectAdapter = serviceModel.getManagedObject();
-        val linkComponent = new Label(objectAction.getName());
-        val whetherReturnsBlobOrClob = ObjectAction.Util.returnsBlobOrClob(objectAction);
-        
-        //linkComponent.addClassName(className);
+    public MenuActionVaa newMenuAction(
+            IsisWebAppCommonContext commonContext, 
+            ManagedObject serviceAction,
+            String named, 
+            ObjectAction objectAction, 
+            boolean isFirstInSection) {
         
-        return LinkAndLabelUiModel.newLinkAndLabel(linkComponent, objectAdapter, objectAction, whetherReturnsBlobOrClob);
+        val objectModel = new ObjectVaa(commonContext, serviceAction);
         
+        return new MenuActionVaa(
+                        new MenuActionLinkFactoryVaa(objectModel),
+                        named,
+                        objectModel,
+                        objectAction,
+                        isFirstInSection);
     }
-}
\ No newline at end of file
+
+}
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionLinkFactoryVaa.java
similarity index 92%
rename from incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
rename to incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionLinkFactoryVaa.java
index 1c0f716..00a9b25 100644
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/components/actionlink/MenuActionLinkFactory.java
+++ b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/action/MenuActionLinkFactoryVaa.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.incubator.viewer.vaadin.ui.components.actionlink;
+package org.apache.isis.incubator.viewer.vaadin.model.action;
 
 import com.vaadin.flow.component.Component;
 import com.vaadin.flow.component.html.Label;
@@ -30,7 +30,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.val;
 
 @RequiredArgsConstructor
-public class MenuActionLinkFactory implements ActionLinkFactory<Component> {
+public class MenuActionLinkFactoryVaa implements ActionLinkFactory<Component> {
 
     /** model of the service that holds the menu item's action */
     private final ObjectVaa serviceModel; 
diff --git a/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/menu/MenuItemVaa.java b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/menu/MenuItemVaa.java
index 08c3950..ca4d21a 100644
--- a/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/menu/MenuItemVaa.java
+++ b/incubator/viewers/vaadin/model/src/main/java/org/apache/isis/incubator/viewer/vaadin/model/menu/MenuItemVaa.java
@@ -20,13 +20,8 @@ package org.apache.isis.incubator.viewer.vaadin.model.menu;
 
 import com.vaadin.flow.component.Component;
 
-import org.apache.isis.incubator.viewer.vaadin.model.action.MenuActionVaa;
-import org.apache.isis.viewer.common.model.action.ActionUiModel;
 import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
 
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Setter;
 import lombok.val;
 
 /**
@@ -36,8 +31,6 @@ import lombok.val;
 public class MenuItemVaa 
 extends MenuItemUiModel<Component, MenuItemVaa> {
 
-    @Getter @Setter(AccessLevel.PRIVATE) private MenuActionVaa menuActionUiModel;
-    
     public static MenuItemVaa newMenuItem(final String name) {
         return new MenuItemVaa(name);
     }
@@ -52,15 +45,5 @@ extends MenuItemUiModel<Component, MenuItemVaa> {
         subMenuItem.setParent(this);
         return subMenuItem;
     }
-    
-    /**
-     * Optionally creates a sub-menu item invoking an action on the provided 
-     * {@link ActionUiModel}, based on visibility and usability.
-     */
-    public void addMenuItemFor(final MenuActionVaa menuActionModel) {
-        
-        super.addMenuItemFor(menuActionModel, 
-                subMenuItem->((MenuItemVaa)subMenuItem).setMenuActionUiModel(menuActionModel));
-        
-    }
+
 }
diff --git a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
index 9c6098d..b21de01 100644
--- a/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
+++ b/incubator/viewers/vaadin/ui/src/main/java/org/apache/isis/incubator/viewer/vaadin/ui/pages/main/MenuUtil.java
@@ -16,17 +16,16 @@ import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
 import org.apache.isis.applib.services.menu.MenuBarsService.Type;
 import org.apache.isis.core.runtimeservices.menubars.bootstrap3.MenuBarsServiceBS3;
 import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
+import org.apache.isis.incubator.viewer.vaadin.model.action.MenuActionFactoryVaa;
 import org.apache.isis.incubator.viewer.vaadin.model.action.MenuActionVaa;
-import org.apache.isis.incubator.viewer.vaadin.model.entity.ObjectVaa;
 import org.apache.isis.incubator.viewer.vaadin.model.menu.MenuItemVaa;
-import org.apache.isis.incubator.viewer.vaadin.ui.components.actionlink.MenuActionLinkFactory;
+import org.apache.isis.viewer.common.model.menu.MenuModelFactory;
 
 import lombok.val;
 import lombok.experimental.UtilityClass;
-import lombok.extern.log4j.Log4j2;
 
 @UtilityClass
-@Log4j2
+//@Log4j2
 final class MenuUtil {
 
     static Component createMenu(
@@ -62,9 +61,10 @@ final class MenuUtil {
             val menuItem = parentMenu.addItem(menuSectionUiModel.getName());
             val subMenu = menuItem.getSubMenu();
             menuSectionUiModel.getSubMenuItems().forEach(menuItemModel -> {
-                val menuActionModel = menuItemModel.getMenuActionUiModel();
+                val menuActionModel = (MenuActionVaa)menuItemModel.getMenuActionUiModel();
                 
-                if(menuActionModel.isFirstInSection() && subMenu.getItems().size()>0) {
+                if(menuActionModel.isFirstInSection() 
+                        && subMenu.getItems().size()>0) {
                     subMenu.addItem(new Hr());
                 }
                 
@@ -76,15 +76,15 @@ final class MenuUtil {
         };
         
         // top level left aligned ...
-        buildMenuModel(commonContext, bs3MenuBars.getPrimary(), menuSectionUiModel->
-            menuSectionBuilder.accept(leftMenuBar, menuSectionUiModel));
+        buildMenuModel(commonContext, bs3MenuBars.getPrimary(), newMenuItem->
+            menuSectionBuilder.accept(leftMenuBar, newMenuItem));
         
         // top level right aligned ...
-        buildMenuModel(commonContext, bs3MenuBars.getSecondary(), menuSectionUiModel->
-            menuSectionBuilder.accept(rightMenuBar, menuSectionUiModel));
+        buildMenuModel(commonContext, bs3MenuBars.getSecondary(), newMenuItem->
+            menuSectionBuilder.accept(rightMenuBar, newMenuItem));
         // TODO tertiary menu items should get collected under a top level menu labeled with the current user's name 
-        buildMenuModel(commonContext, bs3MenuBars.getTertiary(), menuSectionUiModel->
-            menuSectionBuilder.accept(rightMenuBar, menuSectionUiModel));
+        buildMenuModel(commonContext, bs3MenuBars.getTertiary(), newMenuItem->
+            menuSectionBuilder.accept(rightMenuBar, newMenuItem));
         
         return menuBarContainer;
         
@@ -112,63 +112,17 @@ final class MenuUtil {
         
     }
     
-    // initially copied from org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions.ServiceActionUtil.buildMenu
     private static void buildMenuModel(
             final IsisWebAppCommonContext commonContext,
             final BS3MenuBar menuBar,
-            final Consumer<MenuItemVaa> onMenuSection) {
-
-        // we no longer use ServiceActionsModel#getObject() because the model only holds the services for the
-        // menuBar in question, whereas the "Other" menu may reference a service which is defined for some other menubar
-
-        for (val menu : menuBar.getMenus()) {
-
-            val menuSectionUiModel = MenuItemVaa.newMenuItem(menu.getNamed());
-
-            for (val menuSection : menu.getSections()) {
-
-                boolean isFirstSection = true;
-
-                for (val actionLayoutData : menuSection.getServiceActions()) {
-                    val serviceSpecId = actionLayoutData.getObjectType();
-
-                    val serviceAdapter = commonContext.lookupServiceAdapterById(serviceSpecId);
-                    if (serviceAdapter == null) {
-                        // service not recognized, presumably the menu layout is out of sync
-                        // with actual configured modules
-                        continue;
-                    }
-
-                    val objectAction =
-                            serviceAdapter
-                                    .getSpecification()
-                                    .getObjectAction(actionLayoutData.getId())
-                                    .orElse(null);
-                    if (objectAction == null) {
-                        log.warn("No such action {}", actionLayoutData.getId());
-                        continue;
-                    }
-                    
-                    val objectModel = new ObjectVaa(commonContext, serviceAdapter);
-                    
-                    val saModel =
-                            new MenuActionVaa(
-                                    new MenuActionLinkFactory(objectModel),
-                                    actionLayoutData.getNamed(),
-                                    objectModel,
-                                    objectAction,
-                                    isFirstSection);
-
-                    // Optionally creates a sub-menu item based on visibility and usability
-                    menuSectionUiModel.addMenuItemFor(saModel);
-                    
-                    isFirstSection = false;
-                }
-            }
-            if (menuSectionUiModel.hasSubMenuItems()) {
-                onMenuSection.accept(menuSectionUiModel);
-            }
-        }
+            final Consumer<MenuItemVaa> onNewMenuItem) {
+        
+        MenuModelFactory.buildMenuItems(
+                commonContext, 
+                menuBar,
+                new MenuActionFactoryVaa(),
+                MenuItemVaa::newMenuItem,
+                onNewMenuItem);
     }
     
     
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/MenuActionFactory.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/MenuActionFactory.java
new file mode 100644
index 0000000..88d6461
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/MenuActionFactory.java
@@ -0,0 +1,16 @@
+package org.apache.isis.viewer.common.model.action;
+
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
+
+public interface MenuActionFactory<T> {
+
+    MenuActionUiModel<T> newMenuAction(
+            IsisWebAppCommonContext commonContext, 
+            ManagedObject serviceAction, 
+            String named,
+            ObjectAction objectAction, 
+            boolean isFirstInSection);
+
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuModelFactory.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuModelFactory.java
new file mode 100644
index 0000000..62474e5
--- /dev/null
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menu/MenuModelFactory.java
@@ -0,0 +1,86 @@
+package org.apache.isis.viewer.common.model.menu;
+
+import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.isis.applib.layout.menubars.bootstrap3.BS3Menu;
+import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
+import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
+import org.apache.isis.viewer.common.model.action.MenuActionFactory;
+import org.apache.isis.viewer.common.model.menuitem.MenuItemUiModel;
+
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+public class MenuModelFactory {
+
+    public static <T, M extends MenuItemUiModel<T, M>> 
+    void buildMenuItems(
+            final IsisWebAppCommonContext commonContext,
+            final BS3MenuBar menuBar,
+            final MenuActionFactory<T> menuActionFactory,
+            final Function<String, M> menuItemFactory,
+            final Consumer<M> onNewMenuItem) {
+
+        // we no longer use ServiceActionsModel#getObject() because the model only holds the services for the
+        // menuBar in question, whereas the "Other" menu may reference a service which is defined for some other menubar
+
+        val itemsPerSectionCounter = new LongAdder();
+        
+        for (final BS3Menu menu : menuBar.getMenus()) {
+
+            val menuItemModel = menuItemFactory.apply(menu.getNamed()); 
+
+            for (val menuSection : menu.getSections()) {
+
+                itemsPerSectionCounter.reset();
+                
+                for (val actionLayoutData : menuSection.getServiceActions()) {
+                    val serviceSpecId = actionLayoutData.getObjectType();
+
+                    val serviceAdapter = commonContext.lookupServiceAdapterById(serviceSpecId);
+                    if(serviceAdapter == null) {
+                        // service not recognized, presumably the menu layout is out of sync with actual configured modules
+                        continue;
+                    }
+
+                    val objectAction =
+                            serviceAdapter
+                                    .getSpecification()
+                                    .getObjectAction(actionLayoutData.getId())
+                                    .orElse(null);
+                    if (objectAction == null) {
+                        log.warn("No such action {}", actionLayoutData.getId());
+                        continue;
+                    }
+
+                    val isFirstInSection = itemsPerSectionCounter.intValue()==0; 
+                    
+                    val menuActionUiModel = menuActionFactory.newMenuAction(
+                            commonContext,
+                            serviceAdapter,
+                            actionLayoutData.getNamed(),
+                            objectAction, 
+                            isFirstInSection);
+
+                    // Optionally creates a sub-menu item based on visibility and usability
+                    menuItemModel.addSubMenuItemFor(menuActionUiModel, newSubMenuItem->{
+                        // increment counter only when a sub item was actually added
+                        itemsPerSectionCounter.increment();
+                        newSubMenuItem.setMenuActionUiModel(menuActionUiModel);
+                    });
+                    
+                }
+            }
+            if (menuItemModel.hasSubMenuItems()) {
+                onNewMenuItem.accept(menuItemModel);
+            }
+        }
+        
+    }
+
+    
+    
+}
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java
index 243724f..c3f10af 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/menuitem/MenuItemUiModel.java
@@ -89,6 +89,7 @@ public abstract class MenuItemUiModel<T, U extends MenuItemUiModel<T, U>> {
     @Getter @Setter private boolean blobOrClob = false; // unless set otherwise
     
     @Getter @Setter(AccessLevel.PRIVATE) private T actionLinkComponent;
+    @Getter @Setter private MenuActionUiModel<T> menuActionUiModel;
     
     private final List<U> subMenuItems = _Lists.newArrayList();
     protected void addSubMenuItem(final U cssMenuItem) {
@@ -170,9 +171,9 @@ public abstract class MenuItemUiModel<T, U extends MenuItemUiModel<T, U>> {
      * Optionally creates a sub-menu item invoking an action on the provided 
      * {@link MenuActionWkt action model}, based on visibility and usability.
      */
-    protected void addMenuItemFor(
+    public void addSubMenuItemFor(
             @NonNull final MenuActionUiModel<T> menuActionModel, 
-            @Nullable final Consumer<U> onNewMenuItem) {
+            @Nullable final Consumer<U> onNewSubMenuItem) {
 
         val serviceModel = menuActionModel.getServiceModel();
         val objectAction = menuActionModel.getObjectAction();
@@ -212,8 +213,8 @@ public abstract class MenuItemUiModel<T, U extends MenuItemUiModel<T, U>> {
         
         menutIem.setActionLinkComponent(linkAndLabel.getLinkComponent());
         
-        if(onNewMenuItem!=null) {
-            onNewMenuItem.accept(_Casts.uncheckedCast(menutIem));
+        if(onNewSubMenuItem!=null) {
+            onNewSubMenuItem.accept(_Casts.uncheckedCast(menutIem));
         }
     }
     
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
index 5429167..c248f9d 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/CssMenuItem.java
@@ -57,10 +57,6 @@ implements Serializable {
         subMenuItem.setParent(this);
         return subMenuItem;
     }
-
-    protected void addMenuItemFor(MenuActionWkt menuActionModel) {
-        super.addMenuItemFor(menuActionModel, null);
-    }
     
     // //////////////////////////////////////////////////////////////
     // Build wicket components from the menu item.
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
index e713607..d38662f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionUtil.java
@@ -20,23 +20,24 @@
 package org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.AbstractLink;
 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.isis.applib.layout.component.ServiceActionLayoutData;
-import org.apache.isis.applib.layout.menubars.MenuBars;
-import org.apache.isis.applib.layout.menubars.MenuSection;
-import org.apache.isis.applib.layout.menubars.bootstrap3.BS3Menu;
 import org.apache.isis.applib.layout.menubars.bootstrap3.BS3MenuBar;
 import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.core.commons.internal.base._Strings;
 import org.apache.isis.core.commons.internal.collections._Lists;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.webapp.context.IsisWebAppCommonContext;
+import org.apache.isis.viewer.common.model.action.MenuActionFactory;
+import org.apache.isis.viewer.common.model.menu.MenuModelFactory;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
 import org.apache.isis.viewer.wicket.model.models.ServiceActionsModel;
 import org.apache.isis.viewer.wicket.ui.components.actionmenu.CssClassFaBehavior;
@@ -195,61 +196,46 @@ public final class ServiceActionUtil {
         folderItem.add(subMenuItemsView);
     }
 
-    public static List<CssMenuItem> buildMenu(
-            IsisWebAppCommonContext commonContext,
-            MenuBars menuBars,
-            ServiceActionsModel serviceActionsModel) {
+    
+    private static class MenuActionFactoryWkt implements MenuActionFactory<AbstractLink> {
+
+        @Override
+        public MenuActionWkt newMenuAction(
+                IsisWebAppCommonContext commonContext, 
+                ManagedObject serviceAction,
+                String named, 
+                ObjectAction objectAction, 
+                boolean isFirstInSection) {
+            
+            val objectModel = EntityModel.ofAdapter(commonContext, serviceAction);
+            
+            return new MenuActionWkt(
+                    new MenuActionLinkFactory(PageAbstract.ID_MENU_LINK, objectModel), 
+                    named, 
+                    objectModel, 
+                    objectAction, 
+                    isFirstInSection);
+        }
+        
+    }
+    
+    public static void buildMenu(
+            final IsisWebAppCommonContext commonContext,
+            final ServiceActionsModel serviceActionsModel,
+            final Consumer<CssMenuItem> onNewMenuItem) {
+        
+        val menuBars = commonContext.getMenuBarsService().menuBars();
 
         // TODO: remove hard-coded dependency on BS3
         final BS3MenuBar menuBar = (BS3MenuBar) menuBars.menuBarFor(serviceActionsModel.getMenuBar());
-
-        // we no longer use ServiceActionsModel#getObject() because the model only holds the services for the
-        // menuBar in question, whereas the "Other" menu may reference a service which is defined for some other menubar
-
-        final List<CssMenuItem> menuItems = _Lists.newArrayList();
-        for (final BS3Menu menu : menuBar.getMenus()) {
-
-            final CssMenuItem menuItemModel = CssMenuItem.newMenuItem(menu.getNamed());
-
-            for (final MenuSection menuSection : menu.getSections()) {
-
-                boolean isFirstInSection = true;
-
-                for (final ServiceActionLayoutData actionLayoutData : menuSection.getServiceActions()) {
-                    val serviceSpecId = actionLayoutData.getObjectType();
-
-                    val serviceAdapter = commonContext.lookupServiceAdapterById(serviceSpecId);
-                    if(serviceAdapter == null) {
-                        // service not recognized, presumably the menu layout is out of sync with actual configured modules
-                        continue;
-                    }
-                    final EntityModel serviceEntityModel = EntityModel.ofAdapter(commonContext, serviceAdapter);
-                    final ObjectAction objectAction = serviceAdapter.getSpecification()
-                            .getObjectAction(actionLayoutData.getId()).orElse(null);
-                    if(objectAction == null) {
-                        log.warn("No such action {}", actionLayoutData.getId());
-                        continue;
-                    }
-                    
-                    val menuActionModel =
-                            new MenuActionWkt(
-                                    new MenuActionLinkFactory(PageAbstract.ID_MENU_LINK, serviceEntityModel)::newLink, 
-                                    actionLayoutData.getNamed(), 
-                                    serviceEntityModel, 
-                                    objectAction, 
-                                    isFirstInSection);
-
-                    isFirstInSection = false;
-
-                    // Optionally creates a sub-menu item based on visibility and usability
-                    menuItemModel.addMenuItemFor(menuActionModel);
-                }
-            }
-            if (menuItemModel.hasSubMenuItems()) {
-                menuItems.add(menuItemModel);
-            }
-        }
-        return menuItems;
+        
+        MenuModelFactory.buildMenuItems(
+                commonContext, 
+                menuBar,
+                new MenuActionFactoryWkt(),
+                CssMenuItem::newMenuItem,
+                onNewMenuItem);
+        
     }
 
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
index 195f5ef..86d0469 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
@@ -19,18 +19,18 @@
 
 package org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions;
 
-import java.util.List;
-
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 
 import org.apache.isis.applib.annotation.DomainServiceLayout;
-import org.apache.isis.applib.layout.menubars.MenuBars;
+import org.apache.isis.core.commons.internal.collections._Lists;
 import org.apache.isis.viewer.wicket.model.models.ServiceActionsModel;
 import org.apache.isis.viewer.wicket.ui.ComponentFactory;
 import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
 
+import lombok.val;
+
 /**
  * {@link ComponentFactory} for a {@link ServiceActionsPanel} to represent the
  * {@link org.apache.isis.viewer.wicket.model.models.ServiceActionsModel application action}s.
@@ -60,10 +60,9 @@ public class ServiceActionsPanelFactory extends ComponentFactoryAbstract {
     public Component createComponent(final String id, final IModel<?> model) {
         final ServiceActionsModel serviceActionsModel = (ServiceActionsModel) model;
 
-        final MenuBars menuBars = super.getCommonContext().getMenuBarsService().menuBars();
-
-        final List<CssMenuItem> menuItems = ServiceActionUtil.buildMenu(
-                super.getCommonContext(), menuBars, serviceActionsModel);
+        val menuItems = _Lists.<CssMenuItem>newArrayList();
+        ServiceActionUtil.buildMenu(
+                super.getCommonContext(), serviceActionsModel, menuItems::add);
 
         return new ServiceActionsPanel(id, menuItems);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
index 54f8d99..c36ad84 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
@@ -19,12 +19,11 @@
 
 package org.apache.isis.viewer.wicket.ui.components.actionmenu.serviceactions;
 
-import java.util.List;
-
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 
 import org.apache.isis.applib.annotation.DomainServiceLayout;
+import org.apache.isis.core.commons.internal.collections._Lists;
 import org.apache.isis.viewer.wicket.model.models.ServiceActionsModel;
 import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
@@ -61,10 +60,9 @@ public class TertiaryMenuPanelFactory extends ComponentFactoryAbstract {
     public Component createComponent(final String id, final IModel<?> model) {
         val serviceActionsModel = (ServiceActionsModel) model;
 
-        val menuBars = super.getCommonContext().getMenuBarsService().menuBars();
-
-        final List<CssMenuItem> menuItems = ServiceActionUtil.buildMenu(
-                super.getCommonContext(), menuBars, serviceActionsModel);
+        val menuItems = _Lists.<CssMenuItem>newArrayList();
+        ServiceActionUtil.buildMenu(
+                super.getCommonContext(), serviceActionsModel, menuItems::add);
         
         return new TertiaryActionsPanel(id, menuItems);
     }