AsyncDataProvider and CellTree

1,399 views
Skip to first unread message

Thad

unread,
Jul 30, 2012, 4:02:03 PM7/30/12
to google-we...@googlegroups.com
I have a CellTree in which I want to represent something similar to a directory listing (actually represented in database records):

public class DirectoryObject implements Serializable {
    public String name;
    public String type; // 'file' or 'folder'
    public int group;
    ...
}

This listing comes from a server application which the user must log into. Naturally I can't call the RPC to populate the tree until the login takes place.

My wish is to draw the UI and upon login (or upon pressing a 'List' or 'Refresh' button to return an ArrayList<DirectoryObject>. From this I populate the previously empty tree. Objects of type 'folder' will be the branch nodes and will require a new RPC call with the name as a parameter to find the children (null gets me the highest level).

My question is how, once the empty tree is present, to trigger that call and start my listing. So far I've got what you see below, but I'm not sure if it's right and I'm stumped on the UiHandler for my list button (at the very bottom). I'm trying to follow the CellTree examples, but they use static data or don't start empty.

public class DirectoryPanel extends Composite {

  private static DirectoryPanelUiBinder uiBinder = GWT
      .create(DirectoryPanelUiBinder.class);

  interface DirectoryPanelUiBinder extends
    UiBinder<Widget, DirectoryPanel> {
  }
  
  private static class MyDataProvider extends AsyncDataProvider<DirectoryObject> {
    
    private String folderName;
    
    public MyDataProvider(DirectoryObject directory) {
      if (directory != null)
        folderName = directory.name;
    }

    @Override
    protected void onRangeChanged(HasData<DirectoryObject> display) {
      final Range range = display.getVisibleRange();

      AsyncCallback<ArrayList<DirectoryObject>> callback = 
          new AsyncCallback<ArrayList<DirectoryObject>>() {
        @Override
        public void onFailure(Throwable caught) {
          Window.alert((new GwtOptixException(caught)).getMessage());
        }

        @Override
        public void onSuccess(ArrayList<DirectoryObject> result) {
          int start = range.getStart();
          GWT.log("onRangeChanged, start: "+start);
          updateRowData(start, result);
        }
      };
      Cold.getRpcService().getDirectoryListing(folderName, callback);
    }
  }
  
  private static class DirectoryTreeModel implements TreeViewModel {
    
    private SingleSelectionModel<DirectoryObject> selectionModel = 
        new SingleSelectionModel<DirectoryObject>();

    public DirectoryTreeModel() {
      selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler(){
        public void onSelectionChange(SelectionChangeEvent event){
          /* Huh? 
            but there's no getSelectedObject() in SelectionChangeEvent
            */
          //DirectoryObject rec = (DirectoryObject)event.getSelectedObject();
        }
      });
    }
    
    @Override
    public <T> NodeInfo<?> getNodeInfo(T arg0) {
      if (arg0 instanceof DirectoryObject) {
        Cell<DirectoryObject> cell = new AbstractCell<DirectoryObject>() {
          @Override
          public void render(Context context, DirectoryObject value,
              SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.name);
            }
          }
        };
        MyDataProvider provider = new MyDataProvider((DirectoryObject)arg0);
        return new DefaultNodeInfo<DirectoryObject>(provider, cell, selectionModel, null);
        // or return new DefaultNodeInfo<DirectoryObject>(provider, cell); ??
      }
      return null;
    }

    @Override
    public boolean isLeaf(Object arg0) {
      return arg0 != null && ((DirectoryObject)arg0).name != null &&
          ((DirectoryObject)arg0).type.equals('file');
    }
  }
  
  @UiField(provided = true)
  CellTree tree;
  @UiField
  Button list;
  
  DirectoryTreeModel treeModel;

  public DirectoryPanel() {
    treeModel = new DirectoryTreeModel();
    tree = new CellTree(treeModel, null);
    
    initWidget(uiBinder.createAndBindUi(this));
  }
  
  @UiHandler("list")
  void onList(ClickEvent event) {
    // What here??
  }

}





