Making an RPC call from a CellTable ButtonCell in a view (MVP pattern)

265 views
Skip to first unread message

Drew Spencer

unread,
Jan 16, 2012, 12:06:19 PM1/16/12
to google-we...@googlegroups.com
Hi coders,

I've been working on an app that uses the blobstore to save files and then serve them to users. They are shown in a CellTable by creating it and all of its columns in the view (Filename | DownloadButton | DateUploaded | DeleteButton) and then dropping it into a panel after construction. Snippet from the view:

fileTable = new CellTable<SupplierFile>();
fileNames = new TextColumn<SupplierFile>() // standard text column
{
@Override
public String getValue(SupplierFile object)
{
return object.getFilename();
}
};
downloadButtons = new Column<SupplierFile, String>(new ButtonCell())
{
// the text in the cell or button
public String getValue(SupplierFile object)
{
return "Download";
}
};
downloadButtons.setFieldUpdater(new FieldUpdater<SupplierFile, String>()
{
@Override
public void update(int index, SupplierFile object, String value)
{
GWT.log("Downloading " + object.getFileUrl());
Window.open(object.getFileUrl(), "_blank", "");
}
});
DateTimeFormat dateFormat = DateTimeFormat.getFormat("dd/MM/yyyy"); // DD/MM/YYYY
fileDates = new Column<SupplierFile, Date>(new DateCell(dateFormat))
{
@Override
public Date getValue(SupplierFile object)
{
return object.getDate();
}
};
deleteButtons = new Column<SupplierFile, String>(/*new ClickableTextCell()*/ new ButtonCell())
{
// the text in the cell or button
public String getValue(SupplierFile object)
{
return "Delete";
}
};
deleteButtons.setFieldUpdater(new FieldUpdater<SupplierFile, String>()
{
@Override
public void update(int index, SupplierFile object, String value)
{
GWT.log("Deleting " + object.getFileUrl() + " | " + object.getId());

// WHAT GOES HERE???
}
});

The first 3 columns are all OK as they don't really require any logic - only the DownloadButton really but that's just a simple Window.open();  

My question is how do I call my RPC's delete() function from the view? I should be using an event, right? All of the examples I have seen have some kind of clickHandler() interfaces being watched by the presenter, but how do I implement this using a CellTable? I can't access the eventBus as that's in my presenter. I'm very confused.

Any help is greatly appreciated.

Thanks,

Drew

Drew Spencer

unread,
Jan 17, 2012, 9:47:22 AM1/17/12
to google-we...@googlegroups.com
OK, I am making some headway with this.

I realised I was going the wrong way about it needed to attach some kind of handler in the presenter, and found CellPreviewHandler.

So I added this in to my presenter:

display.getFilesTable().addCellPreviewHandler(new Handler<SupplierFile>()
{
@Override
public void onCellPreview(CellPreviewEvent<SupplierFile> event)
{
// if the event was a click on column with index 3
if(event.getNativeEvent().getType().equals("click") && event.getColumn() == 3)
{
final String fileId = event.getValue().getId().toString();
Window.alert("Deleting file " + fileId);
deleteFile(fileId);
}
}
});

This works and does call function deleteFile when the particular cell is clicked. It's good enough for now, but if anyone knows any better way to do it that would be nice to know.

Cheers

Drew

Patrick Tucker

unread,
Jan 18, 2012, 8:54:05 AM1/18/12
to Google Web Toolkit
setFieldUpdater should also produce the same results. I'm not sure
which is better though...

Drew Spencer

unread,
Jan 19, 2012, 6:34:31 AM1/19/12
to google-we...@googlegroups.com
Hey Patrick,

The thing with setFieldUpdater is that I can't work out how to access it from the Presenter. If I add something like HasCell<T, C> getDeleteColumn() to the interface my view implements, I can only access getFieldUpdater, not setFieldUpdater. In honesty the whole CellTable thing seems like it needs some work, so maybe they have plans for the next release or something.

If anyone can suggest an easier way to do this then I would appreciate it. I thought FlexTable or something similar would be a bad move as cells are meant to be much more efficient and cleaner, so decided to put in the extra work and go for cell widgets straight away.

Eric Metcalf

unread,
Jan 19, 2012, 3:33:05 PM1/19/12
to Google Web Toolkit
I'm using flextable to delete items from it. I pass in the EventBus
and throw an event when a delete occurs.

Drew Spencer

