I found a "workaround" and for those of you who are interested how it's
working:
a) create a fresh instance of your bean in "onBeginSubmit".
b) Tapestry will transfer all form values from the particular bean to
this new instance between "onBeginSubmit" and "onAfterSubmit"
c) loosing the PK can be avoided by adding a redundant hidden field in
the tml
d) make sure submitNotifier is correctly positioned around the fields
edited not wider.
Thanks
Jens
---- FINAL TML ----
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<t:form id="XValueForm" t:id="XValueForm">
<table>
<t:loop source="XValues" value="xValue" encoder="XValueEncoder"
formState="iteration">
<tr t:type="zone" update="show" t:id="row"
id="row_${xValue.pk}">
<t:if test="!XValueChanged">
<t:delegate to="block:readOnly"/>
</t:if>
<t:if test="XValueChanged">
<t:delegate to="block:editable"/>
</t:if>
</tr>
<t:block t:id="readOnly">
<td>${xValue.pk}</td>
<td>${xValue.s}</td>
<td><a t:type="eventlink" t:id="modifyXValue"
context="xValue.pk" zone="row_${xValue.pk}">edit</a></td>
</t:block>
<t:block t:id="editable">
<t:submitNotifier>
<td>${xValue.pk}<t:textfield value="xValue.pk"
type="hidden"/></td>
<td><t:textfield value="xValue.s"/></td>
<td><a t:type="eventlink" t:id="revertXValue"
context="xValue.pk" zone="row_${xValue.pk}">revert</a></td>
</t:submitNotifier>
</t:block>
</t:loop>
</table>
<input t:type="submit" t:defer="true" name="save" value="save"/>
</t:form>
</html>
---- FINAL JAVA ----
(this class is slightly modified to avoid problems introduced by using
static lists instead of services as it should be)
package de.threeoldcoders.grayhair.pages;
import de.threeoldcoders.grayhair.services.XValue;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.Id;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
import javax.inject.Inject;
import java.util.*;
// http://localhost:8080/GH1Z/index.xvalueform
// we had a filter as workaround? mailinglist??
public class Index
{
private static final List<XValue> _allXValues
= new ArrayList<XValue>(Arrays.asList(new XValue(1, "1"), new
XValue(2, "2"), new XValue(3, "3")));
private static final Set<XValue> _changes = new HashSet<XValue>();
@Inject private Request _request;
@Inject private AjaxResponseRenderer _arr;
@Inject @Id("readOnly") private Block _blockReadOnly;
@Inject @Id("editable") private Block _blockEdit;
@Property XValue _xValue;
void onBeginSubmit()
{
_xValue = new XValue(-1, "");
}
void onAfterSubmit()
{
_changes.remove(_xValue);
_changes.add(_xValue); // modified instance replaces old one
}
void onSuccessFromXValueForm()
{
// do whatever you want
for (final XValue change : _changes) {
_allXValues.remove(change);
_allXValues.add(change);
}
_changes.clear();
// keep ordering by PK for clarity reasons
Collections.sort(_allXValues, new Comparator<XValue>()
{
@Override public int compare(final XValue xValue1, final
XValue xValue2)
{
return xValue1.getPk().compareTo(xValue2.getPk());
}
});
}
Object onModifyXValue(final long pk)
{
// just retrieve original instance and mark it as changed
final XValue orig = getXValueEncoder().toValue(Long.toString(pk));
_changes.add(new XValue(orig.getPk(), orig.getS()));
if (_request.isXHR()) {
_xValue = orig;
_arr.addRender("row_" + _xValue.getPk(), _blockEdit);
}
return null;
}
Object onRevertXValue(final long pk)
{
// just retrieve original instance and mark it as changed
final XValue orig = getXValueEncoder().toValue(Long.toString(pk));
_changes.remove(orig);
if (_request.isXHR()) {
_xValue = orig;
_arr.addRender("row_" + _xValue.getPk(), _blockReadOnly);
}
return null;
}
public List<XValue> getXValues()
{
return _allXValues;
}
public boolean isXValueChanged()
{
return _changes.contains(_xValue);
}
public ValueEncoder<XValue> getXValueEncoder()
{
return new ValueEncoder<XValue>()
{
@Override public String toClient(final XValue value)
{
return value.getPk().toString();
}
@Override public XValue toValue(final String clientValue)
{
return getXValues().get(Integer.parseInt(clientValue) - 1);
}
};
}
}
---- THE BEAN ----
package de.threeoldcoders.grayhair.services;
public class XValue
{
private Long _pk;
private String _s;
public XValue()
{
}
public XValue(final long pk, final String s)
{
_pk = pk;
_s = s;
}
public Long getPk()
{
return _pk;
}
public void setPk(final Long pk)
{
_pk = pk;
}
public void setS(final String s)
{
_s = s;
}
public String getS()
{
return _s;
}
@Override
public boolean equals(final Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final XValue xValue = (XValue) o;
if (!_pk.equals(xValue._pk)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return _pk.hashCode();
}
@Override public String toString()
{
final StringBuilder sb = new StringBuilder("XValue{");
sb.append("_pk=").append(_pk);
sb.append(", _s='").append(_s).append('\'');
sb.append('}');
return sb.toString();
}
}
Am 04.11.13 08:14, schrieb Jens Breitenstein:
Hi All!
I am struggling since days with a Tapestry Bug(?) and maybe one of you
have an idea whats wrong or what my mistake may be...
Scenario: I use a loop to display multiple rows in a table. Each row
allows inline editing if the user presses a link. Due to link pressing
the particular row's zone is swapped to show edit fields instead of
pure text. Everything works beside submit. Submit always changes the
last element in the loop not the one it's iterating over. I dubugged
it down to Tapestry's PropBinding and noticed there is always the last
element used as "root" but maybe I am fooled by the proxies.
I can provide a WAR if you like, but maybe the sources are enough
because I tried to strip down to bare minumum.
tml:
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<t:form>
<table>
<t:loop source="XValues" value="xValue"
encoder="XValueEncoder" formState="iteration">
<t:submitNotifier>
<tr t:type="zone" update="show" t:id="row"
id="row_${xValue.pk}">
<t:if test="!XValueChanged">
<t:delegate to="block:readOnly"/>
</t:if>
<t:if test="XValueChanged">
<t:delegate to="block:editable"/>
</t:if>
</tr>
<t:block t:id="readOnly">
<td>${xValue.pk}</td>
<td>${xValue.s}</td>
<td><a t:type="eventlink" t:id="modifyXValue"
context="xValue.pk" zone="row_${xValue.pk}">edit</a></td>
</t:block>
<t:block t:id="editable">
<td>${xValue.pk}</td>
<td>
<t:textfield t:id="xs" value="xValue.s"/>
</td>
<td><a t:type="eventlink" t:id="revertXValue"
context="xValue.pk" zone="row_${xValue.pk}">revert</a></td>
</t:block>
</t:submitNotifier>
</t:loop>
</table>
<input t:type="submit" name="save" value="save"/>
</t:form>
</html>
Basically the loop displays 3 rows and uses a delegate to decide if it
has to show edit fields or pure text. Each row has a unique zone id
based on the pk.
Each eventlink just transports the pk. Regardless of formState
("iterable" or "values") still the wrong element is updated.
Java:
package de.threeoldcoders.grayhair.pages;
import de.threeoldcoders.grayhair.services.XValue;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.Id;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
import javax.inject.Inject;
import java.util.*;
public class Index
{
private static final List<XValue> _allXValues = Arrays.asList(new
XValue(1, "1"), new XValue(2, "2"), new XValue(3, "3"));
private static final List<XValue> _changes = new ArrayList<XValue>();
@Inject private Request _request;
@Inject private AjaxResponseRenderer _arr;
@Inject @Id("readOnly") private Block _blockReadOnly;
@Inject @Id("editable") private Block _blockEdit;
@Property XValue _xValue;
void onAfterSubmit()
{
final XValue orig =
getXValueEncoder().toValue(Long.toString(_xValue.getPk())); // trick
to retrieve original instance
if (! orig.getS().equals(_xValue.getS())) {
orig.setS(_xValue.getS());
}
_changes.remove(orig);
}
Object onModifyXValue(final long pk)
{
// just retrieve original instance and mark it as changed
final XValue orig =
getXValueEncoder().toValue(Long.toString(pk));
_changes.add(orig);
if (_request.isXHR()) {
_xValue = orig;
_arr.addRender("row_" + _xValue.getPk(), _blockEdit);
}
return null;
}
Object onRevertXValue(final long pk)
{
// just retrieve original instance and mark it as changed
final XValue orig =
getXValueEncoder().toValue(Long.toString(pk));
_changes.remove(orig);
if (_request.isXHR()) {
_xValue = orig;
_arr.addRender("row_" + _xValue.getPk(), _blockReadOnly);
}
return null;
}
public List<XValue> getXValues()
{
return _allXValues;
}
public boolean isXValueChanged()
{
return _changes.contains(_xValue);
}
public ValueEncoder<XValue> getXValueEncoder()
{
return new ValueEncoder<XValue>()
{
@Override public String toClient(final XValue value)
{
return value.getPk().toString();
}
@Override public XValue toValue(final String clientValue)
{
return getXValues().get(Integer.parseInt(clientValue)-1);
}
};
}
}
As I wanted it as small as possible this sample makes no use of
services, modules and all, thus my datamodel is simply hardcoded as
static members. I know this is not working in the real world. Each
event handler returns the block according to the internal state so it
toggles to edit mode or readonly mode, depending on the link, which is
working.
When I now press "edit" in the second row and change "2" to "B" for
example and press submit, the "B" appears in row "3" because this is
the last element in the list. It will never change something else
regardless how many rows are in edit mode. The submit notifier
correctly iterates all three rows, but it looks like the "property
set" call comes ways to late on a wrong instance. I even tried "defer"
true / false without any difference.
Thanks in advance
Jens
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org