Pages

Thursday, November 18, 2010

GWT CellTable Example (Using AsyncDataProvider)

GWT version 2.1 has finally been released with the anticipated business level data presentation widgets and other interesting features. CellTable is one of the new widgets that supports pagination. Therefore, there no need to use PagingScrollTable in the gwt incubator or implementations in other third-party libraries.

From the Google official document on how to use data presentation widgets, you can get some examples of using these cell widgets including CellTable. It also includes a simple example of using CellTable with SimplePager to implement pagination. That example uses ListDataProvider which requires all the data that needs to be paged to be set at the client (browser) side. However, in realty, the common practice is to load only one page of data from the server (mostly pulled from a database) to the browser in order to save the bandwidth and improve the response time.

Of course the document indicates there are ways to implement the asynchronized page data loading from remote server.

To illustrate how to use AsyncDataProvider, I modified a simple example from GWT below.

Please note the example do not actually calls the server to get the data. It should be quite easy to modify the example to do so as illustrated in the next code snippet.


import java.util.Arrays;
import java.util.Date;
import java.util.List;

import com.google.gwt.cell.client.DateCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;

public class CellTableExample implements EntryPoint {

  /**
   * A simple data type that represents a contact.
   */
  private static class Contact {
    private final String address;
    private final Date birthday;
    private final String name;

    public Contact(String name, Date birthday, String address) {
      this.name = name;
      this.birthday = birthday;
      this.address = address;
    }
  }

  /**
   * The list of data to display.
   */
  @SuppressWarnings("deprecation")
  private static final List<Contact> CONTACTS = Arrays.asList(
      new Contact("John", new Date(80, 4, 12), "123 Abc Avenue"), 
      new Contact("Joe", new Date(85, 2, 22), "22 Lance Ln"), 
      new Contact("Tom", new Date(85, 3, 22), "33 Lance Ln"), 
      new Contact("Jack", new Date(85, 4, 22), "44 Lance Ln"), 
      new Contact("Tim", new Date(85, 5, 22), "55 Lance Ln"), 
      new Contact("Mike", new Date(85, 6, 22), "66 Lance Ln"), 
      new Contact("George", new Date(46, 6, 6),"77 Lance Ln"));

  public void onModuleLoad() {
    // Create a CellTable.
    final CellTable<Contact> table = new CellTable<Contact>();
    // Display 3 rows in one page
    table.setPageSize(3);

    // Add a text column to show the name.
    TextColumn<Contact> nameColumn = new TextColumn<Contact>() {
      @Override
      public String getValue(Contact object) {
        return object.name;
      }
    };
    table.addColumn(nameColumn, "Name");

    // Add a date column to show the birthday.
    DateCell dateCell = new DateCell();
    Column<Contact, Date> dateColumn = new Column<Contact, Date>(dateCell) {
      @Override
      public Date getValue(Contact object) {
        return object.birthday;
      }
    };
    table.addColumn(dateColumn, "Birthday");

    // Add a text column to show the address.
    TextColumn<Contact> addressColumn = new TextColumn<Contact>() {
      @Override
      public String getValue(Contact object) {
        return object.address;
      }
    };
    table.addColumn(addressColumn, "Address");

    // Associate an async data provider to the table
    // XXX: Use AsyncCallback in the method onRangeChanged
    // to actaully get the data from the server side
    AsyncDataProvider<Contact> provider = new AsyncDataProvider<Contact>() {
      @Override
      protected void onRangeChanged(HasData<Contact> display) {
        int start = display.getVisibleRange().getStart();
        int end = start + display.getVisibleRange().getLength();
        end = end >= CONTACTS.size() ? CONTACTS.size() : end;
        List<Contact> sub = CONTACTS.subList(start, end);
        updateRowData(start, sub);
      }
    };
    provider.addDataDisplay(table);
    provider.updateRowCount(CONTACTS.size(), true);

    SimplePager pager = new SimplePager();
    pager.setDisplay(table);

    VerticalPanel vp = new VerticalPanel();
    vp.add(table);
    vp.add(pager);

    // Add it to the root panel.
    RootPanel.get().add(vp);
  }
}


To make remote calls to retrieve table data from the server, The code snippet should look like the following.
// Associate an async data provider to the table
    AsyncDataProvider<Contact> provider = new AsyncDataProvider<Contact>() {
      @Override
      protected void onRangeChanged(HasData<Contact> display) {
        final int start = display.getVisibleRange().getStart();
        int length = display.getVisibleRange().getLength();
        AsyncCallback<List<Contact>> callback = new AsyncCallback<List<Contact>>() {
          @Override
          public void onFailure(Throwable caught) {
            Window.alert(caught.getMessage());
          }
          @Override
          public void onSuccess(List<Contact> result) {
            updateRowData(start, result);
          }
        };
        // The remote service that should be implemented
        remoteService.fetchPage(start, length, callback);
      }
    };