unread,
Jan 20, 2012, 5:07:32 AM1/20/12
to google-we...@googlegroups.com
I'm starting to think I'm using a sledgehammer to crack a nut here... gonna try using a FlexTable and attach a ClickHandler to the delete link in each row. Maybe when I'm better at GWT I can switch it to CellTable.

Drew Spencer

unread,
Jan 20, 2012, 5:18:36 AM1/20/12
to google-we...@googlegroups.com
Eric, do you have a way to sort the FlexTable by a particular column? That's gonna be an issue for me so if you have any code or tips that would be great. Maybe it's a case of writing a custom function? Thanks

Patrick Tucker

unread,
Jan 20, 2012, 9:51:03 PM1/20/12
to Google Web Toolkit
I think I modified my HasCell to include the setFieldUpdater(). I'm
not sure why they didn't include it...

Eric Metcalf

unread,
Jan 23, 2012, 4:39:17 PM1/23/12
to Google Web Toolkit
Sorry, I meant to say I am using CellTable.

Is there is a reason you don't pass the EventBus to your view? I
found it very simple...

private static final ButtonCell deleteButtonCell = new ButtonCell();
private static final Column<Thing, String> deleteColumn =
new Column<Thing, String>(deleteButtonCell) {
@Override
public String getValue(Thing thing) {
return "delete";
}
};
private final FieldUpdater<Thing, String> deleteFieldUpdater =
new FieldUpdater<Thing, String>() {

@Override
public void update(int index, Thing thing, String value) {
if (Window.confirm("Delete " + thing.getName() + "?" )) {
eventBus.fireEvent(new ThingDeleteEvent(thing, index));
}
}
};

deleteColumn.setFieldUpdater(deleteFieldUpdater);

Drew Spencer

unread,
Jan 24, 2012, 6:58:06 AM1/24/12
to google-we...@googlegroups.com
Thanks a lot for the reply, Eric! It really is appreciated.

I have actually been replacing the CellTable with FlexTable as I said I would, but on the way realised that I could access my eventBus without passing it in to the view. I have a singleton instance of my app so where TheApp.java is my EntryPoint I can go TheApp.get().getEventBus().fireEvent(event) from anywhere. I forgot about that :/ 

I may switch back to using CellTable though, because it uses much less boilerplate and keeps the code cleaner, as well as providing a way to sort on columns, although I am not at that point yet.

Out of interest, what are you building that uses the CellTable in this way? And how do you refresh your table when you delete an item?

Cheers,

Drew

Patrick Tucker

unread,
Jan 24, 2012, 9:38:41 AM1/24/12
to Google Web Toolkit
Views are not supposed to know about the model. Meaning, the view is
not supposed to know that a particular button is going to result in a
Thing being deleted.

I guess it is up to you to decide how closely you want to follow that
though...

Drew Spencer

unread,
Jan 24, 2012, 10:24:40 AM1/24/12
to google-we...@googlegroups.com
It's a weird one, this. It does seem much easier to have 

                        deleteButtons.setFieldUpdater(new FieldUpdater<SupplierFile, String>()
{
@Override
public void update(int index, SupplierFile object, String value)
{
TheApp.get().getEventBus().fireEvent(new DeleteFileEvent(object.getId()));
}
});

in my view than do it any other way. At least I'm firing an event, so the actual logic is carried out in the presenter. It'll do for now I reckon! I don't want to get into modifying classes, etc. I'm only in the early stages of making this app so I'll move on to column sorting for now.

What do you use CellTable for, Patrick?

Eric Metcalf

unread,
Jan 24, 2012, 10:49:29 AM1/24/12
to Google Web Toolkit
MVC-wise I think it's on the fence since it's just throwing an event.
The presenter or the view can be changed without having to change the
delete code on either side.

To update my table on a delete I am passing the index of the row on
the delete event.

** Presenter **

@Override
public void onThingDeleteRequest(ThingDeleteEvent event) {
view.removeThing(event.getIndex(), --numThings);
service.deleteThing(event.getThing(), deleteThingCallback);
}


** View **

@Override
public void removeThing(int index, int numThings) {
dataProvider.getList().remove(index);
tableThings.setRowCount(numThings, true);
tableThings.setVisibleRange(0, numThings);
}

Not sure if those are the correct calls to make on the table but it
works for me. I also am not using paging.

Drew Spencer

unread,
Jan 24, 2012, 12:15:38 PM1/24/12
to google-we...@googlegroups.com
Cheers for that Eric. So you are adding the index of the row to the payload of your DeleteEvent?

