Defer loading data while scrolling Grid

Hi,

I have a large grid, with ~1m rows that are lazily loaded as needed in pages of 50 rows at a time. My datastructure for building the rows is a little tricky, so each page takes ~200ms to render server side. This is great when navigating through the grid, and scrolling at a reasonable pace.

However if I grab the scrollbar and scroll from the top of my data set to the bottom, Grid queues up dozens of requests for new data as I’m moving the scrollbar, which introduces a delay in loading data when I eventually stop scrolling.

I’d like to develop an Extension that will prevent the client requesting new data while the user is dragging the scroll bar. Ideally I only want to defer fetching data while the user is dragging the scrollbar, but NOT chnage the behaviour when the user is scrolling with the mouse wheel, or through drag / touch / velocity events on the actual grid itself - just the scrollbar.

Has anyone tried this before? Any ideas of a good place to hook into Grid’s vertical scrollbar? I was looking at the scrollHandler setup in Escalator.setupScrollbars(), and wondering if this would be a good place to schedule the scroll event for e.g. 500ms in the future, and canceling any existing scheduled scroll event - so we fire one event if the scrollbar hasn’t moved for 500ms.

Many thanks,
Dave

Replaying to my own post…

I’ve been havng a go at this. I found ScrollEventFirer, and created a modified version like thus:

[code]
private class ScrollEventFirer {

    private Timer scrollQuietPeriod = new Timer() {
        @Override
        public void run() {
            if (isBeingFired) {
                Scheduler.get().scheduleDeferred(fireEventCommand);
            }
        }
    };

    private final ScheduledCommand fireEventCommand = new ScheduledCommand() {
        @Override
        public void execute() {
            updateScrollPosFromDom();
            getHandlerManager().fireEvent(new ScrollEvent());
            isBeingFired = false;
            scrollQuietPeriod.schedule(250);
        }
    };

    private boolean isBeingFired;

    public void scheduleEvent() {
        // We'll gather all the scroll events, and only fire once, once everything has calmed down.
        if (scrollQuietPeriod.isRunning()) {

            isBeingFired = true;
            // Extend the quiet period. This cancels and reschedules.
            scrollQuietPeriod.schedule(250);
            
        } else if (!isBeingFired) {
            isBeingFired = true;
            Scheduler.get().scheduleDeferred(fireEventCommand);
        }
    }
}

[/code]and I’m trying to use this by using a declaration in my widgetset.gwt.xml

However this doesn’t seem to be having the effect I expect - Can anyone suggest if I’, trying to attack the wrong bit of code, or is it more likely that this a promissing approach, but my build isn’t doing what I expect?

Many thanks (grid is awesome!)

Dave

Hi,

I don’t really know the internals of Grid, but it definitely shouldn’t choke the backend no matter how heavily user scrolls the table. To my exprience the old Table component was more conservative on it’s way how it asks rows from the backend and I have still been using that for larger data sets.

Thanks for bringing the issue on the table. I hope some of the Grid develoers will look into the issue with you soon!

cheers,
matti

Hi Matti,

I got essentially the code above working - once I had my super-source setup correctly. This seems to help us a little, as it avoids the server being hit for data that’s immediately scrolled out of view. Unfortunately, my dropping of scroll events is a little harsh, and results in constant slow-velocity dragging being a little choppy. I might look at a heuristic to only drop scroll events if the user has dragged a significant amount.

I’ve also been persuing a different aspect - changing the grid CacheStrategy. The default cache strategy tries to fill the cache with 9x the number of visible rows (visible rows + 4x above, and 4x below). This means that if the user drags the scrollbar a significant amount, the cache needs to be fully repopulated, and the client requests 9x visible rows. So if our grid is displaying 50 rows, we have to fetch 450 from the database to display those 50.

So I’m currently playing with a grid extension, where the client connector essentially looks like this:

[code]
protected void extend(ServerConnector target) {
final Grid grid = ((GridConnector)target).getWidget();

    // Probably a better way to change cache strategy, but the datasource isn't available yet.
    registration = grid.addDataAvailableHandler(new DataAvailableHandler() {

        @Override
        public void onDataAvailable(DataAvailableEvent event) {
            setCacheStrategy(grid);
            registration.removeHandler();
        }
    });
}

private void setCacheStrategy(final Grid<JsonObject> grid) {
    RpcDataSource ds = (RpcDataSource) grid.getDataSource();
    ds.setCacheStrategy(new CacheStrategy.AbstractBasicSymmetricalCacheStrategy() {

        @Override
        public int getMinimumCacheSize(int pageSize) {
            return 5;
        }

        @Override
        public int getMaximumCacheSize(int pageSize) {
            // These values actually come from the extensions state,
            // so can be set server side
            return 5 + (int) (0.2 * pageSize);
        }
    });
}

[/code]This caches a much smaller buffer of rows on the client, making grid much less gready for data from the server.

I’d like to investigate a sliglly different behaviour here

  • if none of the visibleRows are currently in the clien-side cache, request a tiny page from the server (visibleRows + 5 +5)
  • once the visible rows have been populated & rendered, then request additional rows to fill the cache.

Dave

Awesome work! I have actually looked if I could limit the amount of rows Grid caches, but didn’t easily find any way to do it. I also think that the default Grid caches is way too big for “lazy loading”. With Table there is an API for defining how many rows are loaded for cache.

cheers,
matti

I doubt I’ll be able to upload an extension, but the code I posted is the only interesting bit from my extension - if you drop that into the standard GridExtension boilerplate your wish of a much less agressive grid will be realised :slight_smile:

There might be a cleaner way of setting the cacheStrategy, but I’ve not had any problems with this approach so far…

The other changes I suggested look more complicated - the fetch behaviour is in the RpcDataSource which loooks a bit trickier to extend / replace.

Dave

Hi Dave,

I took in your code and dropped into the GridExtensionPack. Thank you for your contribution! If you have any further ideas on how we could provide useful tools for customising the Grid, please tell.

//Teemu

Hi Teemu,

Great! After posting this, I noticed a nasty bug -

public void onDataAvailable(DataAvailableEvent event) { setCacheStrategy(grid); registration.removeHandler(); } Changing the cache strategy causes this method to be hit again, and a nice infinite loop before we remove the DataAvailableHandler. I think it’s sufficient to reorder these two statements, but I added a simple guard:

    private boolean isCacheInstalled;

    protected void extend(ServerConnector target) {
        final Grid<JsonObject> grid = ((GridConnector)target).getWidget();
        // Probably a better way to change cache strategy, but the datasource isn't available yet.
        registration = grid.addDataAvailableHandler(new DataAvailableHandler() {
            @Override
            public void onDataAvailable(DataAvailableEvent event) {
                if (!isCacheInstalled) {
                    registration.removeHandler();
                    setCacheStrategy(grid);
                }
            }
        });
    }
    private void setCacheStrategy(final Grid<JsonObject> grid) {
        this.isCacheInstalled = true;
        RpcDataSource ds = (RpcDataSource) grid.getDataSource();
        ds.setCacheStrategy(new CacheStrategy.AbstractBasicSymmetricalCacheStrategy() {
            @Override
            public int getMinimumCacheSize(int pageSize) {
                return 5;
            }
            @Override
            public int getMaximumCacheSize(int pageSize) {
                return 5 + (int) (0.2 * pageSize);
            }
        });
    }

Dave