You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@cocoon.apache.org by Mark Lundquist <ml...@wrinkledog.com> on 2006/07/18 03:04:46 UTC

[CForms] Howto?: bind repeater to collection of String

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! :-)

So the deal is, the user gets to create new items or delete existing 
ones.  (They don't get to change any existing items... just delete 'em. 
  Hence the <fd:output>).

Here's my definition:

     <fd:repeater id="editions">
       <fd:label>Available Editions</fd:label>
       <fd:widgets>
         <fd:output id="name">
           <fd:datatype base="string"/>
           <fd:label>Name</fd:label>
         </fd:output>
         <fd:row-action id="delete" command="delete">
           <fd:label>delete</fd:label>
         </fd:row-action>
       </fd:widgets>
     </fd:repeater>

     <fd:submit id="add-edition">
       <fd:label>Add new</fd:label>
     </fd:submit>

     <fd:field id="new-edition-name">
       <fd:datatype base="string"/>
       <fd:validation>
         <fd:length min="1">
           <fd:failmessage>New edition must have a name</fd:failmessage>
         </fd:length>
       </fd:validation>
     </fd:field>

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>

The "Add new" button works by triggering this bit of flowscript:

         form.lookupWidget ('editions')
                 .addRow()
                 .getChild('name').value =
                     form.lookupWidget ('new-edition-name').value;

...and the "delete" row-action just works... sorta (see below).

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?

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?

Thanks a lot!
—ml—

Re: [CForms] Howto?: bind repeater to collection of String

Posted by Mark Lundquist <ml...@wrinkledog.com>.
Oh yeah, forgot to mention... this is Cocoon 2.1.8, currently not using 
Ajax (just because I am behind the times and not Ajaxed-up yet... 
hopefully once I get this working I will be Ajaxifying it :-).

—ml—

Re: [CForms] Howto?: bind repeater to collection of String

Posted by Mark Lundquist <ml...@wrinkledog.com>.
On Jul 18, 2006, at 3:14 AM, Simone Gianni wrote:

> 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.

OK, I get it... after looking at the source code I see how this works.

This was counterintuitive to me after just recently learning the 
<multi-value> binding.  I expected the repeater binding to work in the 
same way when it comes to actually adding to the List (viz: letting 
JXPath do it).

So, the repeater binding calls a method (given by @addmethod) on the 
context bean, i.e. the owner of the collection, to create and add a new 
item... this is the most flexible way to do it, and in any case I 
understand the need (for beans) to at least delegate creation of the 
model object to something that knows how to create it :-).  I just 
expected something like a factory, instead of of a method that actually 
does the insertion into the collection.  What the multi-value binding 
does is to just go ahead and create the context for the new item, and  
JXPath expands the collection (which must be an Array or a List) by 
adding a null item.

> 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).

It works for loading :-)...

Anyway... I think the "context bean" and the "field" are really not the 
same!  The path for the field isn't relative to the binding context 
path, it's relative to the repeating item, which would be something 
like "/parent/things[3]".  So my field path is really like 
"/parent/things[3]/." (instead of the typical 
"/parent/things/[3]/beanProperty), and since in fact is a String, 
JXPath should be able to set it just fine.

I added an addWhatever() method to my model, and now the new items 
(Strings) are added when I save the form... unfortunately they are 
empty Strings (i.e., as created by my addWhatever() method).  Not sure 
why it isn't working... I guess if I don't figure it out soon here, 
I'll just punt and introduce a new class w/ a bean getter/setter, and 
do it the bean way.

—ml—


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@cocoon.apache.org
For additional commands, e-mail: users-help@cocoon.apache.org


Re: [CForms] Howto?: bind repeater to collection of String

Posted by Mark Lundquist <ml...@wrinkledog.com>.
Hi Simone, thx for the reply...

On Jul 18, 2006, at 3:14 AM, Simone Gianni wrote:

> <snip...>
>> 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.

Hmm, coming back to this today I noticed that I am also getting this 
warning in the log:

	RepeaterBinding has detected rows to delete, but misses the 
<on-delete-row> binding to do it.

???

>>
>> 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>

Yes, yes, I understand that much :-)... but I think an example of what 
the <fb:insert-bean> attributes might look like would help me to 
understand.

