When I add components to Vaadin's component (such as TabSheet
or Tree
) , the added components are cached. When user clicks the tab (or tree nodes) , if it contains db data , it shows stale data , not reflecting the latest db state.
I wonder if there is any way to ensure loading latest data ?
I solve the problem by defining my custom interface :
public interface Reloadable {
void reload();
}
And each component implements this Reloadable interface , such as :
@SpringComponent
public class TeachersView extends VerticalLayout implements Reloadable, Serializable {
@Inject
private TeacherDao teacherDao;
private final static int PAGESIZE = 10;
private MTable<Teacher> mTable = new MTable<>(Teacher.class);
@PostConstruct
public void init() {
// mTable settings skip here
reload();
addComponent(mTable);
}
@Override
public void reload() {
mTable.setBeans(new SortableLazyList<>(
sortablePagingProvider ,
() -> (int) teacherDao.count() ,
PAGESIZE
));
}
private SortableLazyList.SortablePagingProvider<Teacher> sortablePagingProvider =
(firstRow, asc, sortProperty) -> {
return teacherDao.findAll(
new PageRequest(
firstRow / PAGESIZE, PAGESIZE,
asc ? Sort.Direction.ASC : Sort.Direction.DESC,
sortProperty == null ? "id" : sortProperty
)
).getContent();
};
}
And this view is injected to UI
class :
@SpringUI(path = "/ui")
@Theme("valo")
public class VaadinUI extends UI {
@Inject
private TeacherDao teacherDao;
@Inject
private TeachersView teachersView;
@Override
protected void init(VaadinRequest vaadinRequest) {
Panel panel = new Panel("Admin Panel");
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
splitPanel.setSplitPosition(15, Unit.PERCENTAGE);
panel.setContent(splitPanel);
Tree tree = new Tree("Menu");
splitPanel.setFirstComponent(tree);
Label home = new Label("Home");
Map<String, Component> map = new HashMap<>();
map.put("Teachers", teachersView);
map.put("Home", home);
map.forEach((k, v) -> tree.addItem(k));
tree.addItemClickListener(event -> {
Component view = map.get(event.getItemId());
if (view instanceof Reloadable) {
((Reloadable) view).reload();
}
splitPanel.setSecondComponent(view);
});
splitPanel.setSecondComponent(home);
setContent(panel);
} // init()
}
Notice the tree.addItemClickListener
, I have to check each component if it implements Reloadable
, if true , invoke it.
It works . But I don't know if it the standard way achieving this ? I think it should be a common scenario , there should be something like built-in interface for Components to implement , such as onRender
like that (but I cannot find one) . Did I miss anything ?
Thanks.
First of all I'm going to suggest this tutorial on Spring & Vaadin that you may have already seen, but I'll be referencing it in a few places and I think it's a good starting point for Vaadin & Spring integration.
Second, out of curiosity, why are you using a tree to build the menu?
In the example provided you seem to be modelling a navigation between some views feature, which is already available in Vaadin, and since you're using Spring, the Vaadin spring & spring-boot extensions makes it really easy to define and navigate between your views. Then you can define some specific behaviour for each view in their own enter()
method. I've used the Vaadin dashboard demo as inspiration for the changes below:
@SpringView(name = TeachersView.NAME)
public class TeachersView extends VerticalLayout implements View {
public static final String NAME = "Teachers";
private Label title = new Label("Teachers view");
@PostConstruct
void init() {
addComponent(title);
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// recreate or reload stuff here
title.setValue("Teachers view reloaded @ " + new Date());
}
}
@SpringView(name = HomeView.NAME)
public class HomeView extends VerticalLayout implements View {
public static final String NAME = "";
@PostConstruct
void init() {
addComponent(new Label("Home"));
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// meh, nothing special to do here
}
}
public class SpringVaadinUI extends UI {
@Autowired
private SpringViewProvider viewProvider;
@Override
protected void init(VaadinRequest vaadinRequest) {
addStyleName(ValoTheme.UI_WITH_MENU);
Panel panel = new Panel("Admin Panel");
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
splitPanel.setSplitPosition(15, Unit.PERCENTAGE);
panel.setContent(splitPanel);
VerticalLayout navigationBar = new VerticalLayout();
navigationBar.setPrimaryStyleName(ValoTheme.MENU_ROOT);
navigationBar.addComponent(createNavigationButton("Home", FontAwesome.HOME, HomeView.NAME));
navigationBar.addComponent(createNavigationButton("Teachers", FontAwesome.GROUP, TeachersView.NAME));
splitPanel.setFirstComponent(navigationBar);
CssLayout navigationDisplay = new CssLayout();
splitPanel.setSecondComponent(navigationDisplay);
Navigator navigator = new Navigator(this, navigationDisplay);
navigator.addProvider(viewProvider);
setContent(panel);
}
private Button createNavigationButton(String caption, FontAwesome icon, final String viewName) {
Button button = new Button(caption, icon);
button.setPrimaryStyleName(ValoTheme.MENU_ITEM);
button.addStyleName(ValoTheme.BUTTON_SMALL);
button.addStyleName(ValoTheme.BUTTON_BORDERLESS);
button.addClickListener(event -> getUI().getNavigator().navigateTo(viewName));
return button;
}
}
If for some reason you can't or don't want to use the navigator, then your solution looks fine. Nonetheless, whichever solution you chose to use, you should know that by default Spring creates singletons. Except a few such as the UI, you should probably change your components to prototypes so you'll get a new instance each time. Otherwise all your users will get the same instances when accessing the application, which I don't think you want to happen.