Andrea Boscolo

unread,
Jul 31, 2012, 6:50:36 AM7/31/12
to google-we...@googlegroups.com

It's actually selectionModel.getSelectedObject() that returns your real DirectoryObject.
 
        }
      });
    }
    
    @Override
    public <T> NodeInfo<?> getNodeInfo(T arg0) {
      if (arg0 instanceof DirectoryObject) {
        Cell<DirectoryObject> cell = new AbstractCell<DirectoryObject>() {
          @Override
          public void render(Context context, DirectoryObject value,
              SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.name);
            }
          }
        };
        MyDataProvider provider = new MyDataProvider((DirectoryObject)arg0);
        return new DefaultNodeInfo<DirectoryObject>(provider, cell, selectionModel, null);
        // or return new DefaultNodeInfo<DirectoryObject>(provider, cell); ??

The first line: generally you should provide a selection model for all your nodes if you want to do something when they are selected (the actual expansion, if not a leaf, is a provider's job).

      }
      return null;
    }

    @Override
    public boolean isLeaf(Object arg0) {
      return arg0 != null && ((DirectoryObject)arg0).name != null &&
          ((DirectoryObject)arg0).type.equals('file');
    }
  }
  
  @UiField(provided = true)
  CellTree tree;
  @UiField
  Button list;
  
  DirectoryTreeModel treeModel;

  public DirectoryPanel() {
    treeModel = new DirectoryTreeModel();
    tree = new CellTree(treeModel, null);
    
    initWidget(uiBinder.createAndBindUi(this));
  }
  
  @UiHandler("list")
  void onList(ClickEvent event) {
    // What here??
  }

}

I'd:
- create the cell tree only if the user has logged in (and show something like an empty message);
- create a root null node (as you did) but refuse the expansion/selection until the user has logged in (probably an async provider's job, i.e., the inner RPC is used in an authentication mechanism and refuses requests from unauthenticated clients);
- create a default root node that does nothing at all, backed by a no-op (null) selection model and when the used logs in, replace it with the real selection model (I think the only way is re-instantiate the cell tree).

I think the best way to programmatically select a node is by using the selection model, although I don't remember if it also expand inner nodes.

Hope that helps.

Thad

unread,
Aug 1, 2012, 2:46:42 PM8/1/12
to google-we...@googlegroups.com
Thanks, Andrea.

I think (hope?) I'm getting closer. I can trigger my RPC call and get back a List of my objects, but I'm not seeing the magic that converts that list to a visible tree in my browser.

I've modified my data provider's instantiation to

  public MyDataProvider(DirectoryObject directory) {
    GWT.log("new data provider");
    if (directory != null)
      folderName = directory.name;
    AsyncCallback<ArrayList<DirectoryObject>> callback = 
          new AsyncCallback<ArrayList<DirectoryObject>>() {
      @Override
      public void onFailure(Throwable caught) {
        Window.alert((new MyGwtException(caught)).getMessage());
      }

      @Override
      public void onSuccess(ArrayList<DirectoryObject> result) {{
        GWT.log("got "+result.size()+" rows");
        updateRowCount(result.size(), true);
        updateRowData(0, result);
      }
    };
    MyApp.getRpcService().getDirectoryListing(folderName, callback);
  }

I'm wondering, though, about the calls to updateRowCount() and updateRowData() and the onRangeChanged() (which is still the same). These calls are shown in the only CellTree AsyncDataProvider example I can find--https://groups.google.com/forum/?start&hl=en#!searchin/google-web-toolkit/CellTree$20asyncdataprovider/google-web-toolkit/jdQLj_oZTek/fuKmFMrbghsJ
Is this right? It looks like CellTable stuff. There's no HasData<T> for CellTree. What fills in the CellTree?

The tree is declared in UiBinder with a null root. But since a null is also the condition for the server to start at the root of my directory tree, I test for login in my model:

  @Override
  public <T> NodeInfo<?> getNodeInfo(T value) {
    GWT.log("getNodeInfo()");
    if (value == null) {
      GWT.log("value is null");
      if (MyApp.isLoggedIn()) {
        GWT.log("user is logged in");
        // Create a cell to display a folder.
        Cell<DirectoryObject> cell = new AbstractCell<DirectoryObject>() {
          @Override
          public void render(Context context, DirectoryObject value,
              SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.name);
            }
          }
        };
        // Return a node info that pairs the data provider and the cell.
        MyDataProvider provider = new MyDataProvider(null);
        return new DefaultNodeInfo<DirectoryObject>(provider, cell);
      }
      else
        return null;
    }
    else if (value instanceof DirectoryObject) {
      DirectoryObject rec = (DirectoryObject) value;
      if (rec.type.equals("folder")) {
        Cell<DirectoryObject> cell = new AbstractCell<DirectoryObject>() {
          @Override
          public void render(Context context, DirectoryObject value,
              SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.name);
            }
          }
        };
        // Return a node info that pairs the data provider and the cell.
        MyDataProvider provider = new MyDataProvider(rec);
        return new DefaultNodeInfo<DirectoryObject>(provider, cell);
      }
      else { // it's a file
        Cell<DirectoryObject> cell = new AbstractCell<DirectoryObject>() {
          @Override
          public void render(Context context, DirectoryObject value,
              SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.name);
            }
          }
        };
        // Return a node info that pairs the data provider and the cell.
        MyDataProvider provider = new MyDataProvider(rec);
        return new DefaultNodeInfo<DirectoryObject>(provider, cell, selectionModel, null);
      }
    }
    return null;
  }

