FieldBinding for Grid. View remains inconsistent after adding new record to Store
Asked Answered
E

1

9

We implemented a binding for some typical Grid usages in application. It works just fine, except if you modify a store, for example add a record, you'd see n + TWO identical records in view. When I examined store's state, it shown n + 1 values.

It goes as if I have a grid with one record shown in it and call: grid.getStore().add(modelFactory.createModel(event.getBean())); I now have:

Second and third lines ARE equal

The second and the third lines are equal and third one can't be selected. Also, it is not present in grid.getStore().

Sources:

freqsGrid = new AwesomeGridPanel() {
    @Override
    public void createColumns() {/**/}
};
freqBinding = AwesomeGridBinding.createGridBinding(freqsGrid, "frequencies");

Simple binding source. It maps model's List property to grid as is.

public class AwesomeGridBinding {
    public static FieldBinding createGridBinding(AwesomeGridPanel grid, String property) {
        return new FieldBinding(new AwesomeGridAdapterField(grid), property);
    }
}

class AwesomeGridAdapterField<T> extends AdapterField {

    protected AwesomeGridPanel grid;
    private StoreListener<BeanModel> storeChangedListener;

    public AwesomeGridAdapterField(AwesomeGridPanel grid) {
        super(grid);
        this.grid = grid;
        configureGrid(grid, this);
    }

    @Override
    public void setValue(Object value) {
        List data;
        if (value == null)
            data = new ArrayList<>();
        else if (!(value instanceof List))
            throw new IllegalArgumentException();
        else
            data = (List) value;
        grid.getStore().setMonitorChanges(false);
        grid.getStore().setFiresEvents(false);
        setResults(grid.getStore(), data);
        grid.getStore().setFiresEvents(true);
        grid.getStore().setMonitorChanges(true);

If I remove the line below, view stops to show n+2 lines after add, and begins to show added line even after formBinding.bind(createModel(bean)); to another bean.

        grid.getGrid().getView().refresh(false);
    }

    @Override
    public Object getValue() {
        List<T> result = new ArrayList<>();
        for (BeanModel bm : grid.getStore().getModels())
            if (isBeanForResult(bm))
                result.add(extractResult(bm));
        return result;
    }

    protected void setResults(ListStore<BeanModel> store, List data) {
        store.removeAll();
        for (Object obj : data)
            if (obj instanceof BeanModel)
                store.add((BeanModel) obj);
            else
                throw new IllegalArgumentException();
    }

    protected boolean isBeanForResult(BeanModel beanModel) {
        return true;
    }

    protected T extractResult(BeanModel bmFromStore) {
        return bmFromStore.getBean();
    }

    private final EventType[] STORE_EVENTS = {Store.Add, Store.Clear, Store.DataChanged, Store.Remove, Store.Update};

    protected void configureGrid(final AwesomeGridPanel grid, final AdapterField field) {
        grid.getStore().setMonitorChanges(true);
//        grid.getStore().removeAllListeners();
        if (storeChangedListener != null)
            grid.getStore().removeStoreListener(storeChangedListener);

        storeChangedListener = new StoreListener<BeanModel>() {
            @Override
            public void handleEvent(StoreEvent<BeanModel> e) {
                super.handleEvent(e);
                for (EventType se : STORE_EVENTS) {
                    if (se != e.getType())
                        continue;
                    field.fireEvent(Events.Change);
                    return;
                }
            }
        };
        grid.getStore().addStoreListener(storeChangedListener);
    }
}
Eagle answered 15/9, 2015 at 9:11 Comment(8)
I have no experience with gwt/gxt, so just out of curiosity - in your setValue method you disable changes monitoring and events firing before calling setResult, what is the reason?Saltish
@Saltish Another option is to fall into recursion and stack overflow cause setValue changes Store, it fires events, events cause setValue.Eagle
The symptom sounds like multple event handlers responding to an event. I'm not familiar enough with gwt/gxt or Java to dig into the details, but super.handleEvent catches my eye as a possibility. Have you set breakpoints in the method that writes each row to the grid to check the call stack each time it fires?Usurp
Which version og GXT are you using?Bob
There surely are multiple event handlers. When I add an element, OnAdd fires, then OnChange from AdapterField custom handler. BUT. I suspend all handlers in setValue(). I can't define any way to suspend events before OnChange and obtain any records in the view.Eagle
@Max GXT2.6.1a is a pretty old version. Can you use the 3.x Version instead? It might be a bug.Etherize
No, I can't. We've already forced to hack some components inside it because we can't change version:(Eagle
I found the solution, will post it here in a whileEagle
E
0

The only way I found to fix the problem is to avoid the store modification. I used a BeanModel.

