Tree With Link from Enumeration Values

We are currently migrating our code from the v8.0.1 framework to v8.3.2 partly because of the Tree component introduced in 8.2 onwards. At that time, because Tree wasn’t introduced yet, we had a workaround on creating menu items with links defined in an enumeration class which is as follows:

private Component buildMenuItems(){
	for (final DashboardViewType view : DashboardViewType.values()) {
           dashboardLogger.debug("Dashboard enum value: " + view.toString());
           enabled = false;
           if (i == 0 && mainACL.substring(0, 1).equals("1"))
               if (subACL1.substring(i, 1).equals("1"))
                    enabled = true;
           if (i>0 && i < 12 && mainACL.substring(1, 2).equals("1"))
           {
               if (subACL2.substring(i-1, i).equals("1"))
                   enabled = true;
           }
           if (i>=12 && i < 21 && mainACL.substring(2, 3).equals("1"))
           {
               if (subACL3.substring(i-12, i-11).equals("1"))
                   enabled = true;
           }
           if (i>=21 && i < 25 && mainACL.substring(3, 4).equals("1"))
           {
               if (subACL4.substring(i-21, i-20).equals("1"))
                   enabled = true;
           }           
          if (i>=25 && i < 30 && mainACL.substring(4,5).equals("1"))
           {
               if (subACL5.substring(i-25, i-24).equals("1"))
                   enabled = true;
          }           

           if (enabled)
            {
			Component menuItemComponent = new ValoMenuItemButton(view);
			}
		}
		public final class ValoMenuItemButton extends Button {

			private static final String STYLE_SELECTED = "selected";

			private final DashboardViewType view;

			public ValoMenuItemButton(final DashboardViewType view) {
				this.view = view;
				setPrimaryStyleName("valo-menu-item");
				setIcon(view.getIcon());
				setCaption(view.getViewName().substring(0, 1).toUpperCase()
						+ view.getViewName().substring(1));
				DashboardEventBus.register(this);
				addClickListener(new ClickListener() {
					@Override
					public void buttonClick(final ClickEvent event) {
						UI.getCurrent().getNavigator()
								.navigateTo(view.getViewName());
					}
				});

			}

			@Subscribe
			public void postViewChange(final PostViewChangeEvent event) {
				removeStyleName(STYLE_SELECTED);
				if (event.getView() == view) {
					addStyleName(STYLE_SELECTED);
				}
			}
		}
}

Each menu item is controlled by a flag (binary value of 0 or 1) where if it is true, a ValoMenuButtonItem will be instantiated.

Enumeration (DashboardViewType) values example:

DASHBOARD("dashboard", DashboardView.class, FontAwesome.HOME, true),
SALES1("Sales (RM) by Outlet", SalesByOutlet.class, FontAwesome.BAR_CHART_O, false),
SALES_PATTERN1("Sales (RM) by Hour", SalesByHour.class, FontAwesome.BAR_CHART_O, false), 
TOP_SKUs("Top SKUs", TopSkus.class, FontAwesome.BAR_CHART_O, false),
AUDIT3("Missing Sequence", MissSequence.class, FontAwesome.BAR_CHART_O, false);

Constructor:
private DashboardViewType(final String viewName,
            final Class<? extends View> viewClass, final Resource icon,
            final boolean stateful)

We were unsure if we can port over our original code to the Tree. Base on the example code shown in the sampler, we tried the following:

Tree<String> tree = new Tree("My Tree");
TreeData<String> treeData = new TreeData<>(); //should this be the Enum class or String?
treeData.addItems(null,DASHBOARD,SALES,SALES_PATTERN,TOP_SKUS,AUDIT); //this is for the primary items.

