Hi Mark, Mark Lundquist wrote:
> > > OK, I've spent way too long trying to figure this out... :-/ > > I always get annoyed with having to play games in flowscript in order > to use a selection list or a <fd:multivalue-field> to manipulate > collections of /beans/. Those widgets work great for value types like > String, not for entities. But that's another story... Today, I am > trying to bind a repeater to a collection of String! And it's not > working. Looking at the source code, I guess how I'm doing it isn't > advertised to work in the first place... OK, fine, I just need to > learn the correct way to accomplish it! :-) I do perfectly understand, I've been working with cforms on beans for a couple of years right now, and I'm now working on better support for beans in cforms. > > And here's my binding: > > <fb:repeater > id="editions" > parent-path="." > row-path="editions" <!-- a java.util.List --> > > > <fb:identity> > <fb:value id="name" path="." /> <!-- a java.lang.String --> > </fb:identity> > </fb:repeater> An fb:value with path="." ... i never managed to have this working. > The "load" side works fine and the list displays correctly. The delete > row-action appears to work fine (w.r.t. refreshing the page), and so > does the scheme using the two widgets to add a new item. The problem > is that when I do a save, the back end model isn't modified — the new > items aren't added, and the deleted items aren't removed. So, two > questions: > > 1) What is the deal with row deletion? Apparently when binding to an > XML tree, you have to say > > <fb:on-delete-row> > <fb:delete-node/> > </fb:on-delete-row> > > if you want the delete row-action to do anything. Why? And don't you > have to do anything special in the binding to make delete work when > you are binding to a Collection? If so, what? The default behaviour when no on-delete-row is specified is to use JXPath "node" deletion, which actually should remove the item from the collection... as long as the collection is a List it usually works. > > 2) What's the deal with row insertion? I get this warning in the log > > RepeaterJXPathBinding: RepeaterBinding has detected rows to insert, > but misses the <on-insert-row> binding to do it. > > OK, I get it... for a bean, we need to register a factory so that the > binding will know how to create the new thing. Fair enough. But I've > been looking at the documentation > (http://cocoon.apache.org/2.1/userdocs/binding.html), and I don't get > it... how about an example? And anyway, I don't have a bean here. Just > a String. How do I make this work? You have to define a on-insert-row, containing an insert-bean, for example : <fb:on-insert-row> <fb:insert-bean ...> </fb:on-insert-row> The insert bean requires a class and method, the method is referred to the context bean, so you need an addXXX method on you bean. But all this will not solve your problem, this is because there is no way to tell to the repeater binding that the context bean and a field are the same (the fb:value with path="." that never works). I made many simlar pages (in one i have to fill a collection with beans taken from a list retrieved from a service method and displayed in drop downs), and i ended coding my own, super simple RepeaterToBeanList binding, which is quite similar to the simple repeater binding but actually fills the collection with beans (or any other object) taken from a field contained in the repeater. The code for this class follows, I'm working on this kind of problems and will hopefully come up with something intresting when I have time to :( What i does is basically simply clearing the collection, iterate on the repeater rows, get the value of a certaing field contained in every row (your name field) and add it to the collection. The parametrize and the FormHelper are simply a quicker way or parsing configuration attributes, they will not compile in your environment but changing it so that configuration parameters are directly taken from the config element is trivial. Hope this helps, Simone import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.cocoon.forms.binding.AbstractCustomBinding; import org.apache.cocoon.forms.formmodel.Repeater; import org.apache.cocoon.forms.formmodel.Widget; import org.apache.commons.jxpath.JXPathContext; import org.w3c.dom.Element; public class RepeaterToBeanList extends AbstractCustomBinding { protected String subwidgetName = null; protected boolean skipNulls = true; protected void doLoad(Widget frmModel, JXPathContext context) throws Exception { if (!(frmModel instanceof Repeater)) throw new IllegalStateException("The widget " + frmModel.getName() + " is not a repeater."); Repeater repeater = (Repeater)frmModel; Object listobj = context.getValue(super.getXpath()); if (listobj == null) throw new IllegalStateException("The bean for field " + frmModel.getName() + ", found on xpath " + super.getXpath() + " is not a List, it is null"); if (!(listobj instanceof List)) throw new IllegalStateException("The bean for field " + frmModel.getName() + ", found on xpath " + super.getXpath() + " is not a List, it is a " + listobj.getClass().getName()); List list = (List)context.getValue(super.getXpath()); int i = 0; for (Iterator iter = list.iterator(); iter.hasNext();) { Object element = iter.next(); Repeater.RepeaterRow row = null; if (repeater.getSize() <= i) { row = repeater.addRow(); } else { row = repeater.getRow(i); } Widget sub = row.getChild(subwidgetName); if (sub == null) throw new IllegalStateException("Cannot find a widget named " + this.subwidgetName + " inside the repeater "+ frmModel.getName() + " at row " + i); sub.setValue(element); i++; } } protected void doSave(Widget frmModel, JXPathContext context) throws Exception { if (!(frmModel instanceof Repeater)) throw new IllegalStateException("The widget " + frmModel.getName() + " is not a repeater."); Repeater repeater = (Repeater)frmModel; if (!(context.getValue(super.getXpath()) instanceof List)) throw new IllegalStateException("The context bean is not a List."); List list = (List)context.getValue(super.getXpath()); list.clear(); for (int i = 0; i < repeater.getSize(); i++) { Repeater.RepeaterRow row = repeater.getRow(i); Widget sub = row.getChild(subwidgetName); if (sub == null) throw new IllegalStateException("Cannot find a widget named " + this.subwidgetName + " inside the repeater "+ frmModel.getName() + " at row " + i); Object element = sub.getValue(); if (element == null ^ skipNulls) list.add(element); } } public void parametrize(Map params) { this.subwidgetName = (String) params.get("widget"); if (this.subwidgetName == null) throw new IllegalArgumentException("Must specify a widget for Repeater to Bean List binding"); this.skipNulls = params.get("skipnulls") != null && params.get("skipnulls").equals("true"); } public static AbstractCustomBinding createBinding(Element config) throws Exception { RepeaterToBeanList binding = new RepeaterToBeanList(); binding.parametrize(FormsHelper.getParams(config)); return binding; } } -- Simone Gianni --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
