Difficulty creating a Custom Grid Renderer to mimic a TextField

I am attempting to make a column renderer that works like an editable textfield. Eventually, this textfield needs to save data when focus is lost, but currently I can’t even get the textfield to appear at all. Worse, I can’t even get
this simple example
to work.

I have been scouring this forum for some good examples or tips, but other than
this post
I can’t really find too much. Here is what I have attempted to do … if I did something completely stupid, please don’t hesitate to point that out.

Client Side Renderer:

import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.widget.grid.RendererCellReference;

/**
 * Created by dangoloborodko on 7/7/2015.
 */
public class GridEditableRenderer implements Renderer<String> {
    @Override
    public void render(RendererCellReference cell, String text) {
        cell.getElement().setInnerText(text);
    }
}

Server Side Renderer:

import com.vaadin.ui.Grid;

/**
 * Created by dangoloborodko on 7/7/2015.
 */
public class GridEditableRenderer extends Grid.AbstractRenderer<String> {
    public GridEditableRenderer() {
        super(String.class);
    }
}

Connector:

import com.vaadin.client.connectors.AbstractRendererConnector;
import com.vaadin.shared.ui.Connect;
import org.ici.weekly.ui.renderer.GridEditableRenderer;

/**
 * Created by dangoloborodko on 7/7/2015.
 */
@Connect(org.ici.weekly.server_render.GridEditableRenderer.class)
public class GridEditableRendererConnector extends AbstractRendererConnector<String> {
    @Override
    public GridEditableRenderer getRenderer() {
        return (GridEditableRenderer) super.getRenderer();
    }
}

My IDE doesn’t complain about anything so far, but once I try binding the renderer to the Grid, I am having problems. I tried binding it this way:

complexGrid.getColumn("tna").setRenderer(new GridEditableRenderer());

But, it is complaining that it wants something like this:

(com.vaadin.ui.renderers.Renderer<?>)

So, first off, what am I doing wrong so far? But, more importantly, I don’t see how the renderer will be getting the data out of the model to display. It seems to me that my client side renderer needs to implement com.vaadin.ui.renderers.Renderer rather than com.vaadin.client.renderers.Renderer (note the missing ui), but if I do that than it looks nothing like the simple example shown, it instead overrides dozens of other functions that I am not sure are all that necessary.

TLDR, please help … I’m lost.

I haven’t made any renderers myself, so can’t help directly with that, but what you basically are trying to do is to have inline editing without the grid editor opening up? This feature will be included in Vaadin 7.6 (released in October according to the
roadmap
) and you can already now try out the
alpha2 release
. I tried the inline editing, basic functionality worked but there are still bugs in it. Doesn’t help too much if you need a stable release that works today.

Thanks, Kim. I will definitely check it out in a separate branch. But, stability is definitely key. We are supposed to release by Aug 31st, so waiting till October is definitely out of the question.

Is there a way to get just that functionality of the Grid via an extension? … rather than trying to incorporate an Alpha2 release of 7.6?

… or, are there resources available somewhere that can help guide me to achieve what I am trying to do?

Looks like you’ve messed client-side and server-side renderers here.

Or course I did …

Thanks, that helped. I’ll be back when I run into more trouble. Very much appreciated!

Well, that didn’t take long. At this point, I am facing three separate problems, none of which I can figure out.

1, after switching client side and server side renderers and applying it to the table, all the column headers disappeared. Similarly, the column order seems to have switched. I have tried placing the setRenderer call in various places in my constructor, but so far no luck. Is there a trick to this?

2, the column that I assume I am setting the renderer to is blank. Is there anything I need to do to bind the data to it? It seems like this is fairly automatic (based on problem 3), but maybe I am wrong?

3, If I try to change this to a TextField by replacing String everywhere, things go awefully wrong. During the application initialization everything crashes, and i get the following error:

java.lang.IllegalArgumentException: Cannot remove converter, as renderer's presentation type com.vaadin.ui.TextField and column's model java.math.BigDecimal type aren't directly compatible with each other (in Column[propertyId:tna]
)

