Keyboard selection behavior in the Grid

I am having an incredibly difficult time trying to figure out how to get row selection to work with the keyboard when using the grid. In a table environment, when you select a row with your mouse, using the up/down arrows will select the row above or below. In the grid, however, the originally selected row stays selected, but a strage box selection moves up/down/left/right instead. The problem is that no listeners seem to fire when this happens.

My application must work fully with no mouse, so this is problematic. I need to know where selection is so that other things can occur on the page. How do I figure out where the box is? … or, more precisely, when the box is moved, how can I fire an event?

It appears that the ValueChangeListener was either removed or not implemented in the Grid.

This is my guess, if anyone else knows otherwise please respond here.

Thanks

Hi. You can use the
Grid.addSelectionListener(com.vaadin.event.SelectionEvent.SelectionListener)
for keeping track on the selection changes. In Grid, the selection using keyboard works by changing the focused item with the arrow keys (the box you mentioned), and then pressing space will select the currently selected item. This will unselect the previously selected item if the grid is not in multi selection mode.

Hope this helps.

Thanks, Pekka, while that helps, it doesn’t help a great deal. The thing is, I need the row to be selected as the down arrow is pressed. Much happens on the screen on selection, which happens to be triggered by said SelectionListener. This feature is very important for the screen, but unfortunately I can’t seem to capture this down arrow.

I have tried adding a Shortcut Listener to the grid itself listening for the down arrow, but this only seems to fire if a cell is in Edit Mode. Otherwise, I get nothing. Is there a trick to this? If I could at least capture this event, I can overload the operation I need, but so far I have had no luck. Any idea?

SelectionListener != ValueChangeListener

Hello @Pekka

Is it possible to overload the function of the arrow keys?

To be specific, is it possible to have the [arrow key]
act like [arrow key + space]
?

I think the reason why the arrow keys don’t act as selection keys is that this way the user can just navigate with the arrow keys, and select pressing when it is needed, thus making it possible to do non-coherent selection with the arrow keys + space.

So unfortunately for overriding the keys, you’d have to do client side changes, and not sure how trivial those would be. Essentially I think what you would need is an event that is fired when the focused item is changed with keyboard (arrow keys), e.g. the same as ItemClickListener. You can
search
for a feature request ticket or
create
one if it doesn’t.

Well, that’s unfortunate. Do you have insight on how these client side changes might occur? If you could point me in the right direction, I can start looking into it.

Similarly, I assume that not a great deal will change between the current Alpha2 release and the eventual production release slated for October. This leads me to two questions, are there going to be any Grid enhancements in the production release? And, assuming I create a ticket and it is accepted, when could such functionality be realistically expected to make it into a release?

After a quick look you could try to attach a BodyKeyDownHandler/BodyKeyPressHandler to the Grid on the client side, you should be able to do this with an client side Extension. You can check the SpaceSelectHandler for example. But I’m can’t guarantee it will work because the Grid is a rather complex widget…

Much can change between Alpha and Beta, the latter should have API freeze. It seems that planned release date for beta is in
6 weeks
. Without a ticket it is guaranteed that nothing will happen, with a ticket there at least is a chance. While the
roadmap
doesn’t mention anything about Grid enhancements, there still might be some…

During compilation time of my project I did a small test with a custom Extension and came to this working but probably not perfect solution(as my way of finding the Row index of the selected row is quite dirty)
The client side extend method is:

[code]
@Override
protected void extend(ServerConnector target) {
GridConnector gridcon = (GridConnector) target;
final Grid gr = gridcon.getWidget();
gr.addDomHandler(new KeyDownHandler() {

        @Override
        public void onKeyDown(KeyDownEvent event) {
            if(event.getNativeKeyCode()==40){
                for (int i = 0; i < gr.getDataSource().size(); i++) {
                    JsonObject el = gr.getDataSource().getRow(i);
                    if(gr.getSelectedRow().equals(el)){
                        gr.select(gr.getDataSource().getRow(i+1));
                        break;
                    }
                }
            }else if(event.getNativeKeyCode()==38){
                for (int i = 0; i < gr.getDataSource().size(); i++) {
                    JsonObject el = gr.getDataSource().getRow(i);
                    if(gr.getSelectedRow().equals(el)){
                        gr.select(gr.getDataSource().getRow(i-1));
                        break;
                    }
                }
            }
        }
    }, KeyDownEvent.getType());
}

[/code]I tested it with a small DataSource and i feel it might perform quite badly when you have hundreds of rows.

Thanks, guys. I will take a look at getting this working with our project. Our Grid sometimes has as many as 1500 rows, so if there is a significant slowdown that may not be an ideal solution. Unfortunately, paging is out of the question as far as the client is concerned. I can definitely improve on the rowID, so maybe performance won’t be too bad.

Given how much more efficiently the Grid runs as compared to the Table, I will give some thought to creating a few tickets. There are a number of features I would love to see in the Grid so that it could be a viable replcement for the table, at least as far as I’m concerned.

Again, thanks for all your help.

Hello again. Sorry for the delayed response, but it seems like the client wants nothing to do with the Table and is only willing to accept the Grid, once we get it working, so now I am working on this more or less full time. I have a ton to do to get the Grid to work more like our table, and this is a problem that is of top priority.

That being said, I have been trying to get your custom extension working, but so far I am having no luck. I am using
this link as my guide
, but am spinning my wheels and moving nowhere.

For starters, I have opted to try to extend AbstractExtension, and so far all my code compiles, but I am having a hard time figuring out how to bind my grid to this extension. The last section in the page, “There is nothing extension-specific in the following code:”, is confusing me. Where do I put those 3 classes at the top, in my Panel code? Then how do I call it? That doesn’t seem right, because that Override of RefresherState just doesn’t fit. Maybe you guys have a better example I can use? … I have never made an extension in Vaadin before.

