Thank you for reporting this bug. We will make it our priority to review this report.
  1. #1
    Sencha User
    Join Date
    Sep 2011
    Posts
    6
    Vote Rating
    0
    creakie is on a distinguished road

      0  

    Default Grid does not update column-value

    Grid does not update column-value


    Required Information

    Version(s) of Ext GWT
    Ext GWT 3.0.1

    Browser versions and OS
    (and desktop environment, if applicable)
    • Chrome 22, Windows 7
    • Firefox 16, Windows 7
    • Internet Explorer 8, Windows 7
    Virtual Machine
    Both

    Description
    I've got a very simple Grid (expander + 1 column) and I can edit those entrys. After editing in an extra window the new entry will be saved with the RequestFactory and the returned entry will be replaced with the old one.
    So I am using the ListStore#update method after receiving the new entry.
    But sometimes there occurs a bug and the column-value of the Grid will not be updated anymore. But the referenced bean has got everytime the new values.

    Web Application Starter Project - Google Chrome_2012-10-19_14-03-31.png

    In this screenshot you can see in the background the Grid and in the foreground the edit-window. In the edit-window is everytime the current content of the bean, but once the bug occurs in the Grid is everytime the old name. In the content that will be shown by the expander-content-cell everything works fine.

    Run mode
    Both

    Steps to reproduce the problem
    1. Update a Bean in a ListStore that is connected with a Grid through ListStore#update with a new instance of an already existing bean (so equals() should match)
    2. Do this many times, because the bug appears to occur randomly
    Expected result
    The Grid shows everytime the current data from the bean.

    Actual result
    When the bug occured, the grid shows everytime the old content of the bean and not the new. But the current Bean in the ListStore really have the correct data, so in the edit-window (shown in the screenshot above) is everytime the right content.


    Test case
    Code:
    package com.example.bugtest.client;
    
    import java.util.ArrayList;
    import java.util.List;
    
    
    import com.google.gwt.cell.client.AbstractCell;
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.core.client.GWT;
    import com.google.gwt.dom.client.Style.Unit;
    import com.google.gwt.editor.client.Editor.Path;
    import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
    import com.google.gwt.user.client.ui.IsWidget;
    import com.google.gwt.user.client.ui.RootPanel;
    import com.google.gwt.user.client.ui.Widget;
    import com.sencha.gxt.core.client.IdentityValueProvider;
    import com.sencha.gxt.core.client.ValueProvider;
    import com.sencha.gxt.data.shared.LabelProvider;
    import com.sencha.gxt.data.shared.ListStore;
    import com.sencha.gxt.data.shared.ModelKeyProvider;
    import com.sencha.gxt.data.shared.PropertyAccess;
    import com.sencha.gxt.data.shared.Store.Change;
    import com.sencha.gxt.widget.core.client.Window;
    import com.sencha.gxt.widget.core.client.button.TextButton;
    import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
    import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData;
    import com.sencha.gxt.widget.core.client.event.RowDoubleClickEvent;
    import com.sencha.gxt.widget.core.client.event.RowDoubleClickEvent.RowDoubleClickHandler;
    import com.sencha.gxt.widget.core.client.event.SelectEvent;
    import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
    import com.sencha.gxt.widget.core.client.form.FieldLabel;
    import com.sencha.gxt.widget.core.client.form.TextArea;
    import com.sencha.gxt.widget.core.client.form.TextField;
    import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
    import com.sencha.gxt.widget.core.client.grid.ColumnModel;
    import com.sencha.gxt.widget.core.client.grid.Grid;
    import com.sencha.gxt.widget.core.client.grid.RowExpander;
    
    
    public class GridRecordBug implements IsWidget, EntryPoint {
        static class StockBean {
            private static int ID = 0;
    
    
            private int id;
            private String name;
            private String content;
    
    
            public StockBean(int id, String name, String content) {
                this.id = id;
                this.name = name;
                this.content = content;
            }
    
    
            public StockBean(String name, String content) {
                this(++ID, name, content);
            }
    
    
            public String getName() {
                return name;
            }
    
    
            public void setName(String name) {
                this.name = name;
            }
    
    
            public String getContent() {
                return content;
            }
    
    
            public void setContent(String content) {
                this.content = content;
            }
    
    
            public Integer getId() {
                return id;
            }
    
    
            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + id;
                return result;
            }
    
    
            @Override
            public boolean equals(Object obj) {
                if (this == obj)
                    return true;
                if (obj == null)
                    return false;
                if (getClass() != obj.getClass())
                    return false;
                StockBean other = (StockBean) obj;
                if (id != other.id)
                    return false;
                return true;
            }
        }
    
    
        public interface StockBeanProperties extends PropertyAccess<StockBean> {
            @Path("content")
            LabelProvider<StockBean> label();
    
    
            ModelKeyProvider<StockBean> id();
    
    
            ValueProvider<StockBean, String> name();
    
    
            ValueProvider<StockBean, String> content();
        }
    
    
        private Grid<StockBean> grid;
    
    
        @Override
        public void onModuleLoad() {
            RootPanel.getBodyElement().getStyle().setMargin(0, Unit.PX);
            RootPanel.get().add(this);
        }
    
    
        @Override
        public Widget asWidget() {
            VerticalLayoutContainer container = new VerticalLayoutContainer();
            container.setBorders(true);
            container.setPixelSize(200, 300);
    
    
            final StockBeanProperties props = GWT.create(StockBeanProperties.class);
    
    
            // grid
            List<ColumnConfig<StockBean, ?>> columns = new ArrayList<ColumnConfig<StockBean, ?>>();
    
    
            IdentityValueProvider<StockBean> expanderIdentity = new IdentityValueProvider<StockBean>();
            RowExpander<StockBean> expander = new RowExpander<StockBean>(
                    expanderIdentity, new AbstractCell<StockBean>() {
                        @Override
                        public void render(Context context, StockBean value,
                                SafeHtmlBuilder sb) {
                            // maybe the content was changed
                            Change<StockBean, String> change = grid.getStore()
                                    .getRecord(value).getChange(props.content());
    
    
                            sb.appendHtmlConstant("<p style='margin: 5px 5px 10px'>"
                                    + (change == null ? value.getContent() : change
                                            .getValue()) + "</p>");
                        }
                    });
            columns.add(expander);
    
    
            ColumnConfig<StockBean, String> nameColumn = new ColumnConfig<StockBean, String>(
                    props.name(), 200, "Name");
            columns.add(nameColumn);
    
    
            ColumnModel<StockBean> columnModel = new ColumnModel<StockBean>(columns);
            ListStore<StockBean> store = new ListStore<StockBean>(props.id());
            grid = new Grid<StockBean>(store, columnModel);
            grid.getView().setForceFit(true);
            grid.getView().setAutoExpandColumn(nameColumn);
            grid.getView().setEmptyText("Empty grid");
            expander.initPlugin(grid);
    
    
            grid.addRowDoubleClickHandler(new RowDoubleClickHandler() {
                @Override
                public void onRowDoubleClick(RowDoubleClickEvent event) {
                    StockBean clicked = (StockBean) event.getSource().getStore()
                            .get(event.getRowIndex());
    
    
                    openEditor(clicked);
                }
            });
    
    
            generateRandomStocks();
    
    
            container.add(grid);
    
    
            return container;
        }
    
    
        private void generateRandomStocks() {
            for (int i = 0; i < 5; i++) {
                StockBean s = new StockBean(randomString(), randomString());
    
    
                grid.getStore().add(s);
            }
        }
    
    
        public void openEditor(final StockBean bean) {
            final Window window = new Window();
            window.setModal(true);
            window.setWidth(400);
            window.setHeadingText("Edit StockBean");
    
    
            VerticalLayoutContainer container = new VerticalLayoutContainer();
    
    
            final TextField txtName = new TextField();
            txtName.setAllowBlank(false);
            txtName.setValue(bean.getName());
            container.add(new FieldLabel(txtName, "Name"), new VerticalLayoutData(
                    1, -1));
    
    
            final TextArea txtContent = new TextArea();
            txtContent.setHeight(200);
            txtContent.setValue(bean.getContent());
            container.add(new FieldLabel(txtContent, "Content"),
                    new VerticalLayoutData(1, -1));
    
    
            window.add(container);
    
    
            final TextButton btnSave = new TextButton("Save");
            btnSave.addSelectHandler(new SelectHandler() {
                @Override
                public void onSelect(SelectEvent event) {
                    // A new instance would be returned via RequestFactory, so does
                    // not update the already existing one
                    StockBean newBean = new StockBean(bean.getId(), txtName
                            .getValue(), txtContent.getValue());
    
    
                    grid.getStore().update(newBean);
    
    
                    window.hide(btnSave);
                }
            });
            window.addButton(btnSave);
    
    
            window.show();
        }
    
    
        private String randomString() {
            int len = (int) (Math.random() * 10 + 5);
    
    
            String str = "";
    
    
            for (int i = 0; i < len; i++) {
                str += (char) (Math.random() * 26 + 97);
            }
    
    
            return str;
        }
    }
    The problem with this bug is, that I could not say, when it will occur.. it seems to occur randomly. So to reproduce it you have to edit those entrys maaaany times.. sometimes it will occur faster, but sometimes not.

    Helpful Information

    Screenshot or video
    See above.

    Live test
    Not available.

    Debugging already done
    I debugged this and found out, that in the GridView#doRender(List<ColumnData>, List<M>, int) method the record has got an old Bean instance:
    bugreport-grid-record.jpg
    And this old Bean instance hasn't got the new value for the name-attribute.

    Possible fix
    «Not provided»

  2. #2
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,732
    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


    Records currently only are designed to function on the same bean - if you swap in a new instance, then the record might be holding 'changes' that are no different from the new actual value. Additionally, if the record is 'reverted', it won't go back to the original value, but to the new bean's value.

    Code:
                            // maybe the content was changed
                            Change<StockBean, String> change = grid.getStore()
                                    .getRecord(value).getChange(props.content());
    This is creating a Record when it is called. Instead of this, check first if there is a record, and only then ask for the record and the corresponding change.

    As far as I can tell, no other part of your code is interacting with the Record objects - your editing mechanism is completely replacing the bean, so records shouldn't be tested at all.

    The 'bug' is that the Record only wraps the old object, and once you ask for a Record (expand the RowExpander), you create a Record. The Grid only reads values from the Record if it exists (as your RowExpander should do as well), but if it does exist, it must read from it rather than the original object. Make your use of Changes and update consistent, and this bug will go away.

Thread Participants: 1