14 comments:

  1. Hi,

    I am using your second code snippet to retrieve data from the database. I can see my first page fine, but I am not able to get to the next page since it doesn't seem to know about the other pages.
    How does the pagination know the number of elements? I will have over 3000 items and the count() method (in the query class) only seems to be retrieving up to 1000 items. If I query for all the records for a final count of items it takes about 4 seconds and that is too long.

    Any ideas how to display the total number of items?
    ie 1-20 of 4000 --> currently i only see 1-20 of 20 (but there are 4000 items)

    ReplyDelete
  2. Thanks for the quick reply. I found the issue. I wasn't updating the Row count for the entire set of data, just what was returned for the page.

    ReplyDelete
  3. Very very useful example, really the one I was searching for, and the only one all over the web at this moment.
    Thanls a lot !

    ReplyDelete
  4. Very cool sample code... thanks for Sharing!

    ReplyDelete
  5. Thank you very much for providing this example! Good GWT samples seem to be difficult to hunt down.

    I know this is a combination GWT/java question, but do you know how (or where to find out how) to add a style to the table row based on a status? for instance, if Mike has a hidden status field with value "friend", how would you tell GWT to bold Mike's row?

    ReplyDelete
  6. I did something similar to the second example in my code for a celltable. This celltable is part of a composite. The composite and whole application run fine on my desktop, but there's an exception that occurs when I try to add the composite to my entryPoint module class:

    java.lang.RuntimeException: Failed to invoke native method: @com.google.gwt.user.client.rpc.impl.RpcStatsContext::isStatsAvailable() with 0 arguments.

    The error goes away if I comment out the call to my asynchronous method, so I know it must be the issue, but I don't understand what this error is or why my Async call would be causing it.

    ReplyDelete
  7. I am under the gun to produce with the GWT CellTable. This excellent example really helped me out. Thank you!

    ReplyDelete
  8. Thanks for the article. I am a great fan of GWT however the documentation of it leaves a LOT to be desired, especially the 2.x widgets. Many of the Google examples are trivial and unhelpful. For example, there are very few real examples of using Cell widgets asynchronously as they will be in the real world.

    ReplyDelete
  9. Thanks for the example! I've got it mostly working in my app. My CellTable is in a TabPanel. The problem I'm having is that once the display is rendered I have to click the tab to get the table contents to display (the table is empty when the TabPanel is first diplayed.) I have a CellTable.redraw() call as the last statement in the callback from fetching the data from the database.

    Also, if I update the underlying data in a different screen, I'm not seeing the updates in the table when I return to the screen with the table. (Think mailbox where you click to see a message and from the message display can click "Next" to see the next message without having to go back to the mailbox).

    Thanks again!

    ReplyDelete
  10. // Associate an async data provider to the table
    AsyncDataProvider provider = new AsyncDataProvider() {
    @Override
    protected void onRangeChanged(HasData display) {
    final int start = display.getVisibleRange().getStart();
    int length = display.getVisibleRange().getLength();
    AsyncCallback<List> callback = new AsyncCallback<List>() {
    @Override
    public void onFailure(Throwable caught) {
    Window.alert(caught.getMessage());
    }
    @Override
    public void onSuccess(List result) {
    updateRowData(start, result);
    }
    };
    // The remote service that should be implemented
    remoteService.fetchPage(start, length, callback);
    }
    };


    As I am new to GWT, i dont understant where i put this code, if in server, then which file

    ReplyDelete
  11. Hi,

    My question is a bit off-topic but as I am not getting any help anywhere, I am posting here too.

    I have a requirement of a tree like widget in a celltable and so far I haven't found anything that works seamlessly. So, I was evaluating celltree.

    As most of my functionality is ready with celltable and as tree like requirement has come very late into the development I want to incorporate it somehow in the celltable itself rather than re-writing the whole UI again.

    Is it possible to do with celltable?

    ReplyDelete
  12. Hey man, i just wanna thanks you for the simple example i was tried the gwt showcase example and it dont work.

    ReplyDelete
  13. Thank you for your post.

    Just one tip: it is not very good practice to use generic types like List, Set, Map in GWT-RPC services as it will cause GWT type explosion. It means that after compiling the code into JavaScript, every available subtype of List (Set, Map) will be taken into account resulting in large and potentially slow JavaScript and the compilation itself.

    ReplyDelete
  14. Thank you for your valuable post. It is really helpful.

    ReplyDelete