PagedTable

Hi!

I have a few times seen the question: “how do I use pages in table instead of scrolling”. I have also answered that question a couple of time and made a proof of concept (PoC). Recently a member of this forum had found my PoC somehow and asked how I’ve created it. Unfortunately, I couldn’t find the sources to the quick and dirty PoC, so I decided to make a new component for the directory. Therefore, let me introduce PagedTable!

This is still just a experimental release and I will continue the development if people show interest in the component. The sources is also included so it can be a start point for you to create your own implementation to the issue. But if you get around to test it, please add your comments and feedback to this thread and I’ll prioritize them and see what can be done. Expect issues.

When I started the project I wanted it to be a complete server side component to avoid widgetset compilation. For this reason I had to make it a CustomComponent to add the button instead of extending Table directly. In other words, the component is a Table wrapped into a CustomComponent with delegate methods to the table. It also implements the Field interface so that it can be used in a form.

The addon can be found at
Vaadin Directory
and an online demo can be found at
http://jens.virtuallypreinstalled.com/PagedTable
. Issue tracking is found at
https://github.com/Peppe-/PagedTable/issues
.

Known issues:

  • setHeight causes wrong amout of rows to be rendered and renders one (half) row too much. This will cause a very little scrollable scrollbar to the table. Use setPageLength instead if possible.
  • Having different height rows will throw off the rendering. Same symptoms as above.
  • IE8: Sometimes when you load the table for the first time, the page length is wrong, even if the loaded data amount is correct. The issue fixes itself if you change the page or restart the application.
  • The application automatically does some scrolling when the PagedTable is bigger that the panel it is in and you select a row while the first row of the table is not visible. Vaadin issue:
    http://dev.vaadin.com/ticket/6197

Further Improvements to be made:

  • Figure out how multiselect could be used when selection spans over multiple pages

List of implemented features in 0.1.0:

  • Delegate methods to the table
  • Implement Field interface
  • Disable next and previous when you are at the first/last page

Version 0.2.0 is out.

List of implemented features in 0.2.0:

  • The table now provides information about current page (getCurrentPage()) and totoal amount of pages (getTotalAmountOfPages())
  • Page turning can now be done from your own code by calling previousPage() and nextPage(). In other words, all table controls can be implemented by the developer to their liking.
  • You can now attach a PagedTableEvent -handler to the table with setHandler. The table will notify the handler always when the viewed page has changed. This can be used to update the current page label etc.
  • createControls() returns a standard implementation of the controls with page information and next/previous buttons.
  • PagedTable now extends Table instead of CustomComponent. This means that the code is cleaner but the controls can’t be added by default to the table. Use createControls() for similar behaviour as 0.1.0.

Usage example:

VerticalLayout layout = new VerticalLayout();
layout.setSizeUndefined();
PagedTable table = new PagedTable("My caption");
table.setContainerDataSource(myContainer);
layout.addComponent(table);
layout.addComponent(table.createControls());

Please update online demo :slight_smile:

Done! Thanks for pointing it out. I forgot to do it. I also updated the code example and description on the directory page.

Thank you !

This component will greatly improve the user experience of the Vaadin table.

Thanks a lot Kevin!

A features / tweaks you’d like to see in the coming releases?

I took your version of the table and modified it a bit to add features we needed :

  • Advanced navigation controls with page jump and page size selection
  • Possibility to add several synchronized navigation controls for one table (one on the top, the other on the bottom for example)

I had to add a few methods to your PagedTable class, and I changed the “handler” to a Vaadin event manager so that the table could easily send events to multiple listeners (the potential multiple navigation controls).
I also added checks in setPageLength and setCurrentPage so that the table is only refreshed when needed

Here is a demo :

https://order.jst-mfg.com/VaadinTest/

I attach the source code.

Usage example :


HorizontalLayout layout=new HorizontalLayout();
PagedTable table = new PagedTable("My caption");
table.setContainerDataSource(myContainer);

layout.add(new PagedTableNavigation(table));
layout.add(table);
layout.add(new PagedTableNavigation(table));

I also noticed that in your second version, the container is not sorted anymore when clicking on the table’s columns.
11503.zip (2.81 KB)

Cool that shared the code and great input!

Both points added is something that i’ve been thinking of myself.

[quote=Kevin Ferrare]

  • Advanced navigation controls with page jump and page size selection

