Vaadin lets you build secure, UX-first PWAs entirely in Java.
Free ebook & tutorial.
Is it possible to create a grid with subsections?
We recently took Vaadin into use on a prototype project and we're trying to build a flexible database search tool. The search results are shown in a tabular form, currently in a Grid. We're trying to figure out the best way to implement a "Group by" functionality, where the user can select a column and group the results by it. See the attached image that shows a rough mock-up of what we'd like to have. In the image:
- The user has grouped the results over the column 'Title' (the selection is made outside of the grid)
- Each group of results has a corresponding header row that...
- has a checkbox which can be used to select/deselect all rows in that group
- has a button which can be used to collapse/expand all rows in that group (i.e. show/hide)
- shows the name of the column used for grouping, the value in that column, and the amount of rows in the group. The column name and value are shown because the column that is used for grouping is not necessarily visible in the grid
We have a few ideas on how to do this, but we're not quite sure which would be the best approach. It would be great if we could add these section header rows to a grid, but I don't think that's possible (or is it?). Another idea we have is to create a separate grid for all groups. The top grid would be the "main" grid and have the default header and the section header, while the other grids would only have the section header. While this is quite easy to do in itself, it brings several problems with it:
- It makes it tricky to implement things like sorting and column reordering, as you'd need to manually try to make the other grids mirror the state of the main grid
- All listeners should effectively be shared between the grids, as they should behave as a single component to the end user
- The column widths will vary between the grids based on their content and whether there is a scrollbar or not, which doesn't look that great
It feels like if we go down this route we'll end up with a duct tape and bubble gum type solution where we try to glue the grids together and make them act as a consistent whole.
Any suggestions on how to do this would be appreciated. We like the functionality the Grid provides out-of-the-box (multi-column sorting, column reordering, flexibilty with the column rendering, lazy-loading etc.), but we're open to using different components if this would be easier to implement with something else.
This would perhaps be easier if Grid had Hierarchical support.
Nevertheless, I think you may be able to do that by using a filter that filters out collapsed items, and then use styling for the group rows.
There would be two big issues: 1) how to span the group rows over three columns, 2) how to have mixed button/text content.
1) Perhaps you can do that with CSS trickery as well, by using "overflow: visible". If not, just split the content in three columns.
2) You'd have to modify or reimplement ButtonRenderer so that it renders buttons conditionally.
Marko Grönroos: Nevertheless, I think you may be able to do that by using a filter that filters out collapsed items, and then use styling for the group rows.
could you elaborate more on this? I'm trying to do the same thing (...a bit less complex), but can't get past the part of identifying the duplicates and grouping them so that later I could apply some "CSS trickery" (I'm using the latest JPAContainer)
thanks Markus for the question
Basically, you first need some way to determine hierarchy, such as in Container.Hierarchical. This allows you to indicate the hierarchy with a CellStyleGenerator (as for tree-like hierarchy in the first column) or RowStyleGenerator (for group header rows).
You need a way to store the collapsed state of non-leaf nodes, for example in a hashmap or in the container itself. Then, you can allow changing the collapsed states by clicking the collapsible items, which you handle in an ItemClickListener.
Finally, define a custom filter that checks from the collapsed state storage which items should be shown and which not. You need to use the hierarchy information here, in whatever way you have stored it.
You may need to use a GeneratedPropertyContainer for some of that, but I'm not sure if it's needed.
I haven't tried that yet myself, but I don't see a reason why it wouldn't work.
Okay, thanks for the pointers, this was very helpful! I'll try it out and see what happens.
Ok, this might just qualify for the Hack of the Week – see it here.
This is not exactly what you were asking, but the more general case of hierarchical grid, as I've been thinking about it recently. There was couple of issues. One was that the HierarchicalContainer doesn't allow accessing the parent relationships in the unfiltered data, so you have to store it otherwise. There could be better solutions to that than storing the parents in a hashmap.
The code is rather "experimental" and could be cleaned, generalized, and optimized much further. There also seems to be one focus indicator styling bug. Maybe I'll work on it later again.
Yeah, I noticed the EverproTreeGrid today as well, just after finishing my experiment. Pity that it doesn't have a demo. Apparently, some things in it could be improved, like the use of font icons instead of image file for the icon.
The approach seems to be a bit different; it extends Grid and uses a custom ButtonRenderer instead of ItemClickListener to handle expand/collapse clicks. It also sets the expanded/collapsed styles in the renderer instead of CellStyleGenerator. I can't say if that's really better than my purely server-side solution, as in any case the clicks need to be handled in the server-side as well, and the renderer requires a bit unnecessary client-side code. For rendering the styles, the renderer could be more light-weight than my CellStyleGenerator solution, maybe. The add-on uses a custom container wrapper – that is probably a good approach for generalization – I was thinking of extending GeneratedPropertyContainer for the same purpose, but an entirely custom wrapper may be fine as well. It doesn't seem to use container filtering features though, but some custom operations on the item sets. I didn't look it very closely, but it's interesting. It would be great if it proves to be a good way to have the hierarchical support for Grid.
We started looking into the containers now and would appreciate some pointers on how to move forward. We've only been working with Vaadin for a couple of weeks now, so it's still a bit challenging to navigate between the set of alternatives. Currently we use an SQLContainer with our Grid, and we've implemented our own FreeformQuery and FreeformStatementDelegate to go with that (our queries can get rather complex). I'm not quite sure how to get from where we are to having a container that also implements Hierarchical. Extending SQLContainer and implementing Hierarchical in it, somehow bolting the added functionality on top of the SQLContainer (doesn't sound nice)? Creating a totally new container that implements the necessary interfaces (Hierarchical, Indexed, Filterable, others?), and keeping a reference to an SQLContainer inside of it, implementing the additional functionality in the wrapper? Something completely different? I don't know if we can move away from the SQLContainer, as we need to be able to dynamically configure the database queries to include columns from multiple tables via joins, and to filter the results based on arbitrary SQL (such as subselects), and I didn't really find anything else that would fit the bill besides SQLContainer. We don't currently use an ORM, so we're pretty much stuck with writing SQL for now (unfortunately).
Well, if you make a custom hierarchical Grid implementation, you are free to use whatever way to express the hierarchy in the container. In my example code, I just used the getParent() from the Hierarchical interface to get the information.
Using my filtering solution would not be suitable with SQLContainer – you'd definitely need to have a wrapper container that does the filtering by using in-memory expand/collapse states states. EverproTreeGrid seems to use such a wrapper (it doesn't use the Filterable mechanism but custom item set manipulation) – but it also requires that the container implements Hierarchical, so plain SQLContainer would not do. Also, I'm not sure how efficient (memory or CPU) its container wrapper is, which could be an issue if your query results are huge.
Well, there is ContainerHierarchicalWrapper, which purpose is to add Hierarchical interface to containers that do not have it. Well, it's not Indexed, as required by normal Grid, and not Filterable, as required by my example code, but for EverproTreeGrid it probably would be OK. Also, implementing the Hierarchical interface is not necessarily a big task, as shown here.
I got back to this problem last Thursday after doing some other tasks and managed to get the grouping to work. I implemented a new wrapper container that contains an SQLContainer and maintains a mapping of the parent and child item IDs. The group headers are just "virtual" items that the wrapper container adds on top of the items from the SQLContainer. The container implements all the interfaces SQLContainer implements, plus Hierarchical, and in many cases delegates directly to the SQLContainer (and if the grouping is disabled, always delegates to SQLContainer). I don't know if that's the best solution, but it seems to work. So thanks for the help this far! :)
The thing that I got stuck with now is the collapse/expand buttons in the group header rows. Earlier in this thread you said that:
Marko Grönroos: You'd have to modify or reimplement ButtonRenderer so that it renders buttons conditionally.
I haven't yet quite figured out how to do this. The Book of Vaadin seems to be a bit outdated on this topic; the section about custom renderers talks about implementing the public void render(RendererCellReference cell, String text) method in the Renderer interface, but the interface doesn't have that method. Also, I couldn't quite find the place in the code where it decides to render a button from the ButtonRenderer. ButtonRenderer effectively implements Renderer<String>, so it's a bit hard to see where the button stuff comes along.
I do have the logic in place that determines whether a given cell needs to be rendered as a button or just text, but I don't know how to disable the button rendering for the cells that should be rendered as text. And just to be clear, I will split the group header row into several columns, where the first column will only contain the button, and the consequent cells will contain textual content.
i have done something similar to this