Hi @all,
i try to use an AjaxFormLoop to implement a dynamic filter component.
U can interactively add or remove new single filter to a filter form.
Every single filter row has a select to specify the column.
This select replace the input field for the filter expression depending on
the column type with an ajax zone update.
If i try to place a DateField in this scenario i got an parse error after i
click
on the calendar icon of the added DateField, see here
http://postimg.org/image/c57601flj/
It looks like the DateField.js use the hidden "t:formdata input" of the
DateField for reading and writing, which is as far as i understood wrong.
Any ideas, how i can fix this issue???
Kind regards
simple code to (hopefully) reproduce the error:
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.SelectModelFactory;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
public class TestAjaxFormLoopWithDateField {
public enum TempType {
TEXT,
DATE
public class Filter {
private int id;
private Date date;
private TempType type;
private String value;
public Filter() {
public Filter(int id, Date date, TempType type) {
this.setId(id);
this.setDate(date);
this.setType(type);
/**
* @return the date
*/
public Date getDate() {
return date;
/**
* @param date
* the date to set
*/
public void setDate(Date date) {
this.date = date;
/**
* @return the id
*/
public int getId() {
return id;
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
/**
* @return the type
*/
public TempType getType() {
return type;
/**
* @param type the type to set
*/
public void setType(TempType type) {
this.type = type;
/**
* @return the value
*/
public String getValue() {
return value;
/**
* @param value the value to set
*/
public void setValue(String value) {
this.value = value;
// useful services
@Inject
private Request request;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
/*
* Screen fields
*/
@Property
@Persist
private List<Filter> filters;
@Property
private Filter curFilterBean;
@InjectComponent
private Zone inputFieldZone;
@Inject
private SelectModelFactory selectModelFactory;
@Inject
private Block stringValueBlock, dateValueBlock;
void setupRender() {
filters = new ArrayList<TestAjaxFormLoopWithDateField.Filter>();
if (filters.isEmpty())
filters.add(new Filter(1, null, TempType.TEXT));
public ValueEncoder<Filter> getFilterEncoder() {
return new ValueEncoder<Filter>() {
@Override
public Filter toValue(String clientValue) {
return filters.get(Integer.parseInt(clientValue));
@Override
public String toClient(Filter value) {
return String.valueOf(filters.indexOf(value));
};
/**
* Get unique client side id for zone of input field for
* {@link #curFilterBean}.
* @return {@code non-empty} unique client side id
*/
public String getFieldZoneId() {
return "inputField" + getFilterEncoder().toClient(curFilterBean);
/**
* Get block for type of {@link #curFilterBean}.
* @return {@code not null} block
*/
public Block getCase() {
Block result = stringValueBlock;
if (curFilterBean != null & curFilterBean.getType() != null) {
switch (curFilterBean.getType()) {
case DATE:
result = dateValueBlock;
break;
return result;
/**
* Create new {@link Filter} and add it to the list.
* @return {@code not null} {@link Filter}
*/
Filter onAddRow() {
Filter fbean = new Filter();
fbean.setId(this.filters.size());
filters.add(fbean);
return fbean;
/**
* @param newType
* @param filterBeanId
*/
void onValueChangedFromTypeSelect(TempType newType, int filterBeanId) {
for (Filter curFilterBean : filters) {
if (curFilterBean.getId() == filterBeanId) {
this.curFilterBean = curFilterBean;
break;
if (request.isXHR()) {
ajaxResponseRenderer.addRender(inputFieldZone);
<html t:type="mintLayout"
title="test AjaxFormLoop"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
xmlns:p="tapestry:parameter">
<t:form>
<t:ajaxformloop t:id="searchRefine" t:source="filters"
t:value="curFilterBean" t:encoder="filterEncoder" >
<t:if test="curFilterBean">
<div class="row">
<div class="col-xs-5">
<t:select t:id="typeSelect" t:value="curFilterBean.type"
t:zone="prop:fieldZoneId"
t:context="curFilterBean.id" />
</div>
<div class="col-xs-5">
<t:zone t:id="inputFieldZone" id="prop:fieldZoneId">
<t:delegate to="case"/>
</t:zone>
</div>
<div class="col-xs-2">
<t:removerowlink>
<p class="form-control-static">remove</p>
</t:removerowlink>
</div>
</div>
</t:if>
<p:addRow>
<t:addrowlink>Add</t:addrowlink>
</p:addRow>
</t:ajaxformloop>
</t:form>
<t:block t:id="stringValueBlock">
<input t:type="textField" t:value="curFilterBean.value" />
</t:block>
<t:block t:id="dateValueBlock">
<t:DateField t:value="curFilterBean.date" />
</t:block>
</html>
i try to use an AjaxFormLoop to implement a dynamic filter component.
U can interactively add or remove new single filter to a filter form.
Every single filter row has a select to specify the column.
This select replace the input field for the filter expression depending on
the column type with an ajax zone update.
If i try to place a DateField in this scenario i got an parse error after i
click
on the calendar icon of the added DateField, see here
http://postimg.org/image/c57601flj/
It looks like the DateField.js use the hidden "t:formdata input" of the
DateField for reading and writing, which is as far as i understood wrong.
Any ideas, how i can fix this issue???
Kind regards
simple code to (hopefully) reproduce the error:
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.SelectModelFactory;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
public class TestAjaxFormLoopWithDateField {
public enum TempType {
TEXT,
DATE
public class Filter {
private int id;
private Date date;
private TempType type;
private String value;
public Filter() {
public Filter(int id, Date date, TempType type) {
this.setId(id);
this.setDate(date);
this.setType(type);
/**
* @return the date
*/
public Date getDate() {
return date;
/**
* @param date
* the date to set
*/
public void setDate(Date date) {
this.date = date;
/**
* @return the id
*/
public int getId() {
return id;
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
/**
* @return the type
*/
public TempType getType() {
return type;
/**
* @param type the type to set
*/
public void setType(TempType type) {
this.type = type;
/**
* @return the value
*/
public String getValue() {
return value;
/**
* @param value the value to set
*/
public void setValue(String value) {
this.value = value;
// useful services
@Inject
private Request request;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
/*
* Screen fields
*/
@Property
@Persist
private List<Filter> filters;
@Property
private Filter curFilterBean;
@InjectComponent
private Zone inputFieldZone;
@Inject
private SelectModelFactory selectModelFactory;
@Inject
private Block stringValueBlock, dateValueBlock;
void setupRender() {
filters = new ArrayList<TestAjaxFormLoopWithDateField.Filter>();
if (filters.isEmpty())
filters.add(new Filter(1, null, TempType.TEXT));
public ValueEncoder<Filter> getFilterEncoder() {
return new ValueEncoder<Filter>() {
@Override
public Filter toValue(String clientValue) {
return filters.get(Integer.parseInt(clientValue));
@Override
public String toClient(Filter value) {
return String.valueOf(filters.indexOf(value));
};
/**
* Get unique client side id for zone of input field for
* {@link #curFilterBean}.
* @return {@code non-empty} unique client side id
*/
public String getFieldZoneId() {
return "inputField" + getFilterEncoder().toClient(curFilterBean);
/**
* Get block for type of {@link #curFilterBean}.
* @return {@code not null} block
*/
public Block getCase() {
Block result = stringValueBlock;
if (curFilterBean != null & curFilterBean.getType() != null) {
switch (curFilterBean.getType()) {
case DATE:
result = dateValueBlock;
break;
return result;
/**
* Create new {@link Filter} and add it to the list.
* @return {@code not null} {@link Filter}
*/
Filter onAddRow() {
Filter fbean = new Filter();
fbean.setId(this.filters.size());
filters.add(fbean);
return fbean;
/**
* @param newType
* @param filterBeanId
*/
void onValueChangedFromTypeSelect(TempType newType, int filterBeanId) {
for (Filter curFilterBean : filters) {
if (curFilterBean.getId() == filterBeanId) {
this.curFilterBean = curFilterBean;
break;
if (request.isXHR()) {
ajaxResponseRenderer.addRender(inputFieldZone);
<html t:type="mintLayout"
title="test AjaxFormLoop"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
xmlns:p="tapestry:parameter">
<t:form>
<t:ajaxformloop t:id="searchRefine" t:source="filters"
t:value="curFilterBean" t:encoder="filterEncoder" >
<t:if test="curFilterBean">
<div class="row">
<div class="col-xs-5">
<t:select t:id="typeSelect" t:value="curFilterBean.type"
t:zone="prop:fieldZoneId"
t:context="curFilterBean.id" />
</div>
<div class="col-xs-5">
<t:zone t:id="inputFieldZone" id="prop:fieldZoneId">
<t:delegate to="case"/>
</t:zone>
</div>
<div class="col-xs-2">
<t:removerowlink>
<p class="form-control-static">remove</p>
</t:removerowlink>
</div>
</div>
</t:if>
<p:addRow>
<t:addrowlink>Add</t:addrowlink>
</p:addRow>
</t:ajaxformloop>
</t:form>
<t:block t:id="stringValueBlock">
<input t:type="textField" t:value="curFilterBean.value" />
</t:block>
<t:block t:id="dateValueBlock">
<t:DateField t:value="curFilterBean.date" />
</t:block>
</html>