After putting together the first page on writing a dynamic dashboard with PrimeFaces I descovered that the method I had come up with wasn’t as good as it could have been. Most noteably it didn’t work properly when the dashboard was updated via a partial page refresh. Typically that type of update would result in a non-unique ID exception being thrown when the page was rendered.
The solution to this is not as simple as it could be but the first step is to separate the model from the backing bean as discussed in this excellent article. The model will clearly have a lifetime greater than the view but I personally place it in (a CODI) ConversationScope rather than the SessionScope that the article suggests. This separation of concerns is important as a binding should only live for the duration of a view. JSF 2.0 offers the ViewScoped which can be useful here but I’ve also placed my binding in RequestScope with no ill effects. I haven’t included the model bean here because it’s so simple – just a field holding the DashboardModel with getters and setters. Note the injection of the model bean into the backing bean.
In the previous article I created the dashboard in the constructor which, while not wrong, isn’t exactly best practice. The new code creaes the dashboard in an init method marked with @PostConstruct. The addition of components to the dashboard is also now carried out in a separate method
@Named @ViewAccessScoped public class DashboardBacker implements Serializable { private Dashboard dashboard; @Inject private DashboardModelView dashboardModelView; private WidgetProxy widgetProxy; public DashboardBacker() { } @PostConstruct public void init() { FacesContext fc = FacesContext.getCurrentInstance(); Application application = fc.getApplication(); dashboard = (Dashboard) application.createComponent(fc, "org.primefaces.component.Dashboard", "org.primefaces.component.DashboardRenderer"); dashboard.setId("dashboard"); dashboard.setModel(dashboardModelView.getModel()); addChildren(); } private void addChildren() { dashboard.getChildren().clear(); //Loop though calling add for each panel you want to add to the dashboard. dashboard.getChildren().add( yourPanel ); } public Dashboard getDashboard() { addChildren(); return dashboard; } public void setDashboard(Dashboard dashboard) { this.dashboard = dashboard; } public WidgetProxy getWidgetProxy() { return widgetProxy; } public void setWidgetProxy(WidgetProxy widgetProxy) { this.widgetProxy = widgetProxy; } public void handleWidgetAddition() { DashboardModel model = dashboardModelView.getModel(); model.getColumn(0).addWidget(getWidgetProxy()); //Save dashboard state etc. addChildren(); } public void handleReorder(DashboardReorderEvent event) { //Display a message / save the dashboard state / do other stuff here addChildren(); } }
Dynamically Adding UIComponents
A separate addChildren method is useful because it is necessary to re-build the child components of the dashboard after every change or you risk blowing up with a non-unique ID exception. This is true both for additions of new components and simply re-arranging any existing components. Notice how the addChildren method clears the list before adding new components this is vital unless you are going to spend the time traversing the tree looking to duplicates yourself.
The handleWidgetAddition method is called in a command button action which also sets the widgetProxy field. I use a proxy rather than the real thing simply because it’s much lighter weight than swinging around the actual widget itself. Notice, again, that I call addChildren to refresh the component tree. If you are persisting the state of the dashboard this is the place to do it. I have my dashboard persist using JPA which is actually easier said than done – that’s an exercise for the reader though.
If you want to handle users closing panels in the dashboard it’s basically the same as handling a reorder – save state and update the dashboard component tree. The event is registered on the panel rather than the dashboard though.
Update
By request here is the DashboardModelView code. My model can be persisted to the database which makes it somewhat more complex than I’d like to show here. A regular DashboardModel will do fine while you are just experimenting with getting a dynamic dashboard working.
@Named @ConversationScoped public class DashboardModelView implements Serializable { private static final int DEFAULT_COLUMN_COUNT = 3; @Inject private Logger logger; @Inject private DashboardService dashboardService; @Inject @LoggedInUser private User user; private PersistableDashboardModel model; @PostConstruct public void init() { logger.info( "Creating model" ); if( model == null ) { model = dashboardService.find(user); if( model == null ) { model = new PersistableDashboardModel(); model.setUser(user); for( int i = 0, n = DEFAULT_COLUMN_COUNT; i < n; i++ ) { model.addColumn( new PersistableDashboardColumn() ); } dashboardService.create( model ); } } } public PersistableDashboardModel getModel() { return model; } public void setModel(PersistableDashboardModel model) { this.model = model; } }
References
- Excellent article on adding components dynamically. Refers to IceFaces but it’s close enough.