Атрибут VaadinSession и обновление связанных с сеансом компонентов

У меня есть Vaadin Navigator с несколькими элементами просмотра. Каждое представление имеет свою цель, однако некоторые также содержат общие черты, которые я поместил в пользовательские компоненты.

Одним из таких пользовательских компонентов является меню - оно расположено вверху и позволяет перемещаться между различными видами. Я создаю и добавляю этот компонент в конструктор каждого представления (если вы заинтересованы в реализации меню, смотрите конец этого поста). Вот скелет для каждого пользовательского представления:

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") возвращается 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 ярлыков. Даже если я переместу метку в переменную члена класса, 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 это только один из многих моих взглядов.

1 ответ

Решение

Возможно, стоит подумать о том, как долго экземпляры вашего представления должны "жить", так же, как долго они отображаются, до завершения сеанса или их сочетания. Имея это в виду и в зависимости от того, что должно произойти при вводе / повторном вводе представления, у вас есть как минимум следующие 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 с представлениями прототипов и вкладками компонентов.

Другие вопросы по тегам