btw, in case it isn’t clear, the field I am trying to convert is in fact a BigDecimal … this needs formatting, I’ll worry about that later.

… I am really sorry to be such a pain. Maybe I am tackling this problem wrong?

Just trying to update you all on my limited progress. Thus far, I have predominantly attempted to resolve the first problem. It seems no matter what I do, all the column headers in my grid go away, and the column order gets shifted around. The following is my Grid’s initialization code that I have settled on so far:

        complexes = new ArrayList<>();
        container = new BeanItemContainer<>(ComplexModuleContainer.class, complexes);
        complexGrid = new Grid(container);
        complexGrid.getColumn("tna").setRenderer(new GridEditableRenderer());

        complexGrid.setColumnOrder("ct", "fundid", "ticker", "fundname", "tna", "tnac", "nav", "navc", "shares", "sharesc", "nncf");

        complexGrid.getColumn("ct").setHeaderCaption("#");
        complexGrid.getColumn("fundid").setHeaderCaption("Fund ID");
        complexGrid.getColumn("ticker").setHeaderCaption("Ticker");
        complexGrid.getColumn("fundname").setHeaderCaption("Fund Name");
        complexGrid.getColumn("tna").setHeaderCaption("TNA");
        complexGrid.getColumn("tnac").setHeaderCaption("TC");
        complexGrid.getColumn("nav").setHeaderCaption("NAV");
        complexGrid.getColumn("navc").setHeaderCaption("NC");
        complexGrid.getColumn("shares").setHeaderCaption("Shares");
        complexGrid.getColumn("sharesc").setHeaderCaption("SC");
        complexGrid.getColumn("nncf").setHeaderCaption("NNCF");

        complexGrid.getColumn("ct").setWidth(50);
        complexGrid.getColumn("fundid").setWidth(75);
        complexGrid.getColumn("ticker").setWidth(100);
        complexGrid.getColumn("fundname").setWidth(385);
        complexGrid.getColumn("tna").setWidth(140);
        complexGrid.getColumn("tnac").setWidth(40);
        complexGrid.getColumn("nav").setWidth(140);
        complexGrid.getColumn("navc").setWidth(40);
        complexGrid.getColumn("shares").setWidth(140);
        complexGrid.getColumn("sharesc").setWidth(40);
        complexGrid.getColumn("nncf").setWidth(140);

        complexGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
        complexGrid.setWidth("1180px");
        complexGrid.setHeightByRows(11);

The column order I am seeing is: ct, fundid, nav, navc, shares, sharesc, tna, tnac, ticker.

Interestingly enough, changing the ColumnOrder via the setColumnOrder method does nothing in changing the order I see the data on the screen. Hence, it seems that this order is being calculated somehow. Everywhere in my code I more or less use the same order, as you can see in my code above, so I am not at all sure where it is getting its display order.

Also, I have tried changing the Renderer base type from String to BigDecimal. This did nothing for me. No change in display, although when calling setInnerText I had to convert the BigDecimal to a String for it to compile.

If anyone has any ideas on what I can try, I will be most appreciative.

Problems 1 and 2 might be produced by expections during grid initialization. Try a
debug mode
to find them.
Problem 3 could be solved by

grid.getColumn(..).setConverter(new com.vaadin.data.util.converter.StringToBigDecimalConverter())

Thanks for the tip. I tried setting the Converter, but that didn’t help. Both client and server renderers were set to accept Strings (reverting to the code in my original post), but it acted exactly the same as beore … columns in the wrong order and the tna column coming up completely blank.

As for Debug Mode, I have been running in debug mode from day 1, and no exceptions are hitting my Jetty terminal.

I am curious, but maybe I am tackling this problem wrong? Is there a better way to get this funcationality in place within a Grid? … outside of waiting until October for 7.6.

Debug mode shows client-side logs(and exceptions) in client debug window. Look there for the diagnostics.
About your column type incompatibility - please provide a full stack trace and a code snippet.

I see, that makes sense now. Unfortunately, though, I can’t really understand how to trace the exception I am seeing there. Here’s what I see in the console when the page loads:

