vaadinvaadin-flow

Passing components instance from MainView to tab contents component in separate java class file


I was trying to pass two SvgIcon from MainView to separate Java class file which are tab contents, so I could change the icon but I couldn't find the element implementing a function like this:

public SvgIcon getSvgIcon(String classSvgIcon) {
    return (SvgIcon) CurrentInstance.get(SvgIcon.class).findAncestor(SvgIcon.class).hasClassName(classSvgIcon);
} 

because I supposed it should return me the SvgIcon element that corresponds to the class name contained in the element, even if I'd prefer the ID but I see there is no hasIdAttribute() function but it didn't worked...

so then I tried to pass the MainView UI class to access directly to the component I want to change, like this:

public static MainView getCurrent() {
    return (MainView) CurrentInstance.get(UI.class);
}

but even if in the tab content class I can call now MainView.getCurrent().myIconElement but in the function it says Cannot cast from UI to MainView... how it should be rewritten correctly that function so I could access to that svg icon I want to change?

Thanks in advance to all! Cheers!

[EDIT]

There is my sample I'm trying to implement to change icon...

/* ************************************************************ 
 *                                                            *
 *          MainView class                                    *
 *                                                            *
 ************************************************************ */
package com.myexample.application.views.main;

import java.util.HashMap;
import java.util.Map;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.NativeLabel;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.component.tabs.TabsVariant;
import com.vaadin.flow.internal.CurrentInstance;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.HighlightConditions;

import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.server.StreamResource;

@PageTitle("Main")
@Route("")
public class MainView extends VerticalLayout implements RouterLayout, BeforeEnterObserver {

    private Map<Tab, RouterLink> tabSheets = new HashMap<>();
    private Tabs mainTabs; 

    private HorizontalLayout toolbar;       
    private NativeLabel appname;
    public SvgIcon iconToChange;

    public MainView() {
        
        RouteTabs tabsToAdd = new RouteTabs();
        tabsToAdd.addTab(new RouterLink("Tab 1", MyTab1.class), VaadinIcon.EYE.create());
        tabsToAdd.addTab(new RouterLink("Tab 2", MyTab2.class), VaadinIcon.EYE.create());
        
        add(getToolbar(), tabsToAdd);               
    }
    
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if (event.getNavigationTarget() == MainView.class) {
            event.forwardTo(MyTab1.class);
        }       
    }    

    public static MainView getCurrent() {
        return (MainView) CurrentInstance.get(UI.class).getCurrent();       
        //return CurrentInstance.get(UI.class);
        //return UI.getCurrent();
    }    
    
    public SvgIcon getIconToChange() { return (SvgIcon) iconToChange; }
    
    private HorizontalLayout getToolbar() {                     
        
        HorizontalLayout leftLayout = new HorizontalLayout();
        
        appname = new NativeLabel("My Test");
        appname.setClassName("title-style");
        
        leftLayout.add(appname);
        leftLayout.setAlignItems(FlexComponent.Alignment.START);
        leftLayout.setWidth("100%");
                
        HorizontalLayout rightLayout = new HorizontalLayout();
        
        StreamResource svgIconResource = new StreamResource("myOff_Icon.svg",
                () -> getClass().getResourceAsStream("/img/myOff_Icon.svg"));
        svgIconResource.setCacheTime(0);        
        iconToChange = new SvgIcon(svgIconResource);
        iconToChange.addClassName("iconToChange");
        iconToChange.setId("iconToChange");
        iconToChange.setTooltipText("IconToChange");
        
        rightLayout.add(iconToChange);
        rightLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
        rightLayout.setWidth("100%");

        var toolbar = new HorizontalLayout(leftLayout, rightLayout);
        toolbar.setWidthFull();
        toolbar.addClassName("toolbar");      
                
        return toolbar;
    }    
    
    
    private static class RouteTabs extends Tabs implements BeforeEnterObserver {
        private final Map<RouterLink, Tab> routerLinkTabMap = new HashMap<>();
        
        public void addTab(RouterLink routerLink, Icon iconToAdd) {
            routerLink.setHighlightCondition(HighlightConditions.sameLocation());
            routerLink.setHighlightAction(
                (link, shouldHighlight) -> {
                    if (shouldHighlight) setSelectedTab(routerLinkTabMap.get(routerLink));
                }             
            );            
            routerLinkTabMap.put(routerLink, new Tab(iconToAdd, routerLink));
            add(routerLinkTabMap.get(routerLink));
            this.addThemeVariants(TabsVariant.LUMO_EQUAL_WIDTH_TABS);
            this.setMaxWidth("100%");
            this.setWidthFull();
        }

        @Override
        public void beforeEnter(BeforeEnterEvent event) {
            // In case no tabs will match
            setSelectedTab(null);
        }
    }
    
    /*public SvgIcon getSvgIcon(String idSvgIcon) {
        //return (SvgIcon) CurrentInstance.get(SvgIcon.class).equals(getElement().getAttribute("id").equals(idSvgIcon));
        //return (SvgIcon) CurrentInstance.get(SvgIcon.class).findAncestor(SvgIcon.class).hasClassName(idSvgIcon);
        return (SvgIcon) CurrentInstance.get(SvgIcon.class).getElement().hasAttribute("id");
    } */
    
}

