5

Problem:

I currently have a grid that displays content of type SomeModel. When I click an entry of that Grid I would like to navigate to a view that takes an object as its input to display the entries content.

Implementation:

To achive this behaviour I created a DetailLayout like this:

public DetailLayout extends FlexLayout implements HasUrlParameter<SomeModel>{
    /* skipped some details */
    @Override
    public void setParameter(BeforeEvent event, Host parameter) {
        /* This is where I expected to be able to handle the object */
    }
}

From within the Grid I tried to navigate like this:

addSelectionListener((event) -> {
    event.getFirstSelectedItem().ifPresent(somemodel -> {
        getUI().ifPresent(ui -> {
            ui.navigate(DetailLayout.class, somemodel);
        });
    });
});

But unfortunately this behaviour is not supported by Vaadin even tho its syntax is perfectly fine.


Question:

Do you know of another way to pass an object while navigation or did I miss a certain part of the official documentation documentation ?

Thank you in advance

Gerrit Sedlaczek
  • 1,231
  • 1
  • 14
  • 32

3 Answers3

6

Key-Value collection

As discussed in the comments on the other Answer, if you do not wish to expose the ID value as part of the URL, then work behind the scenes by using the key-value collection provided by Vaadin.

Vaadin actually provides key-value collections at three levels of scope:

  • Context
    Your entire web-app at runtime
  • Session
    Each user
  • UI
    Each web browser window/tab, as Vaadin supports multi-window web-apps

The app-wide key-value collection is available on the VaadinContext, via getAttribute & setAttribute methods.

VaadinService.getCurrent().getContext().setAttribute( key , value ) ;

The per-user key-value collection is available on the VaadinSession, via getAttribute & setAttribute methods.

VaadinSession.getCurrent().setAttribute( key , value ) ;

➥ The per-browser-window/tab collection (what you want for your needs in this Question) is not quite so readily available. You have to go through an indirect step. On the ComponentUtil class, call setData & getData methods. In addition to passing your key and your value, pass the current UI object.

Component c = UI.getCurrent() ;
String key = "com.example.acmeapp.selectedProductId" ;
Object value = productId ;
ComponentUtil.setData( c , key , value ) ;

Please vote for my ticket # 6287, a feature-request to add setAttribute/getAttribute methods on UI class, to match those of VaadinSession and VaadinContext.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • I prefer this answer over mine, as it does not expose the id in the url. More specifically, the user is not able to be sneaky and try out random productIds in the url. It's a little more complicated than my approach but well worth it in my opinion. – kscherrer Aug 22 '19 at 07:36
  • @KasparScherrer Actually, your Answer is preferable in some scenarios where exposing the IDs in the URLs is permissible and possibly even desirable. For example, a product catalog where you may want the convenience of having the user (a customer or clerk) see, and perhaps even type, the product identifier in the URL. It all depends on the scenario’s business rules. – Basil Bourque Aug 22 '19 at 07:43
  • While changing my detailview logic to use this approach I noticed that a new UI instance is being created when refreshing the page. The initial navigation from list-view to detail-view works, but as soon as I refresh the detail-view, the UI does not have the productId stored anymore. Therefore, storing the id in the Session is probably better, although this will probably introduce issues when having multiple tabs open. – kscherrer Aug 22 '19 at 09:56
  • @KasparScherrer The `UI` object should be stable as long as the browser window/tab is open (by definition). You must have some other code involved, and you likely should fix it. Try a new throwaway project to verify behavior when storing attributes on the `UI`. – Basil Bourque Aug 22 '19 at 16:40
  • @KasparScherrer Uh-oh. I am trying a new throwaway Vaadin 14.0.1 app, and I may be seeing what you reported: the `UI` object being replaced while routing. I'll explore further and report back here. – Basil Bourque Aug 23 '19 at 00:54
  • @KasparScherrer Yes there seems to be some crazy behavior on the `UI` class in Vaadin 14.0.1. I posted a description and simple example app in the Vaadin Forums: https://vaadin.com/forum/thread/17810577 – Basil Bourque Aug 24 '19 at 02:44
  • Maybe this is why there's no `setAttribute` / `getAttribute` on the UI.. I am very interested in what the recommended way for this will be. – kscherrer Aug 26 '19 at 07:53
5

Instead of giving the whole somemodel object as parameter of navigate(), you can pass its id

ui.navigate(DetailLayout.class, somemodel.getId());

And in the DetailLayout.setParameter() you can load the somemodel by its id

@Override
public void setParameter(BeforeEvent beforeEvent, Long someModelId) {
    if(someModelId == null){
        throw new SomeModelNotFoundException("No SomeModel was provided");
    }

    SomeModel someModel = someModelService.findById(someModelId);
    if(someModel == null){
        throw new SomeModelNotFoundException("There is no SomeModel with id "+someModelId);
    }

    // use someModel here as you wish. probably use it for a binder?
}
kscherrer
  • 5,486
  • 2
  • 19
  • 59
  • Thank you for your reply. Thats actually the approach I tried to avoid as it introduces the need to check if the user actually has the right to see the element of the id he passes. – Gerrit Sedlaczek Jan 30 '19 at 14:56
  • That is true, any user could type into the url /somemodel/19 and hope that there is a somemodel with id 19. If there are somemodels that only certain users can see, then a check if they have the right is indeed due here. – kscherrer Jan 30 '19 at 15:20
  • 2
    There are couple of other approaches as well. If you are using CDI or Spring, you could share the object as e.g. UIScoped bean. Also one alternative could be to share the object as session attribute. – Tatu Lund Jan 31 '19 at 06:59
  • @TatuLund Would you implement that by using a custom UI as described in the old vaadin documentation: https://vaadin.com/docs/v8/framework/articles/SettingAndReadingSessionAttributes.html – Gerrit Sedlaczek Jan 31 '19 at 07:29
  • 2
    @gsedlacz No, I would use ComponentUtil.setData(UI.getCurrent(),..) instead, see details: https://vaadin.com/api/platform/12.0.5/com/vaadin/flow/component/ComponentUtil.html#setData-com.vaadin.flow.component.Component-java.lang.String-java.lang.Object- – Tatu Lund Feb 03 '19 at 16:10
0

If you are using Spring with Vaadin Flow then you could create a @UIScoped bean and add your own fields storing state related to the current browser window/tab. The bean will be available as long as the UI is present.

Steffen Harbich
  • 2,639
  • 2
  • 37
  • 71