Wed Jul 08 09:15:56 GMT-400 2015 com.vaadin.client.ApplicationConnection
SEVERE: Error sending state change eventscom.google.gwt.event.shared.UmbrellaException: Exception caught: undefined
    at Unknown.Eg(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Ig(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Og(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Hx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Kx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.fx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.QBb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.cCb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eyb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.$xb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.kxb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.ixb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Lyb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Mx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.hy(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eval(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.li(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.oi(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eval(org.ici.weekly.AppWidgetSet-0.js)
Caused by: java.lang.ClassCastException
    at Unknown.Eg(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Mg(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Isd(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.jB(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.LOb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.VLb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.ox(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.fx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.QBb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.cCb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eyb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.$xb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.kxb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.ixb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Lyb(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.Mx(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.hy(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eval(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.li(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.oi(org.ici.weekly.AppWidgetSet-0.js)
    at Unknown.eval(org.ici.weekly.AppWidgetSet-0.js)

This is what my setRenderer line looks like now:

        complexGrid.getColumn("tna").setRenderer(new GridEditableRenderer()).setConverter(new com.vaadin.data.util.converter.StringToBigDecimalConverter());

Then your next step is
superdev
mode.
But, I recommend you to try Vaadin 7.6.
You can copy the sources and make your own Grid, or use sources and tests as examples.
Note the Grid.setBuffered(false) - it seems to me, that is exactly the feature what are you looking for.

I will try this in superdev mode shortly, I have several other issues I need to attend to first, but I am curious … is there an ETA for Vaadin 7.6 in Beta rather than Alpha? I may be able to give it a shot in Beta, but at this point I am worried that an alpha version may break some existing funcationality.

To understand a bit more about custom renderers I tried to create a Renderer like you want one (if i didn’t misunderstood your post) and i think i did it. I made a Renderer which display a gwt TextBox in each cell of the column and on Value change (so on enter or Tab, …) it will make an rpc to the server side and change the value of the item property to the new value. I think i probably overcomplicated and i don’t know if the RPC was really necessary or if it could’ve been done on the client side but here is the code. Hope it helps:

Server side:

import com.test.client.TextFieldValChangeRPC;
import com.vaadin.data.Container.Indexed;
import com.vaadin.ui.Grid.AbstractRenderer;

public class TextFieldRenderer<T> extends AbstractRenderer<T> {
    private static final long serialVersionUID = 1L;

    protected TextFieldRenderer(Class<T> arg0) {
        super(arg0);
        registerRpc(new TextFieldValChangeRPC() {
            private static final long serialVersionUID = 1L;

            @Override
            public void valChange(String propID, String index, String newValue) {
                Indexed cont = getParentGrid().getContainerDataSource();
                cont.getItem(cont.getItemIds().toArray()[Integer.valueOf(index)]
).getItemProperty(cont.getContainerPropertyIds().toArray()[Integer.valueOf(propID)]
).setValue(newValue);
            }
        });
    }
}
[/code]Client  side:

[code]
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.ui.TextBox;
import com.vaadin.client.renderers.WidgetRenderer;
import com.vaadin.client.widget.grid.RendererCellReference;

public class TextFieldRenderer extends WidgetRenderer<String, TextBox> {

    @Override
    public TextBox createWidget() {
         TextBox txt = GWT.create(TextBox.class);
         txt.setStylePrimaryName("v-textfield");
         return txt;
    }

    @Override
    public void render(final RendererCellReference cell, String data, TextBox widget) {
        widget.setText(data);
        final int rowindex = cell.getRowIndex();
        widget.addValueChangeHandler(new ValueChangeHandler<String>() {
            
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                fireValChange(cell.getColumnIndex()+"", rowindex+"", event.getValue()+"");
            }
        });
    }
    
    public interface ValChangeListener{
        public void valChange(Object propID, Object index, String newValue);
    }
    
    private List<ValChangeListener> listeners = new ArrayList<TextFieldRenderer.ValChangeListener>();
    public void addValChangeListener(ValChangeListener listener){
        listeners.add(listener);
    }
    public void removeValChangeListener(ValChangeListener listener){
        listeners.remove(listener);
    }
    
    public void fireValChange(Object propID, Object index, String newValue){
        for (Iterator it_listener = listeners.iterator(); it_listener.hasNext();) {
            ValChangeListener listener = (ValChangeListener) it_listener.next();
            listener.valChange(propID, index, newValue);
        }
    }

}
[/code]


[code]
import com.test.client.TextFieldRenderer.ValChangeListener;
import com.vaadin.client.connectors.AbstractRendererConnector;
import com.vaadin.shared.ui.Connect;

@Connect(com.test.TextFieldRenderer.class)
public class TextFieldRendererConnector extends AbstractRendererConnector<String> {
     @Override
        public TextFieldRenderer getRenderer() {
            return (TextFieldRenderer) super.getRenderer();
        }
    
     ValChangeListener listener;
     @Override
    protected void init() {    
        getRenderer().addValChangeListener(listener = new ValChangeListener() {
            
            @Override
            public void valChange(Object propID, Object index, String newValue) {
                getRpcProxy(TextFieldValChangeRPC.class).valChange(propID+"", index+"", newValue);
            }
        });
        super.init();
    }
     @Override
    public void onUnregister() {
         getRenderer().removeValChangeListener(listener);
        super.onUnregister();
    }
}
[/code]


[code]
import com.vaadin.shared.communication.ServerRpc;

public interface TextFieldValChangeRPC extends ServerRpc{
    public void valChange(String propID, String index, String newValue);
}
[/code]
My test looks like:[code]
Grid grid = new Grid();
        IndexedContainer cont = new IndexedContainer();
        cont.addContainerProperty("TEST", String.class, "testval");
        cont.addItem();
        cont.addItem();
        cont.addItem();
        cont.addItem();
        cont.addItem();
        cont.addItem();
        cont.addItem();
        cont.addItem();
        grid.setContainerDataSource(cont);
        grid.getColumn("TEST").setRenderer(new TextFieldRenderer(String.class));

I made these while compiling my main project so i can’t guarantee that it works perfectly.

Vaadin Beta ETA is Sept’15

Marius, you are amazing! I can’t express how greatful I am for that. Strangely, it didn’t work in my project at all, giving exactly the same results as my failed renderer (columns out of order, and the tna column simply blank) but at least now I see a few of the things I did wrong and once I work out this kink, I should be able to move forward much more quickly.

I am going to try out the superdev mode later in the afternoon once I finish up my other responsibilities and will hopefully work through the issues. I’m confident that it is something ridiculously stupid. Once identified, I’ll be sure to post my results here in case someone else hits a similar snag.

None the less, I am immensely appreciative of all the help I’ve received. You guys are doing a great job!

As for Vaadin 7.6, I will probably hold off for a bit before giving that a shot. Once available, however, I’m sure we will implement that solution.

I’m glad i could help.

Have you tried it with a simple Grid with just a few basic properties? …because what you describe sounds like a problem with the Grid setup.
Also: I made this Renderer only with Properties of type String in mind. I just tried it with BigDecimal and it crashed on the server side. So it shouldn’t be that difficult to convert the value there when to the Class type of the Renderer parameter.

As I noted before, there is a StringToBigDecimalConverter for that.

I was thinking the same thing. My Grid is overly complicated, for sure, but this can’t be helped. I’m guessing that my problem lies in my grid’s containerDatasource. You use an IndexedContainer where I use a BeanItemContainer. I will try changing this later in the afternoon to see how it goes. Shouldn’t be too much work.

If that doesn’t work, I’ll strip it down to its bare essentials and try building up slowly. The problem is that I have a working solution already using a table, it’s just slower than the customer would like. And the project still requires a great deal of new development as well as bug fixes, so I can’t really devote the time needed to get this grid working. I’ll get it eventually, no doubt. But, there are still a number of other requirements that this Grid will have to perform that I haven’t mentioned, and I’m not confident that the Grid will be able to handle them all in a reasonable amount of time, which isn’t helping in motivating me to work faster on this.

BeanItemContainer is, in fact, an Indexed interface implementation, and works pretty fine with Grid.
Try the renderer provided by Marius for text column first, then try to use BigDecimals.