(//for loop as shown above){
	if (enabled){
				if(StringUtils.startsWith(view.getViewName(), SALES)){
                    if (StringUtils.contains(view.getViewName(), SALES_PATTERN)){
                        treeData.addItem(SALES_PATTERN, view.getViewName()); //attempt to add item to the parent.
                    }else{
                        treeData.addItem(SALES, view.getViewName());
                    }
                }else if (StringUtils.startsWith(view.getViewName(), TOP_SKUS)){
                    treeData.addItem(TOP_SKUS, view.getViewName());
                }else if (StringUtils.startsWith(view.getViewName(), AUDIT)){
                    treeData.addItem(AUDIT, view.getViewName());
                }else if (StringUtils.startsWith(view.getViewName(), DASHBOARD)){
                    treeData.addItem(DASHBOARD, view.getViewName());
                }
               
			    //this is to define each item (caption, icon and clickitemlistener
                setPrimaryStyleName("valo-menu-item");
                setIcon(view.getIcon());
                setCaption(view.getViewName().substring(0, 1).toUpperCase()
                        + view.getViewName().substring(1));
                DashboardEventBus.register(this);
                tree.addItemClickListener(event -> {
                   if(event.getItem() != null){
                       UI.getCurrent().getNavigator()
                                .navigateTo(view.getViewName());
                   } 
                });
	}
	tree.setDataProvider(new TreeDataProvider<>(treeData));
    tree.expand(SALES,SALES_PATTERN,TOP_SKUS,AUDIT);
    menuItemsLayout.addComponent(tree);
}

The result above was that the Treedata doesn’t allow add item multiple times (exception is thrown).
We still want to retain the menu item control, where the menu item only appears if the enabled flag value is true.

What would be the most appropriate way to have a tree with links taken from an enum class? Otherwise, if enum is not appropriate for this situation in moving to tree, is there another good practice approach?

Appreciate any answer to that above.

Hi Melvin,