Just in case, here is the code I have setup.

package org.ici.weekly.extension;

import com.vaadin.server.AbstractExtension;
import com.vaadin.ui.Grid;

public class ArrowExtension extends AbstractExtension {
    public ArrowExtension(Grid grid) {
        extend(grid);
    }
}
package org.ici.weekly.ui.refresher;

import com.vaadin.shared.communication.ServerRpc;

public interface ArrowRpc extends ServerRpc {
    public void refresh();
}
package org.ici.weekly.ui.refresher;

import com.vaadin.shared.communication.SharedState;

public class ArrowState extends SharedState {
    public int interval;
}
package org.ici.weekly.ui.refresher;

import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.user.client.Timer;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.connectors.GridConnector;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.client.widgets.Grid;
import com.vaadin.shared.ui.Connect;
import elemental.json.JsonObject;
import org.ici.weekly.extension.ArrowExtension;

@Connect(ArrowExtension.class)
public class ArrowConnector extends AbstractExtensionConnector {

    private Timer timer = new Timer() {
        @Override
        public void run() {
            getRpcProxy(ArrowRpc.class).refresh();
        }
    };

    @Override
    public void onStateChanged(StateChangeEvent event) {
        super.onStateChanged(event);
        timer.cancel();
        if (isEnabled()) {
            timer.scheduleRepeating(getState().interval);
        }
    }

    @Override
    public void onUnregister() {
        timer.cancel();
    }

    @Override
    protected void extend(ServerConnector target) {
        GridConnector gridcon = (GridConnector) target;
        final Grid<JsonObject> gr = gridcon.getWidget();
        gr.addDomHandler(new KeyDownHandler() {

            @Override
            public void onKeyDown(KeyDownEvent event) {
                if(event.getNativeKeyCode()==40){
                    for (int i = 0; i < gr.getDataSource().size(); i++) {
                        JsonObject el = gr.getDataSource().getRow(i);
                        if(gr.getSelectedRow().equals(el)){
                            gr.select(gr.getDataSource().getRow(i+1));
                            break;
                        }
                    }
                }else if(event.getNativeKeyCode()==38){
                    for (int i = 0; i < gr.getDataSource().size(); i++) {
                        JsonObject el = gr.getDataSource().getRow(i);
                        if(gr.getSelectedRow().equals(el)){
                            gr.select(gr.getDataSource().getRow(i-1));
                            break;
                        }
                    }
                }
            }
        }, KeyDownEvent.getType());
    }

    @Override
    public ArrowState getState() {
        return (ArrowState) super.getState();
    }
}

You can solve it using Javascript and check if the HTML-Classes in your Table change.
If yes, you can perform a click event on the Highlighted cell that selects the whole row and also triggers a selectionChangeEvent.

It’s not a good solution and not a clean one, but it works.

Script.js (Using JQuery)

function addRowChangeListener(id) {
   
    window.setInterval( function() {   

      if(!$('#'+id+' tr.v-grid-row-selected.v-grid-row-focused').length)
      {
        $("#"+id+"  .v-grid-cell-focused").click();
      }
        
    },10);
};

var id = "YourTablesId";
addRowChangeListener(id);

Thanks, Rene. I didn’t think about that. Another developer on the team ended up solving this by fixing my code. I can try posting the resulting code if someone is interested, but it involved quite a bit so I’m not sure I will get it all.

If it’s not too much trouble for you and if you have a clean solution i’d be happy to see it.
I’m not that happy with my javascript solution and i’m sure we’re not the only ones dealing with this problem.

No problem. I will post it shortly. Need to meet up with the guy who actually got it working so that I don’t post half-assed code. I will try to get it online in the next few hours.


STEP 1:


Add the following to AppWidgetSet.gwt.xml:


STEP 2:


Add An Extention class for the Grid:

public class GridExtension extends AbstractExtension {
protected GridExtension( Grid grid ) { extend( grid ); }
public static GridExtension apply(Grid field) { return new GridExtension(field); }
}


STEP 3:


Add the following to the code submitted by @Marius Reinwald above:

Create a class that extends AbstractExtensionConnector, note the @Connect annotation pointing to the Grid extension defined in Step 2.

@Connect(GridExtension.class)
public class GridFocusChangeConnector extends AbstractExtensionConnector


STEP 4:


Finally, apply the GridExtension to the Grid:

GridExtension.apply(Grid);

Please let me know if any part of the message is unclear. Thanks.

Nice Step-by-Step explanation Blanford Robinson. Just to add remember to put the Connector classes in the client package below the Widgetset. (Also i have never inherited Resources in the Widgetset as far as i know)
René’s Javascript solution actually gave me an idea to make my code better performance-wise by making a JSNI jquery method call on Key Press.
To make things easier. I’m going to attach the source for the java Files of the Project. There is also a basic TextfieldRenderer and a UI to test it in there if someone needs it.
Both the Extension and the Renderer could maybe use some work though if used in a productive environment but it should have the basic functionality in them.
21151.zip (38.2 KB)

@Marius Reinwald

Have you noticed strange behavior when you use the arrow keys to navigate to the
header column
(top of the Grid) or to the end of the Grid?

Totaly forgot that you can also select the header like this. Yes it will in that case try to Sort the Column which is not ideal.
Changing the JS part in th Connector to

$wnd.$(".v-grid-body .v-grid-cell-focused").click(); seems to do the trick. The only problem is that when you press up to select the header the first row is still selected so when you then go back down using “arrow down” it deselects the first row. If your Grid can’t be unselected this might not be a problem. If not you could maybe find a way to exclude rows from the jQuery selector that have a parent with .v-grid-row-selected in their classname.