PDA

View Full Version : [CLOSED] Store.commitChanges() bug



Grandiosa
2 Jul 2008, 12:28 AM
I have a table listener that retrieves a Record for the selected model (TableItem.getModel ), starts an editing operation, and changes a property:


Record record = store.getRecord((MyModel)item.getModel());
record.beginEdit();
record.set("propname", value);
Now, looking in the Record.set method, the store.afterEdit() method is only called if record is not in editing mode.


public Object set(String name, Object value) {
Object oldValue = get(name);
if (!editing) {
model.set(name, value);
if (store != null) {
store.afterEdit(this);
}
} else {

Is this a bug? The afterEdit method is the only place where the list of modified Store records is updated. So in this example the update record is never added to the Store's list of modified records.
When I try to commit the record changes in an other callback by doing Store.commitChanges() it doesn't work because its list of modified record is empty.

darrellmeyer
2 Jul 2008, 6:40 AM
When in "edit" mode, the Records's model and Store's modified list are not updated until you end the "edit" by calling endEdit().

Grandiosa
3 Jul 2008, 4:09 AM
Darrel, I believe that is not correct. The Record.endEdit() operation has no effect on the list of modifed items in Store. It only calls commit(), which triggers the Store.afterCommit() method, and that method actually tries to remove the record from the "modified" list (which is empty)

As far as I can see the only way for a record to end up in the Store's modified list is via the Store.afterEdit() method. And the only place this method is called is from the Record.set method, but strangely it happens only if record is not in editing mode (ref my previous posting)

I can't find any samples using the Store/Record/Commit facilities. Is the code tested? Below is a "pasteable" entry point that shows the problem.

1. Select an member, click "beginEdit", and change the name
2. Repeat for another member in the list
3. press button to dump modified records on store.... (yields nothing)

http://img77.imageshack.us/img77/6691/screendumpiz3.th.jpg (http://img77.imageshack.us/my.php?image=screendumpiz3.jpg)



public class Main implements EntryPoint {

DateTimeFormat formatter = DateTimeFormat.getFormat("dd.mm.yyyy");

public class Member extends BaseModel implements Serializable {
public Member() { }

public Member(Integer id, String name, Date birth) {
set("id", id);
set("name", name);
set("birth", birth);
}
public Integer getId() { return (Integer)get("id"); }
public String getName() { return (String)get("name"); }
public Date getBirthDate() { return (Date)get("birth"); }
public String getFormattedBirthDate() { return formatter.format(getBirthDate()); }

@Override
public String toString() {
return new StringBuilder("Member: ").append(getId()).append(",")
.append(getName()).append(",").append(getFormattedBirthDate()).toString();
}
}

private List<Member> getData() {
List<Member> mlist = new ArrayList<Member>();

mlist.add(new Member(1, "Rebecca", formatter.parse("01.01.1988")));
mlist.add(new Member(2, "Farah", formatter.parse("04.08.1991")));
mlist.add(new Member(3, "Angelina", formatter.parse("29.10.1981")));

return mlist;
}


public void onModuleLoad() {

Viewport viewport = new Viewport();
viewport.setLayout(new FitLayout());

ContentPanel panel = new ContentPanel();
panel.setFrame(true);
panel.setButtonAlign(Style.HorizontalAlignment.CENTER);
panel.setHeading("Member edit");
panel.setLayout(new BorderLayout());

LayoutContainer west = new LayoutContainer(new FitLayout());
BorderLayoutData wData = new BorderLayoutData(Style.LayoutRegion.WEST, 250);
LayoutContainer center = new LayoutContainer(new RowLayout());
BorderLayoutData cData = new BorderLayoutData(Style.LayoutRegion.CENTER);

panel.add(west, wData);
panel.add(center, cData);


final DataList list = new DataList();
list.setSelectionMode(Style.SelectionMode.SINGLE);
list.setFlatStyle(true);
list.setBorders(false);

west.add(list);

final ListStore<Member> store = new ListStore<Member>();
store.add(getData());

DataListBinder<Member> binder = new DataListBinder<Member>(list, store);
binder.setDisplayProperty("name");
binder.setStringProvider(new ModelStringProvider<Member>() {

public String getStringValue(Member member, String property) {
if (store.getRecord(member).isDirty()) {
return "<span style='color: red'>" + member.getId() + " - " + member.getName() + "</span>";
}
else {
return "" + member.getId() + " - " + member.getName() ;
}
};
});
binder.init();

final TextField idText = new TextField();
idText.setFieldLabel("Id");
final TextField nameText = new TextField();
nameText.setFieldLabel("Name");
final TextField birthText = new TextField();
birthText.setFieldLabel("Birthdate");

idText.setEnabled(false);
nameText.setEnabled(false);
birthText.setEnabled(false);

final FormPanel form = new FormPanel();
form.setHeaderVisible(false);
form.setWidth(350);

form.add(idText);
form.add(nameText);
form.add(birthText);


list.addListener(Events.SelectionChange, new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
DataList l = (DataList) ce.component;
Record member = store.getRecord((Member)l.getSelectedItem().getModel());
idText.setValue(member.get("id"));
nameText.setValue(member.get("name"));
birthText.setValue(formatter.format((Date)member.get("birth")));

nameText.setEnabled(member.isDirty());

}});

ToolBar toolBar = new ToolBar();
TextToolItem tti1 = new TextToolItem("BeginEdit");
tti1.addSelectionListener(new SelectionListener<ComponentEvent>() {
@Override
public void componentSelected(ComponentEvent ce) {
if (list.getSelectedItem() != null) {
Record member = store.getRecord((Member) list.getSelectedItem().getModel()); // idempotent
member.beginEdit(); // idempotent
member.set("notused", null); // force record dirty
nameText.setEnabled(true);

store.update((Member)member.getModel());
}
}
});
TextToolItem tti2 = new TextToolItem("EndEdit");
tti2.addSelectionListener(new SelectionListener<ComponentEvent>() {
@Override
public void componentSelected(ComponentEvent ce) {
if (list.getSelectedItem() != null) {
Record member = store.getRecord((Member) list.getSelectedItem().getModel()); // idempotent
if (member.isDirty()) {
member.set("name", nameText.getValue());
member.endEdit();
nameText.setEnabled(false);
store.update((Member) member.getModel());

}
}
}

});

toolBar.add(tti1);
toolBar.add(tti2);

ContentPanel wrapper = new ContentPanel(new FitLayout());
wrapper.setFrame(false);
wrapper.setHeaderVisible(false);
final TextArea ta = new TextArea();
wrapper.add(ta);
Button button = new Button("Show modded records in ListStore");
button.addSelectionListener(new SelectionListener<ButtonEvent>() {
@Override
public void componentSelected(ButtonEvent ce) {
if (store.getModifiedRecords().isEmpty()) {
ta.setValue("Store modification list is empty");
}
else {
StringBuffer mods = new StringBuffer();
for (Record r : store.getModifiedRecords()) {
mods.append(r.getModel().toString()).append("\n");
}
ta.setValue(mods.toString());
}
}
});
wrapper.setTopComponent(button);

form.setTopComponent(toolBar);

center.add(form, new RowData(1, -1));
center.add(wrapper, new RowData(1, 1, new Margins(10)));

viewport.add(panel, new FitData(10));

RootPanel.get().add(viewport);

}

darrellmeyer
3 Jul 2008, 9:26 AM
The Record code is not currently used by the library and needed a little more testing. I have reviewed the code and made several changes. Record will come into play with 1.1.

I tested your code, which is cool by the way, and it now works. However, I had to made a single change. In the "EndEdit" listener, the record is not dirty since it has never been updated, so I just commented out the dirtly check and everything works.

With the upcoming form binding, the record would be updated by the field automatically. Take a look at the changes and let me know if you find any issues.

Changes are in SVN.

Grandiosa
4 Jul 2008, 4:47 AM
Great, thanks!

I rewrote the testapp a bit and now it works great.

There are still some issues that my example app works around.
First a trivial one. Can you kindly add a new method Record.setDirty(). This is useful for apps that need to set a record in a editing mode. (this fact can then be picked up in ModelRenders etc)

More importantly, monitorChanges does not seem to work for ListStore's . I ran my example in the debugger and noticed that whenever a Record editing ends or commits the Store fires a corresponding event in this method:

Store.java:

protected void fireStoreEvent(int type, RecordUpdate operation, Record record) {
StoreEvent evt = createStoreEvent();
evt.operation = operation;
evt.record = record;
fireEvent(type, evt);
}
The model object is not copied to the StoreEvent, only the record. The model object is available inside the record but it is not used by the eventHandler on the DataListBinder object that receives the event:

DataListBinder.java:

protected void onUpdate(StoreEvent se) {
if (se.model != null) {
update((M) se.model);
}
}
So the DataListBinder.update(M m) method is never called, and store items will not be re-rendered as a result of these events.

In my example I explicitly do Store.update , but I think this should be unncessessary when using automatic store change monitoring.

And a final comment, when you wrap a model object into a Record you no longer have access to any getters/setters on your model object. Thus all Record update operations must use the set() method, which is rather ugly.

I can't really think of a solution to this so I have started to use property name constants on my model objects instead of specific getters/setters. At least usage of the set/get stuff will be more safe.

In case people are interested in seeing how all this works I have attached a modified test program which works great now.

cheers!



public class Main implements EntryPoint {

DateTimeFormat formatter = DateTimeFormat.getFormat("dd.mm.yyyy");
ListStore<Member> store;
TextArea ta;

public class Member extends BaseModel implements Serializable {
public Member() { }

public Member(Integer id, String name, Date birth) {
set("id", id);
set("name", name);
set("birth", birth);
}
public Integer getId() { return (Integer)get("id"); }
public String getName() { return (String)get("name"); }
public Date getBirthDate() { return (Date)get("birth"); }
public String getFormattedBirthDate() { return formatter.format(getBirthDate()); }

@Override
public String toString() {
return new StringBuilder("Member: ").append(getId()).append(",")
.append(getName()).append(",").append(getFormattedBirthDate()).toString();
}
}

private List<Member> getData() {
List<Member> mlist = new ArrayList<Member>();

mlist.add(new Member(1, "Rebecca", formatter.parse("01.01.1988")));
mlist.add(new Member(2, "Farah", formatter.parse("04.08.1991")));
mlist.add(new Member(3, "Angelina", formatter.parse("29.10.1981")));

return mlist;
}


public void onModuleLoad() {

Viewport viewport = new Viewport();
viewport.setLayout(new FitLayout());

ContentPanel panel = new ContentPanel();
panel.setFrame(true);
panel.setButtonAlign(Style.HorizontalAlignment.CENTER);
panel.setHeading("Member edit");
panel.setLayout(new BorderLayout());

LayoutContainer west = new LayoutContainer(new FitLayout());
BorderLayoutData wData = new BorderLayoutData(Style.LayoutRegion.WEST, 250);
LayoutContainer center = new LayoutContainer(new RowLayout());
BorderLayoutData cData = new BorderLayoutData(Style.LayoutRegion.CENTER);

panel.add(west, wData);
panel.add(center, cData);


final DataList list = new DataList();
list.setSelectionMode(Style.SelectionMode.SINGLE);
list.setFlatStyle(true);
list.setBorders(false);

west.add(list);

store = new ListStore<Member>();
store.setMonitorChanges(true);
store.add(getData());

DataListBinder<Member> binder = new DataListBinder<Member>(list, store);
binder.setDisplayProperty("name");
binder.setStringProvider(new ModelStringProvider<Member>() {
public String getStringValue(Member member, String property) {
Record r = store.getRecord(member);
if (r.isDirty()) {
return "<span style='color: red'>" + r.get("id") + " - " + r.get("name") + "</span>";
} else {
return "" + member.getId() + " - " + member.getName();
}
};
});
binder.init();

final TextField idText = new TextField();
idText.setFieldLabel("Id");
final TextField nameText = new TextField();

KeyListener keyl = new KeyListener() {
@Override
public void componentKeyUp(ComponentEvent event) {
Record member = store.getRecord((Member)list.getSelectedItem().getModel());
member.beginEdit();
member.set("name", nameText.getValue());
member.endEdit();
}
};
nameText.addKeyListener(keyl);

nameText.setFieldLabel("Name");
final TextField birthText = new TextField();
birthText.setFieldLabel("Birthdate");

idText.setEnabled(false);
nameText.setEnabled(false);
birthText.setEnabled(false);

final FormPanel form = new FormPanel();
form.setHeaderVisible(false);
form.setWidth(350);

form.add(idText);
form.add(nameText);
form.add(birthText);


list.addListener(Events.SelectionChange, new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
DataList l = (DataList) ce.component;
Record member = store.getRecord((Member)l.getSelectedItem().getModel());
idText.setValue(member.get("id"));
nameText.setValue(member.get("name"));
birthText.setValue(formatter.format((Date)member.get("birth")));

nameText.setEnabled(member.isDirty());

}});

ToolBar toolBar = new ToolBar();
TextToolItem tti1 = new TextToolItem("Edit Record");
tti1.addSelectionListener(new SelectionListener<ComponentEvent>() {
@Override
public void componentSelected(ComponentEvent ce) {
if (list.getSelectedItem() != null) {
Record member = store.getRecord((Member) list.getSelectedItem().getModel()); // idempotent
member.beginEdit(); // idempotent
forceDirty(member);
member.endEdit();
nameText.setEnabled(true);

store.update((Member)member.getModel());
showRecordsUnderEditing();
}
}
});
TextToolItem tti2 = new TextToolItem("Commit Record");
tti2.addSelectionListener(new SelectionListener<ComponentEvent>() {
@Override
public void componentSelected(ComponentEvent ce) {
if (list.getSelectedItem() != null) {
Record member = store.getRecord((Member) list.getSelectedItem().getModel()); // idempotent
if (member.isDirty()) {
member.set("name", nameText.getValue());
member.endEdit();
member.commit(false);
nameText.setEnabled(false);
store.update((Member) member.getModel());
showRecordsUnderEditing();
}
}
}
});

TextToolItem tti3 = new TextToolItem("Commit All");
tti3.addSelectionListener(new SelectionListener<ComponentEvent>() {
@Override
public void componentSelected(ComponentEvent ce) {
store.commitChanges();
nameText.setEnabled(false);
for (Member m : store.getModels()) {
store.update(m);
}
showRecordsUnderEditing();
}
});

toolBar.add(tti1);
toolBar.add(tti2);
toolBar.add(tti3);

ContentPanel wrapper = new ContentPanel(new FitLayout());
wrapper.setFrame(false);
wrapper.setHeaderVisible(false);
wrapper.setTopComponent(new WidgetComponent(new Label("Records currently under editing")));

ta = new TextArea();
wrapper.add(ta);

form.setTopComponent(toolBar);

center.add(form, new RowData(1, -1));
center.add(wrapper, new RowData(1, 1, new Margins(10)));

viewport.add(panel, new FitData(10));

RootPanel.get().add(viewport);

}

/**
* hack to force a record dirty ( we need a Record.setDirty() method)
* @param member
*/
private void forceDirty(Record member) {
if (member.get("__foo__") != null) {
Integer v = (Integer) member.get("__foo__");
member.set("__foo__", v + 1);
}
else {
member.set("__foo__", 0);
}
}

private void showRecordsUnderEditing() {
if (store.getModifiedRecords().isEmpty()) {
ta.setValue("");
}
else {
StringBuffer mods = new StringBuffer();
for (Record r : store.getModifiedRecords()) {
mods.append(r.getModel().toString()).append("\n");
}
ta.setValue(mods.toString());
}
}


}

darrellmeyer
7 Jul 2008, 11:33 AM
Can you kindly add a new method Record.setDirty().
Done.


The model object is not copied to the StoreEvent, only the record.
Fixed.

Changes are in SVN.