Widget:

schedulesGridPanel = new AwesomeGridView<RcTaskSchedule>(ListBinding.createEmptyStore(), NavigationTarget.RADIOCONTROL_TASK_DIALOG, "RCTaskDialogSchedulesGridPanel") {
    @Override
    public void createColumns() {...}
};
formBinding.addFieldBinding(new ListBinding(schedulesGridPanel.getGrid(), "schedules"));

...

@Override
public BeanModel getBeanModel() {
    return (BeanModel) formBinding.getModel();
}

... Presenter:

eventBus.addBeanCreatedEventHandler(RcTaskSchedule.class, NavigationTarget.RC_TASK_SCHEDULE_DIALOG, new BeanCreatedEvent.Handler<RcTaskSchedule>() {
    @Override
    public void onBeanCreated(BeanCreatedEvent<RcTaskSchedule> event) {
        ListBinding.addListItemInBeanModel(display.getBeanModel(), "schedules", schedulesFactory.createModel(event.getBean()));
    }
});

eventBus.addBeanModifiedEventHandler(RcTaskSchedule.class, NavigationTarget.RC_TASK_SCHEDULE_DIALOG, new BeanModifiedEvent.Handler<RcTaskSchedule>() {
    @Override
    public void onBeanModified(BeanModifiedEvent<RcTaskSchedule> event) {
        ListBinding.updateListItemInBeanModel(display.getBeanModel(), "schedules",
                schedulesFactory.createModel(event.getOldBean()), schedulesFactory.createModel(event.getModifiedBean()));
    }
});   

...

public class ListBinding extends FieldBinding {

    private Grid grid;
    private ChangeListener listener = null;
    private MemoryProxy memoryProxy = null;

    public ListBinding(Grid grid, String property) {
        super(new AdapterField(grid), property);
        this.grid = grid;
        if (!(grid.getStore().getLoader() instanceof BaseListLoader))
            return;
        BaseListLoader loader = (BaseListLoader) grid.getStore().getLoader();
        if (!(loader.getProxy() instanceof MemoryProxy))
            return;
        memoryProxy = (MemoryProxy) loader.getProxy();
    }

    @Override
    public void bind(ModelData model) {
        super.bind(model);

        if (memoryProxy == null)
            return;
        grid.getStore().removeAll();
        memoryProxy.setData(getModel().get(getProperty()));
        grid.getStore().getLoader().load();

        if (!(model instanceof BeanModel))
            return;
        BeanModel bm = (BeanModel) model;

        listener = new ChangeListener() {
            @Override
            public void modelChanged(ChangeEvent event) {
                if (!(event instanceof PropertyChangeEvent))
                    return;
                if (!property.equals(((PropertyChangeEvent) event).getName()))
                    return;
                grid.getStore().removeAll();
                memoryProxy.setData(getModel().get(getProperty()));
                grid.getStore().getLoader().load();
            }
        };
        bm.addChangeListener(listener);
    }

    @Override
    public void unbind() {
        super.unbind();
        grid.getStore().removeAll();
        if (listener == null)
            return;
        if (!(this.getModel() instanceof BeanModel))
            return;
        BeanModel bm = (BeanModel) this.getModel();
        bm.removeChangeListener(listener);
    }

    public static ListStore<BeanModel> createEmptyStore() {
        return new ListStore<>(new BaseListLoader(new MemoryProxy(new BoundList<>())/*, new BeanModelReader()*/));
    }

    public static void addListItemInBeanModel(BeanModel beanModel, String property, BeanModel newItem) {
        if (beanModel == null || !(beanModel.get(property) instanceof List) || newItem == null)
            return;
        List<BeanModel> list = beanModel.get(property);
        list.add(newItem);
        beanModel.set(property, null);
        beanModel.set(property, list);
    }

    public static void updateListItemInBeanModel(BeanModel beanModel, String property, BeanModel oldItem, BeanModel newItem) {
        if (beanModel == null || !(beanModel.get(property) instanceof List) || newItem == null || oldItem == null)
            return;
        List<BeanModel> list = beanModel.get(property);
        int index = list.indexOf(oldItem);
        if (index < 0)
            return;
        list.set(index, newItem);
        beanModel.set(property, list);
    }

    public static void removeListItemsInBeanModel(BeanModel beanModel, String property, List<BeanModel> items) {
        if (beanModel == null || !(beanModel.get(property) instanceof List) || items == null || items.isEmpty())
            return;
        List<BeanModel> list = beanModel.get(property);
        list.removeAll(items);
        beanModel.set(property, list);
    }

}
Eagle answered 28/9, 2015 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.