I actually just switched to using ListDataProvider instead of just calling setData() every time something is removed or added. I was basically reloading the presenter every time something changed. So my understand is that with ListDataProvider I only have to remove the item from the list and then it can update the CellTable automatically. Is that right?

I have my list with sortable columns now too - coming along nicely! Never thought I would spend so much time on a table though :/ Still, that's GWT and GAE for you.

Drew

Eric Metcalf

unread,
Jan 24, 2012, 1:52:30 PM1/24/12
to Google Web Toolkit


On Jan 24, 11:15 am, Drew Spencer <slugmand...@gmail.com> wrote:
> Cheers for that Eric. So you are adding the index of the row to the payload
> of your DeleteEvent?

Yes, you can see I pass the index in my event.


> I actually just switched to using ListDataProvider instead of just calling
> setData() every time something is removed or added. I was basically
> reloading the presenter every time something changed. So my understand is
> that with ListDataProvider I only have to remove the item from the list and
> then it can update the CellTable automatically. Is that right?

I think you will need to get the list from ListDataProvider, remove
the item, and set ListDataProvider with the new list. I believe it
should update automatically from there. I think they suggest doing
rowcount and visible range.


Drew Spencer

unread,
Jan 26, 2012, 5:50:40 AM1/26/12
to google-we...@googlegroups.com
> I actually just switched to using ListDataProvider instead of just calling
> setData() every time something is removed or added. I was basically
> reloading the presenter every time something changed. So my understand is
> that with ListDataProvider I only have to remove the item from the list and
> then it can update the CellTable automatically. Is that right?

I think you will need to get the list from ListDataProvider, remove
the item, and set ListDataProvider with the new list.  I believe it
should update automatically from there.  I think they suggest doing
rowcount and visible range.

The problem I have is that I am deleting a blob from the blobstore, not just removing it from the table, so once the RPC call returns it seems the best way is to reload the presenter using History.fireCurrentHistoryState() which makes the app query the DS again, and fill the table from scratch. It's only me that will be using this delete function, users will just be able to download so it's not a major problem in terms of efficiency.

I'm having a weird problem though - sometimes the RPC call returns successful after a delete and then when I reload the presenter it still gets the list including the file I just deleted. A refresh always sorts it out though - maybe a memcache problem or something? I am using Objectify so not sure if that may affect it.

Drew

Patrick Tucker

unread,
Jan 26, 2012, 9:49:59 AM1/26/12
to Google Web Toolkit
You should be able to watch your traffic to see whether or not the
browser is returning a cached copy of the result. What browser are
you using?

On Jan 26, 5:50 am, Drew Spencer <slugmand...@gmail.com> wrote:
> > > I actually just switched to using ListDataProvider instead of just
> > calling
> > > setData() every time something is removed or added. I was basically
> > > reloading the presenter every time something changed. So my understand
> > is
> > > that with ListDataProvider I only have to remove the item from the list
> > and
> > > then it can update the CellTable automatically. Is that right?
>
> > I think you will need to get the list from ListDataProvider, remove
> > the item, and set ListDataProvider with the new list.  I believe it
> > should update automatically from there.  I think they suggest doing
> > rowcount and visible range.
>
> The problem I have is that I am deleting a blob from the blobstore, not
> just removing it from the table, so once the RPC call returns it seems the
> best way is to reload the presenter using History.fireCurrentHistoryState()
> which makes the app query the DS again, and fill the table from scratch.
> It's only me that will be using this delete function, users will just be
> able to download so it's not a major problem in terms of efficiency.
>
> *I'm having a weird problem though* - sometimes the RPC call returns

Drew Spencer

unread,
Jan 26, 2012, 10:30:46 AM1/26/12
to google-we...@googlegroups.com
On Thursday, 26 January 2012 14:49:59 UTC, Patrick Tucker wrote:
You should be able to watch your traffic to see whether or not the
browser is returning a cached copy of the result.  What browser are
you using?

Using Chrome. I deployed the app earlier to test it live and it seems to work fine so maybe it's just the development version of the datastore service playing up a bit.

I would be interested to know how to watch the traffic though.

Patrick Tucker

unread,
Jan 27, 2012, 10:33:13 AM1/27/12
to Google Web Toolkit
Using Chrome, you can watch it under the Network tab of Developer
Tools. If you look at the "Size Content" column it will say (from
cache)...

IE9 has a similar feature.
Reply all
Reply to author
Forward
0 new messages