Note, I'm kind of trying for 2 different things here: (1) really 
understanding how repeater bindings work, and (2) getting my problem 
solved one way or the other :-).  That is why I'm asking all these 
questions that may not seem related :-).

>
> 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.

See above... how 'bout an example? :-)

>
> But all this will not solve your problem,

right... I know :-)

> 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 :(
>

Thanks a lot, I will take a look and see if I can figure it out... :-)

Best regards,
—ml—


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@cocoon.apache.org
For additional commands, e-mail: users-help@cocoon.apache.org


Re: [CForms] Howto?: bind repeater to collection of String

Posted by Simone Gianni <s....@thebug.it>.
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


Re: [CForms] Howto?: bind repeater to collection of String

Posted by Mark Lundquist <ml...@wrinkledog.com>.
On Jul 18, 2006, at 1:28 AM, Bruno Dumon wrote:

> I don't know much about repeater binding and didn't read your problem 
> in
> detail, but if you're just binding to a collection of strings you could
> as well use the fb:simple-repeater binding (not sure if this works for
> beans though).

That's a no-go, already tried it, I get

	Cannot create a relative context for a non-existent node: 
/publication/editions[3]

> For the normal binding, note that your string values need to be unique
> for it to work (since you're using the string as identity).

Yes.

—ml—


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@cocoon.apache.org
For additional commands, e-mail: users-help@cocoon.apache.org


Re: [CForms] Howto?: bind repeater to collection of String

Posted by Bruno Dumon <br...@outerthought.org>.
On Mon, 2006-07-17 at 18:04 -0700, 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! :-)
> 
> So the deal is, the user gets to create new items or delete existing
> ones.  (They don't get to change any existing items... just delete
> 'em.  Hence the <fd:output>).
> 
> Here's my definition:
> 
>     <fd:repeater id="editions">
>       <fd:label>Available Editions</fd:label>
>       <fd:widgets>
>         <fd:output id="name">
>           <fd:datatype base="string"/>
>           <fd:label>Name</fd:label>
>         </fd:output>
>         <fd:row-action id="delete" command="delete">
>           <fd:label>delete</fd:label>
>         </fd:row-action>
>       </fd:widgets>
>     </fd:repeater>
> 
>     <fd:submit id="add-edition">
>       <fd:label>Add new</fd:label>
>     </fd:submit>
>     
>     <fd:field id="new-edition-name">
>       <fd:datatype base="string"/>
>       <fd:validation>
>         <fd:length min="1">
>           <fd:failmessage>New edition must have a
> name</fd:failmessage>
>         </fd:length>
>       </fd:validation>
>     </fd:field>
> 
> 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>    
> 
> The "Add new" button works by triggering this bit of flowscript:
> 
>         form.lookupWidget ('editions')
>                 .addRow()
>                 .getChild('name').value =
>                     form.lookupWidget ('new-edition-name').value;
> 
> ...and the "delete" row-action just works... sorta (see below).
> 
> 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?
> 
> 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?

I don't know much about repeater binding and didn't read your problem in
detail, but if you're just binding to a collection of strings you could
as well use the fb:simple-repeater binding (not sure if this works for
beans though).

For the normal binding, note that your string values need to be unique
for it to work (since you're using the string as identity).

For the insert-bean, I guess this won't work for strings as they're
immutable.

-- 
Bruno Dumon                             http://outerthought.org/
Outerthought - Open Source, Java & XML Competence Support Center
bruno@outerthought.org                          bruno@apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@cocoon.apache.org
For additional commands, e-mail: users-help@cocoon.apache.org