VaadinSession 属性和更新会话绑定组件

VaadinSession attribute and updating session-bound components

我有一个带有多个 View 元素的 Vaadin Navigator。每个视图都有不同的用途,但有些还包含我放在自定义组件中的共同特征。

这些自定义组件之一是菜单 - 它位于顶部并允许在不同视图之间导航。我在每个视图的构造函数中创建并添加了这个组件(如果您对菜单的实现感兴趣,请参阅本 post 的末尾)。这是每个自定义视图的框架:

class MyViewX implements View {

  MenuViewComponent mvc;

  public MyViewX() {
    mvc = new MenuViewComponent();
    addComponent(mvc);
  }

  @Override
  public void enter(ViewChangeEvent event) {
  }
}

到目前为止,还不错。为了简单起见,我将使用一个简单的标签而不是我的其他自定义组件来解释我的问题,但我将在此处描述的依赖性对于这些组件与标签一样。

假设我有一个标签,其唯一目的是显示带有用户用户名的问候语。为此,我在存储属性的地方使用 VaadinSession。这是由我的 LoginController 完成的,它通过查看数据库来验证用户,如果用户存在,则设置属性并自动打开其中一个视图。问题是 VaadinSession.getCurrent().getAttribute("username") returns null 在构造函数内部调用时。这当然是有道理的,因为构造函数不应受会话属性的约束。

到目前为止,我已经设法使用 enter() 方法,在检索会话属性时没有问题:

class MyViewX implements View {

  MenuViewComponent mvc;

  public MyViewX() {
    mvc = new MenuViewComponent();
    addComponent(mvc);
  }

  @Override
  public void enter(ViewChangeEvent event) {
    String username = (String)VaadinSession.getCurrent().getAttribute("username");
    Label greeting = new Label("Hello " + username);
    addComponent(greeting);
  }
}

由此产生的问题很明显 - 每当我打开存在此标签的视图时,都会添加一个新标签,因此如果我重新访问该视图 10 次,我将获得 10 个标签。即使我将标签移动为 class 成员变量,addComponent(...) 也会把事情搞砸。我的一些自定义组件确实依赖于 username 属性(以便显示用户特定的内容)因此我还必须将它们放在 enter(...) 方法中。 addComponent(...) 弄得一团糟。我什至尝试了删除组件然后重新添加它的肮脏方法!徒劳无功:

class MyViewX implements View {

  MenuViewComponent mvc;
  Label greeting;

  public MyViewX() {
    mvc = new MenuViewComponent();
    addComponent(mvc);
  }

  @Override
  public void enter(ViewChangeEvent event) {
    String username = (String)VaadinSession.getCurrent().getAttribute("username");
    greeting = new Label("Hello " + username);

    // Remove if present
    try { removeComponent(greeting); }
    catch(Exception ex) { }

    // Add again but with new content
    addComponent(greeting);
  }
}

但还是不行。所以我的问题是:更新需要会话绑定属性的组件的最简单方法是什么?

通过菜单自定义组件进行导航不是这里的问题,因为菜单的所有组件都加载到它的构造函数中。这就是为什么它也加载该组件,特别是在视图自己的构造函数中。这是我的菜单中打开视图的按钮示例:

@SuppressWarnings("serial")
@PreserveOnRefresh
public class MenuViewComponent extends CustomComponent {
  public MenuViewComponent(boolean adminMode) {
    HorizontalLayout layout = new HorizontalLayout();
    Label title = new Label("<h2><b>Vaadin Research Project</b></h2>");
    title.setContentMode(ContentMode.HTML);
    layout.addComponent(title);
    layout.setComponentAlignment(title, Alignment.TOP_LEFT);

    Button personalDashboardButton = new Button("Personal dashboard", new Button.ClickListener() {
      @Override
      public void buttonClick(ClickEvent event) {
        getUI().getNavigator().navigateTo(MainController.PERSONALDASHBOARDVIEW);
      }
    });

    personalDashboardButton.setStyleName(BaseTheme.BUTTON_LINK);
    layout.addComponent(personalDashboardButton);
    layout.setComponentAlignment(personalDashboardButton, Alignment.TOP_CENTER);

    // Add other buttons for other views

    layout.setSizeUndefined();
    layout.setSpacing(true);
    setSizeUndefined();
    setCompositionRoot(layout);
  }
}

PERSONALDASHBOARDVIEW只是我的众多观点之一。

可能值得考虑您的视图实例应该显示多长时间 "live",只要它们显示,直到会话结束或两者混合。考虑到这一点并根据 enter/re-enter 视图时需要发生的情况,您至少有以下 3 个选项:

1) 重新创建整个视图(允许早期视图垃圾收集)

  • 首先注册一个 ClassBasedViewProvider(而不是 StaticViewProvider),它不包含对创建的视图的引用:

    navigator = new Navigator(this, viewDisplay);
    navigator.addProvider(new Navigator.ClassBasedViewProvider(MyView.NAME, MyView.class));
    
  • 简单的视图实现

    public class MyView extends VerticalLayout implements View {
    
        public static final String NAME = "myViewName";
    
        @Override
        public void enter(ViewChangeListener.ViewChangeEvent event) {
            // initialize tables, charts and all the other cool stuff
            addComponent(new SweetComponentWithLotsOfStuff());
        }
    }
    

2) 保留一些已经创建的组件并替换其他组件

public class MyView extends VerticalLayout implements View {

    private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;

    public MyView() {
        // initialize only critical stuff here or things that don't change on enter
        addComponent(new MyNavigationBar());
    }

    @Override
    public void enter(ViewChangeListener.ViewChangeEvent event) {
        // oh, so the user does indeed want to see stuff. great, let's do some cleanup first
        removeComponent(mySweetComponentWithLotsOfStuff);

        // initialize tables, charts and all the other cool stuff
        mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();

        // show it
        addComponent(mySweetComponentWithLotsOfStuff);
    }
}

3) 输入时延迟创建和更新(或不更新)内容

public class MyView extends VerticalLayout implements View {

    private boolean isFirstDisplay = true;
    private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;

    public MyView() {
        // initialize only critical stuff here, as the user may not even see this view
    }

    @Override
    public void enter(ViewChangeListener.ViewChangeEvent event) {
        // oh, so the user does indeed want to see stuff
        if (isFirstDisplay) {
            isFirstDisplay = false;
            // lazily initialize tables, charts and all the other cool stuff
            mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
            addComponent(mySweetComponentWithLotsOfStuff);
        } else {
            // maybe trigger component updates, or simply don't do anything
            mySweetComponentWithLotsOfStuff.updateWhateverIsRequired();
        }
    }
}

我确定(并且很好奇)可能还有其他选择,但我主要使用了 1) 的变体,使用 spring 原型视图和组件选项卡。