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);
    }
}
Maxim Popravko
  • 4,100
  • 3
  • 29
  • 43
  • 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? – Sva.Mu Sep 17 '15 at 16:16
  • @Sva.Mu Another option is to fall into recursion and stack overflow cause setValue changes Store, it fires events, events cause setValue. – Maxim Popravko Sep 17 '15 at 22:59
  • 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? – brichins Sep 18 '15 at 22:54
  • Which version og GXT are you using? – El Hoss Sep 19 '15 at 09:41
  • 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. – Maxim Popravko Sep 21 '15 at 21:24
  • @Max GXT2.6.1a is a pretty old version. Can you use the 3.x Version instead? It might be a bug. – dARKpRINCE Sep 28 '15 at 11:09
  • No, I can't. We've already forced to hack some components inside it because we can't change version:( – Maxim Popravko Sep 28 '15 at 11:54
  • I found the solution, will post it here in a while – Maxim Popravko Sep 28 '15 at 11:55

1 Answers1

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);
    }

}
Maxim Popravko
  • 4,100
  • 3
  • 29
  • 43