Get menu items in tree selector

Hi,

I want users to select some shortcuts from the side menu. Now, the available menu items differ and are partly created dynamically (not only in web-menu.xml).

I would like to provide a tree selector with their menu shown as items and have therefore created a custom value datasource based on a non-persistent entity that resembles the menu items:

@NamePattern("%s|caption")
@MetaClass(name = "base$MenuEntity")
public class MenuEntity extends BaseStringIdEntity implements HasUuid {
    private static final long serialVersionUID = 4397712108622636264L;

    @Id
    @MetaProperty(mandatory = true)
    protected String id;

    @MetaProperty
    protected UUID uuid;

    @MetaProperty
    protected String caption;

    @MetaProperty
    protected MenuEntity parent;

   // continues with all getters and setters ...
}

The custom datasource:

public class MenuItemDatasource extends CustomValueHierarchicalDatasource {

    private HashMap<String, MenuEntity> menuItems;

    /**
     * Custom method to pass side menu as a list of values
     */
    private void addMenuItems(MenuEntity parent, List<SideMenu.MenuItem> children) {
        // Fill collection with information
        children.forEach(i -> {
            MenuEntity m = metadata.create(MenuEntity.class);

            m.setCaption(i.getCaption());
            m.setId(i.getId());
            m.setParent(parent);

            menuItems.put(m.getId(), m);

            // Parse the tree
            if (i.getChildren() != null)
                addMenuItems(m, i.getChildren());
        });
    }

    /**
     * Simply return the list with menu items
     */
    @Override
    protected Collection<KeyValueEntity> getEntities(Map<String, Object> params) {
        // Initialise (or clear) current collection
        menuItems = new HashMap<>();

        // Create a root entry and add it to the set
        MenuEntity root = metadata.create(MenuEntity.class);
        root.setCaption("root");
        root.setId("root");
        root.setParent(null);
        menuItems.put(root.getId(), root);

        // Process the list of menu items
        ExtAppMainWindow mainWindow = (ExtAppMainWindow) AppUI.getCurrent().getTopLevelWindow();
        addMenuItems(root, mainWindow.getSideMenu().getMenuItems());

        // Transform into collection
        Collection<KeyValueEntity> items = new ArrayList<>();
        menuItems.forEach((id, m) -> {
            KeyValueEntity e = new KeyValueEntity();
            e.setIdName("id");
            e.setMetaClass(metadata.getClass(MenuEntity.class));

            e.setId(id);
            e.setValue(m.getCaption(), m);
        });

        return items;
    }
}

When I use this datasource in a screen, it is loaded correctly but not shown in the tree component.

   <dsContext>
        <valueHierarchicalDatasource id="menuItemsDs"
                                     hierarchyProperty="parent"
                                     datasourceClass="com.company.app.web.general.MenuItemDatasource">
            <properties idProperty="id">
                <property name="id" datatype="string"/>
                <property name="caption" datatype="string"/>
                <property name="parent" class="com.company.app.general.MenuEntity"/>
            </properties>
        </valueHierarchicalDatasource>

    </dsContext>

<!-- layout definition -->

           <tree id="menuSelection" width="500px">
                 <treechildren datasource="menuItemsDs" captionProperty="caption" hierarchyProperty="parent"/>
           </tree>

And in the controller:


    @Inject
    private ValueHierarchicalDatasourceImpl menuItemsDs;

    @Inject
    private Tree<MenuEntity> menuSelection;

    @Override
    public void postInit() {
        super.postInit();

        // Refresh datasource
        menuItemsDs.refresh();

        // Let something happen when clicked
        menuSelection.setItemClickAction(new BaseAction("treeClickAction")
                .withHandler(actionPerformedEvent -> {
                    MenuEntity item = menuSelection.getSingleSelected();
                    if (item != null) {
                        showNotification(item.getId());
                    }
                }));
     }

Any ideas why the tree component remains empty? The datasource holds the items and all the data seems ok.

Thanks for help or any suggestions.
-b

Hi Berend,

Why do you use KeyValueEntity and value-datasource if you have the MenuItem entity which is capable of modelling the hierarchy?
Try to simplify the situation and use CustomHierarchicalDatasource with the MenuItem entities.

Hi Konstantin,

Thanks for your reply. I tried the custom hierarchical datasource but couldn’t get the menu items in there properly. Could you supply me a short setup or hint on how to do this?

-b

Hi Berend,
Here it is.

Entity class:

package com.company.sales.entity;

import com.haulmont.chile.core.annotations.MetaClass;
import com.haulmont.chile.core.annotations.MetaProperty;
import com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.cuba.core.entity.BaseUuidEntity;

import javax.persistence.*;

@MetaClass(name = "sales_MenuItem")
@NamePattern("%s|name")
public class MenuItem extends BaseUuidEntity {
    @MetaProperty
    protected String name;

    @MetaProperty
    protected MenuItem parent;

    public MenuItem getParent() {
        return parent;
    }

    public void setParent(MenuItem parent) {
        this.parent = parent;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Datasource class:

package com.company.sales.web.menu;

import com.company.sales.entity.MenuItem;
import com.haulmont.cuba.gui.data.impl.CustomHierarchicalDatasource;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;

public class MenuItemsDatasource extends CustomHierarchicalDatasource<MenuItem, UUID> {

    @Override
    protected Collection<MenuItem> getEntities(Map<String, Object> params) {
        MenuItem root = new MenuItem();
        root.setName("root");

        MenuItem child1 = new MenuItem();
        child1.setName("child1");
        child1.setParent(root);

        MenuItem child2 = new MenuItem();
        child2.setName("child2");
        child2.setParent(root);

        MenuItem child11 = new MenuItem();
        child11.setName("child11");
        child11.setParent(child1);

        return Arrays.asList(root, child1, child2, child11);
    }
}

Screen descriptor:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        class="com.company.sales.web.menu.MenuScreen"
        messagesPack="com.company.sales.web.menu"
        caption="msg://caption">
    <dsContext>
        <hierarchicalDatasource id="menuDs" class="com.company.sales.entity.MenuItem"
                                datasourceClass="com.company.sales.web.menu.MenuItemsDatasource"
                                hierarchyProperty="parent"/>
    </dsContext>
    <layout>
        <tree id="menu">
            <treechildren datasource="menuDs" hierarchyProperty="parent"/>
        </tree>
    </layout>
</window>

Result screen:
image

Hi Konstantin,

This is great, but… I was referring to the menu item class from the Cuba platform (as part of the side menu). This is not a UUID entity and the custom data source therefore does not work. I tried, but couldn’t.

That’s why I used the custom value data source but could not get it to work. The data source seems correct (holds the values) but the tree selector doesn’t show anything.

-b

So still stuck on the problem.

Then I need the whole picture. A test project would help a lot.

That’s easier said than done but will give this a try in a week or two (unable to do that before).

But really, the basic idea is to give a user the opportunity to select a menu item from the side menu using a tree selector. This will serves as a ‘favourite’ or shortcut later on.

-b