/* ************************************************************ 
 *                                                            *
 *          MainView class                                    *
 *                                                            *
 ************************************************************ */
 
 
/* ************************************************************ 
 *                                                            *
 *          MyTab1.class class                                *
 *                                                            *
 ************************************************************ */
 
 package com.myexample.application.views.main;

import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;

@Route(value="mytab1", layout = MainView.class)
public class MyTab1 extends VerticalLayout {
    
    /**
     * 
     */
    private SvgIcon iconChangeTo;
    private Text localText;
    private Button localButton;
        
    public ARDevices() {                    
        localText = new Text("My Tab 1");
            
        localButton = new Button("Change Icon");
        localButton.addClickListener(e -> {
            //iconChangeTo = Element.getAttribute("id").
            //iconChangeTo = UI.getCurrent().navigate(SvgIcon.class).ifPresent(icon -> icon.hasClassName("iconToChange")).      
            
            //SvgIcon icon = ((MainView) MainView.getCurrent()).getIconToChange();
            //MainView.getCurrent()

            SvgIcon icon = MainView.getCurrent().getIconToChange()
            
            
            //UIclass.iconToChange.setSrc( new StreamResource("myON_Icon.svg",
            //  () -> getClass().getResourceAsStream("/img/myON_Icon.svg")) );
        });
                
        add(localText, localButton);
    }   
}
/* ************************************************************ 
 *                                                            *
 *          MyTab1.class class                                *
 *                                                            *
 ************************************************************ */

Solution

  • There's three ways that I'm aware of to get the MainView instance from a child view.

    First option is to get the current UI instance and find the mainView within its children:

    MainView mainView = (MainView) UI.getCurrent().getChildren()
                .filter(component -> component.getClass() == MainView .class).findFirst().orElse(null);
    
    

    Second option is similar but vice versa, you start at the child view and look upwards. It looks uglier since you can only get 1 parent at a time in contrast to getChildren which returns a filterable Stream. I'm also unsure if this works within the constructor of the child view:

    MainView mainView = null;
    Optional<Component> parentOpt = this.getParent(); // this == some child view
    while(parentOpt.isPresent()) {
        Component parent = parentOpt.get();
        if(parent instanceof MainView) {
            mainView = (MainView) parent;
            parentOpt = Optional.empty(); // stop the loop
        } else {
            // look further
            parentOpt = parent.getParent();
        }
    }
    

    And finally the third option, which is what I use, but it requires dependency injection. In both the MainView as well as all possible child views that need it, we inject a MainViewBus (not so sure about the naming.. Find a better name for it if you can), which we can use to get the MainView directly. No HTML element child/parent crawling necessary here.

    @Component
    @UIScope
    public class MainViewBus {
        private MainView mainView;
    
        public MainViewBus() {
        }
    
        public MainView getMainView() {
            return this.mainView;
        }
        public void setMainView(MainView mainView) {
            this.mainView = mainView;
        }
    }
    
    public class MainView implements RouterLayout {
        public MainView(MainViewBus mainViewBus){
            mainViewBus.setMainView(this);
        }
        
        public void someMethod(){
            Notification.show("You just invoked a method on MainView from a child view.");
        }
    }
    
    @Route("foo", layout = MainView.class)
    public class FooView extends VerticalLayout {
        public FooView(MainViewBus mainViewBus){
            add(new Button("Test MainView Bus", click -> mainViewBus.getMainView().someMethod()));
        }
    }