From a first look this is quite possible. I would need your help on some things to understand what you are trying to achieve.

  1. What are you looping over in this (//for loop as shown above){?
  2. Are the hierarchies hardcoded in the if conditions on the snippet shown?
  3. Is enabled a global setting or a setting for the itmes in the loop?

TreeData should work fine with enum or string or any other type, but you can’t mix types with TreeData, you have to use either String or Enum but not either one at different calls (unless you type it to Object in which case it’s possible).

Once I get an idea of the things you’re trying to achieve I can provide a sample for you using Enum and some hierarchy.

BR,
Mirjan

Posted an answer at your SO posting, hope this helps. :slight_smile:

https://stackoverflow.com/questions/49850944/vaadin-8-creating-a-tree-with-enum-class

Mirjan Merruko:
Hi Melvin,

From a first look this is quite possible. I would need your help on some things to understand what you are trying to achieve.

  1. What are you looping over in this (//for loop as shown above){?
  2. Are the hierarchies hardcoded in the if conditions on the snippet shown?
  3. Is enabled a global setting or a setting for the itmes in the loop?

TreeData should work fine with enum or string or any other type, but you can’t mix types with TreeData, you have to use either String or Enum but not either one at different calls (unless you type it to Object in which case it’s possible).

Once I get an idea of the things you’re trying to achieve I can provide a sample for you using Enum and some hierarchy.

BR,
Mirjan

Hi Mirjan,

Please find my replies to your queries.

1. What are you looping over in this (//for loop as shown above){?

The loop refers to the loop code that was written in the workaround approach. It’s purpose is for user-level control where first it checks the main menu item flags.

for (final DashboardViewType view : DashboardViewType.values()) {
           enabled = false;
           if (i == 0 && mainACL.substring(0, 1).equals("1"))
               if (subACL1.substring(i, 1).equals("1"))
                    enabled = true;
           if (i>0 && i < 12 && mainACL.substring(1, 2).equals("1"))
           {
               if (subACL2.substring(i-1, i).equals("1"))
                   enabled = true;
           }
           if (i>=12 && i < 21 && mainACL.substring(2, 3).equals("1"))
           {
               if (subACL3.substring(i-12, i-11).equals("1"))
                   enabled = true;
           }
                 

           if (enabled)
            {
			Component menuItemComponent = new ValoMenuItemButton(view);
			}
		}

E.g: If the main value is 10110, main menu items 1,3 and 4 will be enabled.

Next it will check inside the first enabled category to determine the available sub-menu items.

E.g if value under main menu item 1 is 1000011111, items 1, 6 to 10 under main menu item 1.

Are the hierarchies hardcoded in the if conditions on the snippet shown?

Yes it is for now. We plan to move to using a better practice in future releases.

Is enabled a global setting or a setting for the itmes in the loop?

The enabled flag is a setting for the items in the loop.

Best regards,

Melvin

TreeData won’t allow duplicate values, so anything whose equals() comparison with another item returns true is not possible. That means that you can’t achieve what you want by using Strings.

Create a wrapper class like this

import com.vaadin.navigator.View;
import com.vaadin.server.Resource;

public class TreeMenuItem {

    private DashboardViewType view;

    public TreeMenuItem(DashboardViewType view) {
        this.view = view;
    }

    public Class<? extends View> getViewClass() {
        return view.getViewClass();
    }

    public String getViewName() {
        return view.getViewName();
    }

    public Resource getIcon() {
        return view.getIcon();
    }

    public boolean isStateful() {
        return view.isStateful();
    }

}

Replace your loop with this

        Tree<TreeMenuItem> tree = new Tree<>("Tree");
        TreeData<TreeMenuItem> treeData = new TreeData<>();
        
        for (DashboardViewType type : DashboardViewType.values()) {
            TreeMenuItem menuItem = new TreeMenuItem(type);
            treeData.addItem(null, menuItem);
            getSumbenuItems(type).forEach(subView -> {
                TreeMenuItem subItem = new TreeMenuItem(subView);
                treeData.addItem(menuItem, subItem);
            });
            
        }

Create a method that has your application logic for getting the subviews for a view. In my example I did this to generate dummy data :

private List<DashboardViewType> getSumbenuItems(DashboardViewType type) {
        switch(type) {
        case AUDIT3:
            return Arrays.asList(DashboardViewType.DASHBOARD,DashboardViewType.DASHBOARD); 
        case DASHBOARD:
            return Arrays.asList(DashboardViewType.AUDIT3,DashboardViewType.SALES_PATTERN1);
        case SALES1:
            return Arrays.asList(DashboardViewType.TOP_SKUs,DashboardViewType.AUDIT3);
        case SALES_PATTERN1:
            break;
        case TOP_SKUs:
            break;
        default:
            break;
        
        }
        return Collections.emptyList();
    }

Then populate your tree :

	tree.setDataProvider(new TreeDataProvider<>(treeData));
    tree.setItemCaptionGenerator(TreeMenuItem::getViewName);

As TreeMenuItem.equals() is not implemented each item is considered different unless it’s the same object by reference. This allows you to have “multiple” items with the same behavior.

For more than two levels adapt this to work recursively and you should be good to go. Let me know if this helps!

BR,
Mirjan

Hi Mirjan,

I’ve already incorporated your sample, and I’ve modified the proposed getSubView logic that you’ve suggested including adding the user-level control check. I’m using a LinkedList so that subviews that are not related to the user will be removed.

FYI, one thing happened was that the size text is too long that my CSSLayout of the menu sidebar gets truncated by about a quarter and the colored bar doesn’t stretch to the bottom of the screen unless the tree is expanded. I suspect that it’s the CSS themes that needs adjustment, if the code is not the cause.

Thank you. It filled in the missing piece of the puzzle :slight_smile:

BR,
Melvin
17051338.png

Good to hear Melvin! Adjust your CSS here and there and see how things go for the truncation issue. Stefan’s solution was similar to this but I added the extra details just in case someone runs into similar issues in the future.

Hi Mirjan,

The alpha-release users loved the tree, but have asked if it is possible to have other parent nodes asides from the parent of the child item selected collapse recursively, or otherwise on grounds of they not wanting to scroll back to the top to go to the criteria bar for each page of the selected report page.

It is said that scenario can be handled via a Item Clicklistener from framework 8.4 onwards, but I am unsure if a condition to collapse all bar the current is allowed and is possible to be implemented. Appreciate help on this.

Thanks!