cocoon-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Simone Gianni <s.gia...@thebug.it>
Subject Re: [CForms] Howto?: bind repeater to collection of String
Date Tue, 18 Jul 2006 10:14:07 GMT
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: users-unsubscribe@cocoon.apache.org
For additional commands, e-mail: users-help@cocoon.apache.org


Mime
View raw message