1. #1
    Sencha User
    Join Date
    Aug 2012
    Posts
    42
    Vote Rating
    0
    ramgovind is on a distinguished road

      0  

    Default LiveGridView performance issue

    LiveGridView performance issue


    I observed that at Server log the DB queries are executing for long time to fetch the records, after fetching the records also some queries are still executing at server end(this is new observation after adding this live grid). Is there any steps which i missing?

    Step1:
    Add this below line into ui.xml
    <ui:with type="com.sencha.gxt.widget.core.client.grid.LiveGridView" field="fLiveGridView">
    <ui:attributes stripeRows="true" forceFit="true" emptyText="No Results" cacheSize="5"></ui:attributes>
    </ui:with>

    Step2:
    <grid:Grid ui:field="grid" loadMask="true" view="{fLiveGridView}"

    Step3: Add these lines into Java class

    @UiField
    LiveGridView<ItemProxy> fLiveGridView;

    fLiveGridView = new LiveGridView<ItemProxy>();
    fLiveGridView.setForceFit(true);



    grid.setView(fLiveGridView);


    grid = new Grid<ItemProxy>(store, cm) {


    @Override
    protected void onAfterFirstAttach() {
    super.onAfterFirstAttach();
    Scheduler.get().scheduleDeferred(new ScheduledCommand() {


    @Override
    public void execute() {
    loader.load(0, fLiveGridView.getCacheSize());
    }
    });
    }


    };

    Please help us on the issue.

  2. #2
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    DB queries are executing for long time
    Are you saying the server is taking too long to run the queries? There isn't anything that GXT (i.e. client code) can do about that - it is up to the server to respond in a timely manner. No GXT code runs on the server, which means GXT is compatible with any server implementation.

    One possibility is that you are facing an old bug (which version are you using?) - try turning off forceFit=true. The bug was that if you have forcefit, the *client* gets into a loop of resizing itself, but I don't see how that could affect the DB queries.

  3. #3
    Sencha User
    Join Date
    Aug 2012
    Posts
    42
    Vote Rating
    0
    ramgovind is on a distinguished road

      0  

    Default


    Thanks Colin.

    We use version GXT 3.x and in the database has only 280 records but has relationship with 5 tables. With that said, I see the log is repeating again and again the query, is not able to understand why it repeats. I will try with forceFit to false.

  4. #4
    Sencha User
    Join Date
    Oct 2012
    Posts
    17
    Vote Rating
    0
    wilfre is on a distinguished road

      0  

    Default


    Colin, I could agree to ramgovind that LiveGridView is too slow implementation. I investigated to what's going on with DOM when one is using LiveGridView... Well... It can't work fast. LiveGridView is almost always manipulating with <table>'s <tr>s. It generates some, injects into table others, removes either others. This is not a fast capable implementation. I've looked at samples for ExtJS and found that there thereis a fast implementation for LiveGridView. So, keeping that in mind I've tried to patch existing GridView to try-out some. Here's my patch against GXT 3.0.1 GPL:

    Code:
    --- GridView.their.java    2012-12-07 14:38:26.000000000 +0400
    +++ GridView.my.java    2012-12-10 15:39:10.000000000 +0400
    @@ -71,6 +71,9 @@
     import com.sencha.gxt.data.shared.event.StoreRemoveEvent;
     import com.sencha.gxt.data.shared.event.StoreSortEvent;
     import com.sencha.gxt.data.shared.event.StoreUpdateEvent;
    +import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
    +import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfigBean;
    +import com.sencha.gxt.data.shared.loader.PagingLoader;
     import com.sencha.gxt.messages.client.DefaultMessages;
     import com.sencha.gxt.widget.core.client.Component;
     import com.sencha.gxt.widget.core.client.ComponentHelper;
    @@ -85,6 +88,7 @@
     import com.sencha.gxt.widget.core.client.menu.Item;
     import com.sencha.gxt.widget.core.client.menu.Menu;
     import com.sencha.gxt.widget.core.client.menu.MenuItem;
    +import com.company.controlcenter.shared.dynamics.FilterPagingLoader;
     
     /**
      * This class encapsulates the user interface of an {@link Grid}. Methods of
    @@ -308,6 +312,45 @@
             }
     
         };
    +
    +    private DelayedTask refreshContentsTask = new DelayedTask() {
    +        @SuppressWarnings({ "unchecked", "rawtypes" })
    +        @Override
    +        public void onExecute() {
    +            if (grid.getLoader() instanceof PagingLoader<?, ?> &&
    +                ((PagingLoader) grid.getLoader()).getTotalCount() == grid.getStore().size()) {
    +                    return;
    +            }
    +            
    +            FilterPagingLoader loader = (FilterPagingLoader) grid.getLoader();
    +            int limit = loader.getLimit();
    +            int position = 0;
    +            if (scroller.getHeight(false) > 0) {
    +                position = loader.getLimit()
    +                        * (scroller.getScrollTop() * 100 / scroller
    +                                .getHeight(false)) / 100;
    +            }
    +            int currentPage = grid.getStore().size() / limit; // 0 for first, 1
    +                                                                // for second
    +                                                                // etc...
    +            /*
    +             * if (loaderLoadHandler == null ||
    +             * loaderLoadHandler.isOffsetLoaded(limit * currentPage)) { return;
    +             * }
    +             */
    +            if (position >= (limit * currentPage - limit * 0.2)) {
    +                FilterPagingLoadConfig config = new FilterPagingLoadConfigBean();
    +                config.setOffset(limit * currentPage);
    +                config.setLimit(limit);
    +                config.setFilters(((FilterPagingLoadConfig) loader
    +                        .getLastLoadConfig()).getFilters());
    +                config.setSortInfo(((FilterPagingLoadConfig) loader
    +                        .getLastLoadConfig()).getSortInfo());
    +                loader.load(config);
    +            }
    +        }
    +    };
    +
         private boolean adjustForHScroll = true;
         private GridAppearance appearance;
         private ColumnConfig<M, ?> autoExpandColumn;
    @@ -1909,6 +1952,7 @@
                     if (scroller.isOrHasChild(Element.as(event.getEventTarget()))) {
                         syncScroll();
                     }
    +                onScroll(event);
                     break;
                 }
             } else if (overRow != null) {
    @@ -1921,6 +1965,7 @@
                 if (scroller.isOrHasChild(Element.as(event.getEventTarget()))) {
                     syncScroll();
                 }
    +            onScroll(event);
             }
         }
     
    @@ -2308,6 +2353,11 @@
         protected void onAdd(List<M> models, int index) {
             if (grid != null && grid.isViewReady()) {
                 insertRows(index, index + (models.size() - 1), false);
    +            updateLiveScrollBar();
    +            if (isScrolledToBottomVisibleEdge()) {
    +                refreshContentsTask.delay(0);
    +            }
    +            //refreshContentsTask.delay(500);
                 addTask.delay(10);
             }
         }
    @@ -2423,6 +2473,11 @@
             if (!grid.viewReady)
                 return;
             refresh(false);
    +        updateLiveScrollBar();
    +        if (isScrolledToBottomVisibleEdge()) {
    +            refreshContentsTask.delay(0);
    +        }
    +        //refreshContentsTask.delay(500);
             if (grid != null && grid.isLoadMask()) {
                 if (grid.isEnabled()) {
                     grid.unmask();
    @@ -2795,7 +2850,13 @@
             }
     
             if (!grid.isAutoHeight()) {
    +            if (this.grid.getLoader() instanceof PagingLoader<?, ?>) {
    +                updateLiveScrollBar();
    +
    +            } // else {
                 scroller.setSize(vw, vh);
    +            // scroller.setId("grid-scroller");
    +            // }
             }
     
             if (headerElem != null) {
    @@ -3113,4 +3174,74 @@
                     || cm.isFixed(columnIndex);
         }
     
    +    protected void updateLiveScrollBar() {
    +        if (this.grid.getLoader() instanceof PagingLoader<?, ?>) {
    +            @SuppressWarnings("rawtypes")
    +            int totalRowCount = ((PagingLoader) this.grid.getLoader())
    +                    .getTotalCount();
    +            StringBuilder sb = new StringBuilder();
    +            XElement holder = XElement.createElement("div");
    +            holder.setId("liveScroller-fixture");
    +            if (totalRowCount != 0 && hasRows()) {
    +                int rowHeight = getRow(0).getClientHeight();
    +                int totalHeight = rowHeight * totalRowCount
    +                        - dataTableBody.getHeight(false);
    +                int count = totalHeight / 1000000;
    +                if (count > 0) {
    +                    int itemHeight = totalHeight / count;
    +                    for (int i = 0; i < count; i++) {
    +                        sb.append("<div style=\"width: ");
    +                        sb.append(scroller.getSize().getWidth());
    +                        sb.append("px; height: ");
    +                        sb.append(itemHeight);
    +                        sb.append("px;\">&nbsp;</div>");
    +
    +                    }
    +                }
    +                if (totalHeight % 1000000 > 0) {
    +                    sb.append("<div style=\"width: ");
    +                    sb.append(scroller.getSize().getWidth());
    +                    sb.append("px; height: ");
    +                    sb.append(totalHeight % 1000000);
    +                    sb.append("px;\">&nbsp;</div>");
    +                }
    +            }
    +            for (int i = 0; i < scroller.getChildNodes().getLength(); i++) {
    +                if (scroller.getChild(i).<XElement> cast().getId()
    +                        .equals("liveScroller-fixture")) {
    +                    scroller.removeChild(scroller.getChild(i));
    +                    break;
    +                }
    +            }
    +            holder.setInnerHTML(sb.toString());
    +            scroller.appendChild(holder);
    +        }
    +    }
    +
    +    protected void onScroll(Event event) {
    +        refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
    +    }
    +
    +    protected void onMouseWheel(Event event) {
    +        if (event.getMouseWheelVelocityY() > 0) {
    +            refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
    +        }
    +    }
    +
    +    protected void onKeyDown(Event event) {
    +        refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
    +    }
    +    
    +    protected boolean isScrolledToBottomVisibleEdge() {
    +        if (this.grid.getStore().size() == 0) {
    +            return true;
    +        }
    +        int rowHeight = getRow(0).getClientHeight();
    +         //trigger immediate load new data at 10 rows before most bottom visible row
    +        int triggerPosition = rowHeight * this.grid.getStore().size() - rowHeight * 10;
    +        if (scroller.getScrollTop() >= triggerPosition) {
    +            return true;
    +        }
    +        return false;
    +    }
     }
    This obviously still depends on Grid.setLoader() is called before anything could work properly. This implementation is not affected by keyboard navigation limitations of original LiveGridView. It simply appends (and only appends) data as it is needed during user's scroll-like page manipulations.

    It still does not keep any kind of beeing clean of bugs (it's rough implementation) and does not have capability to keep scroll position when sorting and filtering is applied (especially in case of remote sorting and filtering). I'd love to see this kind of LiveGridView in GXT.

  5. #5
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    I'm not sure I understand what you are trying to do, wilfre - you are adding code to the GridView, a class designed to function either with entirely local or remote data (and even both, given a clever enough ListStore or a subclass designed to handle it like LiveGridView), but intending these changes to help LiveGridView? How should developers who need no live anything clean out the extra event handlers and delayed tasks you've built into this superclass?

    If I am reading this all correctly, this might be better suited as a possible replacement to LiveGridView? This implementation also requires that the user provide a ListLoader, but doesn't do any of the convinience wiring that the LiveGridView manages automatically - wiring that should always be the same (pass loader to grid, wire store to loader, wire store to grid), which will result in unnecessary boilerplate when any grid is created.

    With regard to actually rendering items as they are scrolled, it appears your approach and that of the current LiveGridView take two opposite ideas. Yours seems to (please correct me if I am misreading) keep all rendered at all times, which could be fatal for millions of rows, while the LiveGridView has two buffers keeping items out of the DOM - the local ListStore cache, around 3x as many items than can be drawn, and the server with the full compliment of items. The current LiveGridView only renders *exactly* what can be seen at a time, so no more than that active page is visible and in the dom. While yes, this means more dom changes happen as the user manipulates the page, the other side of that coin is that the dom is many many times smaller, and each change made is much smaller.

    It is from this particular performance detail that we lose keyboard navigation - some deliberate KeyNav must be used instead, since there isn't actually a real scroller in place. It is also from this that most *servers* introduce a real performance hit, by being even slower than the client in fetching items. The goal of the LiveGridView is not to build the fastest database-to-browser-pixels grid implementation, but to favor fast servers with extremely fast rendering. Assuming my interpretation is correct so far (doubtful at this point, but I'm looking forward to more discussion), yours will favor a more middling approach - overall better performance by forcing the browser to act as a cache for the server and database. This may help in some cases, and may result in slower apps in others.

    As I hope I've made clear, we're interested in continued discussion on this point, and would especially be interested in some comparison between this implementation you've provided and the existing LiveGridView, particularly browser performance metrics, not just time spent waiting for the server to complete requests.

  6. #6
    Sencha User
    Join Date
    Oct 2012
    Posts
    17
    Vote Rating
    0
    wilfre is on a distinguished road

      0  

    Default


    Quote Originally Posted by Colin Alworth View Post
    I'm not sure I understand what you are trying to do, wilfre - you are adding code to the GridView, a class designed to function either with entirely local or remote data (and even both, given a clever enough ListStore or a subclass designed to handle it like LiveGridView), but intending these changes to help LiveGridView? How should developers who need no live anything clean out the extra event handlers and delayed tasks you've built into this superclass?
    As I mentioned, my patch was a rough implementation just to see what's possible to do with GridView. This is a standalone class (but I'm sure you could produce it yourself if needed):
    Code:
    package com.sencha.gxt.widget.core.client.grid;
    
    import java.util.List;
    
    import com.google.gwt.dom.client.Element;
    import com.google.gwt.user.client.Event;
    import com.sencha.gxt.core.client.dom.XElement;
    import com.sencha.gxt.core.client.util.DelayedTask;
    import com.sencha.gxt.data.shared.event.StoreDataChangeEvent;
    import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
    import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfigBean;
    import com.sencha.gxt.data.shared.loader.PagingLoader;
    import com.company.controlcenter.shared.dynamics.FilterPagingLoader;
    
    public class PagedLiveGridView<M> extends GridView<M> {
    
        private DelayedTask refreshContentsTask = new DelayedTask() {
            @SuppressWarnings({ "unchecked", "rawtypes" })
            @Override
            public void onExecute() {
                if (grid.getLoader() instanceof PagingLoader<?, ?> &&
                    ((PagingLoader) grid.getLoader()).getTotalCount() == grid.getStore().size()) {
                        return;
                }
                
                FilterPagingLoader loader = (FilterPagingLoader) grid.getLoader();
                int limit = loader.getLimit();
                int position = 0;
                if (scroller.getHeight(false) > 0) {
                    position = loader.getLimit()
                            * (scroller.getScrollTop() * 100 / scroller
                                    .getHeight(false)) / 100;
                }
                /*
                 * 0 for first, 1 for second, etc...
                 */
                int currentPage = grid.getStore().size() / limit;
    
                if (position >= (limit * currentPage - limit * 0.2)) {
                    FilterPagingLoadConfig config = new FilterPagingLoadConfigBean();
                    config.setOffset(limit * currentPage);
                    config.setLimit(limit);
                    config.setFilters(((FilterPagingLoadConfig) loader
                            .getLastLoadConfig()).getFilters());
                    config.setSortInfo(((FilterPagingLoadConfig) loader
                            .getLastLoadConfig()).getSortInfo());
                    loader.load(config);
                }
            }
        };
        
        @Override
        protected void handleComponentEvent(Event event) {
            if (event.getTypeInt() == Event.ONSCROLL) {
                if (scroller.isOrHasChild(Element.as(event.getEventTarget()))) {
                    syncScroll();
                }
                onScroll(event);
            } else {
                super.handleComponentEvent(event);
            }
        }
        
        @Override
        protected void onAdd(List<M> items, int index) {
            if (grid != null && grid.isViewReady()) {
                super.onAdd(items, index);
                updateLiveScrollBar();
                if (isScrolledToBottomVisibleEdge()) {
                    refreshContentsTask.delay(0);
                }
            }
        }
        
        @Override
        protected void onDataChanged(StoreDataChangeEvent<M> se) {
            if (!grid.viewReady)
                return;
            super.onDataChanged(se);
            updateLiveScrollBar();
            if (isScrolledToBottomVisibleEdge()) {
                refreshContentsTask.delay(0);
            }
        }
        
        @Override
        protected void resize() {
            super.resize();
            if (!grid.isAutoHeight() && this.grid.getLoader() instanceof PagingLoader<?, ?>) {
                updateLiveScrollBar();
            }
        }
        
        /*
         * this implementation is based on GXT's LiveGridView (about 90% copy-pasted)
         */
        protected void updateLiveScrollBar() {
            if (this.grid.getLoader() instanceof PagingLoader<?, ?>) {
                @SuppressWarnings("rawtypes")
                int totalRowCount = ((PagingLoader) this.grid.getLoader())
                        .getTotalCount();
                StringBuilder sb = new StringBuilder();
                XElement holder = XElement.createElement("div");
                holder.setId("liveScroller-fixture");
                if (totalRowCount != 0 && hasRows()) {
                    int rowHeight = getRow(0).getClientHeight();
                    int totalHeight = rowHeight * totalRowCount
                            - dataTableBody.getHeight(false);
                    int count = totalHeight / 1000000;
                    if (count > 0) {
                        int itemHeight = totalHeight / count;
                        for (int i = 0; i < count; i++) {
                            sb.append("<div style=\"width: ");
                            sb.append(scroller.getSize().getWidth());
                            sb.append("px; height: ");
                            sb.append(itemHeight);
                            sb.append("px;\">&nbsp;</div>");
    
                        }
                    }
                    if (totalHeight % 1000000 > 0) {
                        sb.append("<div style=\"width: ");
                        sb.append(scroller.getSize().getWidth());
                        sb.append("px; height: ");
                        sb.append(totalHeight % 1000000);
                        sb.append("px;\">&nbsp;</div>");
                    }
                }
                for (int i = 0; i < scroller.getChildNodes().getLength(); i++) {
                    if (scroller.getChild(i).<XElement> cast().getId()
                            .equals("liveScroller-fixture")) {
                        scroller.removeChild(scroller.getChild(i));
                        break;
                    }
                }
                holder.setInnerHTML(sb.toString());
                scroller.appendChild(holder);
            }
        }
    
        protected void onScroll(Event event) {
            refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
        }
    
        protected void onMouseWheel(Event event) {
            if (event.getMouseWheelVelocityY() > 0) {
                refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
            }
        }
    
        protected void onKeyDown(Event event) {
            refreshContentsTask.delay(isScrolledToBottomVisibleEdge() ? 0 : 500);
        }
        
        protected boolean isScrolledToBottomVisibleEdge() {
            if (this.grid.getStore().size() == 0) {
                return true;
            }
            int rowHeight = getRow(0).getClientHeight();
             //trigger immediate load of new data at 10 rows before most bottom visible row
            int triggerPosition = rowHeight * this.grid.getStore().size() - rowHeight * 10;
            if (scroller.getScrollTop() >= triggerPosition) {
                return true;
            }
            return false;
        }
    }
    FilterPagingLocader mentioned in imports:
    Code:
    package com.company.controlcenter.shared.dynamics;
    
    import com.sencha.gxt.data.shared.loader.DataProxy;
    import com.sencha.gxt.data.shared.loader.DataReader;
    import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfig;
    import com.sencha.gxt.data.shared.loader.FilterPagingLoadConfigBean;
    import com.sencha.gxt.data.shared.loader.PagingLoadResult;
    import com.sencha.gxt.data.shared.loader.PagingLoader;
    
    public class FilterPagingLoader<C extends FilterPagingLoadConfig, D extends PagingLoadResult<?>>
            extends PagingLoader<C, D> {
    
        public FilterPagingLoader(DataProxy<C, D> proxy) {
            super(proxy);
        }
    
        public <T> FilterPagingLoader(DataProxy<C, T> proxy, DataReader<D, T> reader) {
            super(proxy, reader);
        }
    
        @SuppressWarnings("unchecked")
        @Override
        protected C newLoadConfig() {
            return (C) new FilterPagingLoadConfigBean();
        }
    
    }

    If I am reading this all correctly, this might be better suited as a possible replacement to LiveGridView? This implementation also requires that the user provide a ListLoader, but doesn't do any of the convinience wiring that the LiveGridView manages automatically - wiring that should always be the same (pass loader to grid, wire store to loader, wire store to grid), which will result in unnecessary boilerplate when any grid is created.
    You didn't pay needed attention to my implementation, because it uses Grid.getLoader(), not private/protected own property. So, usage is:
    Code:
    grid.setLoader(loader);
    grid.setView(new PagedLiveGridView());
    With regard to actually rendering items as they are scrolled, it appears your approach and that of the current LiveGridView take two opposite ideas. Yours seems to (please correct me if I am misreading) keep all rendered at all times, which could be fatal for millions of rows, while the LiveGridView has two buffers keeping items out of the DOM - the local ListStore cache, around 3x as many items than can be drawn, and the server with the full compliment of items. The current LiveGridView only renders *exactly* what can be seen at a time, so no more than that active page is visible and in the dom. While yes, this means more dom changes happen as the user manipulates the page, the other side of that coin is that the dom is many many times smaller, and each change made is much smaller.


    It is from this particular performance detail that we lose keyboard navigation - some deliberate KeyNav must be used instead, since there isn't actually a real scroller in place. It is also from this that most *servers* introduce a real performance hit, by being even slower than the client in fetching items. The goal of the LiveGridView is not to build the fastest database-to-browser-pixels grid implementation, but to favor fast servers with extremely fast rendering. Assuming my interpretation is correct so far (doubtful at this point, but I'm looking forward to more discussion), yours will favor a more middling approach - overall better performance by forcing the browser to act as a cache for the server and database. This may help in some cases, and may result in slower apps in others.
    Well. First, yes. My implementation is to keep all data in client. With one exception: load data chunked and only as needed. This causes a 1M records data table to explode browser's memory consumption metrics, I know. This is an issue to work on. I think it could be implemented configurable way (for example PagedLiveGridView.setOptimizedForHugeTable(boolean)) and based on `true` given as argument unload data from store that is, for example 10 pages away from current scroller's position back and forward, placing a placeholder of a needed height before and after view's tableBody (as it is with given PagedLiveGridView.updateLiveScrollBar()). The other thing to mention is LiveGridView's issues:
    1. Have you ever tested it with complexed and a bit large ColumnConfigs? My List<ColumnConfig<?, ?>> consists 20 entries with 3 of them relying on model's relationships: User.Department, User.Department.Company and User.List<UserEmail>. Given this config LiveGridView is completely buggy to render any scroll attempt: some (almost always the uppermost) rows are not replaced, some get and I see a mesh of data with no way to get any and every row of my table.
    2. Lost KeyNav for LiveGridView. It is a too reasonable bug for us to not use current LiveGridView implementation. Our users want to navigate by any of input device they have just as if it's a huge plain text page.
    3. Preformance of LiveGridView. I've seen no browser not occupying much CPU time when huge DOM manipulations are performed. Especially notice that GridView is based on <table> and hence all it's successors classes (not excluding LiveGridView). <table> is a headache to any modern browser I've seen. If GridView could be implemented with <div>/<span> combination - it'd be a great CPU optimization, but it's too complex to support this implementation with every target browser family and version.

    As I've already mentioned just try current LiveGridView with a bit complex List<ColumnConfig> with at least 20 columns. I couldn't manage it to get work. And as I've already mentioned too: the implementation I've provided is based on the ideas of ExtJS's LiveGrid sample and GoogleDocs spreadsheet's positive user experience.

    I do not force you take my implementation and import it into GXT's code tree right now. But if I could helped to someone with their experience of LiveGridView's issues - that's enough.

  7. #7
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Thanks - this additional context is exactly what I need to both hold up my end of the conversation and make sure that we're fitting the needs of our library users.

    1. I'll try this out - have you reported any bugs on this? This seems as though it should be pretty easy to reproduce, though I don't recall any bugs focusing on this in the last few months.
    2. This is a feature we would like to implement. It does get slightly interesting trying to be consistent cross browser, though we can almost certainly go for 'makes sense' rather than 'feels like a normal scrollable element'.
    3. If all sizes can be known ahead of time (i.e. at compile time) you are probably right, but if not, you end up at least absolutely positioning or statically sizing every single div, rather than informing the browser once, dynamically, of the size of the column, and letting it build them the same on each row. This is a performance bump we get over 2.x which did both - one table per row, size each cell in each row. Cross browser does get a little ugly too.

    A possible detail you may or may not have considered so far - instead of loading many items into the store, keep it in a cache at the DataProxy, probably a custom implementation. The downside of that is that the client now needs to manage all of these objects - many older browsers can misbehave a bit with that many items in memory, as opposed to asking the server (or browser cache...) for the data.

    I'll test out the LiveGridView with a few dozen columns, and see what I can see in terms of performance numbers. I'd also encourage you to consider a slightly different approach for a spreadsheet as opposed to a grid - the Grid is really built to have a fixed number of columns, with a distinct field/cell per column. In contrast, a spreadsheet often has a single generalized cells drawn over an arbitrary number of rows/columns - and the caching problem as far as drawing is more of a window in a 2d plane of data. Your idea of divs/spans really would shine here, since you know what will be drawn in each column or row closer to the origin from the current viewport. I'd still probably make it a Cell Widget to provide for future features, but skip out on Loader/Reader/Proxy if 100% of the data is available locally.

  8. #8
    Sencha User
    Join Date
    Oct 2012
    Posts
    17
    Vote Rating
    0
    wilfre is on a distinguished road

      0  

    Default


    Quote Originally Posted by Colin Alworth View Post

    1. I'll try this out - have you reported any bugs on this? This seems as though it should be pretty easy to reproduce, though I don't recall any bugs focusing on this in the last few months.
    No, I haven't yet. But to get clear: how do I file a bug report if our company does not have a support subscription? I posted my updates to russian translation file a couple of weeks ago to Bugs forum and got still no reply. There was a mess with message formatting but to get no reply even as 're-format your message or try to explain your report with no format' is a reason to start thinking nobody cares.

    3. If all sizes can be known ahead of time (i.e. at compile time) you are probably right, but if not, you end up at least absolutely positioning or statically sizing every single div, rather than informing the browser once, dynamically, of the size of the column, and letting it build them the same on each row. This is a performance bump we get over 2.x which did both - one table per row, size each cell in each row. Cross browser does get a little ugly too.
    Current GridView base implementation relies on <table> and supports column widths assigning (so thanks for that - it makes us able to save user's own settings of hidden columns and every column's width a particular user has ever set. For a <div> based implementation it could be done with stylesheet dynamically injected into DOM but as I understand this is neither GWT-way nor GXT-way and neither does it resolve cross-browser issues.

    A possible detail you may or may not have considered so far - instead of loading many items into the store, keep it in a cache at the DataProxy, probably a custom implementation. The downside of that is that the client now needs to manage all of these objects - many older browsers can misbehave a bit with that many items in memory, as opposed to asking the server (or browser cache...) for the data.
    As I understand DataProxies are mostly client-side objects, so this makes it a nonsense to split data between ListStore and DataProxy (especially for a case of a huge data table bound to a Grid). For a fast and almost no-time data fetching one could extend his application environment with any caching engine (I've used ehcache and thus my data is delivered in low-cost timespan comparing to rendering). As for me, keeping a huge amount of data on client side to manage data fetching payloads is a way to getting guaranteed issues reports and bad design in applications development. But we got performance issues at early first 1 thousand records.

  9. #9
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    ListStore isn't a intended as a persistence mechanism - it isn't a 'client-side database', and should never be used as one. Several key points hopefully should make this clear:

    * Filter operations affect methods like .get(int), .getSize(), etc - the store acts as though these items no longer exist.
    * Sort operations cannot be 'removed' - there is no original sort state, only how the items are current sorted.

    Both stores are intended instead as a way to back the view they are currently wired to. Only non-filtered items in the store are visible in the grid, and any change made to any item in the store (via store methods like Record.addChange or Store.update) are reflected in the Grid. In the same way, the Grid does not have another structure that holds the items - it relies on the ListStore to fulfill this functionality.

    As a result of this, the store that is directly bound to the view (in this case a grid) should only hold things that should presently be visible. There is no concept of pages *within* the store - the store is just the current page. All code in GXT that needs to get additional items (ComboBox with remote filter and paging, Grid column sorting, GridFilters filtering, PagingToolbar paging/refresh, etc) interacts with a Loader of some kind. The Loader API describes how to listen for available data and to initiate requests. The DataProxy then is just the abstracted out mechanism of actually getting that data - it can even wrap up a much much bigger ListStore and return the specific slice of items that was requested, reading from the offset/size properties and any filtering, etc.

    As far as filing bugs, I apologize if we missed your issue. We have the template to streamline the process of getting specific details. As the bug template post notes, if you are not in 'plain text' mode, the template doesn't always behave properly.

    For that particular issue (I'll reply there to have it be revisited), we'll need a CLA signed by you and your organization to give us permission to use your work in the GXT release.

    Using a support ticket is a way to expedite our processing of your issue, as well as a way of requesting help with specific problems in your own application. Filing issues is no more formal there, but the support agreement is a way of indicating that it is high priority for them so we too should make it high priority. We still need good bug reports to make them happen.

Thread Participants: 2