[/quote]Nice going! The page jump feature was an item on my todo-list but I’m starting to favor that the one taking in use the table should also implement the jumping himself. This is because there are so many alternatives on how to implement it and I can’t really satisfy everybody with one implementation. You went with a select for the pages, but other alternatives are textfield instead of select, row of links to every page, logical grouping of links like jump five pages forward etc. Another question is that do we need the previous and next button after the addition of page jump as many of the solutions would contain buttons to those.

The page size selection was a feature that I hadn’t thought about, but a very practical feature nonetheless.

[quote=Kevin Ferrare]

  • Possibility to add several synchronized navigation controls for one table (one on the top, the other on the bottom

[/quote]When I started implementing the handler, I was in fact implementing a listener - I switched it over to a handler as I didn’t see any use cases of having multiple listeners to one table. How I would have created the view you presented would have been that the view having the table would have implemented the handler, and it would have notified both the top and bottom page controls. I could see that it is possible to have some very complex table controls where they are in different sections of the whole view and that’s why I’m thinking of changing the handler back to listeners. It would also be more in line with other components in Vaadin (as well with the Table itself) where everything is listeners and handlers are seldom used.

Sending an event object with everything needed in it is a good idea, instead of sending primitives with data.

That what I don’t get is why you’ve used reflection to send away the page change events. It can easily break down after code changes as the change can’t be checked to be working on compile time, you can’t do refactoring and you also need some funky “this should never happen”-catches. I would prefer instead that there would be a list of registered listeners and instead of fireEvent(new PagedTableChangeEvent(this)); the table would do:

PagedTableChangeEvent event = new PageTableChangeEvent(this);
for(PagedTableChangeListener listener : listeners){
  listener.pageChanged(event);
}

[quote=Kevin Ferrare]
I also added checks in setPageLength and setCurrentPage so that the table is only refreshed when needed
[/quote]Handy!

[quote=Kevin Ferrare]
I also noticed that in your second version, the container is not sorted anymore when clicking on the table’s columns.
[/quote]I will look into this. I am fairly certain that it doesn’t work because the column containers are sorting the shown container and not the real containers. The problem is that in Vaadin the table is taking care of the sorting and not the container, and the table has only access to one page of items at a time. Another decision that has to be made is whether the index should stay the same after a sort or should it go back to 0. I think I’m going to favor keeping the index.

I will try to make time to look into this this week and implement some of the features into the next version.

Thanks again for the input!

Nice you liked it !

I am very new to Vaadin (I started to use it myself full time around a week ago) so I am probably not making things “the right way”.

The navigation control I developed is probably not the best for everyone, but that’s how it was in the Echo2 applications we are trying to port, and I guess it covers pretty much all the functionalities one would like to have in a paged table.
Anyway it is not very difficult to make another custom implementation.

For the event part, I looked at how this was done in other vaadin core components and did the same, I don’t like to have to specify a string for the method name either.

For the sort, I know it should not be working, but it actually is, sort of, which is a disconcerting thing.
In the example I made, try to sort the ID column (clicking twice on it) and then go to the next page, the table gets sorted … mystery …
Looking at the source code this should not happen …
Also in your first version, sorting was working (I don’t know if this was intentional as it didn’t seemed to be implemented in your code)

[quote=Jens Jansson]

The Vaadin Table component does not do any sorting itself, but delegates that to the container. Maybe you are thinking of the simple case where a Table has an implicit IndexedContainer if you haven’t set any other container for it. Or is the implementation of PagedTable very different with respect to this?

Also, if the sort behavior is erratic, maybe it matters whether the table is in immediate mode or not - I think it should not for sorting, but it would be good to check.

Oh I just spotted a problem

If you add a ColumnGenerator, everything works fine on the first page, but when you go to the second page, it breaks a bit :

  • The generator’s generateCell method gets called a lot of times (on the first page it’s only called once per cell)
  • The property’s value returned in the generator by source.getItem(itemId).getItemProperty(columnId); gets null (it is set to the normal value in the first page)

When using a normal Vaadin table, those 2 problems are not occuring.
Investigating …

I coded a bit yesterday so a new release is inbound when I get a little bit testing done. The fixes are fore page jump, new default controls (again), multiple listeners instead of one handler and sorting of columns.

Kevin, I haven’t had the time to look into generated columns yet but I’ll try to do that over the weekend.

Henri, the problem with delegating the functionality to the container is that in the PagedTable it is delegated to the wrong container. PagedTable uses two containers instead of one. One is the real container that is never attached to the table, where all the delegations should go, while the other one is the visible one that is attached to the table. One page length of data is copied over from the real container to the shown container on page changes.

New in version 0.3.0

  • Changed handler to listners so that multiple items can listen to paged table events
  • Changed the listener event to contain a PagedTableChangeEvent object with all data in it instead of providing separate primitives. This is more in line with the genreral way events are handled in Vaadin.
  • Added a setCurrentPage(int) method for page jumping
  • remade the default controls to contain page jumping and page size selector
  • added style names to all components for theming
  • implemented column sorting.

Big thanks goes to Kevin Ferrare for providing valuable feedback and improvements on the previous version.

Directory package, screenshot and online demo has been updated.

Thank you !

I was eagerly waiting for your update -_-

This is perfectly usable, excepted with column generators :

  • Everything works fine on the first page (all the values are shown and the column generator’s generateCell method is only called the required number of times
  • When the page number is changed, 2 problems occur :
    - The value returned by source.getContainerProperty(itemId, propertyId); in the generateCell method becomes null for the last row (this does not happen in tables without column generator)
    - The column generator’s generateCell method is called many more times than needed.
    In my test application, the method is called 10 times the first time the page is shown (the number of rows in this table, which is correct) and 165 times when I request the next page (so creating 165 components when only 10 are needed).

Here is the code of the column generator :


class UnitPriceColumnGenerator implements ColumnGenerator{
	//show the number of times we have been called, for debug
	private Label nbCallsLabel;
	private int nbCalls=0;

	public UnitPriceColumnGenerator(Label nbCallsLabel){
		this.nbCallsLabel=nbCallsLabel;
	}

	public Component generateCell(Table source, Object itemId, Object propertyId) {
		Property property = source.getContainerProperty(itemId, propertyId);
		Label price=new Label(property.getValue()+"JPY");
		price.setSizeUndefined();
		//show the number of times we have been called
		nbCalls++;
		nbCallsLabel.setValue(nbCalls);
		return price;
	}
}

You can see it here :

https://order.jst-mfg.com/VaadinTest/

Also, it would be a nice improvement if when there are less rows to display than the table’s “rowsPerPage”, the table would stop showing blank rows (happens for example on the last page when the number of elements to display is not a multiple of the page length).

Thanks for testing it.

I added generated columns problem on my work list (first post in this thread) yesterday, and that is what I’m going to look into next.

So do you mean that the last page would roll back the index so much that it will have items for the whole page length? So for example a table with 55 items and 25 pagelength would show 1-25 on first page, 26-50 on second and 31-55 on the third page (instead of 51-55)? Easy change in the code to do that but I’m just wondering what should happen when you go back one page. Should the item count go back to 26-50, or should it just roll back 25 items so that the new page view would show 6-30? I’d prefer that it would go back to the normal pages (pagelength counted from 0) but it might be a little weird that the first “previous” jumps only a few items back.

Thoughts?

Page 1 would be 1-25
Page 2 would be 26-50
Page 3 would be 51-55
Basically as it is now, excepted that the table showing the last page would shrink to only 5 rows, avoiding displaying 20 blank rows.
I don’t know if my explanation is understandable …
I think it could be done by having a “wantedPageSize” stored inside the pagedTable (in your example it would always be be 25 unless the user changes the page length), and the “pageSize” of the underlying table would be variable, depending on the size of the generated “shownContainer”.

I had a look at the generated columns. In my tests I couldn’t reproduce the problem with the last row becoming null.Did you debug the column generator and see if the generateColumn() was called for the last row? I have to look into this a little more.

On the amount of times called - I was able to reproduce it. generate columns was called 1325 on a table with 25 pagelength after the first page. I have a hunch on how to fix it and I’ll give it a shot.

I’ll try to roll out a new version under the next week.

In my tests the function is called more than once for every rows, even the last one.
The problem is that for some reasons the model returns a property with a null value inside for that row.
I tried to understand the source of this bug, but I have been unsuccessful so far.

I attach the source code of a simple test that reproduces the problem.
11521.gz (2.32 KB)

New in version 0.4.0

  • Performance boost: generatedCells were called even thousands on times on page change. Now it is called only once per row so total call amount is the same as page length.

  • Performance boost: Tables refreshRenderCells was called over a hundred times on page change and because of that all cells were rendered way too many times. Now it is called only 12 times.

  • Bug fix: made the getCurrentPage() and getTotalAmountOfPages() to return value 1 when the container is empty. It caused some funky “page 1 of 0” states and it now shows “page 1 of 1” that is more logical
    reordered the code in the default control bar so that it doesn’t cause a page change event.

The issues reported have been fixed and as I’ve haven’t got any new ones in a while. The component seems thus to work and I’m promoting it from it’s Experimental stage to Beta stage.