this is another example like : Tapestry5HowToAddBindingPrefix
This binding prefix will return different value for each call, and we will use it for zebra effect on grid or any other loop.
First let's see usage example:
<t:grid .... rowClass="cycle:line1,line2"> ... </t:grid> or any loop <t:loop ...> <div class="${cycle:line1,line2}">aaaa</div> </tloop> but not separately: <div class="${cycle:line1,line2}">aaaa</div> <div class="${cycle:line1,line2}">aaaa</div> <div class="${cycle:line1,line2}">aaaa</div>
that is it, no need for counter, and counter % 2 and stuff..
To make it work you have to tell tapestry about the "cycle:" binding prefix. So, add following to your module:
public static void contributeBindingSource( MappedConfiguration<String, BindingFactory> configuration, BindingSource bindingSource ) { configuration.add("cycle",new CycleBindingFactory(bindingSource)); }
And you'll need the implementation:
import java.util.ArrayList; import java.util.List; import org.apache.tapestry5.Binding; import org.apache.tapestry5.BindingConstants; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.ioc.Location; import org.apache.tapestry5.services.BindingFactory; import org.apache.tapestry5.services.BindingSource; /** * Implementation of the cycle: binding prefix -- we parse list of bindings * and generate delegate bindings for each element<br> * default binding is literal, other bindings can be used by specifying prefix.<br> * example: "cycle:prop:name,prop:lastName,sth,sth else" */ public class CycleBindingFactory implements BindingFactory { private final BindingSource _bindingSource; public CycleBindingFactory(BindingSource source){ this._bindingSource = source; } public Binding newBinding(String description, ComponentResources container, ComponentResources component, String expression, Location location) { List<Binding> delegates = new ArrayList<Binding>(); String[] bindingNames = expression.split(","); for (String bindingName : bindingNames){ String defaultBinding = BindingConstants.LITERAL; Binding binding = _bindingSource.newBinding(description, container, component, defaultBinding, bindingName, location); delegates.add(binding); } CycleBinding cycleBinding = new CycleBinding(delegates); container.addPageLifecycleListener(cycleBinding); return cycleBinding; } }
And the binding itself:
Note: that starting with Tapestry 5.2 PageBindings do not appear to be thread safe when running without page pools. So now we store the index in a ThreadLocal which works around this upgrade issue. (The previous version simply used an int for the index instead of a ThreadLocal).
import java.util.List; import org.apache.tapestry5.Binding; import org.apache.tapestry5.internal.bindings.AbstractBinding; import org.apache.tapestry5.runtime.PageLifecycleListener; public class CycleBinding extends AbstractBinding implements PageLifecycleListener{ private final List<Binding> delegates; private ThreadLocal<Integer> index; public CycleBinding(List<Binding> delegates) { this.delegates = delegates; } public Object get() { Object ret = delegates.get(getIndex()).get(); incrIndex(); return ret; } @Override public boolean isInvariant() { return false; } @Override public Class<Object> getBindingType() { return Object.class; } public void containingPageDidDetach() { index.remove(); } public void containingPageDidAttach() { index = createThreadLocal(); } public void containingPageDidLoad() {} public void restoreStateBeforePageAttach() {} private ThreadLocal<Integer> createThreadLocal() { return new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return Integer.valueOf(0); }; }; } private int getIndex() { return index.get().intValue(); } private void incrIndex() { int i = index.get().intValue() + 1; if (i >= delegates.size()) { i = 0; } index.set(Integer.valueOf(i)); } }