I'm assuming this code creates a cell with the name from my DirectoryObject (later I want to expand this to add a folder icon).

My button's action is now

  @UiHandler("list")
  void onList(ClickEvent event) {
    treeModel.getNodeInfo(null);
  }

When I click the button I can see the log results I expect:

00:00:42.800 [INFO] getNodeInfo()
00:00:42.800 [INFO] value is null
00:00:42.800 [INFO] user is logged in
00:00:42.801 [INFO] new data provider
00:00:49.376 [INFO] got 31 rows


However I see nothing in the browser. Firebug shows an empty <div> where the CellTree belongs:

<div style="overflow: auto; position: relative; height: 200px; width: 100px;">
  <div style="position: relative;">
    <div class="GPBYFDEAG GPBYFDEKK" __gwtcellbasedwidgetimpldispatchingfocus="true" __gwtcellbasedwidgetimpldispatchingblur="true"></div>
  </div>
</div>

What magic am I missing that turns my ArrayList<DirectoryObject> into the cells of a CellTree?
 

Andrea Boscolo

unread,
Aug 2, 2012, 7:24:14 AM8/2/12
to google-we...@googlegroups.com
How do you bind a cell tree using uibinder?
I think you can't call directly the getNodeInfo and expect you tree to be populated. The async data provider fills the tree by calling the onRangeChanged(), there is where you need to put you RPC call.
The http://google-web-toolkit.googlecode.com/svn/javadoc/2.5/com/google/gwt/view/client/AsyncDataProvider.html can be used in both celltree and celltable and holds a HasData<T> display.
In the getNodeInfo the null check should be used to fill in the root element(s).

Try to simplify things i.e., removing authenticaton and the load button.

Thad

unread,
Aug 2, 2012, 10:43:46 AM8/2/12
to google-we...@googlegroups.com
Ah ha! Thanks, Andrea. 

I hard coded the authentication into the RPC  getDirectoryListing() and it works. Files and folders display. Clicking on folders makes the RPC call lists the contents.

Now my challenge is to figure out how to add the CellTree only after the login has been made (or maybe add the entire panel its place in a TabLayoutPanel) only after login. Not to mention fixing its appearance. The default looks terrible (and each cell is--oddly--underlined; I loath CSS).
Reply all
Reply to author
Forward
0 new messages